dio_client.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import 'dart:io';
  2. import 'package:dio/dio.dart' hide LogInterceptor;
  3. import 'package:sino_med_cloud/core/constants/api_constants.dart';
  4. import 'package:sino_med_cloud/core/network/interceptors/auth_interceptor.dart';
  5. import 'package:sino_med_cloud/core/network/interceptors/error_interceptor.dart';
  6. import 'package:sino_med_cloud/core/network/interceptors/log_interceptor.dart';
  7. import 'package:sino_med_cloud/core/network/interceptors/network_interceptor.dart';
  8. import 'package:sino_med_cloud/core/utils/logger.dart';
  9. import 'package:sino_med_cloud/base/common_response.dart';
  10. import '../../base/list_response.dart';
  11. /// Dio 客户端 - 统一网络请求管理
  12. class DioClient {
  13. // 使用一个内部方法来创建和配置 Dio 实例
  14. static final Dio _dio = _createDio();
  15. static Dio _createDio() {
  16. AppLogger.d('DioClient - 开始创建 Dio 实例并配置拦截器');
  17. final dio = Dio(
  18. BaseOptions(
  19. baseUrl: ApiConstants.baseUrl,
  20. connectTimeout: Duration(seconds: ApiConstants.connectTimeout),
  21. receiveTimeout: Duration(seconds: ApiConstants.receiveTimeout),
  22. sendTimeout: Duration(seconds: ApiConstants.sendTimeout),
  23. headers: {
  24. ApiConstants.contentType: ApiConstants.applicationJson,
  25. },
  26. ),
  27. );
  28. // 立即添加拦截器
  29. dio.interceptors.addAll([
  30. AuthInterceptor(), // 认证拦截器
  31. LogInterceptor(), // 日志拦截器
  32. ErrorInterceptor(), // 错误拦截器
  33. NetworkInterceptor(), // 网络状态拦截器(在最后检查,确保异常能被 ErrorInterceptor 捕获)
  34. ]);
  35. AppLogger.d('DioClient - Dio 实例初始化完成,拦截器数量: ${dio.interceptors.length}');
  36. return dio;
  37. }
  38. // 暴露 dio 实例
  39. static Dio get dio => _dio;
  40. /// 重置 Dio 实例(用于重新配置)
  41. static void reset() {
  42. _dio.interceptors.clear();
  43. _dio.interceptors.addAll([
  44. AuthInterceptor(),
  45. LogInterceptor(),
  46. ErrorInterceptor(),
  47. NetworkInterceptor(),
  48. ]);
  49. }
  50. // ==================== GET 请求 ====================
  51. /// GET 请求
  52. ///
  53. /// [path] 请求路径(相对于 baseUrl)
  54. /// [fromJsonT] JSON 转换为 T 类型的函数
  55. /// [queryParameters] 查询参数
  56. /// [options] 请求选项
  57. ///
  58. /// 返回 [BaseCommonResponse] 对象
  59. static Future<BaseCommonResponse<T>> get<T>(
  60. String path, {
  61. required T Function(Object? json) fromJsonT,
  62. Map<String, dynamic>? queryParameters,
  63. Options? options,
  64. CancelToken? cancelToken,
  65. ProgressCallback? onReceiveProgress,
  66. }) async {
  67. try {
  68. final response = await dio.get(
  69. path,
  70. queryParameters: queryParameters,
  71. options: options,
  72. cancelToken: cancelToken,
  73. onReceiveProgress: onReceiveProgress,
  74. );
  75. return BaseCommonResponse<T>.fromJson(
  76. response.data as Map<String, dynamic>,
  77. fromJsonT,
  78. );
  79. } on DioException {
  80. // 错误已由 ErrorInterceptor 处理,直接抛出
  81. rethrow;
  82. }
  83. }
  84. static Future<BaseListResponse<T>> getList<T>(
  85. String path, {
  86. required T Function(Object? json) fromJsonT,
  87. Map<String, dynamic>? queryParameters,
  88. Options? options,
  89. CancelToken? cancelToken,
  90. ProgressCallback? onReceiveProgress,
  91. }) async {
  92. try {
  93. final response = await dio.get(
  94. path,
  95. queryParameters: queryParameters,
  96. options: options,
  97. cancelToken: cancelToken,
  98. onReceiveProgress: onReceiveProgress,
  99. );
  100. return BaseListResponse<T>.fromJson(
  101. response.data as Map<String, dynamic>,
  102. fromJsonT,
  103. );
  104. } on DioException {
  105. // 错误已由 ErrorInterceptor 处理,直接抛出
  106. rethrow;
  107. }
  108. }
  109. // ==================== POST 请求 ====================
  110. /// POST 请求
  111. ///
  112. /// [path] 请求路径(相对于 baseUrl)
  113. /// [fromJsonT] JSON 转换为 T 类型的函数
  114. /// [data] 请求体数据
  115. /// [queryParameters] 查询参数
  116. /// [options] 请求选项
  117. ///
  118. /// 返回 [BaseCommonResponse] 对象
  119. static Future<BaseCommonResponse<T>> post<T>(
  120. String path, {
  121. required T Function(Object? json) fromJsonT,
  122. dynamic data,
  123. Map<String, dynamic>? queryParameters,
  124. Options? options,
  125. CancelToken? cancelToken,
  126. ProgressCallback? onSendProgress,
  127. ProgressCallback? onReceiveProgress,
  128. }) async {
  129. try {
  130. final response = await dio.post(
  131. path,
  132. data: data,
  133. queryParameters: queryParameters,
  134. options: options,
  135. cancelToken: cancelToken,
  136. onSendProgress: onSendProgress,
  137. onReceiveProgress: onReceiveProgress,
  138. );
  139. return BaseCommonResponse<T>.fromJson(
  140. response.data as Map<String, dynamic>,
  141. fromJsonT,
  142. );
  143. } on DioException {
  144. // 错误已由 ErrorInterceptor 处理,直接抛出
  145. rethrow;
  146. }
  147. }
  148. // ==================== DELETE 请求 ====================
  149. /// DELETE 请求
  150. ///
  151. /// [path] 请求路径(相对于 baseUrl)
  152. /// [fromJsonT] JSON 转换为 T 类型的函数
  153. /// [data] 请求体数据(可选)
  154. /// [queryParameters] 查询参数
  155. /// [options] 请求选项
  156. ///
  157. /// 返回 [BaseCommonResponse] 对象
  158. static Future<BaseCommonResponse<T>> delete<T>(
  159. String path, {
  160. required T Function(Object? json) fromJsonT,
  161. dynamic data,
  162. Map<String, dynamic>? queryParameters,
  163. Options? options,
  164. CancelToken? cancelToken,
  165. }) async {
  166. try {
  167. final response = await dio.delete(
  168. path,
  169. data: data,
  170. queryParameters: queryParameters,
  171. options: options,
  172. cancelToken: cancelToken,
  173. );
  174. return BaseCommonResponse<T>.fromJson(
  175. response.data as Map<String, dynamic>,
  176. fromJsonT,
  177. );
  178. } on DioException {
  179. // 错误已由 ErrorInterceptor 处理,直接抛出
  180. rethrow;
  181. }
  182. }
  183. // ==================== PUT 请求 ====================
  184. /// PUT 请求
  185. ///
  186. /// [path] 请求路径(相对于 baseUrl)
  187. /// [fromJsonT] JSON 转换为 T 类型的函数
  188. /// [data] 请求体数据
  189. /// [queryParameters] 查询参数
  190. /// [options] 请求选项
  191. ///
  192. /// 返回 [BaseCommonResponse] 对象
  193. static Future<BaseCommonResponse<T>> put<T>(
  194. String path, {
  195. required T Function(Object? json) fromJsonT,
  196. dynamic data,
  197. Map<String, dynamic>? queryParameters,
  198. Options? options,
  199. CancelToken? cancelToken,
  200. ProgressCallback? onSendProgress,
  201. ProgressCallback? onReceiveProgress,
  202. }) async {
  203. try {
  204. final response = await dio.put(
  205. path,
  206. data: data,
  207. queryParameters: queryParameters,
  208. options: options,
  209. cancelToken: cancelToken,
  210. onSendProgress: onSendProgress,
  211. onReceiveProgress: onReceiveProgress,
  212. );
  213. return BaseCommonResponse<T>.fromJson(
  214. response.data as Map<String, dynamic>,
  215. fromJsonT,
  216. );
  217. } on DioException {
  218. // 错误已由 ErrorInterceptor 处理,直接抛出
  219. rethrow;
  220. }
  221. }
  222. // ==================== PATCH 请求 ====================
  223. /// PATCH 请求
  224. ///
  225. /// [path] 请求路径(相对于 baseUrl)
  226. /// [fromJsonT] JSON 转换为 T 类型的函数
  227. /// [data] 请求体数据
  228. /// [queryParameters] 查询参数
  229. /// [options] 请求选项
  230. ///
  231. /// 返回 [BaseCommonResponse] 对象
  232. static Future<BaseCommonResponse<T>> patch<T>(
  233. String path, {
  234. required T Function(Object? json) fromJsonT,
  235. dynamic data,
  236. Map<String, dynamic>? queryParameters,
  237. Options? options,
  238. CancelToken? cancelToken,
  239. ProgressCallback? onSendProgress,
  240. ProgressCallback? onReceiveProgress,
  241. }) async {
  242. try {
  243. final response = await dio.patch(
  244. path,
  245. data: data,
  246. queryParameters: queryParameters,
  247. options: options,
  248. cancelToken: cancelToken,
  249. onSendProgress: onSendProgress,
  250. onReceiveProgress: onReceiveProgress,
  251. );
  252. return BaseCommonResponse<T>.fromJson(
  253. response.data as Map<String, dynamic>,
  254. fromJsonT,
  255. );
  256. } on DioException {
  257. // 错误已由 ErrorInterceptor 处理,直接抛出
  258. rethrow;
  259. }
  260. }
  261. // ==================== 文件上传 ====================
  262. /// 单文件上传
  263. ///
  264. /// [path] 请求路径(相对于 baseUrl)
  265. /// [fromJsonT] JSON 转换为 T 类型的函数
  266. /// [file] 要上传的文件
  267. /// [fileKey] 文件字段名,默认为 'file'
  268. /// [fileName] 文件名,如果不提供则使用文件的原始名称
  269. /// [fields] 其他表单字段(可选)
  270. /// [queryParameters] 查询参数
  271. /// [options] 请求选项
  272. /// [onSendProgress] 上传进度回调
  273. ///
  274. /// 返回 [BaseCommonResponse] 对象
  275. ///
  276. /// 示例:
  277. /// ```dart
  278. /// final file = File('/path/to/image.jpg');
  279. /// final response = await DioClient.uploadFile(
  280. /// '/api/upload',
  281. /// file: file,
  282. /// fileKey: 'image',
  283. /// fileName: 'avatar.jpg',
  284. /// fields: {'userId': '123'},
  285. /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
  286. /// );
  287. /// ```
  288. static Future<BaseCommonResponse<T>> uploadFile<T>(
  289. String path, {
  290. required File file,
  291. required T Function(Object? json) fromJsonT,
  292. String fileKey = 'file',
  293. String? fileName,
  294. Map<String, dynamic>? fields,
  295. Map<String, dynamic>? queryParameters,
  296. Options? options,
  297. CancelToken? cancelToken,
  298. ProgressCallback? onSendProgress,
  299. }) async {
  300. try {
  301. // 检查文件是否存在
  302. if (!await file.exists()) {
  303. throw DioException(
  304. requestOptions: RequestOptions(path: path),
  305. error: '文件不存在: ${file.path}',
  306. type: DioExceptionType.unknown,
  307. );
  308. }
  309. // 构建文件名
  310. final name = fileName ?? file.path.split('/').last;
  311. // 创建 FormData
  312. final formData = FormData.fromMap({
  313. fileKey: await MultipartFile.fromFile(
  314. file.path,
  315. filename: name,
  316. ),
  317. if (fields != null) ...fields,
  318. });
  319. // 设置请求选项,使用 multipart/form-data
  320. final requestOptions = options ?? Options();
  321. requestOptions.headers ??= {};
  322. requestOptions.headers!['Content-Type'] = 'multipart/form-data';
  323. final response = await dio.post(
  324. path,
  325. data: formData,
  326. queryParameters: queryParameters,
  327. options: requestOptions,
  328. cancelToken: cancelToken,
  329. onSendProgress: onSendProgress,
  330. );
  331. return BaseCommonResponse<T>.fromJson(
  332. response.data as Map<String, dynamic>,
  333. fromJsonT,
  334. );
  335. } on DioException {
  336. // 错误已由 ErrorInterceptor 处理,直接抛出
  337. rethrow;
  338. }
  339. }
  340. /// 多文件上传
  341. ///
  342. /// [path] 请求路径(相对于 baseUrl)
  343. /// [fromJsonT] JSON 转换为 T 类型的函数
  344. /// [files] 要上传的文件列表
  345. /// [fileKey] 文件字段名,默认为 'files'
  346. /// [fields] 其他表单字段(可选)
  347. /// [queryParameters] 查询参数
  348. /// [options] 请求选项
  349. /// [onSendProgress] 上传进度回调
  350. ///
  351. /// 返回 [BaseCommonResponse] 对象
  352. ///
  353. /// 示例:
  354. /// ```dart
  355. /// final files = [
  356. /// File('/path/to/image1.jpg'),
  357. /// File('/path/to/image2.jpg'),
  358. /// ];
  359. /// final response = await DioClient.uploadFiles(
  360. /// '/api/upload/multiple',
  361. /// files: files,
  362. /// fileKey: 'images',
  363. /// fields: {'albumId': '123'},
  364. /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
  365. /// );
  366. /// ```
  367. static Future<BaseCommonResponse<T>> uploadFiles<T>(
  368. String path, {
  369. required List<File> files,
  370. required T Function(Object? json) fromJsonT,
  371. String fileKey = 'files',
  372. Map<String, dynamic>? fields,
  373. Map<String, dynamic>? queryParameters,
  374. Options? options,
  375. CancelToken? cancelToken,
  376. ProgressCallback? onSendProgress,
  377. }) async {
  378. try {
  379. if (files.isEmpty) {
  380. throw DioException(
  381. requestOptions: RequestOptions(path: path),
  382. error: '文件列表不能为空',
  383. type: DioExceptionType.unknown,
  384. );
  385. }
  386. // 检查所有文件是否存在
  387. for (final file in files) {
  388. if (!await file.exists()) {
  389. throw DioException(
  390. requestOptions: RequestOptions(path: path),
  391. error: '文件不存在: ${file.path}',
  392. type: DioExceptionType.unknown,
  393. );
  394. }
  395. }
  396. // 创建 MultipartFile 列表
  397. final multipartFiles = await Future.wait(
  398. files.map((file) async {
  399. final fileName = file.path.split('/').last;
  400. return MultipartFile.fromFile(
  401. file.path,
  402. filename: fileName,
  403. );
  404. }),
  405. );
  406. // 创建 FormData
  407. final formData = FormData.fromMap({
  408. fileKey: multipartFiles,
  409. if (fields != null) ...fields,
  410. });
  411. // 设置请求选项,使用 multipart/form-data
  412. final requestOptions = options ?? Options();
  413. requestOptions.headers ??= {};
  414. requestOptions.headers!['Content-Type'] = 'multipart/form-data';
  415. final response = await dio.post(
  416. path,
  417. data: formData,
  418. queryParameters: queryParameters,
  419. options: requestOptions,
  420. cancelToken: cancelToken,
  421. onSendProgress: onSendProgress,
  422. );
  423. return BaseCommonResponse<T>.fromJson(
  424. response.data as Map<String, dynamic>,
  425. fromJsonT,
  426. );
  427. } on DioException {
  428. // 错误已由 ErrorInterceptor 处理,直接抛出
  429. rethrow;
  430. }
  431. }
  432. /// 自定义 FormData 上传
  433. ///
  434. /// 提供更灵活的文件上传方式,可以自定义 FormData 的内容
  435. ///
  436. /// [path] 请求路径(相对于 baseUrl)
  437. /// [fromJsonT] JSON 转换为 T 类型的函数
  438. /// [formData] 自定义的 FormData 对象
  439. /// [queryParameters] 查询参数
  440. /// [options] 请求选项
  441. /// [onSendProgress] 上传进度回调
  442. ///
  443. /// 返回 [BaseCommonResponse] 对象
  444. ///
  445. /// 示例:
  446. /// ```dart
  447. /// final formData = FormData.fromMap({
  448. /// 'file1': await MultipartFile.fromFile('/path/to/file1.jpg'),
  449. /// 'file2': await MultipartFile.fromFile('/path/to/file2.jpg'),
  450. /// 'userId': '123',
  451. /// 'description': '上传描述',
  452. /// });
  453. /// final response = await DioClient.uploadFormData(
  454. /// '/api/upload/custom',
  455. /// formData: formData,
  456. /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
  457. /// );
  458. /// ```
  459. static Future<BaseCommonResponse<T>> uploadFormData<T>(
  460. String path, {
  461. required FormData formData,
  462. required T Function(Object? json) fromJsonT,
  463. Map<String, dynamic>? queryParameters,
  464. Options? options,
  465. CancelToken? cancelToken,
  466. ProgressCallback? onSendProgress,
  467. }) async {
  468. try {
  469. // 设置请求选项,使用 multipart/form-data
  470. final requestOptions = options ?? Options();
  471. requestOptions.headers ??= {};
  472. requestOptions.headers!['Content-Type'] = 'multipart/form-data';
  473. final response = await dio.post(
  474. path,
  475. data: formData,
  476. queryParameters: queryParameters,
  477. options: requestOptions,
  478. cancelToken: cancelToken,
  479. onSendProgress: onSendProgress,
  480. );
  481. return BaseCommonResponse<T>.fromJson(
  482. response.data as Map<String, dynamic>,
  483. fromJsonT,
  484. );
  485. } on DioException {
  486. // 错误已由 ErrorInterceptor 处理,直接抛出
  487. rethrow;
  488. }
  489. }
  490. }