dio_client.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:connectivity_plus/connectivity_plus.dart';
  4. import 'package:dart_json_mapper/dart_json_mapper.dart';
  5. import 'package:dio/dio.dart';
  6. import 'package:flutter/cupertino.dart';
  7. import 'package:logger/logger.dart';
  8. import 'package:naiyouwl/app/common/LogHelper.dart';
  9. import 'package:synchronized/synchronized.dart' as synchronized;
  10. import '../common/SharedPreferencesUtil.dart';
  11. import '../data/model/LocalUser.dart';
  12. //import 'custom_interceptors.dart';
  13. class DioClient {
  14. static final DioClient _instance = DioClient._internal();
  15. late Dio _dio;
  16. factory DioClient() => _instance;
  17. final Logger _logger = Logger();
  18. DioClient._internal() {
  19. _dio = Dio(BaseOptions(
  20. baseUrl: 'https://api.androidrj01.top', // 你的API地址
  21. connectTimeout: 5000,
  22. receiveTimeout: 3000,
  23. ));
  24. // // 仅在调试模式下添加日志拦截器
  25. // assert(() {
  26. // _dio.interceptors.add(LogInterceptor(
  27. // request: true,
  28. // requestBody: true,
  29. // responseBody: true,
  30. // error: true,
  31. // logPrint: _logger.d, // 使用 Logger 插件打印日志
  32. // ));
  33. // return true;
  34. // }());
  35. // 修改拦截器添加顺序
  36. // 1. 先添加 Token 拦截器
  37. _dio.interceptors.add(TokenInterceptor(_dio));
  38. // 2. 再添加响应处理拦截器
  39. _dio.interceptors.add(InterceptorsWrapper(
  40. onResponse: (Response<dynamic> response, ResponseInterceptorHandler handler) async {
  41. final responseData = response.data as Map<String, dynamic>;
  42. //LogHelper().d("data:==== ${responseData} ");
  43. await SharedPreferencesUtil().setString("last_successful_url", response.requestOptions.baseUrl);
  44. if (responseData['ret'] == 1) {
  45. handler.next(
  46. Response<dynamic>(
  47. data: responseData['data'],
  48. headers: response.headers,
  49. requestOptions: response.requestOptions,
  50. statusCode: response.statusCode,
  51. ),
  52. );
  53. } else {
  54. handler.reject(
  55. DioError(
  56. requestOptions: response.requestOptions,
  57. error: AppException(message: responseData['msg'] ?? 'Unknown Error',statusCode: responseData['ret'] ?? -1),
  58. ),
  59. );
  60. }
  61. },
  62. ));
  63. // 3. 最后添加自定义拦截器(处理备用地址)
  64. final customInterceptor = CustomInterceptors(_dio);
  65. _dio.interceptors.add(customInterceptor);
  66. }
  67. void updateBaseUrl(String newBaseUrl) {
  68. _dio.options.baseUrl = newBaseUrl;
  69. }
  70. Dio get dio => _dio;
  71. }
  72. class TokenInterceptor extends Interceptor {
  73. final _lock = synchronized.Lock();
  74. bool _isRefreshing = false;
  75. final Dio _dio; // 添加 Dio 实例引用
  76. TokenInterceptor(this._dio); // 添加构造函数
  77. Future<String?> _refreshToken() async {
  78. try {
  79. // 从本地获取用户信息
  80. final localUserStr = await SharedPreferencesUtil().getString("localUser");
  81. if (localUserStr == null) {
  82. return null;
  83. }
  84. // 解析本地用户信息
  85. final localUser = JsonMapper.deserialize<LocalUser>(localUserStr);
  86. if (localUser == null) {
  87. return null;
  88. }
  89. // 使用保存的邮箱和密码重新登录
  90. final response = await _dio.post(
  91. '/api/client/v3/login',
  92. data: {
  93. 'email': localUser.email?.trim(),
  94. 'password': localUser.password,
  95. },
  96. );
  97. // 直接使用 response.data,因为响应拦截器已经处理过了
  98. final accessToken = response.data['access_token'];
  99. if (accessToken != null) {
  100. // 更新本地用户信息
  101. await SharedPreferencesUtil().setString(
  102. "localUser",
  103. JsonMapper.serialize(
  104. LocalUser(
  105. email: localUser.email,
  106. password: localUser.password,
  107. accessToken: accessToken,
  108. )
  109. )
  110. );
  111. return accessToken;
  112. }
  113. return null;
  114. } catch (e) {
  115. LogHelper().e("刷新token失败: $e");
  116. return null;
  117. }
  118. }
  119. @override
  120. Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
  121. // 只处理401错误,其他错误传递给下一个拦截器
  122. if (err.response?.statusCode == 401 && !err.requestOptions.path.contains('/user/login')) {
  123. LogHelper().d("Token过期,尝试刷新...");
  124. RequestOptions options = err.requestOptions;
  125. return await _lock.synchronized(() async {
  126. try {
  127. if (!_isRefreshing) {
  128. _isRefreshing = true;
  129. final newToken = await _refreshToken();
  130. if (newToken != null) {
  131. // 更新token
  132. await SharedPreferencesUtil().setString("token", newToken);
  133. // 使用新token重试原请求
  134. options.headers["Authorization"] = "Bearer $newToken";
  135. // 创建新的dio实例来避免拦截器循环
  136. final clonedDio = Dio()..options = _dio.options;
  137. // 添加响应处理拦截器
  138. clonedDio.interceptors.add(InterceptorsWrapper(
  139. onResponse: (Response<dynamic> response, ResponseInterceptorHandler handler) async {
  140. final responseData = response.data as Map<String, dynamic>;
  141. if (responseData['ret'] == 1) {
  142. handler.next(
  143. Response<dynamic>(
  144. data: responseData['data'],
  145. headers: response.headers,
  146. requestOptions: response.requestOptions,
  147. statusCode: response.statusCode,
  148. ),
  149. );
  150. } else {
  151. handler.reject(
  152. DioError(
  153. requestOptions: response.requestOptions,
  154. error: AppException(
  155. message: responseData['msg'] ?? 'Unknown Error',
  156. statusCode: responseData['ret'] ?? -1
  157. ),
  158. ),
  159. );
  160. }
  161. },
  162. ));
  163. final response = await clonedDio.fetch(options);
  164. _isRefreshing = false;
  165. return handler.resolve(response);
  166. }
  167. }
  168. } catch (e) {
  169. LogHelper().e("刷新token失败: $e");
  170. }
  171. _isRefreshing = false;
  172. err.error = AppException(message: "登录已过期,请重新登录");
  173. return handler.next(err);
  174. });
  175. }
  176. return handler.next(err);
  177. }
  178. // 添加请求拦截器来设置token
  179. @override
  180. Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
  181. final token = await SharedPreferencesUtil().getString("token");
  182. if (token != null) {
  183. options.headers["Authorization"] = "Bearer $token";
  184. }
  185. return super.onRequest(options, handler);
  186. }
  187. }
  188. class CustomInterceptors extends Interceptor {
  189. int _retryCount = 0;
  190. final List<String> _backupUrls = ['http://107.148.49.147:8383','https://api.androidrj02.top','https://api.androidrj88.com','https://api.androidrj03.top'];
  191. final Dio _dio; // 添加 Dio 作为参数
  192. CustomInterceptors(this._dio);
  193. Future<bool> isConnected() async {
  194. var connectivityResult = await (Connectivity().checkConnectivity());
  195. return connectivityResult != ConnectivityResult.none;
  196. }
  197. void resetRetryCount() {
  198. _retryCount = 0;
  199. }
  200. @override
  201. Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
  202. //LogHelper().d("onError === $_retryCount");
  203. // 检查网络连接状态
  204. bool isConnectNetWork = await isConnected();
  205. if (!isConnectNetWork) {
  206. // 无网络连接,设置友好的错误消息
  207. err.error = AppException(message: "当前网络不可用,请检查您的网络");
  208. return handler.next(err);
  209. } else if (err.error is SocketException || err.type == DioErrorType.other || err.type == DioErrorType.connectTimeout || err.type == DioErrorType.sendTimeout || err.type == DioErrorType.receiveTimeout) {
  210. //LogHelper().d("错误类型:==== ${err.type}");
  211. if (_retryCount < _backupUrls.length) {
  212. // 有网络连接但请求失败,尝试使用备用地址
  213. err.requestOptions.baseUrl = _backupUrls[_retryCount];
  214. LogHelper().d("切换地址:==== ${err.requestOptions.baseUrl}");
  215. _retryCount++;
  216. try {
  217. final Response response = await _dio.fetch(err.requestOptions);
  218. return handler.resolve(response);
  219. } catch (e) {
  220. if (e is DioError) {
  221. return onError(e, handler); // Recursive call
  222. } else {
  223. // Handle other exceptions if needed or rethrow them
  224. rethrow;
  225. }
  226. }
  227. }
  228. }
  229. // 其他错误,统一处理
  230. AppException appException = AppException.create(err);
  231. // debugPrint('DioError===: ${appException.toString()}');
  232. err.error = appException;
  233. return handler.next(err);
  234. }
  235. }
  236. class AppException implements Exception {
  237. final int? statusCode; // 添加了 statusCode
  238. final String message;
  239. AppException({required this.message,this.statusCode});
  240. static AppException create(DioError err) {
  241. switch (err.type) {
  242. case DioErrorType.cancel:
  243. return AppException(message: '请求被取消');
  244. case DioErrorType.connectTimeout:
  245. return AppException(message: '连接超时');
  246. case DioErrorType.sendTimeout:
  247. return AppException(message: '请求超时');
  248. case DioErrorType.receiveTimeout:
  249. return AppException(message: '响应超时');
  250. case DioErrorType.response:
  251. return AppException(
  252. message: '服务器返回异常,状态码:${err.response?.statusCode}',
  253. statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置
  254. );
  255. case DioErrorType.other:
  256. return AppException(
  257. message: '网络连接失败',
  258. statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置
  259. );
  260. default:
  261. return AppException(
  262. message: err.message,
  263. statusCode: err.response?.statusCode, // 设置statusCode,即使在default中也可以选择设置
  264. );
  265. }
  266. }
  267. @override
  268. String toString() {
  269. return message;
  270. }
  271. }
  272. extension DioClientExtension on DioClient {
  273. Future<dynamic> get(String path, {Map<String, dynamic>? queryParameters}) async {
  274. final response = await _dio.get(path, queryParameters: queryParameters);
  275. return _handleResponse(response);
  276. }
  277. Future<dynamic> post(String path, {Map<String, dynamic>? data}) async {
  278. final response = await _dio.post(path, data: data);
  279. return _handleResponse(response);
  280. }
  281. Future<dynamic> put(String path, {Map<String, dynamic>? data}) async {
  282. final response = await _dio.put(path, data: data);
  283. return _handleResponse(response);
  284. }
  285. Future<void> download(String urlPath, String savePath) async {
  286. await _dio.download(urlPath, savePath);
  287. }
  288. dynamic _handleResponse(Response response) {
  289. if (response.data is List) {
  290. return response.data as List<dynamic>;
  291. } else if (response.data is Map) {
  292. return response.data as Map<String, dynamic>;
  293. } else {
  294. throw Exception('Unsupported data type');
  295. }
  296. }
  297. }