309 lines
10 KiB
Dart
309 lines
10 KiB
Dart
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||
import 'package:crypto/crypto.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:dio/io.dart';
|
||
import 'package:hgdj/config/config.dart';
|
||
import 'package:hgdj/hj_utils/text_util.dart';
|
||
import 'package:hgdj/tools_base/debug_log.dart';
|
||
import 'package:hgdj/tools_base/net/base_resp_bean.dart';
|
||
import 'package:hgdj/tools_base/net/http_response_interceptor.dart';
|
||
import 'package:hgdj/tools_base/net/net_code.dart';
|
||
import 'package:hgdj/tools_base/net/net_manager.dart';
|
||
import 'package:uuid/uuid.dart';
|
||
|
||
import '../../track_event_manager/device_service.dart';
|
||
import '../../track_event_manager/track_session.dart';
|
||
|
||
final httpManager = _HttpManagerImp();
|
||
|
||
enum HttpMethod {
|
||
get('GET'),
|
||
post('POST'),
|
||
delete('DELETE');
|
||
|
||
final String method;
|
||
const HttpMethod(this.method);
|
||
}
|
||
|
||
abstract class HttpManger {
|
||
final dio = Dio();
|
||
String _baseUrl = '';
|
||
|
||
String get baseUrl => _baseUrl;
|
||
|
||
/// 服务器时间校准
|
||
int _diffTimeInSeconds = 0;
|
||
DateTime? _serverTime;
|
||
|
||
initDefault() {
|
||
// 选线发生在 resetBaseUrl 之前,这里必须先给 dio.options 配 connectTimeout,
|
||
// 否则线路握手挂起时永不超时,导致启动页"选线中..."一直转(iOS release 高发)
|
||
dio.options.connectTimeout = const Duration(seconds: 15);
|
||
dio.options.sendTimeout = const Duration(seconds: 15);
|
||
dio.options.receiveTimeout = const Duration(seconds: 15);
|
||
_addDioIns();
|
||
}
|
||
|
||
init(String baseUrl) {
|
||
resetBaseUrl(baseUrl);
|
||
}
|
||
|
||
// 同步服务器时间
|
||
setServerTime(String? serverTimeS) {
|
||
if (TextUtil.isNotEmpty(serverTimeS)) {
|
||
_serverTime = DateTime.parse(serverTimeS!);
|
||
_diffTimeInSeconds = DateTime.now().difference(_serverTime!).inSeconds;
|
||
debugLog(
|
||
"============>server diff from local in seconds:$_diffTimeInSeconds");
|
||
}
|
||
}
|
||
|
||
/// get
|
||
Future<BaseRespBean> fetchResponseByGET(
|
||
String url, {
|
||
Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
});
|
||
|
||
/// post
|
||
Future<BaseRespBean> fetchResponseByPOST(
|
||
String url, {
|
||
Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
});
|
||
|
||
/// post
|
||
|
||
Future<BaseRespBean> fetchResponseByDELETE(
|
||
String url, {
|
||
Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
});
|
||
|
||
Future<BaseRespBean> _requestByUrl(
|
||
String url, {
|
||
Map<String, dynamic>? data,
|
||
Map<String, dynamic>? queryParameters,
|
||
required Options options,
|
||
CancelToken? cancelToken,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
}) async {
|
||
// 给默认值兜底:若 response 非 null 但 data 不是 Map/BaseRespBean(null/String/List),
|
||
// 下面 resultData 不会被赋值,late 变量访问会抛 LateInitializationError
|
||
BaseRespBean resultData =
|
||
BaseRespBean(Code.NETWORK_ERROR, msg: '网络异常请稍后再试~', data: null);
|
||
Response? response;
|
||
try {
|
||
response = await dio.request(url,
|
||
queryParameters: queryParameters,
|
||
options: options,
|
||
data: data,
|
||
cancelToken: cancelToken);
|
||
} on DioException catch (e) {
|
||
debugLog('DioException $e');
|
||
if (e.type == DioExceptionType.cancel) {
|
||
resultData = BaseRespBean(Code.LOCAL_CANCEL_REQUEST,
|
||
msg: '请求已经取消~,请重试', data: null);
|
||
} else if (e.type == DioExceptionType.connectionTimeout ||
|
||
e.type == DioExceptionType.receiveTimeout ||
|
||
e.type == DioExceptionType.sendTimeout) {
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_TIMEOUT, msg: '网络连接超时~', data: null);
|
||
}
|
||
final connectivityResult = await Connectivity().checkConnectivity();
|
||
if (connectivityResult == ConnectivityResult.none) {
|
||
//没有网络
|
||
resultData = BaseRespBean(Code.LOCAL_NO_NETWORK,
|
||
msg: '暂无网络,请检查网络设置', data: null);
|
||
} else {
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_ERROR, msg: '网络异常,请重新试试~', data: null);
|
||
}
|
||
} on SocketException catch (e) {
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_TIMEOUT, msg: '网络连接超时~', data: null);
|
||
debugLog('SocketException $e');
|
||
} on HttpException catch (e) {
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_ERROR, msg: '网络异常请稍后再试~', data: null);
|
||
debugLog('HttpException $e');
|
||
} on FormatException catch (e) {
|
||
debugLog('FormatException $e');
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_ERROR, msg: '网络异常请稍后再试~', data: null);
|
||
} catch (e) {
|
||
resultData =
|
||
BaseRespBean(Code.NETWORK_ERROR, msg: '网络异常请稍后再试~', data: null);
|
||
debugLog(e);
|
||
}
|
||
|
||
if (response == null) {
|
||
return resultData;
|
||
} else if (response.data is BaseRespBean) {
|
||
resultData = response.data;
|
||
} else if (response.data is Map<String, dynamic>) {
|
||
resultData = BaseRespBean.fromJson(response.data);
|
||
}
|
||
if (resultData.isSuccess) {
|
||
if (jsonTransformation != null) {
|
||
final data_ = resultData.data;
|
||
if (data_ is Map) {
|
||
try {
|
||
resultData.data =
|
||
jsonTransformation.call(Map<String, dynamic>.from(data_));
|
||
} catch (e) {
|
||
print('jsonTransformation error $e');
|
||
resultData.data = null;
|
||
}
|
||
}
|
||
}
|
||
} else if (resultData.data is String) {
|
||
// 非成功响应 data 仍是未解密的密文串;service 层若 `return result.data`(返回类型是 Model?)
|
||
// 会把 String 强转模型,抛 'String is not a subtype of FutureOr<Model?>'。统一置空,降级返回 null。
|
||
resultData.data = null;
|
||
}
|
||
return resultData;
|
||
}
|
||
|
||
// dio 添加拦截
|
||
_addDioIns() {
|
||
dio.interceptors.add(HttpResponseInterceptor());
|
||
}
|
||
|
||
// 重制地址
|
||
resetBaseUrl(String baseUrl) {
|
||
_baseUrl = baseUrl;
|
||
final options = BaseOptions(
|
||
connectTimeout: const Duration(seconds: 15),
|
||
receiveTimeout: const Duration(seconds: 15),
|
||
validateStatus: (int? status) => (status ?? 600) < 600,
|
||
baseUrl: baseUrl,
|
||
);
|
||
|
||
options.headers[HttpHeaders.acceptEncodingHeader] = "*";
|
||
|
||
dio.options = options;
|
||
|
||
var adapter = DefaultHttpClientAdapter();
|
||
|
||
adapter.onHttpClientCreate = (client) {
|
||
client.badCertificateCallback =
|
||
(X509Certificate cert, String host, int port) => true;
|
||
return client;
|
||
};
|
||
dio.httpClientAdapter = adapter;
|
||
}
|
||
|
||
// 获取统一的请求头
|
||
Future<Options> generateRequestOption(String apiUrl,
|
||
{Options? options, required HttpMethod method}) async {
|
||
//调用方可以自带 options(比如 X-Request-ID 这种单接口的头),公共头往上加,method 一律以本次请求为准
|
||
options ??= Options();
|
||
options.method = method.method;
|
||
options.headers ??= {};
|
||
final token = await netManager.getToken();
|
||
if (token.isNotEmpty == true) {
|
||
options.headers?["Authorization"] = token;
|
||
}
|
||
if (options.method == "GET") {
|
||
//options.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||
} else if (options.method == "POST") {
|
||
options.headers?["Content-Type"] = "application/json;charset=UTF-8";
|
||
}
|
||
options.headers?["User-Agent"] = await netManager.userAgent();
|
||
options.headers?["api_version"] = "1.0.0";
|
||
options.headers?["device"] = Platform.operatingSystem;
|
||
Uri? baseUri = Uri.tryParse(baseUrl);
|
||
Uri targetUri = Uri(
|
||
scheme: baseUri?.scheme,
|
||
host: baseUri?.host,
|
||
port: baseUri?.port,
|
||
path: baseUri!.path + apiUrl);
|
||
options.headers?["x-api-key"] = await _sign(targetUri.path);
|
||
options.headers?["sid"] = TrackSessionManager().currentSid;
|
||
options.headers?["DeviceModel"] = DeviceInfoService.model;
|
||
return options;
|
||
}
|
||
|
||
/// 签名
|
||
Future<String> _sign(String path) async {
|
||
Map<String, dynamic> signObj = {};
|
||
final timeDate = DateTime.now().add(Duration(seconds: -_diffTimeInSeconds));
|
||
int timestamp = timeDate.toUtc().millisecondsSinceEpoch ~/ 1000;
|
||
signObj['nonce'] = const Uuid().v4();
|
||
signObj['path'] = path;
|
||
signObj['timestamp'] = timestamp.toString();
|
||
signObj['token'] = await netManager.getToken();
|
||
signObj['userAgent'] = await netManager.userAgent();
|
||
var key = utf8.encode(Config.signKey);
|
||
var bytes = utf8.encode(jsonEncode(signObj).toString());
|
||
var sha1Encrypt = Hmac(sha1, key);
|
||
var digest = sha1Encrypt.convert(bytes);
|
||
return 'timestamp=$timestamp;sign=${digest.toString()};nonce=${signObj['nonce']}';
|
||
}
|
||
}
|
||
|
||
class _HttpManagerImp extends HttpManger {
|
||
@override
|
||
Future<BaseRespBean> fetchResponseByDELETE(String url,
|
||
{Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation}) async {
|
||
final reqOptions = await generateRequestOption(url,
|
||
options: options, method: HttpMethod.delete);
|
||
return await _requestByUrl(url,
|
||
options: reqOptions,
|
||
data: param,
|
||
jsonTransformation: jsonTransformation);
|
||
}
|
||
|
||
@override
|
||
Future<BaseRespBean> fetchResponseByGET(
|
||
String url, {
|
||
Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
}) async {
|
||
final reqOptions = await generateRequestOption(url,
|
||
options: options, method: HttpMethod.get);
|
||
return await _requestByUrl(url,
|
||
options: reqOptions,
|
||
queryParameters: param,
|
||
jsonTransformation: jsonTransformation);
|
||
}
|
||
|
||
@override
|
||
Future<BaseRespBean> fetchResponseByPOST(
|
||
String url, {
|
||
Map<String, dynamic>? param,
|
||
Options? options,
|
||
Function(Map<String, dynamic> json)? jsonTransformation,
|
||
}) async {
|
||
final reqOptions = await generateRequestOption(url,
|
||
options: options, method: HttpMethod.post);
|
||
return await _requestByUrl(url,
|
||
options: reqOptions,
|
||
data: param,
|
||
jsonTransformation: jsonTransformation);
|
||
}
|
||
|
||
Future<BaseRespBean> fetchDetectLineResponse(String url,
|
||
{Options? options, CancelToken? cancelToken}) async {
|
||
options ??= Options(
|
||
method: HttpMethod.get.method,
|
||
sendTimeout: const Duration(seconds: 10),
|
||
receiveTimeout: const Duration(seconds: 10),
|
||
headers: {},
|
||
);
|
||
final result =
|
||
await _requestByUrl(url, options: options, cancelToken: cancelToken);
|
||
return result;
|
||
}
|
||
}
|