// API 客户端 — Dio 封装 + JWT 注入 + 离线感知 // // 核心职责: // - 封装 Dio HTTP 客户端,统一配置超时和头信息 // - JWT token 自动注入(请求拦截器) // - 离线状态感知(网络不可用时抛出明确异常) // - 为 SyncEngine 提供远程操作能力 import 'package:dio/dio.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; /// 网络离线异常 — 网络不可用时由 ApiClient 抛出 class OfflineException implements Exception { final String message; const OfflineException([this.message = '网络不可用,请检查网络连接']); @override String toString() => 'OfflineException: $message'; } /// API 客户端 — 所有远程请求的统一入口 class ApiClient { late final Dio _dio; String? _token; /// 基础 URL,默认指向本地开发服务器 final String baseUrl; ApiClient({this.baseUrl = 'http://localhost:8080/api/v1'}) { _dio = Dio(BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 30), sendTimeout: const Duration(seconds: 30), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, )); // 请求拦截器:注入 JWT token _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { if (_token != null) { options.headers['Authorization'] = 'Bearer $_token'; } handler.next(options); }, )); // 响应拦截器:统一错误处理 _dio.interceptors.add(InterceptorsWrapper( onError: (error, handler) { // 401 时自动清除 token(需要重新登录) if (error.response?.statusCode == 401) { _token = null; } handler.next(error); }, )); } /// 设置 JWT token(登录成功后调用) void setToken(String token) => _token = token; /// 清除 JWT token(退出登录时调用) void clearToken() => _token = null; /// 当前是否已登录 bool get isAuthenticated => _token != null; /// 检查网络是否可用 Future _isOnline() async { final result = await Connectivity().checkConnectivity(); // connectivity_plus 返回 List return result.any((r) => r != ConnectivityResult.none); } /// 确保网络可用,否则抛出 OfflineException Future _ensureOnline() async { final online = await _isOnline(); if (!online) { throw const OfflineException(); } } // ===== CRUD 方法 ===== /// GET 请求 Future> get( String path, { Map? queryParams, }) async { await _ensureOnline(); return _dio.get(path, queryParameters: queryParams); } /// POST 请求(创建资源) Future> post( String path, { dynamic data, }) async { await _ensureOnline(); return _dio.post(path, data: data); } /// PUT 请求(更新资源) Future> put( String path, { dynamic data, }) async { await _ensureOnline(); return _dio.put(path, data: data); } /// DELETE 请求 Future> delete( String path, { dynamic data, }) async { await _ensureOnline(); return _dio.delete(path, data: data); } /// PATCH 请求(部分更新) Future> patch( String path, { dynamic data, }) async { await _ensureOnline(); return _dio.patch(path, data: data); } /// 文件上传(multipart/form-data) Future> upload( String path, { required String filePath, required String fileName, String fieldName = 'file', Map? extraFields, }) async { await _ensureOnline(); final formData = FormData.fromMap({ fieldName: await MultipartFile.fromFile(filePath, filename: fileName), ...?extraFields, }); return _dio.post(path, data: formData); } }