2025-12-12 09:11:30 +08:00
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 ,
2025-12-24 16:07:53 +08:00
bool isHaveToken = true ,
2025-12-12 09:11:30 +08:00
} ) 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';
2025-12-24 16:07:53 +08:00
if ( token ! = null & & token . isNotEmpty & & ! isHeartbeat & & isHaveToken ) {
2025-12-12 09:11:30 +08:00
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 ( ) ) ;
}
}
}