二次开发源码提交

This commit is contained in:
2026-05-15 08:52:48 +08:00
parent 4131f7321a
commit eb35a1d067
191 changed files with 34566 additions and 0 deletions
+452
View File
@@ -0,0 +1,452 @@
import 'dart:async';
import 'package:dio/dio.dart';
import '../config/api_config.dart';
import '../core/exceptions/app_exception.dart';
import '../core/utils/app_logger.dart';
/// API响应
class ApiResponse<T> {
final int code;
final String message;
final T? data;
final String? error;
final String? correlationId;
ApiResponse({
required this.code,
required this.message,
this.data,
this.error,
this.correlationId,
});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
return ApiResponse<T>(
code: json['code'] as int? ?? 0,
message: json['msg'] as String? ?? '',
data: json['data'] as T?,
error: json['error'] as String?,
correlationId: json['correlation_id'] as String?,
);
}
bool get isSuccess => code == 0;
bool get isContinue => code == 203;
}
/// API服务
class ApiService {
late Dio _dio;
static ApiService? _instance;
bool _isRefreshing = false;
final List<Completer<void>> _refreshSubscribers = [];
bool _initialized = false;
/// 获取 token 的回调
Future<String?> Function()? getTokenCallback;
/// 刷新 token 的回调
Future<void> Function()? refreshTokenCallback;
/// 清除认证数据的回调
Future<void> Function()? clearAuthCallback;
ApiService._() {
_dio = Dio(
BaseOptions(
baseUrl: ApiConfig.defaultBaseUrl,
connectTimeout: const Duration(seconds: ApiConfig.connectTimeout),
receiveTimeout: const Duration(seconds: ApiConfig.receiveTimeout),
sendTimeout: const Duration(seconds: ApiConfig.sendTimeout),
headers: {'Content-Type': 'application/json'},
),
);
_dio.interceptors.add(_requestInterceptor());
_dio.interceptors.add(_responseInterceptor());
_dio.interceptors.add(_errorInterceptor());
}
/// 是否正在刷新token
bool get isRefreshing => _isRefreshing;
/// 暴露 Dio 实例(用于二进制下载等不走 _parseResponse 的场景)
Dio get dio => _dio;
/// 获取单例
static ApiService get instance {
_instance ??= ApiService._();
return _instance!;
}
/// 设置认证回调
/// 由 AuthProvider 在初始化时调用
static void setAuthCallbacks({
required Future<String?> Function() getToken,
required Future<void> Function() refreshToken,
required Future<void> Function() clearAuth,
}) {
final service = instance;
service.getTokenCallback = getToken;
service.refreshTokenCallback = refreshToken;
service.clearAuthCallback = clearAuth;
}
/// 初始化API服务(设置正确的baseUrl)
Future<void> init() async {
if (_initialized) return;
final baseUrl = await ApiConfig.baseUrl;
_dio.options.baseUrl = baseUrl;
_initialized = true;
}
/// 动态设置 API baseUrl
Future<void> setBaseUrl(String baseUrl) async {
_dio.options.baseUrl = baseUrl;
AppLogger.d('ApiService baseUrl 已更新为: $baseUrl');
}
/// 请求拦截器
Interceptor _requestInterceptor() {
return InterceptorsWrapper(
onRequest: (options, handler) async {
// 从回调获取 Token
if (getTokenCallback != null) {
final token = await getTokenCallback!();
if (token != null && token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
}
return handler.next(options);
},
);
}
/// 响应拦截器
Interceptor _responseInterceptor() {
return InterceptorsWrapper(
onResponse: (response, handler) {
AppLogger.d(
'API Response: ${response.statusCode} - ${response.requestOptions.uri}',
);
// 检查 JSON 响应中的 code 字段
if (response.data is Map<String, dynamic>) {
final data = response.data as Map<String, dynamic>;
final code = data['code'] as int?;
AppLogger.d('_responseInterceptor -> JSON code: $code');
if (code == 401) {
// HTTP 200 但 JSON code 是 401,需要处理未授权
final isNoAuth =
response.requestOptions.extra['noAuth'] as bool? ?? false;
AppLogger.d('_responseInterceptor -> isNoAuth: $isNoAuth');
if (!isNoAuth) {
// 直接在响应拦截器中处理 401
AppLogger.d('_responseInterceptor -> 触发 401 处理');
// 异步处理,不阻塞响应
_handle401InResponse(response.requestOptions);
}
}
}
return handler.next(response);
},
);
}
/// 在响应拦截器中处理 401 错误
Future<void> _handle401InResponse(RequestOptions requestOptions) async {
final path = requestOptions.path;
if (path.contains('/session/token/refresh')) {
return;
}
if (_isRefreshing) {
return;
}
_isRefreshing = true;
try {
AppLogger.d('_handle401InResponse -> 开始刷新 token');
if (refreshTokenCallback != null) {
await refreshTokenCallback!();
}
AppLogger.d('_handle401InResponse -> token 刷新完成');
} catch (e) {
AppLogger.d('_handle401InResponse -> 刷新失败: $e');
if (clearAuthCallback != null) {
await clearAuthCallback!();
}
} finally {
_isRefreshing = false;
}
}
/// 错误拦截器
Interceptor _errorInterceptor() {
return InterceptorsWrapper(
onError: (error, handler) async {
AppLogger.d("_errorInterceptor -> 获取files列表: response");
AppLogger.d('API Error: ${error.requestOptions.uri} - ${error.message}');
// 检查是否是 401 错误(HTTP 401 或 JSON code: 401
bool is401Error = error.response?.statusCode == 401;
if (!is401Error && error.response?.data is Map<String, dynamic>) {
final data = error.response!.data as Map<String, dynamic>;
is401Error = data['code'] == 401;
}
if (is401Error) {
final isNoAuth =
error.requestOptions.extra['noAuth'] as bool? ?? false;
if (!isNoAuth) {
// 不是noAuth请求,需要刷新token
final response = await _handle401Error(error, handler);
if (response != null) {
return handler.resolve(response);
}
}
}
// 处理错误
if (error.response == null) {
// 网络错误
throw NetworkException(
'网络连接失败,请检查网络设置',
code: error.response?.statusCode,
);
}
final statusCode = error.response?.statusCode;
final responseData = error.response?.data;
AppLogger.d('Error Response Data: $responseData');
if (responseData is Map<String, dynamic>) {
final response = ApiResponse.fromJson(responseData);
throw ServerException(response.message, code: response.code);
}
throw ServerException(
responseData?.toString() ?? '请求失败',
code: statusCode,
);
},
);
}
/// 处理401错误,尝试刷新token
Future<Response?> _handle401Error(
DioException error,
ErrorInterceptorHandler handler,
) async {
// 检查是否需要跳过token检查(如刷新token请求本身)
final path = error.requestOptions.path;
if (path.contains('/session/token/refresh')) {
// 刷新token的请求也失败了,直接返回错误
return null;
}
// 如果正在刷新token,等待刷新完成后再重试
if (_isRefreshing) {
final completer = Completer<void>();
_refreshSubscribers.add(completer);
// 等待刷新完成
await completer.future;
// 刷新完成后,移除旧的 Authorization header,让拦截器重新添加新 token
error.requestOptions.headers.remove('Authorization');
// 重试请求(拦截器会自动添加新 token)
return await _dio.fetch(error.requestOptions);
}
// 开始刷新token
_isRefreshing = true;
try {
// 调用回调刷新 token
if (refreshTokenCallback != null) {
await refreshTokenCallback!();
}
_isRefreshing = false;
// 通知所有等待的请求可以重试了
for (final subscriber in _refreshSubscribers) {
if (!subscriber.isCompleted) {
subscriber.complete();
}
}
_refreshSubscribers.clear();
// 重试当前请求:移除旧 header,让拦截器重新添加新 token
error.requestOptions.headers.remove('Authorization');
return await _dio.fetch(error.requestOptions);
} catch (e) {
AppLogger.d('Refresh token failed: $e');
_isRefreshing = false;
// 刷新失败,清除认证数据
if (clearAuthCallback != null) {
await clearAuthCallback!();
}
// 通知所有等待的请求
for (final subscriber in _refreshSubscribers) {
if (!subscriber.isCompleted) {
subscriber.completeError(e);
}
}
_refreshSubscribers.clear();
// 返回null,让原始错误继续传播
return null;
}
}
/// GET请求 , 如果是分享请求, 则不进入 _parseResponse
Future<T> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
bool noAuth = false,
bool isNoData = false,
Map<String, dynamic>? headers,
}) async {
final response = await _dio.get<T>(
path,
queryParameters: queryParameters,
options: Options(extra: {'noAuth': noAuth}, headers: headers),
);
// 如果是分享请求, 则不进入 _parseResponse
if (isNoData) {
return response.data as T;
}
return _parseResponse<T>(response);
}
/// POST请求
Future<T> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
bool noAuth = false,
Map<String, dynamic>? headers,
bool isNoData = false,
}) async {
AppLogger.d('API POST Request: $path');
AppLogger.d('Request Data: $data');
final response = await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(extra: {'noAuth': noAuth}, headers: headers),
);
AppLogger.d('Response Data: ${response.data}');
var isActivEmail = 0;
if (response.statusCode == 200) {
Map<String, dynamic>? tmp = response.data as Map<String, dynamic>?;
isActivEmail = tmp?['code'] as int;
}
if (isNoData || isActivEmail == 203) {
return response.data as T;
}
return _parseResponse<T>(response);
}
/// POST请求(带上传进度)
Future<T> postWithProgress<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
bool noAuth = false,
Map<String, dynamic>? headers,
ProgressCallback? onSendProgress,
}) async {
AppLogger.d('API POST Request with progress: $path');
final response = await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(extra: {'noAuth': noAuth}, headers: headers),
onSendProgress: onSendProgress,
);
AppLogger.d('Response Data: ${response.data}');
return _parseResponse<T>(response);
}
/// PUT请求
Future<T> put<T>(
String path, {
dynamic data,
bool noAuth = false,
bool isNoData = false,
}) async {
final response = await _dio.put<T>(
path,
data: data,
options: Options(extra: {'noAuth': noAuth}),
);
// 当请求的接口为创建分享时, 逻辑上不适合走到 _parseResponse -> ApiResponse.fromJson 直接返回结果即可
if (isNoData) {
return response.data as T;
}
return _parseResponse<T>(response);
}
/// PATCH请求
Future<T> patch<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
bool noAuth = false,
}) async {
final response = await _dio.patch<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(extra: {'noAuth': noAuth}),
);
return _parseResponse<T>(response);
}
/// DELETE请求
Future<T> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
bool noAuth = false,
}) async {
final response = await _dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(extra: {'noAuth': noAuth}),
);
return _parseResponse<T>(response);
}
/// 解析响应
T _parseResponse<T>(Response response) {
final data = response.data;
if (data is Map<String, dynamic>) {
final apiResponse = ApiResponse<T>.fromJson(data);
if (!apiResponse.isSuccess && !apiResponse.isContinue) {
throw ServerException(apiResponse.message, code: apiResponse.code);
}
return apiResponse.data as T;
}
return data as T;
}
}