187 lines
6.7 KiB
Dart
187 lines
6.7 KiB
Dart
|
|
import 'dart:convert';
|
|||
|
|
import 'dart:io';
|
|||
|
|
import 'dart:ui';
|
|||
|
|
import 'package:dio/dio.dart';
|
|||
|
|
|
|||
|
|
import 'package:qhd_prevention/services/SessionService.dart';
|
|||
|
|
import 'package:qhd_prevention/tools/tools.dart';
|
|||
|
|
|
|||
|
|
/// 全局接口异常
|
|||
|
|
class ApiException implements Exception {
|
|||
|
|
final String result;
|
|||
|
|
final String message;
|
|||
|
|
ApiException(this.result, this.message);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() => 'ApiException($result): $message';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// HTTP 方法枚举
|
|||
|
|
enum Method { get, post, put, delete }
|
|||
|
|
|
|||
|
|
/// HTTP 管理器 单例
|
|||
|
|
class HttpManager {
|
|||
|
|
HttpManager._internal() {
|
|||
|
|
_dio = Dio(BaseOptions(
|
|||
|
|
connectTimeout: const Duration(milliseconds: 20000),
|
|||
|
|
receiveTimeout: const Duration(milliseconds: 20000),
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': Headers.formUrlEncodedContentType,
|
|||
|
|
},
|
|||
|
|
));
|
|||
|
|
_initInterceptors();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static final HttpManager _instance = HttpManager._internal();
|
|||
|
|
factory HttpManager() => _instance;
|
|||
|
|
late final Dio _dio;
|
|||
|
|
|
|||
|
|
// 添加401处理回调
|
|||
|
|
static VoidCallback? onUnauthorized;
|
|||
|
|
void _initInterceptors() {
|
|||
|
|
_dio.interceptors
|
|||
|
|
..add(LogInterceptor(request: true, responseBody: true, error: true))
|
|||
|
|
..add(InterceptorsWrapper(onError: (err, handler) {
|
|||
|
|
// TODO 暂不处理
|
|||
|
|
// 捕获401错误
|
|||
|
|
if (err.response?.statusCode == 401) {
|
|||
|
|
// 触发全局登出回调
|
|||
|
|
onUnauthorized?.call();
|
|||
|
|
// 创建自定义异常
|
|||
|
|
final apiException = ApiException(
|
|||
|
|
'提示',
|
|||
|
|
'您的账号已在其他设备登录,已自动下线'
|
|||
|
|
);
|
|||
|
|
// 直接抛出业务异常,跳过后续错误处理
|
|||
|
|
return handler.reject(
|
|||
|
|
DioException(
|
|||
|
|
requestOptions: err.requestOptions,
|
|||
|
|
error: apiException,
|
|||
|
|
response: err.response,
|
|||
|
|
type: DioExceptionType.badResponse,
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
handler.next(err);
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 通用请求方法,返回完整后台 JSON
|
|||
|
|
Future<Map<String, dynamic>> request(
|
|||
|
|
String baseUrl,
|
|||
|
|
String path, {
|
|||
|
|
Method method = Method.post,
|
|||
|
|
Map<String, dynamic>? data,
|
|||
|
|
Map<String, dynamic>? params,
|
|||
|
|
CancelToken? cancelToken,
|
|||
|
|
String? contentType, // Content-Type,默认为 jsonContentType
|
|||
|
|
bool isHeartbeat = false,
|
|||
|
|
}) async {
|
|||
|
|
printLongString('参数:${jsonEncode(data)}');
|
|||
|
|
Response resp;
|
|||
|
|
final url = baseUrl + path;
|
|||
|
|
// 动态 headers,默认使用 jsonContentType
|
|||
|
|
final String contentTypeValue = contentType ?? Headers.jsonContentType;
|
|||
|
|
final headers = {
|
|||
|
|
'Content-Type': contentTypeValue,
|
|||
|
|
};
|
|||
|
|
final token = SessionService.instance.token ?? '';
|
|||
|
|
// final token = 'jjb-saas-auth:oauth:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJjbGllbnRJZFwiOlwieGdmemRcIixcImFjY291bnRJZFwiOjE5OTE2NzQ0MzEzMzY4NDk0MDgsXCJ1c2VyVHlwZUVudW1cIjpcIlBMQVRGT1JNXCIsXCJ1c2VySWRcIjoxOTkxNjc0NDI4MjYxNTMxNjQ4LFwidGVuYW50SWRcIjoxOTkxNjc0NDI4MjYxNTMxNjQ4LFwidGVuYW50TmFtZVwiOlwi5Yas5rOz55u45YWz5pa5XCIsXCJ0ZW5hbnRUeXBlSWRcIjoxOTkwNjkzMzg4MDcyMTI0NDE2LFwidGVuYW50UGFyZW50SWRzXCI6XCIwLDE5ODM3NzMwMTMwODYwNDgyNTYsMTk5MTY3NDQyODI2MTUzMTY0OFwiLFwibmFtZVwiOlwi5Yas5rOz55u45YWz5pa5XCIsXCJhY2Nlc3NUaWNrZXRcIjpcIkg0YXBlMkFaRVcxZFR1OTIwOXNzSDREc3pPWjBoTkZ4eEVlZzRmYTJZaFRVUFA0QkZVZXZmSklhTVdoS1wiLFwicmVmcmVzaFRpY2tldFwiOlwiRlRlZUxIaXJVblhueTBMcXNMcUdyc2dFaGpqVlRRN0pncVptVTBLS0JHVkFCU1ExeENtT3RTWmxRbUdpXCIsXCJleHBpcmVJblwiOjYwNDgwMCxcInJlZnJlc2hFeHBpcmVzSW5cIjo2MDQ4MDAsXCJvcmdJZFwiOjE5OTE2NzQ0MjgyNjE1MzE2NDgsXCJvcmdOYW1lXCI6XCLlhqzms7Pnm7jlhbPmlrlcIixcIm9yZ0lkc1wiOlsxOTkxNjc0NDI4MjYxNTMxNjQ4XSxcInJvbGVzVHlwZXNcIjpbXCJHT1ZfQ0hJTERfQUNDT1VOVFwiXSxcInJvbGVJZHNcIjpbMTk5MDY5MjE3NTA2NjgyNDcwNV0sXCJzY29wZXNcIjpbXSxcInJwY1R5cGVFbnVtXCI6XCJIVFRQXCIsXCJiaW5kTW9iaWxlU2lnblwiOlwiRkFMU0VcIn0iLCJpc3MiOiJwcm8tc2VydmVyIiwiZXhwIjoxNzY1OTU4NDIzfQ.RphPGGnh18RdGZ2vB0-2gKHp6bQg3-rKR4xPvDgH1ek';
|
|||
|
|
if (token != null && token.isNotEmpty && !isHeartbeat) {
|
|||
|
|
headers['token'] = token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final options = Options(
|
|||
|
|
method: method.name.toUpperCase(),
|
|||
|
|
contentType: contentTypeValue,
|
|||
|
|
headers: headers,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
switch (method) {
|
|||
|
|
case Method.get:
|
|||
|
|
resp = await _dio.get(url,
|
|||
|
|
queryParameters: {...?params, ...?data},
|
|||
|
|
cancelToken: cancelToken,
|
|||
|
|
options: options);
|
|||
|
|
break;
|
|||
|
|
case Method.post:
|
|||
|
|
resp = await _dio.post(url,
|
|||
|
|
data: data,
|
|||
|
|
queryParameters: params,
|
|||
|
|
cancelToken: cancelToken,
|
|||
|
|
options: options);
|
|||
|
|
break;
|
|||
|
|
case Method.put:
|
|||
|
|
resp = await _dio.put(url,
|
|||
|
|
data: data,
|
|||
|
|
queryParameters: params,
|
|||
|
|
cancelToken: cancelToken,
|
|||
|
|
options: options);
|
|||
|
|
break;
|
|||
|
|
case Method.delete:
|
|||
|
|
resp = await _dio.delete(url,
|
|||
|
|
queryParameters: params,
|
|||
|
|
cancelToken: cancelToken,
|
|||
|
|
options: options);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} on DioException catch (e) {
|
|||
|
|
if (e.error is ApiException) throw e.error as ApiException;
|
|||
|
|
throw ApiException('network_error', e.message ?? e.toString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final json = resp.data is Map<String, dynamic>
|
|||
|
|
? resp.data as Map<String, dynamic>
|
|||
|
|
: <String, dynamic>{};
|
|||
|
|
return json;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 上传文件扩展
|
|||
|
|
extension HttpManagerUpload on HttpManager {
|
|||
|
|
Future<Map<String, dynamic>> uploadImages({
|
|||
|
|
required String baseUrl,
|
|||
|
|
required String path,
|
|||
|
|
required Map<String, dynamic> fromData,
|
|||
|
|
CancelToken? cancelToken,
|
|||
|
|
}) async {
|
|||
|
|
fromData['corpinfoId'] = '1983773013086048256';
|
|||
|
|
final form = FormData.fromMap(fromData);
|
|||
|
|
|
|||
|
|
final token = SessionService.instance.token ?? '';
|
|||
|
|
final headers = <String, dynamic>{
|
|||
|
|
'Content-Type': 'multipart/form-data',
|
|||
|
|
};
|
|||
|
|
if (token.isNotEmpty) {
|
|||
|
|
headers['token'] = token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
final resp = await _dio.post(
|
|||
|
|
baseUrl + path,
|
|||
|
|
data: form,
|
|||
|
|
cancelToken: cancelToken,
|
|||
|
|
options: Options(
|
|||
|
|
method: Method.post.name.toUpperCase(),
|
|||
|
|
// contentType 可以省略或保留,multipart/form-data 会被 Dio 正确处理(boundary 自动添加)
|
|||
|
|
contentType: 'multipart/form-data',
|
|||
|
|
headers: headers,
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
final json = resp.data is Map<String, dynamic>
|
|||
|
|
? resp.data as Map<String, dynamic>
|
|||
|
|
: <String, dynamic>{};
|
|||
|
|
|
|||
|
|
return json;
|
|||
|
|
} on DioException catch (e) {
|
|||
|
|
// 如果是我们在拦截器里构造的 ApiException(例如 401),则向上抛出该业务异常
|
|||
|
|
if (e.error is ApiException) {
|
|||
|
|
throw e.error as ApiException;
|
|||
|
|
}
|
|||
|
|
// 其它情况统一抛出 ApiException,便于上层统一处理
|
|||
|
|
throw ApiException('network_error', e.message ?? e.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|