525 lines
16 KiB
Dart
525 lines
16 KiB
Dart
import 'dart:convert';
|
||
import 'dart:io';
|
||
import 'dart:math';
|
||
import 'package:circle_app/router/routers.dart';
|
||
import 'package:circle_app/utils/util.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:dio/io.dart';
|
||
import 'package:flutter_bugly/flutter_bugly.dart';
|
||
|
||
import '../utils/SharedPreferencesHelper.dart';
|
||
import '../utils/device.dart';
|
||
import 'api.dart';
|
||
import 'package:connectivity/connectivity.dart';
|
||
|
||
// const String baseUrl = Api.baseUrl;
|
||
|
||
/// Dio 请求方法
|
||
|
||
enum DioMethod {
|
||
get,
|
||
post,
|
||
put,
|
||
delete,
|
||
postBody,
|
||
}
|
||
|
||
class DioManager {
|
||
factory DioManager() => getInstance();
|
||
|
||
static DioManager get instance => getInstance();
|
||
static DioManager? _instance;
|
||
|
||
static DioManager getInstance() {
|
||
if (_instance == null) {
|
||
_instance = DioManager._init();
|
||
}
|
||
return _instance!;
|
||
}
|
||
|
||
static CancelToken _globalCancelToken = CancelToken(); // 添加全局CancelToken
|
||
|
||
// 存储每个请求的 CancelToken
|
||
Map<String, CancelToken> _cancelTokens = {};
|
||
|
||
Dio? _dio;
|
||
|
||
DioManager._init() {
|
||
_dio ??= Dio(BaseOptions(
|
||
baseUrl:
|
||
// Api.testBaseUrl,
|
||
Api.baseUrl1,
|
||
// 连接服务器超时时间,单位是毫秒
|
||
connectTimeout: const Duration(seconds: 15),
|
||
// 接收数据的最长时限
|
||
receiveTimeout: const Duration(seconds: 15),
|
||
|
||
));
|
||
_dio!.interceptors.add(LogInterceptor(
|
||
responseBody: true,
|
||
));
|
||
_dio!.interceptors.add(TimingInterceptor()); // 添加拦截器
|
||
// _dio?.httpClientAdapter = IOHttpClientAdapter()
|
||
// ..createHttpClient = () {
|
||
// // 设置最大连接数
|
||
// HttpClient client = HttpClient();
|
||
// client.idleTimeout = Duration(seconds: 15); // 连接池的空闲超时
|
||
// client.maxConnectionsPerHost = 15; // 设置同一主机的最大连接数为 15
|
||
// return client;
|
||
// };
|
||
}
|
||
|
||
setDioTimeOut(int time) {
|
||
_dio?.options.connectTimeout = Duration(milliseconds: time);
|
||
}
|
||
|
||
|
||
setDioBaseUrl(String url) {
|
||
if (url != _dio!.options.baseUrl && _dio!.options.baseUrl == Api.testBaseUrl)
|
||
_dio?.options = BaseOptions(
|
||
// 请求基地址
|
||
baseUrl: url,
|
||
// 连接服务器超时时间,单位是毫秒
|
||
connectTimeout: const Duration(seconds: 30),
|
||
// 接收数据的最长时限
|
||
receiveTimeout: _dio!.options!.receiveTimeout,
|
||
);
|
||
}
|
||
|
||
|
||
cancelAllRequest() {
|
||
// _globalCancelToken.cancel("用户登出,取消所有请求");
|
||
// _globalCancelToken = CancelToken(); // 重新初始化 CancelToken 以便后续请求
|
||
}
|
||
|
||
// 生成请求 Key(根据 URL 和参数)
|
||
String _generateRequestKey(String url, Map<String, dynamic>? params) {
|
||
return "$url-${params?.toString() ?? ''}";
|
||
}
|
||
|
||
|
||
|
||
setReceiveTimeout (int time) {
|
||
_dio!.options = BaseOptions(
|
||
// 请求基地址
|
||
baseUrl: _dio!.options!.baseUrl,
|
||
// 连接服务器超时时间,单位是毫秒
|
||
connectTimeout: const Duration(seconds: 15),
|
||
// 接收数据的最长时限
|
||
receiveTimeout: Duration(seconds: time),
|
||
);
|
||
}
|
||
|
||
Future download(String urlPath, String savePath,
|
||
ProgressCallback progressCallback) async {
|
||
return await _dio!
|
||
.download(urlPath, savePath, onReceiveProgress: progressCallback);
|
||
}
|
||
|
||
/// get请求
|
||
/// [isShowErrorToast] 出现错误的情况是否自动弹窗提示错误,默认true
|
||
/// [isAddTokenInHeader] 请求头是否添加token
|
||
/// [fromJson]是将json转为model的方法
|
||
Future get<T>({
|
||
required String url,
|
||
bool isShowErrorToast = true,
|
||
bool isAddTokenInHeader = true,
|
||
Map<String, dynamic>? params,
|
||
}) async {
|
||
return await requestHttp(
|
||
url,
|
||
method: DioMethod.get,
|
||
isShowErrorToast: isShowErrorToast,
|
||
params: params,
|
||
);
|
||
}
|
||
|
||
/// post 请求
|
||
Future post<T>(
|
||
{required String url,
|
||
Map<String, dynamic>? params,
|
||
bool isAddTokenInHeader = true,
|
||
bool isShowErrorToast = true,
|
||
FormData? formData,
|
||
CancelToken? cancelToken,
|
||
ProgressCallback? onSendProgress,
|
||
ProgressCallback? onReceiveProgress}) async {
|
||
return await requestHttp<T>(url,
|
||
method: DioMethod.post,
|
||
isShowErrorToast: isShowErrorToast,
|
||
params: params,
|
||
formData: formData,
|
||
cancelToken: cancelToken,
|
||
onSendProgress: onSendProgress,
|
||
onReceiveProgress: onReceiveProgress);
|
||
}
|
||
|
||
/// post 请求
|
||
Future postBody<T>({required String url,
|
||
Map<String, dynamic>? params,
|
||
bool isAddTokenInHeader = true,
|
||
bool isShowErrorToast = true,
|
||
FormData? formData,
|
||
CancelToken? cancelToken,
|
||
ProgressCallback? onSendProgress,
|
||
ProgressCallback? onReceiveProgress}) async {
|
||
return await requestHttp<T>(url,
|
||
method: DioMethod.postBody,
|
||
isShowErrorToast: isShowErrorToast,
|
||
params: params,
|
||
formData: formData,
|
||
cancelToken: cancelToken,
|
||
onSendProgress: onSendProgress,
|
||
onReceiveProgress: onReceiveProgress);
|
||
}
|
||
|
||
/// put 请求
|
||
Future put<T>({
|
||
required String url,
|
||
Map<String, dynamic>? params,
|
||
bool isAddTokenInHeader = true,
|
||
bool isShowErrorToast = true,
|
||
FormData? formData,
|
||
CancelToken? cancelToken,
|
||
ProgressCallback? onSendProgress,
|
||
ProgressCallback? onReceiveProgress,
|
||
}) async {
|
||
return await requestHttp<T>(
|
||
url,
|
||
method: DioMethod.put,
|
||
// 修改请求方法为 put
|
||
isShowErrorToast: isShowErrorToast,
|
||
params: params,
|
||
formData: formData,
|
||
cancelToken: cancelToken,
|
||
onSendProgress: onSendProgress,
|
||
onReceiveProgress: onReceiveProgress,
|
||
);
|
||
}
|
||
|
||
Future delete<T>({
|
||
required String url,
|
||
Map<String, dynamic>? params,
|
||
bool isAddTokenInHeader = true,
|
||
bool isShowErrorToast = true,
|
||
FormData? formData,
|
||
CancelToken? cancelToken,
|
||
ProgressCallback? onSendProgress,
|
||
ProgressCallback? onReceiveProgress,
|
||
}) async {
|
||
return await requestHttp<T>(
|
||
url,
|
||
method: DioMethod.delete,
|
||
// 修改请求方法为 delete
|
||
isShowErrorToast: isShowErrorToast,
|
||
params: params,
|
||
formData: formData,
|
||
cancelToken: cancelToken,
|
||
onSendProgress: onSendProgress,
|
||
onReceiveProgress: onReceiveProgress,
|
||
);
|
||
}
|
||
|
||
/// Dio request 方法
|
||
Future requestHttp<T>(String url,
|
||
{DioMethod method = DioMethod.get,
|
||
Map<String, dynamic>? params,
|
||
bool isShowErrorToast = true,
|
||
bool isAddTokenInHeader = true,
|
||
FormData? formData,
|
||
CancelToken? cancelToken,
|
||
ProgressCallback? onSendProgress,
|
||
ProgressCallback? onReceiveProgress}) async {
|
||
const methodValues = {
|
||
DioMethod.get: 'get',
|
||
DioMethod.post: 'post',
|
||
DioMethod.delete: 'delete',
|
||
DioMethod.put: 'put'
|
||
};
|
||
var connectivityResult = await Connectivity().checkConnectivity();
|
||
|
||
if (connectivityResult == ConnectivityResult.none) {
|
||
// showOKToast("网络不流畅哦,请检查网络情况");
|
||
return {'code': 404, 'msg': '网络不流畅哦,请检查网络情况'};
|
||
}
|
||
|
||
try {
|
||
Response response;
|
||
|
||
var options = Options();
|
||
bool isAgreemement = await getAgreemement();
|
||
if (isAgreemement) {
|
||
options = Options(method: methodValues[method], headers: {
|
||
"Authorization": await getAuthorization(),
|
||
'VersionName': await getVersionName(),
|
||
'VersionCode': await getVersionCode(),
|
||
'Platform': Platform.isIOS ? '1' : '0',
|
||
'OsVersion': await getDeviceId(),
|
||
'Imei': await getImei(),
|
||
'Brand': await getBrand(),
|
||
});
|
||
} else {
|
||
options = Options(method: methodValues[method], headers: {
|
||
"Authorization": await getAuthorization(),
|
||
'VersionName': await getVersionName(),
|
||
'VersionCode': await getVersionCode(),
|
||
'Platform': Platform.isIOS ? '1' : '0',
|
||
});
|
||
}
|
||
|
||
print(">>>>>$params");
|
||
// 如果没有提供 CancelToken,则使用全局的
|
||
cancelToken ??= _globalCancelToken;
|
||
|
||
String key = _generateRequestKey(url, params ?? {});
|
||
|
||
// 如果该请求已经存在,则取消上一个
|
||
if (_cancelTokens.containsKey(key)) {
|
||
_cancelTokens[key]!.cancel("重复请求,取消上一个");
|
||
}
|
||
|
||
// 创建新的 CancelToken
|
||
cancelToken = CancelToken();
|
||
_cancelTokens[key] = cancelToken;
|
||
|
||
|
||
/// 不同请求方法,不同的请求参数,按实际项目需求分.
|
||
/// 这里 get 是 queryParameters,其它用 data. FormData 也是 data
|
||
/// 注意: 只有 post 方法支持发送 FormData.
|
||
switch (method) {
|
||
case DioMethod.get:
|
||
response = await _dio!
|
||
.request(url, queryParameters: params, options: options,cancelToken: cancelToken);
|
||
break;
|
||
case DioMethod.post:
|
||
response = await _dio!.post(url,
|
||
data: params, cancelToken: cancelToken, options: options);
|
||
break;
|
||
case DioMethod.postBody:
|
||
response = await _dio!.post(
|
||
url, data: params, cancelToken: cancelToken, options: options);
|
||
break;
|
||
case DioMethod.put:
|
||
response =
|
||
await _dio!.put(url, data: json.encode(params), options: options,cancelToken: cancelToken);
|
||
break;
|
||
case DioMethod.delete:
|
||
response = await _dio!
|
||
.delete(url, queryParameters: params, options: options,cancelToken: cancelToken);
|
||
break;
|
||
default:
|
||
// 如果有formData参数,说明是传文件,忽略params的参数
|
||
if (formData != null) {
|
||
response = await _dio!.post(url,
|
||
data: formData,
|
||
cancelToken: cancelToken,
|
||
onSendProgress: onSendProgress,
|
||
onReceiveProgress: onReceiveProgress);
|
||
} else {
|
||
response = await _dio!.request(url,
|
||
data: params, cancelToken: cancelToken, options: options);
|
||
}
|
||
}
|
||
|
||
// 请求完成后移除 token
|
||
_cancelTokens.remove(key);
|
||
// json转model
|
||
String jsonStr = json.encode(response.data);
|
||
Map<String, dynamic> responseMap = json.decode(jsonStr);
|
||
if (responseMap["code"] == 5003 || responseMap["code"] == 30003) {
|
||
showOKToast(responseMap['msg']);
|
||
pushLoginPage();
|
||
} else {
|
||
switch (responseMap["code"]) {
|
||
case 4001:
|
||
case 4002:
|
||
case 4003:
|
||
case 4004:
|
||
case 4100:
|
||
case 4101:
|
||
case 4102:
|
||
case 5002:
|
||
case 5001:
|
||
case 5000:
|
||
pushLoginPage();
|
||
break;
|
||
}
|
||
if (responseMap["code"] != 200 && responseMap["code"] != 10000 && !url.contains(Api.checkWxNumState) && responseMap["code"] != 21201 && responseMap['code'] != 32104 && responseMap['code'] != 4005) {
|
||
showOKToast(responseMap['msg']);
|
||
}
|
||
}
|
||
return responseMap;
|
||
} on DioException catch (e) {
|
||
if (e.type == DioExceptionType.cancel) return {'code': 500, 'msg': ''};
|
||
print("请求失败: ${e.message}");
|
||
print("请求URL: ${e.requestOptions.uri}");
|
||
print("请求Headers: ${e.requestOptions.headers}");
|
||
print("请求数据: ${e.requestOptions.data}");
|
||
print("错误类型: ${e.type}"); // 检查 DioErrorType
|
||
// 获取连接池信息(仅适用于 Dart 原生 HttpClient)
|
||
// final adapter = _dio!.httpClientAdapter as IOHttpClientAdapter;
|
||
// HttpClient client = adapter.createHttpClient!();
|
||
// print("最大连接数: ${client.maxConnectionsPerHost}");
|
||
|
||
|
||
SharedPreferencesHelper sp = await SharedPreferencesHelper.getInstance();
|
||
if (sp.getString(SharedPreferencesHelper.LOGINPHONE) == '18800000100') {
|
||
return {'code': 500, 'msg': ''};
|
||
}
|
||
// 其他类型的错误(如协议错误、解析错误等)
|
||
var info = await FlutterBugly.uploadException(
|
||
message: url + ':' + e.error.toString(),
|
||
detail: e.message ?? '',
|
||
);
|
||
print(info);
|
||
return {'code': 500, 'msg': '当前使用用户过多,请稍后再试'};
|
||
} catch (e) {
|
||
SharedPreferencesHelper sp = await SharedPreferencesHelper.getInstance();
|
||
if (sp.getString(SharedPreferencesHelper.LOGINPHONE) == '18800000100') {
|
||
return {'code': 500, 'msg': ''};
|
||
}
|
||
// 其他类型的错误(如协议错误、解析错误等)
|
||
var info = await FlutterBugly.uploadException(
|
||
message: url + e.toString(),
|
||
detail: '',
|
||
);
|
||
// 其他一些意外的报错
|
||
return {'code': 500, 'msg': '当前使用用户过多,请稍后再试'};
|
||
}
|
||
}
|
||
//
|
||
// // 错误判断
|
||
// void onErrorInterceptor(DioError err) {
|
||
// // 异常分类
|
||
// switch (err.type) {
|
||
// // 4xx 5xx response
|
||
// case DioErrorType.response:
|
||
// err.requestOptions.extra["errorMsg"] = err.response?.data ?? "连接异常";
|
||
// break;
|
||
// case DioErrorType.connectTimeout:
|
||
// err.requestOptions.extra["errorMsg"] = "连接超时";
|
||
// break;
|
||
// case DioErrorType.sendTimeout:
|
||
// err.requestOptions.extra["errorMsg"] = "发送超时";
|
||
// break;
|
||
// case DioErrorType.receiveTimeout:
|
||
// err.requestOptions.extra["errorMsg"] = "接收超时";
|
||
// break;
|
||
// case DioErrorType.cancel:
|
||
// err.requestOptions.extra["errorMsg"] =
|
||
// err.message.isNotEmpty ? err.message : "取消连接";
|
||
// break;
|
||
// case DioErrorType.other:
|
||
// default:
|
||
// err.requestOptions.extra["errorMsg"] = "连接异常";
|
||
// break;
|
||
// }
|
||
// }
|
||
}
|
||
|
||
class BaseResponse<T> {
|
||
int code;
|
||
String msg;
|
||
dynamic? data;
|
||
|
||
BaseResponse({required this.code, required this.msg, required this.data});
|
||
|
||
isSuccess() {
|
||
return code == 200;
|
||
}
|
||
|
||
factory BaseResponse.fromJson(
|
||
Map<String, dynamic> json, T Function(dynamic) fromJsonData) {
|
||
dynamic dataJson = json['data'];
|
||
T? data;
|
||
|
||
if (dataJson != null) {
|
||
if (dataJson is String) {
|
||
// 处理 dataJson 是 String 类型的情况
|
||
// 例如,可以直接将其赋值给 data 变量
|
||
data = null; // 根据你的需求修改赋值语句
|
||
} else if (dataJson is Map<String, dynamic>) {
|
||
// 处理 dataJson 是 Map<String, dynamic> 类型的情况
|
||
if (fromJsonData != null) {
|
||
data = fromJsonData(dataJson);
|
||
} else {
|
||
throw Exception('未提供 fromJsonData 函数来解析数据。');
|
||
}
|
||
} else if (dataJson is List) {
|
||
data = fromJsonData(dataJson);
|
||
} else {
|
||
// throw Exception('无效的数据格式。期望是 String 或 Map<String, dynamic> 类型。');
|
||
}
|
||
}
|
||
|
||
return BaseResponse(
|
||
code: json['code'],
|
||
msg: json['msg'],
|
||
data: data,
|
||
);
|
||
}
|
||
}
|
||
|
||
class ConnectivityInterceptor extends Interceptor {
|
||
@override
|
||
Future<void> onRequest(
|
||
RequestOptions options, RequestInterceptorHandler handler) async {
|
||
if (!await isInternetAvailable()) {
|
||
showOKToast("网络不流畅哦,请检查网络情况");
|
||
}
|
||
return handler.next(options);
|
||
}
|
||
|
||
Future<bool> isInternetAvailable() async {
|
||
var connectivityResult = await Connectivity().checkConnectivity();
|
||
return connectivityResult != ConnectivityResult.none;
|
||
}
|
||
}
|
||
|
||
class QnTokenData {
|
||
final String token;
|
||
final String cdnPrefix;
|
||
|
||
QnTokenData({required this.token, required this.cdnPrefix});
|
||
|
||
factory QnTokenData.fromJson(Map<String, dynamic> json) {
|
||
return QnTokenData(
|
||
token: json['token'],
|
||
cdnPrefix: json['cdn_prefix'],
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
class TimingInterceptor extends Interceptor {
|
||
@override
|
||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||
options.extra["startTime"] = DateTime.now(); // 记录开始时间
|
||
super.onRequest(options, handler);
|
||
}
|
||
|
||
@override
|
||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||
DateTime startTime = response.requestOptions.extra["startTime"];
|
||
DateTime endTime = DateTime.now();
|
||
Duration duration = endTime.difference(startTime);
|
||
|
||
print("请求 URL: ${response.requestOptions.uri}");
|
||
print("请求耗时: ${duration.inMilliseconds} ms");
|
||
|
||
super.onResponse(response, handler);
|
||
}
|
||
|
||
@override
|
||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||
DateTime startTime = err.requestOptions.extra["startTime"];
|
||
DateTime endTime = DateTime.now();
|
||
Duration duration = endTime.difference(startTime);
|
||
|
||
print("请求失败 URL: ${err.requestOptions.uri}");
|
||
print("请求失败耗时: ${duration.inMilliseconds} ms");
|
||
|
||
super.onError(err, handler);
|
||
}
|
||
}
|