import 'package:dio/dio.dart'; import 'package:flustars/flustars.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart' hide Response; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import '../kt_pages/kt_routes.dart'; import '../kt_utils/kt_keys.dart'; import '../kt_utils/kt_toast_utils.dart'; import '../kt_utils/kt_user_utils.dart'; import 'kt_apis.dart'; import 'kt_request_interceptor.dart'; /// HTTP请求配置类 class HttpConfig { static const String baseUrl = KtApis.baseUrl; static const int connectTimeout = 5000; // 连接超时时间(ms) static const int receiveTimeout = 15000; // 接收超时时间(ms) static const int sendTimeout = 5000; // 发送超时时间(ms) } /// API响应结果类 class ApiResponse { final bool success; final T? data; final String? message; final int? code; ApiResponse({required this.success, this.data, this.message, this.code}); factory ApiResponse.fromJson(Map json) { return ApiResponse( success: json['success'] ?? false, data: json['data'], message: json['msg'], code: json['code'], ); } Map toJson() { return {'success': success, 'data': data, 'message': message, 'code': code}; } } /// HTTP请求方法枚举 enum HttpMethod { get, post, put, delete, patch } /// 统一的API异常类 class ApiException implements Exception { final int? code; final String message; final dynamic error; ApiException({this.code, required this.message, this.error}); @override String toString() { return 'ApiException{code: $code, message: $message, error: $error}'; } } /// Dio请求封装类 class HttpClient { static final HttpClient _instance = HttpClient._internal(); factory HttpClient() => _instance; late Dio _dio; HttpClient._internal() { _initDio(); } void _initDio() { BaseOptions options = BaseOptions( baseUrl: HttpConfig.baseUrl, connectTimeout: Duration(seconds: HttpConfig.connectTimeout), receiveTimeout: Duration(seconds: HttpConfig.receiveTimeout), sendTimeout: Duration(seconds: HttpConfig.sendTimeout), responseType: ResponseType.json, ); _dio = Dio(options); _dio.interceptors.add(RequestInterceptor()); // 添加拦截器 _dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { return handler.next(options); }, onResponse: (response, handler) { return handler.next(response); }, onError: (DioException e, handler) { if (kDebugMode) { print('请求错误: ${e.message}'); if (e.response != null) { print('错误响应: ${e.response!.data}'); } } return handler.next(_handleError(e)); }, ), ); _dio.interceptors.add( PrettyDioLogger(requestBody: true, requestHeader: true), ); } /// 处理Dio错误 DioException _handleError(DioException e) { switch (e.type) { case DioExceptionType.connectionTimeout: return DioException( requestOptions: e.requestOptions, error: ApiException(code: -1, message: 'Connection timeout'), ); case DioExceptionType.sendTimeout: return DioException( requestOptions: e.requestOptions, error: ApiException(code: -2, message: 'Connection timeout'), ); case DioExceptionType.receiveTimeout: return DioException( requestOptions: e.requestOptions, error: ApiException(code: -3, message: 'Connection timeout'), ); case DioExceptionType.badResponse: final int? statusCode = e.response?.statusCode; String message = 'Connection failed'; if (statusCode != null) { switch (statusCode) { case 400: message = '请求参数错误'; break; case 401: message = 'Token已过期'; SpUtil.remove(KtKeys.token); clearAuthToken(); Get.offAllNamed(KtRoutes.splash); break; case 402: message = '未授权,请先登录'; // ToastUtils.show( // 'Please log out of the account on other devices in time, otherwise the App on other devices will be unavailable.', // ); KtUserUtil().register(toHome: false); // SpUtil.remove(SpKeys.token); // clearAuthToken(); // Get.offAllNamed(AppRoutes.splash); break; case 403: message = '禁止访问,权限不足'; break; case 404: message = '请求资源不存在'; break; case 500: message = '服务器内部错误'; break; case 502: message = '网关错误'; break; case 503: message = '服务不可用'; break; case 504: message = '网关超时'; break; } } return DioException( requestOptions: e.requestOptions, error: ApiException( code: statusCode ?? -4, message: message, error: e.response?.data, ), ); case DioExceptionType.cancel: return DioException( requestOptions: e.requestOptions, error: ApiException(code: -5, message: '请求已取消'), ); case DioExceptionType.unknown: return DioException( requestOptions: e.requestOptions, error: ApiException(code: -6, message: '未知错误,请稍后再试', error: e.error), ); default: return e; } } /// 统一的请求方法 Future> request( String path, { HttpMethod method = HttpMethod.post, dynamic data, Map? queryParameters, Options? options, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, T Function(dynamic data)? parser, bool showErrorToast = false, }) async { try { Options requestOptions = options ?? Options(); requestOptions.method = method.name.toUpperCase(); Response response; switch (method) { case HttpMethod.get: response = await _dio.get( path, queryParameters: queryParameters, options: requestOptions, onReceiveProgress: onReceiveProgress, ); break; case HttpMethod.post: response = await _dio.post( path, data: data, queryParameters: queryParameters, options: requestOptions, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); break; case HttpMethod.put: response = await _dio.put( path, data: data, queryParameters: queryParameters, options: requestOptions, ); break; case HttpMethod.delete: response = await _dio.delete( path, data: data, queryParameters: queryParameters, options: requestOptions, ); break; case HttpMethod.patch: response = await _dio.patch( path, data: data, queryParameters: queryParameters, options: requestOptions, ); break; } // 解析响应数据 if (response.statusCode == 200) { final responseData = response.data; if (responseData['code'] == 200) { // 如果有自定义解析器,使用解析器处理数据 if (parser != null) { return ApiResponse(success: true, data: parser(responseData)); } // 默认解析 if (responseData is Map) { responseData.putIfAbsent('success', () => true); return ApiResponse.fromJson(responseData); } else { return ApiResponse(success: true, data: responseData as T?); } } else { if (showErrorToast) KtToastUtils.showSuccess(placeholder: responseData['msg']); return ApiResponse( success: false, message: responseData['msg'], data: responseData as T?, ); } } else { throw ApiException( code: response.statusCode, message: '请求失败,状态码: ${response.statusCode}', error: response.data, ); } } catch (e) { if (e is ApiException) { rethrow; } else { throw ApiException(code: -1000, message: '请求发生异常: $e', error: e); } } } /// 设置认证令牌 void setAuthToken(String token) { _dio.options.headers['Authorization'] = token; // _dio.options.headers['Authorization'] = 'Bearer $token'; } /// 清除认证令牌 void clearAuthToken() { _dio.options.headers.remove('Authorization'); } /// 添加自定义请求头字段 void addHeader(String key, String value) { _dio.options.headers[key] = value; } /// 批量添加自定义请求头字段 void addHeaders(Map headers) { headers.forEach((key, value) => _dio.options.headers[key] = value); } /// 移除指定请求头字段 void removeHeader(String key) { _dio.options.headers.remove(key); } /// 清除所有自定义请求头字段 void clearHeaders() { // 保留基础的Content-Type final contentType = _dio.options.headers['Content-Type']; _dio.options.headers.clear(); if (contentType != null) { _dio.options.headers['Content-Type'] = contentType; } } }