QinGang_interested/lib/http/HttpManager.dart

187 lines
6.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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());
}
}
}