import 'dart:convert'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dart_json_mapper/dart_json_mapper.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:logger/logger.dart'; import 'package:naiyouwl/app/common/LogHelper.dart'; import 'package:synchronized/synchronized.dart' as synchronized; import '../common/SharedPreferencesUtil.dart'; import '../data/model/LocalUser.dart'; //import 'custom_interceptors.dart'; class DioClient { static final DioClient _instance = DioClient._internal(); late Dio _dio; factory DioClient() => _instance; final Logger _logger = Logger(); DioClient._internal() { _dio = Dio(BaseOptions( baseUrl: 'https://api.androidrj01.top', // 你的API地址 connectTimeout: 5000, receiveTimeout: 3000, )); // // 仅在调试模式下添加日志拦截器 assert(() { _dio.interceptors.add(LogInterceptor( request: true, requestBody: true, responseBody: true, error: true, logPrint: _logger.d, // 使用 Logger 插件打印日志 )); return true; }()); // 修改拦截器添加顺序 // 1. 先添加 Token 拦截器 _dio.interceptors.add(TokenInterceptor(_dio)); // 2. 再添加响应处理拦截器 _dio.interceptors.add(InterceptorsWrapper( onResponse: (Response response, ResponseInterceptorHandler handler) async { final responseData = response.data as Map; //LogHelper().d("data:==== ${responseData} "); await SharedPreferencesUtil().setString("last_successful_url", response.requestOptions.baseUrl); if (responseData['ret'] == 1) { handler.next( Response( data: responseData['data'], headers: response.headers, requestOptions: response.requestOptions, statusCode: response.statusCode, ), ); } else { handler.reject( DioError( requestOptions: response.requestOptions, error: AppException(message: responseData['msg'] ?? 'Unknown Error',statusCode: responseData['ret'] ?? -1), ), ); } }, )); // 3. 最后添加自定义拦截器(处理备用地址) final customInterceptor = CustomInterceptors(_dio); _dio.interceptors.add(customInterceptor); } void updateBaseUrl(String newBaseUrl) { _dio.options.baseUrl = newBaseUrl; } Dio get dio => _dio; } class TokenInterceptor extends Interceptor { final _lock = synchronized.Lock(); bool _isRefreshing = false; final Dio _dio; // 添加 Dio 实例引用 TokenInterceptor(this._dio); // 添加构造函数 Future _refreshToken() async { try { // 从本地获取用户信息 final localUserStr = await SharedPreferencesUtil().getString("localUser"); if (localUserStr == null) { return null; } // 解析本地用户信息 final localUser = JsonMapper.deserialize(localUserStr); if (localUser == null) { return null; } // 使用保存的邮箱和密码重新登录 final response = await _dio.post( '/api/client/v3/login', data: { 'email': localUser.email?.trim(), 'password': localUser.password, }, ); // 直接使用 response.data,因为响应拦截器已经处理过了 final accessToken = response.data['access_token']; if (accessToken != null) { // 更新本地用户信息 await SharedPreferencesUtil().setString( "localUser", JsonMapper.serialize( LocalUser( email: localUser.email, password: localUser.password, accessToken: accessToken, ) ) ); return accessToken; } return null; } catch (e) { LogHelper().e("刷新token失败: $e"); return null; } } @override Future onError(DioError err, ErrorInterceptorHandler handler) async { // 只处理401错误,其他错误传递给下一个拦截器 if (err.response?.statusCode == 401 && !err.requestOptions.path.contains('/user/login')) { LogHelper().d("Token过期,尝试刷新..."); RequestOptions options = err.requestOptions; return await _lock.synchronized(() async { try { if (!_isRefreshing) { _isRefreshing = true; final newToken = await _refreshToken(); if (newToken != null) { // 更新token await SharedPreferencesUtil().setString("token", newToken); // 使用新token重试原请求 options.headers["Authorization"] = "Bearer $newToken"; // 创建新的dio实例来避免拦截器循环 final clonedDio = Dio()..options = _dio.options; // 添加响应处理拦截器 clonedDio.interceptors.add(InterceptorsWrapper( onResponse: (Response response, ResponseInterceptorHandler handler) async { final responseData = response.data as Map; if (responseData['ret'] == 1) { handler.next( Response( data: responseData['data'], headers: response.headers, requestOptions: response.requestOptions, statusCode: response.statusCode, ), ); } else { handler.reject( DioError( requestOptions: response.requestOptions, error: AppException( message: responseData['msg'] ?? 'Unknown Error', statusCode: responseData['ret'] ?? -1 ), ), ); } }, )); final response = await clonedDio.fetch(options); _isRefreshing = false; return handler.resolve(response); } } } catch (e) { LogHelper().e("刷新token失败: $e"); } _isRefreshing = false; err.error = AppException(message: "登录已过期,请重新登录"); return handler.next(err); }); } return handler.next(err); } // 添加请求拦截器来设置token @override Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async { final token = await SharedPreferencesUtil().getString("token"); if (token != null) { options.headers["Authorization"] = "Bearer $token"; } return super.onRequest(options, handler); } } class CustomInterceptors extends Interceptor { int _retryCount = 0; final List _backupUrls = ['http://107.148.49.147:8383','https://api.androidrj02.top','https://api.androidrj88.com','https://api.androidrj03.top']; final Dio _dio; // 添加 Dio 作为参数 CustomInterceptors(this._dio); Future isConnected() async { var connectivityResult = await (Connectivity().checkConnectivity()); return connectivityResult != ConnectivityResult.none; } void resetRetryCount() { _retryCount = 0; } @override Future onError(DioError err, ErrorInterceptorHandler handler) async { //LogHelper().d("onError === $_retryCount"); // 检查网络连接状态 bool isConnectNetWork = await isConnected(); if (!isConnectNetWork) { // 无网络连接,设置友好的错误消息 err.error = AppException(message: "当前网络不可用,请检查您的网络"); return handler.next(err); } else if (err.error is SocketException || err.type == DioErrorType.other || err.type == DioErrorType.connectTimeout || err.type == DioErrorType.sendTimeout || err.type == DioErrorType.receiveTimeout) { //LogHelper().d("错误类型:==== ${err.type}"); if (_retryCount < _backupUrls.length) { // 有网络连接但请求失败,尝试使用备用地址 err.requestOptions.baseUrl = _backupUrls[_retryCount]; LogHelper().d("切换地址:==== ${err.requestOptions.baseUrl}"); _retryCount++; try { final Response response = await _dio.fetch(err.requestOptions); return handler.resolve(response); } catch (e) { if (e is DioError) { return onError(e, handler); // Recursive call } else { // Handle other exceptions if needed or rethrow them rethrow; } } } } // 其他错误,统一处理 AppException appException = AppException.create(err); // debugPrint('DioError===: ${appException.toString()}'); err.error = appException; return handler.next(err); } } class AppException implements Exception { final int? statusCode; // 添加了 statusCode final String message; AppException({required this.message,this.statusCode}); static AppException create(DioError err) { switch (err.type) { case DioErrorType.cancel: return AppException(message: '请求被取消'); case DioErrorType.connectTimeout: return AppException(message: '连接超时'); case DioErrorType.sendTimeout: return AppException(message: '请求超时'); case DioErrorType.receiveTimeout: return AppException(message: '响应超时'); case DioErrorType.response: return AppException( message: '服务器返回异常,状态码:${err.response?.statusCode}', statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置 ); case DioErrorType.other: return AppException( message: '网络连接失败', statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置 ); default: return AppException( message: err.message, statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置 ); } } @override String toString() { return message; } } extension DioClientExtension on DioClient { Future get(String path, {Map? queryParameters}) async { final response = await _dio.get(path, queryParameters: queryParameters); return _handleResponse(response); } Future post(String path, {Map? data}) async { final response = await _dio.post(path, data: data); return _handleResponse(response); } Future put(String path, {Map? data}) async { final response = await _dio.put(path, data: data); return _handleResponse(response); } Future download(String urlPath, String savePath) async { await _dio.download(urlPath, savePath); } dynamic _handleResponse(Response response) { if (response.data is List) { return response.data as List; } else if (response.data is Map) { return response.data as Map; } else { throw Exception('Unsupported data type'); } } }