319 lines
8.1 KiB
Dart
319 lines
8.1 KiB
Dart
/// 时间: 2025-03-21
|
||
/// 功能: HTTP客户端工具类(使用纯Dart dio库)
|
||
/// 介绍: 提供统一的HTTP请求方法,支持GET、POST等请求,用于项目中的网络请求
|
||
/// 最新变化: 移除CORS代理,需后台服务器配置CORS响应头
|
||
|
||
import 'dart:convert';
|
||
|
||
import 'package:dio/dio.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
|
||
class HttpClient {
|
||
static const String _baseUrl = 'https://yy.vogov.cn/api/';
|
||
static const Duration _timeout = Duration(seconds: 30);
|
||
|
||
static final BaseOptions _options = BaseOptions(
|
||
baseUrl: _baseUrl,
|
||
connectTimeout: _timeout,
|
||
receiveTimeout: _timeout,
|
||
sendTimeout: _timeout,
|
||
headers: {
|
||
'Content-Type': 'application/json; charset=UTF-8',
|
||
'Accept': 'application/json',
|
||
'User-Agent': 'Poes-Flutter/1.0.0',
|
||
},
|
||
);
|
||
|
||
static final Dio _dio = Dio(_options);
|
||
|
||
/// 添加调试日志
|
||
static void _debugLog(String message) {
|
||
if (kDebugMode) {
|
||
print('HttpClient: $message');
|
||
}
|
||
}
|
||
|
||
/// GET请求
|
||
static Future<HttpResponse> get(
|
||
String path, {
|
||
Map<String, dynamic>? queryParameters,
|
||
Map<String, String>? headers,
|
||
Duration? timeout,
|
||
}) async {
|
||
return _request(
|
||
'GET',
|
||
path,
|
||
queryParameters: queryParameters,
|
||
headers: headers,
|
||
timeout: timeout,
|
||
);
|
||
}
|
||
|
||
/// POST请求 - JSON格式
|
||
static Future<HttpResponse> post(
|
||
String path, {
|
||
Map<String, dynamic>? data,
|
||
Map<String, String>? headers,
|
||
Duration? timeout,
|
||
}) async {
|
||
return _request(
|
||
'POST',
|
||
path,
|
||
data: data,
|
||
headers: headers,
|
||
timeout: timeout,
|
||
);
|
||
}
|
||
|
||
/// POST请求 - FormData格式
|
||
static Future<HttpResponse> postForm(
|
||
String path, {
|
||
Map<String, dynamic>? data,
|
||
Map<String, String>? headers,
|
||
Duration? timeout,
|
||
}) async {
|
||
return _requestForm(
|
||
'POST',
|
||
path,
|
||
data: data,
|
||
headers: headers,
|
||
timeout: timeout,
|
||
);
|
||
}
|
||
|
||
/// 通用请求方法 - JSON格式
|
||
static Future<HttpResponse> _request(
|
||
String method,
|
||
String path, {
|
||
Map<String, dynamic>? queryParameters,
|
||
Map<String, dynamic>? data,
|
||
Map<String, String>? headers,
|
||
Duration? timeout,
|
||
}) async {
|
||
try {
|
||
final url = '$_baseUrl$path';
|
||
_debugLog('请求 $method $url');
|
||
if (queryParameters != null) {
|
||
_debugLog('查询参数: $queryParameters');
|
||
}
|
||
|
||
final options = Options(
|
||
method: method,
|
||
headers: headers != null
|
||
? {..._options.headers!, ...headers}
|
||
: _options.headers,
|
||
);
|
||
|
||
if (timeout != null) {
|
||
options.connectTimeout = timeout;
|
||
options.receiveTimeout = timeout;
|
||
options.sendTimeout = timeout;
|
||
}
|
||
|
||
Response response;
|
||
|
||
if (method.toUpperCase() == 'GET') {
|
||
response = await _dio.get(
|
||
url,
|
||
queryParameters: queryParameters,
|
||
options: options,
|
||
);
|
||
} else if (method.toUpperCase() == 'POST') {
|
||
response = await _dio.post(
|
||
url,
|
||
data: data,
|
||
queryParameters: queryParameters,
|
||
options: options,
|
||
);
|
||
} else {
|
||
throw UnsupportedError('HTTP method $method is not supported');
|
||
}
|
||
|
||
_debugLog('响应状态: ${response.statusCode}');
|
||
_debugLog('响应数据: ${response.data}');
|
||
|
||
return HttpResponse(
|
||
statusCode: response.statusCode ?? 0,
|
||
body: response.data is String
|
||
? response.data
|
||
: json.encode(response.data),
|
||
headers: response.headers.map.cast<String, String>(),
|
||
);
|
||
} on DioException catch (e) {
|
||
_debugLog('Dio异常: ${e.type} - ${e.message}');
|
||
String message;
|
||
switch (e.type) {
|
||
case DioExceptionType.connectionTimeout:
|
||
case DioExceptionType.sendTimeout:
|
||
case DioExceptionType.receiveTimeout:
|
||
message = '请求超时,请检查网络连接';
|
||
break;
|
||
case DioExceptionType.connectionError:
|
||
message = '网络连接失败,请检查网络设置';
|
||
break;
|
||
case DioExceptionType.badResponse:
|
||
message = '服务器错误: ${e.response?.statusCode} - ${e.response?.data}';
|
||
break;
|
||
default:
|
||
message = '请求失败: ${e.message}';
|
||
}
|
||
throw HttpException(message);
|
||
} catch (e) {
|
||
_debugLog('未知异常: $e');
|
||
throw HttpException('请求失败:$e');
|
||
}
|
||
}
|
||
|
||
/// FormData格式请求方法
|
||
static Future<HttpResponse> _requestForm(
|
||
String method,
|
||
String path, {
|
||
Map<String, dynamic>? queryParameters,
|
||
Map<String, dynamic>? data,
|
||
Map<String, String>? headers,
|
||
Duration? timeout,
|
||
}) async {
|
||
try {
|
||
final url = '$_baseUrl$path';
|
||
_debugLog('FormData请求 $method $url');
|
||
if (queryParameters != null) {
|
||
_debugLog('查询参数: $queryParameters');
|
||
}
|
||
if (data != null) {
|
||
_debugLog('表单数据: $data');
|
||
}
|
||
|
||
final formData = FormData.fromMap(data ?? {});
|
||
|
||
final options = Options(
|
||
method: method,
|
||
headers: headers != null
|
||
? {..._options.headers!, ...headers}
|
||
: _options.headers,
|
||
);
|
||
|
||
options.headers?['Content-Type'] = 'multipart/form-data';
|
||
|
||
if (timeout != null) {
|
||
options.connectTimeout = timeout;
|
||
options.receiveTimeout = timeout;
|
||
options.sendTimeout = timeout;
|
||
}
|
||
|
||
Response response;
|
||
|
||
if (method.toUpperCase() == 'POST') {
|
||
response = await _dio.post(
|
||
url,
|
||
data: formData,
|
||
queryParameters: queryParameters,
|
||
options: options,
|
||
);
|
||
} else {
|
||
throw UnsupportedError('FormData only supports POST method');
|
||
}
|
||
|
||
_debugLog('响应状态: ${response.statusCode}');
|
||
_debugLog('响应数据: ${response.data}');
|
||
|
||
return HttpResponse(
|
||
statusCode: response.statusCode ?? 0,
|
||
body: response.data is String
|
||
? response.data
|
||
: json.encode(response.data),
|
||
headers: response.headers.map.cast<String, String>(),
|
||
);
|
||
} on DioException catch (e) {
|
||
_debugLog('Dio异常: ${e.type} - ${e.message}');
|
||
String message;
|
||
switch (e.type) {
|
||
case DioExceptionType.connectionTimeout:
|
||
case DioExceptionType.sendTimeout:
|
||
case DioExceptionType.receiveTimeout:
|
||
message = '请求超时,请检查网络连接';
|
||
break;
|
||
case DioExceptionType.connectionError:
|
||
message = '网络连接失败,请检查网络设置';
|
||
break;
|
||
case DioExceptionType.badResponse:
|
||
message = '服务器错误: ${e.response?.statusCode} - ${e.response?.data}';
|
||
break;
|
||
default:
|
||
message = '请求失败: ${e.message}';
|
||
}
|
||
throw HttpException(message);
|
||
} catch (e) {
|
||
_debugLog('未知异常: $e');
|
||
throw HttpException('请求失败:$e');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// HTTP响应类
|
||
class HttpResponse {
|
||
final int statusCode;
|
||
final String body;
|
||
final Map<String, String> headers;
|
||
|
||
HttpResponse({
|
||
required this.statusCode,
|
||
required this.body,
|
||
required this.headers,
|
||
});
|
||
|
||
/// 是否成功 (2xx状态码)
|
||
bool get isSuccess => statusCode >= 200 && statusCode < 300;
|
||
|
||
/// 解析JSON响应
|
||
Map<String, dynamic> get jsonData {
|
||
try {
|
||
return json.decode(body) as Map<String, dynamic>;
|
||
} catch (e) {
|
||
throw FormatException('Invalid JSON response: $body');
|
||
}
|
||
}
|
||
|
||
/// 获取响应消息
|
||
String get message {
|
||
if (isSuccess) {
|
||
try {
|
||
return jsonData['msg'] ?? 'Success';
|
||
} catch (e) {
|
||
return 'Success';
|
||
}
|
||
} else {
|
||
return body;
|
||
}
|
||
}
|
||
|
||
/// 获取响应数据
|
||
dynamic get data {
|
||
if (isSuccess) {
|
||
return jsonData['data'];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// 获取响应代码
|
||
int get code {
|
||
if (isSuccess) {
|
||
try {
|
||
return jsonData['code'] ?? 0;
|
||
} catch (e) {
|
||
return 0;
|
||
}
|
||
}
|
||
return statusCode;
|
||
}
|
||
}
|
||
|
||
/// HTTP异常类
|
||
class HttpException implements Exception {
|
||
final String message;
|
||
|
||
const HttpException(this.message);
|
||
|
||
@override
|
||
String toString() => 'HttpException: $message';
|
||
}
|