news_getx/lib/utils/http.dart

408 lines
14 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
}
}