408 lines
14 KiB
Dart
408 lines
14 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:dio/dio.dart';
|
||
import 'package:dio/io.dart';
|
||
import 'package:get/get.dart' hide FormData;
|
||
import 'package:news_getx/config/cache.dart';
|
||
import 'package:news_getx/config/proxy.dart';
|
||
import 'package:news_getx/config/server.dart';
|
||
import 'package:cookie_jar/cookie_jar.dart';
|
||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||
import 'package:news_getx/data/services/config.dart';
|
||
import 'package:news_getx/data/services/user.dart';
|
||
import 'package:news_getx/utils/loading.dart';
|
||
import 'package:news_getx/utils/net_cache.dart';
|
||
|
||
/*
|
||
* http 操作类
|
||
*
|
||
* 手册
|
||
* https://github.com/cfug/dio/blob/main/dio/README-ZH.md
|
||
*
|
||
* 从 4 升级到 5
|
||
* https://github.com/cfug/dio/blob/main/dio/migration_guide.md
|
||
*/
|
||
class HttpUtil {
|
||
late Dio dio;
|
||
CancelToken cancelToken = CancelToken();
|
||
|
||
// 每次调用都会重新生成一个dio对象
|
||
static HttpUtil _instance = HttpUtil._internal();
|
||
|
||
factory HttpUtil() => _instance;
|
||
|
||
HttpUtil._internal() {
|
||
// BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
|
||
BaseOptions options = BaseOptions(
|
||
// 请求基地址,可以包含子路径
|
||
baseUrl: ServerApiUrl,
|
||
//连接服务器超时时间,单位是毫秒.
|
||
connectTimeout: Duration(seconds: 10),
|
||
// 响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||
receiveTimeout: Duration(seconds: 5),
|
||
// Http请求头.
|
||
headers: {
|
||
// 云端Mock鉴权使用
|
||
"apifoxToken": ServerApiToken,
|
||
},
|
||
|
||
/// 请求的Content-Type,默认值是"application/json; charset=utf-8".
|
||
/// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
|
||
/// 可以设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio]
|
||
/// 就会自动编码请求体.
|
||
contentType: "application/json; charset=utf-8",
|
||
|
||
/// [responseType] 表示期望以那种格式(方式)接受响应数据。
|
||
/// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
|
||
///
|
||
/// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。
|
||
/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.
|
||
///
|
||
/// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.
|
||
responseType: ResponseType.json,
|
||
);
|
||
|
||
dio = Dio(options);
|
||
|
||
// 代理设置
|
||
if (!ConfigService.to.isRelease && ProxyEnable) {
|
||
dio.httpClientAdapter = IOHttpClientAdapter(
|
||
createHttpClient: () {
|
||
final client = HttpClient();
|
||
client.findProxy = (uri) {
|
||
return "PROXY $ProxyIP:$ProxyPort";
|
||
};
|
||
// 代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
|
||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||
return client;
|
||
}
|
||
);
|
||
}
|
||
|
||
// Cookie管理
|
||
CookieJar cookieJar = CookieJar();
|
||
// 增加拦截器CookieManager
|
||
dio.interceptors.add(CookieManager(cookieJar));
|
||
// 添加内存缓存
|
||
if (CacheEnable) {
|
||
dio.interceptors.add(NetCache());
|
||
}
|
||
|
||
// 添加拦截器
|
||
dio.interceptors.add(InterceptorsWrapper(
|
||
onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
|
||
// Do something before request is sent
|
||
return handler.next(options);
|
||
// 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。
|
||
// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
|
||
//
|
||
// 如果你想终止请求并触发一个错误,你可以返回一个`DioException`对象,如`handler.reject(error)`,
|
||
// 这样请求将被中止并触发异常,上层catchError会被调用。
|
||
},
|
||
onResponse: (response, handler) {
|
||
// Do something with response data
|
||
return handler.next(response);
|
||
// 如果你想终止请求并触发一个错误,你可以 reject 一个`DioException`对象,如`handler.reject(error)`,
|
||
// 这样请求将被中止并触发异常,上层catchError会被调用。
|
||
},
|
||
onError: (DioException err, ErrorInterceptorHandler handler) {
|
||
// Do something with response error
|
||
Loading.dismiss();
|
||
ErrorEntity entity = createErrorEntity(err);
|
||
onError(entity);
|
||
return handler.next(err);
|
||
// 如果你想完成请求并返回一些自定义数据,可以resolve 一个`Response`,如`handler.resolve(response)`。
|
||
// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
|
||
},
|
||
));
|
||
}
|
||
|
||
/// 统一错误处理
|
||
void onError(ErrorEntity errorEntity) {
|
||
print(
|
||
'error.code -> ${errorEntity.code}, error.message -> ${errorEntity.message}');
|
||
switch (errorEntity.code) {
|
||
case 401:
|
||
// 会话过期 返回到登录页
|
||
UserService.to.onLogout();
|
||
EasyLoading.showError(errorEntity.message);
|
||
break;
|
||
default:
|
||
EasyLoading.showError('未知错误');
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 错误信息
|
||
ErrorEntity createErrorEntity(DioException error) {
|
||
switch (error.type) {
|
||
case DioExceptionType.cancel:
|
||
return ErrorEntity(code: -1, message: "请求取消");
|
||
case DioExceptionType.connectionTimeout:
|
||
return ErrorEntity(code: -1, message: "连接超时");
|
||
case DioExceptionType.sendTimeout:
|
||
return ErrorEntity(code: -1, message: "请求超时");
|
||
case DioExceptionType.receiveTimeout:
|
||
return ErrorEntity(code: -1, message: "响应超时");
|
||
case DioExceptionType.badResponse:
|
||
{
|
||
try {
|
||
int errCode =
|
||
error.response != null ? error.response!.statusCode! : -1;
|
||
// String errMsg = error.response.statusMessage;
|
||
// return ErrorEntity(code: errCode, message: errMsg);
|
||
switch (errCode) {
|
||
case 400:
|
||
return ErrorEntity(code: errCode, message: "请求语法错误");
|
||
case 401:
|
||
return ErrorEntity(code: errCode, message: "没有权限");
|
||
case 403:
|
||
return ErrorEntity(code: errCode, message: "服务器拒绝执行");
|
||
case 404:
|
||
return ErrorEntity(code: errCode, message: "无法连接服务器");
|
||
case 405:
|
||
return ErrorEntity(code: errCode, message: "请求方法被禁止");
|
||
case 500:
|
||
return ErrorEntity(code: errCode, message: "服务器内部错误");
|
||
case 502:
|
||
return ErrorEntity(code: errCode, message: "无效的请求");
|
||
case 503:
|
||
return ErrorEntity(code: errCode, message: "服务器挂了");
|
||
case 505:
|
||
return ErrorEntity(code: errCode, message: "不支持HTTP协议请求");
|
||
default:
|
||
{
|
||
// return ErrorEntity(code: errCode, message: "未知错误");
|
||
return ErrorEntity(
|
||
code: errCode,
|
||
message: error.response != null
|
||
? error.response!.statusMessage!
|
||
: "",
|
||
);
|
||
}
|
||
}
|
||
} on Exception catch (_) {
|
||
return ErrorEntity(code: -1, message: "未知错误");
|
||
}
|
||
}
|
||
default:
|
||
{
|
||
return ErrorEntity(code: -1, message: error.message ?? "未定义错误");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 取消请求
|
||
/// 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
|
||
/// 所以参数可选
|
||
void cancelRequests(CancelToken token) {
|
||
token.cancel("cancelled");
|
||
}
|
||
|
||
/// 读取本地配置
|
||
Map<String, dynamic>? getAuthorizationHeader() {
|
||
var headers = <String, dynamic>{};
|
||
if (Get.isRegistered<UserService>() && UserService.to.hasToken) {
|
||
headers['Authorization'] = 'Bearer ${UserService.to.token}';
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
/// restful get 操作
|
||
/// refresh 是否下拉刷新 默认 false
|
||
/// noCache 是否不缓存 默认 true
|
||
/// list 是否列表 默认 false
|
||
/// cacheKey 缓存key
|
||
/// cacheDisk 是否磁盘缓存
|
||
Future<dynamic> get(
|
||
String path, {
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
bool refresh = false,
|
||
bool noCache = !CacheEnable,
|
||
bool list = false,
|
||
String? cacheKey,
|
||
bool cacheDisk = false,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
// 设置自定义字段 用于后续使用
|
||
requestOptions.extra ??= <String, dynamic>{
|
||
"refresh": refresh,
|
||
"noCache": noCache,
|
||
"list": list,
|
||
"cacheKey": cacheKey,
|
||
"cacheDisk": cacheDisk,
|
||
};
|
||
// 设置头信息
|
||
requestOptions.headers ??= {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
|
||
var response = await dio.get(
|
||
path,
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful post 操作
|
||
Future post(
|
||
String path, {
|
||
dynamic data,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
var response = await dio.post(
|
||
path,
|
||
data: data,
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful put 操作
|
||
Future put(
|
||
String path, {
|
||
dynamic data,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
var response = await dio.put(
|
||
path,
|
||
data: data,
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful patch 操作
|
||
Future patch(
|
||
String path, {
|
||
dynamic data,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
var response = await dio.patch(
|
||
path,
|
||
data: data,
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful delete 操作
|
||
Future delete(
|
||
String path, {
|
||
dynamic data,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
var response = await dio.delete(
|
||
path,
|
||
data: data,
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful post form 表单提交操作
|
||
Future postForm(
|
||
String path, {
|
||
required Map<String, dynamic> data,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
var response = await dio.post(
|
||
path,
|
||
data: FormData.fromMap(data),
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
|
||
/// restful post Stream 流数据
|
||
Future postStream(
|
||
String path, {
|
||
dynamic data,
|
||
int dataLength = 0,
|
||
Map<String, dynamic>? queryParameters,
|
||
Options? options,
|
||
}) async {
|
||
Options requestOptions = options ?? Options();
|
||
requestOptions.headers = requestOptions.headers ?? {};
|
||
Map<String, dynamic>? authorization = getAuthorizationHeader();
|
||
if (authorization != null) {
|
||
requestOptions.headers!.addAll(authorization);
|
||
}
|
||
requestOptions.headers!.addAll({
|
||
Headers.contentLengthHeader: dataLength.toString(),
|
||
});
|
||
var response = await dio.post(
|
||
path,
|
||
data: Stream.fromIterable(data.map((e) => [e])),
|
||
queryParameters: queryParameters,
|
||
options: requestOptions,
|
||
cancelToken: cancelToken,
|
||
);
|
||
return response.data;
|
||
}
|
||
}
|
||
|
||
class ErrorEntity implements Exception {
|
||
int code = -1;
|
||
String message = "";
|
||
|
||
ErrorEntity({required this.code, required this.message});
|
||
|
||
@override
|
||
String toString() {
|
||
if (message.isEmpty) return "Exception";
|
||
return "Exception: code $code, $message";
|
||
}
|
||
}
|