128 lines
4.3 KiB
Dart
128 lines
4.3 KiB
Dart
import 'package:dio/dio.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
|
||
|
||
import '../app/app_user_cubit.dart';
|
||
import '../di/injection.dart';
|
||
import '../infrastructure/logging/sentry_logger_impl.dart';
|
||
import '../logging/i_logger_service.dart';
|
||
|
||
class DioClient {
|
||
static Dio create() {
|
||
final dio = Dio(
|
||
BaseOptions(
|
||
baseUrl: HttpApiConsts.baseUrl,
|
||
connectTimeout: const Duration(seconds: 5),
|
||
receiveTimeout: const Duration(seconds: 5),
|
||
headers: {'Content-Type': 'application/json'},
|
||
),
|
||
);
|
||
if (kDebugMode) {
|
||
dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: true));
|
||
}
|
||
// dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: true));
|
||
// dio.interceptors.add(
|
||
// InterceptorsWrapper(
|
||
// onRequest: (options, handler) {
|
||
// // 1. 从 GetIt 中获取全局的 AppUserCubit
|
||
// final userCubit = sl<AppUserCubit>();
|
||
//
|
||
// // 2. 从 Cubit 的状态中提取 Token
|
||
// final token = userCubit.state.user?.token;
|
||
//
|
||
// // 3. 如果 Token 存在,则添加到请求头
|
||
// if (token != null && token.isNotEmpty) {
|
||
// options.headers['Authorization'] = 'Bearer $token';
|
||
// }
|
||
//
|
||
// // 继续发送请求
|
||
// return handler.next(options);
|
||
// },
|
||
// ),
|
||
// );
|
||
// return dio;
|
||
// }
|
||
dio.interceptors.add(InterceptorsWrapper(
|
||
onError: (DioException error, ErrorInterceptorHandler handler) async {
|
||
// 捕获所有网络异常 (超时、无网络、DNS 错误等)
|
||
await _reportToSentry(error);
|
||
return handler.next(error);
|
||
},
|
||
onResponse: (Response response, ResponseInterceptorHandler handler) async {
|
||
// 捕获业务错误 (如 HTTP 4xx, 5xx)
|
||
if (response.statusCode != null && response.statusCode! >= 400) {
|
||
await _reportHttpError(response);
|
||
}
|
||
return handler.next(response);
|
||
},
|
||
onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
|
||
// 原有 Token 逻辑保持不变
|
||
try {
|
||
final userCubit = sl<AppUserCubit>();
|
||
final token = userCubit.state.user?.token;
|
||
if (token != null && token.isNotEmpty) {
|
||
options.headers['Authorization'] = 'Bearer $token';
|
||
}
|
||
} catch (e) {
|
||
// 防止获取 Token 时出错阻断请求
|
||
debugPrint('获取 Token 失败: $e');
|
||
}
|
||
return handler.next(options);
|
||
},
|
||
));
|
||
|
||
return dio;
|
||
}
|
||
// 上报 HTTP 状态码错误 (404, 500 等)
|
||
static Future<void> _reportHttpError(Response response) async {
|
||
try {
|
||
final logger = GetIt.I<ILoggerService>() as SentryLoggerImpl;
|
||
|
||
// 记录详细的错误上下文
|
||
logger.logWithLevel(
|
||
'HTTP 请求失败: ${response.statusCode}',
|
||
level: 'ERROR',
|
||
data: {
|
||
'url': response.requestOptions.uri.toString(),
|
||
'method': response.requestOptions.method,
|
||
'statusCode': response.statusCode,
|
||
'responseData': response.data, // 注意:如果响应包含敏感信息,请脱敏
|
||
},
|
||
);
|
||
|
||
// 可选:如果希望每个 4xx/5xx 都生成一个独立 Issue,取消下面注释
|
||
// await Sentry.captureMessage(
|
||
// 'HTTP Error ${response.statusCode}: ${response.requestOptions.path}',
|
||
// level: SentryLevel.error,
|
||
// extra: {'data': response.data},
|
||
// );
|
||
} catch (e) {
|
||
debugPrint('Sentry 上报 HTTP 错误失败: $e');
|
||
}
|
||
}
|
||
|
||
// 上报网络异常 (超时、断开等)
|
||
static Future<void> _reportToSentry(DioException error) async {
|
||
try {
|
||
final logger = GetIt.I<ILoggerService>() as SentryLoggerImpl;
|
||
|
||
logger.logWithLevel(
|
||
'网络异常: ${error.type}',
|
||
level: 'ERROR',
|
||
data: {
|
||
'url': error.requestOptions.uri.toString(),
|
||
'method': error.requestOptions.method,
|
||
'message': error.message,
|
||
'type': error.type.name,
|
||
},
|
||
);
|
||
|
||
// 捕获完整堆栈
|
||
logger.captureException(error, stackTrace: error.stackTrace);
|
||
} catch (e) {
|
||
debugPrint('Sentry 上报网络异常失败: $e');
|
||
}
|
||
}
|
||
}
|