初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+308
View File
@@ -0,0 +1,308 @@
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/BaseRespBeannull/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;
}
}