From f5817af75976d4837049749bddcd2b8502a11636 Mon Sep 17 00:00:00 2001 From: zhuce <821293938@qq.com> Date: Thu, 7 Mar 2024 21:03:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0http=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/login_api.dart | 20 + lib/pages/login/login_page.dart | 43 +- lib/util/xhttp.dart | 826 ++++++++++++++++++++++++++++++++ pubspec.lock | 94 ++-- pubspec.yaml | 2 + 5 files changed, 943 insertions(+), 42 deletions(-) create mode 100644 lib/api/login_api.dart create mode 100644 lib/util/xhttp.dart diff --git a/lib/api/login_api.dart b/lib/api/login_api.dart new file mode 100644 index 0000000..e08b416 --- /dev/null +++ b/lib/api/login_api.dart @@ -0,0 +1,20 @@ +import 'package:autosos_flutter/util/xhttp.dart'; + +class LoginApi { + static XHttp xHttp = XHttp.getInstance(); + + static void login(int type, String username, String password, String code, + String cid, String openid, String unionid) async { + var data = { + "type": type, + "username": username, + "password": password, + "code": code, + "getui_cid": cid, + "wx_openid": openid, + "wx_unionid": unionid + }; + var post = await xHttp.post("/v2/auth/get-access-token", data); + print(post); + } +} diff --git a/lib/pages/login/login_page.dart b/lib/pages/login/login_page.dart index ace9081..38d889d 100644 --- a/lib/pages/login/login_page.dart +++ b/lib/pages/login/login_page.dart @@ -1,3 +1,5 @@ +import 'package:autosos_flutter/util/xhttp.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class LoginPage extends StatefulWidget { @@ -49,8 +51,8 @@ class _LoginPageState extends State { ), const Expanded( child: TextField( - decoration: InputDecoration(hintText: "请输入手机号"), - )), + decoration: InputDecoration(hintText: "请输入手机号"), + )), ], ), ), @@ -60,26 +62,37 @@ class _LoginPageState extends State { children: [ Expanded( child: TextField( - decoration: InputDecoration(hintText: "请输入密码"), - )) + decoration: InputDecoration(hintText: "请输入密码"), + )) ], ), ), Padding( - padding: const EdgeInsets.all(20), - child: Container( - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.only(top: 40), - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration(color: Colors.grey,borderRadius: BorderRadius.circular(21)), - child: const Center( - child: Text( - "登录", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ))), + padding: const EdgeInsets.all(20), + child: GestureDetector( + onTap:()=>_login(), + child: Container( + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(top: 40), + width: MediaQuery + .of(context) + .size + .width, + decoration: BoxDecoration(color: Colors.grey, + borderRadius: BorderRadius.circular(21)), + child: const Center( + child: Text( + "登录", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ))),) ) ], ), ); } + + void _login() async{ + + } } diff --git a/lib/util/xhttp.dart b/lib/util/xhttp.dart new file mode 100644 index 0000000..f00c135 --- /dev/null +++ b/lib/util/xhttp.dart @@ -0,0 +1,826 @@ +import 'dart:convert'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +/// 全局网络请求 dio 实例 单例 XHttp +class XHttp { + static const String GET = "GET"; + static const String POST = "POST"; + static const String PUT = "PUT"; + static const String PATCH = "PATCH"; + static const String DELETE = "DELETE"; + + static const CUSTOM_ERROR_CODE = 'DIO_CUSTOM_ERROR'; // 自定义错误代码 + static const REQUEST_TYPE_STR = 'REQUEST'; // 请求类型字符串 + static const RESPONSE_TYPE_STR = 'RESPONSE'; // 响应类型字符串 + static const ERROR_TYPE_STR = 'RESPONSE_ERROR'; // 错误类型字符串 + static const DEFAULT_LOAD_MSG = '请求中...'; // 默认请求提示文字 + + static const CONNECT_TIMEOUT = 60000; // 连接超时时间 + static const RECEIVE_TIMEOUT = 60000; // 接收超时时间 + static const SEND_TIMEOUT = 60000; // 发送超时时间 + + static const DIALOG_TYPE_OTHERS = 'OTHERS'; // 结果处理-其他类型 + static const DIALOG_TYPE_TOAST = 'TOAST'; // 结果处理-轻提示类型 + static const DIALOG_TYPE_ALERT = 'ALERT'; // 结果处理-弹窗类型 + static const DIALOG_TYPE_CUSTOM = 'CUSTOM'; // 结果处理-自定义处理 + + static String loadMsg = DEFAULT_LOAD_MSG; // 请求提示文字 + + static String errorShowTitle = '发生错误啦'; // 错误提示标题 + + static String errorShowMsg = ''; // 错误提示文字 + + static CancelToken cancelToken = CancelToken(); // 取消网络请求 token,默认所有请求都可取消。 + + static CancelToken whiteListCancelToken = CancelToken(); // 取消网络请求白名单 token,此 token 不会被取消。 + + final Map _pendingRequests = {}; // 正在请求列表 + + static Dio dio = Dio(); + // static Dio dio; + + String _getBaseUrl() => 'https:xxx/'; + /// 通用全局单例,第一次使用时初始化。 + XHttp._internal() { + if ('' == dio.options.baseUrl) { + dio = Dio(BaseOptions( + baseUrl: _getBaseUrl(), + // contentType: , + // responseType: , + headers: {'Content-Type': 'application/json'}, + connectTimeout: const Duration(milliseconds: CONNECT_TIMEOUT), + receiveTimeout: const Duration(milliseconds: RECEIVE_TIMEOUT), + sendTimeout: const Duration(milliseconds: SEND_TIMEOUT), + extra: {'cancelDuplicatedRequest': true}, // 是否取消重复请求 + )); + _init(); + } + } + + /// 获取单例本身 + static final XHttp _instance = XHttp._internal(); + + /// 取消重复的请求 + void _removePendingRequest(String tokenKey) { + if (_pendingRequests.containsKey(tokenKey)) { + // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除。 + _pendingRequests[tokenKey]?.cancel(tokenKey); + _pendingRequests.remove(tokenKey); + } + } + + /// 初始化 dio + void _init() { + // 添加拦截器 + dio.interceptors.add( + InterceptorsWrapper( + onRequest: (RequestOptions options, handler) async { + if (kDebugMode) { + print("请求之前"); + } + if (dio.options.extra['cancelDuplicatedRequest'] == true && options.cancelToken == null) { + String tokenKey = [ + options.method, + options.baseUrl + options.path, + jsonEncode(options.data ?? {}), + jsonEncode(options.queryParameters ?? {}) + ].join('&'); + _removePendingRequest(tokenKey); + options.cancelToken = CancelToken(); + options.extra['tokenKey'] = tokenKey; + _pendingRequests[tokenKey] = options.cancelToken!; + } + _handleRequest(options, handler); + // 有 token 时,添加 token。放打印日志后面,避免泄露 token。 + // 也可以登录成功后掉用 XHttp.setToken() 方法设置 token,但是持久化的话还是要这样最好。 + // String token = 'Bearer xxxxx'; + // if (token != dio.options.headers['authorization']) { + // dio.options.headers['authorization'] = token; + // options.headers['authorization'] = token; // 不设置的话第一次的请求会有问题,上面的是全局设置尚未对本条请求生效。 + // } + return handler.next(options); + }, + onResponse: (Response response, ResponseInterceptorHandler handler) { + if (kDebugMode) { + print("响应之前"); + } + _handleResponse(response, handler); + RequestOptions option = response.requestOptions; + if (dio.options.extra['cancelDuplicatedRequest'] == true && option.cancelToken == null) { + _removePendingRequest(option.extra['tokenKey']); + } + String code = (response?.data ?? {})['code'].toString(); + String msg = (response?.data ?? {})['message'] ?? response.statusMessage; + // // 静态数据 或者 根据后台实际返回结构解析,即 code == '200' 时,data 为有效数据。 + bool isSuccess = option.contentType != null && option.contentType!.contains("text") || code == '200'; + response.data = Result(response.data, isSuccess, response.statusCode!, msg, headers: response.headers); + return handler.next(response); + }, + onError: (DioException error, handler) { + if (kDebugMode) { + print("出错之前"); + } + _handleError(error); + if (!CancelToken.isCancel(error) && dio.options.extra['cancelDuplicatedRequest'] == true) { + _pendingRequests.clear(); // 不可抗力错误则清空列表 + } + // 发生错误同时也会返回一个 Result 结构,通过这个 Result 可以拿到响应状态等信息。 + if (error.response != null && error.response?.data != null) { + error.response?.data = Result( + error.response?.data, false, error.response!.statusCode!, errorShowMsg ?? error.response!.statusMessage!, + headers: error.response?.headers); + } else { + throw Exception(errorShowMsg); + } + return handler.next(error); + }, + ), + ); + // print("初始化 Dio 完成\n请求超时限制:$CONNECT_TIMEOUT ms\n接收超时限制:$RECEIVE_TIMEOUT ms\n发送超时限制:$SEND_TIMEOUT ms\nDio-BaseUrl:${dio.options.baseUrl}\nDio-Headers:${dio.options.headers}"); + } + + /// 请求 request 之前统一处理 + void _handleRequest(RequestOptions options, handler) { + Toast.hide(); + Toast.loading(loadMsg); + Map logData = { + 'url': options.baseUrl + options.path, + 'method': options.method, + 'headers': options.headers, + 'data': options.data ?? options.queryParameters, // GET 请求参数可以在 url 中,也可以使用 queryParameters,所以需要增加此判断。 + }; + _dealRequestInfo(logData, REQUEST_TYPE_STR); + } + + /// 响应 response 之前统一处理 + void _handleResponse(Response response, handler) { + Map logData = { + 'url': response.requestOptions.uri, + 'method': response.requestOptions.method, + 'headers': response.headers, + 'data': response.data, + 'statusCode': response.statusCode, + 'statusMessage': response.statusMessage, + }; + _dealRequestInfo(logData, RESPONSE_TYPE_STR); + Toast.hide(); + } + + /// 错误 error 统一处理 + void _handleError(DioException error) { + // 也可以在此处根据状态码并处理错误信息,例如退出登录等等。 + String errorTypeInfo = '其他错误!'; + switch (error.type) { + case DioExceptionType.connectionTimeout: + errorTypeInfo = '连接超时!'; + break; + case DioExceptionType.sendTimeout: + errorTypeInfo = "请求超时!"; + break; + case DioExceptionType.receiveTimeout: + errorTypeInfo = "响应超时!"; + break; + case DioExceptionType.badResponse: + errorTypeInfo = "服务异常!"; + break; + case DioExceptionType.cancel: + errorTypeInfo = "请求取消!"; + break; + case DioExceptionType.unknown: + default: + break; + } + Map logData = { + 'url': error.requestOptions.baseUrl + error.requestOptions.path, + 'method': error.requestOptions.method, + 'headers': error.response?.headers, + 'data': error.response?.data, + 'statusCode': error.response?.statusCode, + 'statusMessage': error.response?.statusMessage, + 'errorType': error.type, + 'errorMessage': error.message, + 'errorTypeInfo': errorTypeInfo, + }; + _dealRequestInfo(logData, ERROR_TYPE_STR); + Toast.hide(); + errorShowMsg = + "$errorShowTitle ${error.response?.statusCode ?? 'unknown'} $errorTypeInfo \n ${error.response?.statusMessage ?? ''} ${error.message ?? ''} \n ${error.response?.data ?? ''}"; + } + + /// 合并打印请求日志 REQUEST RESPONSE RESPONSE_ERROR + String _dealRequestInfo(Map logData, String logType) { + String logStr = "\n"; + logStr += "========================= $logType START =========================\n"; + logStr += "- URL: ${logData['url']} \n"; + logStr += "- METHOD: ${logData['method']} \n"; + // logStr += "- HEADER: \n { \n"; + // logStr += parseData(logData['headers']); + // logStr += "\n } \n"; + if (logData['data'] != null) { + logStr += "- ${logType}_BODY: \n"; + logStr += "!!!!!----------*!*##~##~##~##*!*##~##~##~##*!*----------!!!!! \n"; + logStr += "${parseData(logData['data'])} \n"; + logStr += "!!!!!----------*!*##~##~##~##*!*##~##~##~##*!*----------!!!!! \n"; + } + if (logType.contains(RESPONSE_TYPE_STR)) { + logStr += "- STATUS_CODE: ${logData['statusCode']} \n"; + logStr += "- STATUS_MSG: ${logData['message']} \n"; + } + if (logType == ERROR_TYPE_STR) { + logStr += "- ERROR_TYPE: ${logData['errorType']} \n"; + logStr += "- ERROR_MSG: ${logData['errorMessage']} \n"; + logStr += "- ERROR_TYPE_INFO: ${logData['errorTypeInfo']} \n"; + } + logStr += "========================= $logType E N D =========================\n"; + logWrapped(logStr); + return logStr; + } + + /// 统一结果提示处理 + Future _showResultDialog(Response? response, resultDialogConfig) async { + if (response == null) { + return; + } + resultDialogConfig = resultDialogConfig ?? {}; + String dialogType = resultDialogConfig['type'] ?? XHttp.DIALOG_TYPE_TOAST; + if (dialogType == XHttp.DIALOG_TYPE_OTHERS) { + return; // 其他类型 OTHERS 自定义处理 + } + bool isSuccess = response?.data?.success ?? false; + String msg = response?.data?.msg ?? '未知错误'; + if (dialogType == XHttp.DIALOG_TYPE_TOAST) { + // resultDialogConfig 可以有 successMsg, errorMsg + isSuccess + ? Toast.show(resultDialogConfig['successMsg'] ?? msg, type: Toast.SUCCESS) + : Toast.show(resultDialogConfig['errorMsg'] ?? msg, type: Toast.ERROR); + return; + } + if (dialogType == XHttp.DIALOG_TYPE_ALERT) { + // resultDialogConfig 可以有 title, content, closeable, showCancel, cancelText, confirmText, confirmCallback, cancelCallback, closeCallback ... + // Utils.showDialog(...); + return; + } + if (dialogType == XHttp.DIALOG_TYPE_CUSTOM) { + // resultDialogConfig 可以有 onSuceess, onError + if (isSuccess) { + if (resultDialogConfig['onSuccess'] != null) { + resultDialogConfig['onSuccess'](response.data); + } + } else { + if (resultDialogConfig['onError'] != null) { + resultDialogConfig['onError'](response.data); + } + } + } + } + + /// 处理异常 + void _catchOthersError(e) { + String errMsg = "${errorShowMsg ?? e}$CUSTOM_ERROR_CODE".split(CUSTOM_ERROR_CODE)[0]; + int errMsgLength = errMsg.length; + String errshowMsg = errMsgLength > 300 ? errMsg.substring(0, 150) : errMsg; + if (e is DioException) { + if (CancelToken.isCancel(e)) { + Toast.show('Cancel Request Successful'); // 取消重复请求可能会多次弹窗 + return; + } + Toast.show(errshowMsg, type: Toast.WARNING); + return; + } + Toast.show(errshowMsg + "\n......", type: Toast.ERROR); + } + + /// 本可以直接 XHttp.xxx 调用(添加 static 关键字给之后的 get/post 等方法),但是考虑多台服务器的情况,建议 XHttp.getInstance().xxx 调用。 + static XHttp getInstance({String? baseUrl, String? msg}) { + String targetBaseUrl = baseUrl ?? _instance._getBaseUrl(); + loadMsg = msg ?? DEFAULT_LOAD_MSG; + if (dio.options.baseUrl != targetBaseUrl) { + dio.options.baseUrl = targetBaseUrl; + } + return _instance; + } + + /// 取消普通请求 + static XHttp cancelRequest() { + Toast.hide(); + if (dio.options.extra['cancelDuplicatedRequest'] == true) { + _instance._pendingRequests.forEach((tokenKey, cancelToken) { + cancelToken.cancel('cancel request $tokenKey'); + }); + } else { + cancelToken.cancel('cancel request'); + cancelToken = CancelToken(); // 坑!取消后必须重新创建 cancelToken 否则后面使用原来 cancelToken 的请求会无效 + } + return _instance; + } + + /// 取消所有白名单 cancelToken 的请求 + static XHttp cancelWhiteListRequest() { + Toast.hide(); + whiteListCancelToken.cancel('cancel whiteList request'); + whiteListCancelToken = CancelToken(); + return _instance; + } + + /// 获取 cancelToken + static CancelToken getCancelToken() { + return cancelToken; + } + + /// 获取 whiteListCancelToken + static CancelToken getWhiteListCancelToken() { + return whiteListCancelToken; + } + + /// 获取一个新的 cancelToken + static CancelToken getNewCancelToken() { + return CancelToken(); + } + + /// get 请求 + Future get(String url, [Map? params, resultDialogConfig, bool isCancelWhiteList = false]) async { + // 可转为使用 request 代替,简化代码。 + // 写中括号可以忽略参数名称,因为必须按顺序传参。 + Response response = {} as Response; + CancelToken requestToken = CancelToken(); + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + if (params != null) { + response = await dio.get(url, queryParameters: params, cancelToken: requestToken); + return response.data; + } else { + response = await dio.get(url, cancelToken: requestToken); + return response.data; + } + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(response, resultDialogConfig); + } + } + + /// post 请求 + Future post(String url, [Map? data, resultDialogConfig, bool isCancelWhiteList = false]) async { + // 可转为使用 request 代替,简化代码。 + Response response; + CancelToken requestToken = CancelToken(); + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + response = await dio.post(url, data: data, cancelToken: requestToken); + return response.data; + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(null, resultDialogConfig); + } + } + + /// put 请求 + Future put(String url, [Map? data, resultDialogConfig, bool isCancelWhiteList = false]) async { + // 可转为使用 request 代替,简化代码。 + Response response = {} as Response; + CancelToken requestToken = {} as CancelToken ; + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + response = await dio.put(url, data: data, cancelToken: requestToken); + return response.data; + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(response, resultDialogConfig); + } + } + + /// patch 请求 + Future patch(String url, [Map? data, resultDialogConfig, bool isCancelWhiteList = false]) async { + // 可转为使用 request 代替,简化代码。 + Response response = {} as Response; + CancelToken requestToken = {} as CancelToken ; + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + response = await dio.patch(url, data: data, cancelToken: requestToken); + return response.data; + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(response, resultDialogConfig); + } + } + + /// delete 请求 + Future delete(String url, [Map? data, resultDialogConfig, bool isCancelWhiteList = false]) async { + // 可转为使用 request 代替,简化代码。 + Response response = {} as Response; + CancelToken requestToken = {} as CancelToken ; + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + response = await dio.delete(url, data: data, cancelToken: requestToken); + return response.data; + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(response, resultDialogConfig); + } + } + + /// request + static Future request( + String url, { + String method = XHttp.GET, + Map? queryParameters, + Map? data, + bool isCancelWhiteList = false, + resultDialogConfig, + Options? options, + void Function(int, int)? onSendProgress, + void Function(int, int)? onReceiveProgress, + String? msg, + required String baseUrl, + }) async { + XHttp.getInstance(baseUrl: baseUrl, msg: msg); + Response response = {} as Response; + CancelToken requestToken = {} as CancelToken ; + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + + try { + response = await dio.request( + url, + options: options ?? Options(method: method, contentType: Headers.formUrlEncodedContentType), + queryParameters: queryParameters, + data: data, + cancelToken: requestToken, + onReceiveProgress: onReceiveProgress, + onSendProgress: onSendProgress, + ); + return response.data; + } catch (e) { + _instance._catchOthersError(e); + } finally { + _instance._showResultDialog( + response, + resultDialogConfig ?? {'type': XHttp.DIALOG_TYPE_OTHERS}, + ); // request 请求默认都需自己处理结果 + } + } + + /// 下载文件 + Future downloadFile(urlPath, savePath, [resultDialogConfig, bool isCancelWhiteList = false]) async { + Response response = {} as Response; + CancelToken requestToken = {} as CancelToken; + if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + if (isCancelWhiteList) { + requestToken = whiteListCancelToken; + } else { + requestToken = cancelToken; + } + } + try { + response = await dio.download(urlPath, savePath, onReceiveProgress: (int count, int total) { + // 进度 + print("$count $total"); + }, cancelToken: requestToken); + return response.data; + } catch (e) { + _catchOthersError(e); + } finally { + _showResultDialog(response, resultDialogConfig); + } + } + + // /// post 表单请求 【Web】 + // Future postForm(String url, [Map params, resultDialogConfig, bool isCancelWhiteList = false]) async { + // Response response; + // var requestToken; + // if (dio.options.extra['cancelDuplicatedRequest'] != true || isCancelWhiteList) { + // if (isCancelWhiteList) { + // requestToken = whiteListCancelToken; + // } else { + // requestToken = cancelToken; + // } + // } + // try { + // response = await dio.post(url, queryParameters: params, cancelToken: requestToken); + // return response.data; + // } catch (e) { + // _catchOthersError(e); + // } finally { + // _showResultDialog(response, resultDialogConfig); + // } + // } + + /// +++++++++++++++++++++++++ 小扩展 【待增加:retry、代理/proxy、根据状态码自动退出与重连等】 +++++++++++++++++++++++++ + + /// 获取当前的 baseUrl + static String getBaseUrl() { + return dio.options.baseUrl; + } + + /// 设置当前的 baseUrl + static XHttp setBaseUrl(String baseUrl) { + dio.options.baseUrl = baseUrl; + return _instance; + } + + /// 获取当前 headers + static Map getHeaders() { + return dio.options.headers; + } + + /// 获取当前 headers 属性 + static dynamic getHeader(String key) { + return dio.options.headers[key]; + } + + /// 设置当前 headers + // static XHttp setHeaders(Map headers) { + // dio.options.headers = headers; + // return _instance; + // } + + /// 设置当前 headers 属性 + static XHttp setHeader(String key, String value) { + dio.options.headers[key] = value; + return _instance; + } + + /// 删除当前的请求头属性 + static XHttp removeHeader(String key) { + dio.options.headers.remove(key); + return _instance; + } + + /// 删除当前的所有请求头属性 + static XHttp removeAllHeaders() { + dio.options.headers.clear(); + return _instance; + } + + /// 获取当前的所有超时时间 + static Map getRequestTimeout() { + return { + 'connectTimeout': dio.options.connectTimeout, + 'receiveTimeout': dio.options.receiveTimeout, + 'sendTimeout': dio.options.sendTimeout + }; + } + + /// 设置当前的所有超时时间 + static XHttp setRequestTimeout(int timeout) { + dio.options.connectTimeout = Duration(milliseconds: timeout); + dio.options.receiveTimeout = Duration(milliseconds: timeout); + dio.options.sendTimeout = Duration(milliseconds: timeout); + return _instance; + } + + /// 设置当前的连接超时时间 + static XHttp setConnectTimeout(int timeout) { + dio.options.connectTimeout = Duration(milliseconds: timeout); + return _instance; + } + + /// 设置当前的接收超时时间 + static XHttp setReceiveTimeout(int timeout) { + dio.options.receiveTimeout = Duration(milliseconds: timeout); + return _instance; + } + + /// 设置当前的发送超时时间 + static XHttp setSendTimeout(int timeout) { + dio.options.sendTimeout = Duration(milliseconds: timeout); + return _instance; + } + + /// 获取用户数据 + static Map? getAuthUser() { + String token = dio.options.headers['authorization']; + if (null == token) { + return null; + } + // 解析token + return {'account': 'xxx', 'name': 'xxx', 'roles': 'xxx'}; + } + + /// 设置当前 token + static XHttp setAuthToken([String? token]) { + if (null == token) { + dio.options.headers.remove('authorization'); + } else { + dio.options.headers['authorization'] = token; + } + return _instance; + } + + /// 设置错误提示标题 + static XHttp setErrorTitle(String msg) { + errorShowTitle = msg; + return _instance; + } + + /// 判断是否是取消异常 + static bool isCancel(e) { + return CancelToken.isCancel(e); + } + +// /// 设置当前的请求数据格式 +// static XHttp setContentType(String contentType) { +// dio.options.contentType = contentType; +// return _instance; +// } + +// /// 设置当前的请求数据格式 +// static XHttp setContentTypeMultipartForm() { +// dio.options.contentType = "multipart/form-data"; +// return _instance; +// } + +// /// 设置当前的请求返回数据格式 +// static XHttp setDataType(ResponseType dataType) { +// dio.options.responseType = dataType; +// return _instance; +// } + +// /// 设置当前的请求返回数据格式 +// static XHttp setDataTypeJson() { +// dio.options.responseType = ResponseType.json; +// return _instance; +// } + +// ----- [cookie/charset/accept/encoder/decoder] 这些都可以通过设置 headers 实现 ----- +} + +/// ====================================================== 以下内容为工具方法 ====================================================== + +/// 解析数据 +String parseData(data) { + String responseStr = ""; + if (data is Map) { + responseStr += data.mapToStructureString(); + } else if (data is FormData) { + final formDataMap = Map() + ..addEntries(data.fields) + ..addEntries(data.files); + responseStr += formDataMap.mapToStructureString(); + } else if (data is List) { + responseStr += data.listToStructureString(); + } else { + responseStr += data.toString(); + } + return responseStr; +} + +/// 分段 log,可以写到 log 中。 +void logWrapped(String text) { + final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk + pattern.allMatches(text).forEach((match) => print(match.group(0))); +} + +/// Map 拓展,Map 转结构化字符串输出。 +extension Map2StringEx on Map { + String mapToStructureString({int indentation = 0, String space = " "}) { + if (this == null || this.isEmpty) { + return "$this"; + } + String result = ""; + String indentationContent = space * indentation; + result += "{"; + this.forEach((key, value) { + if (value is Map) { + result += "\n$indentationContent" + "\"$key\": ${value.mapToStructureString(indentation: indentation + 1)},"; + } else if (value is List) { + result += "\n$indentationContent" + "\"$key\": ${value.listToStructureString(indentation: indentation + 1)},"; + } else { + result += "\n$indentationContent" + "\"$key\": ${value is String ? "\"$value\"," : "$value,"}"; + } + }); + result = result.substring(0, result.length - 1); // 去掉最后一个逗号 + result += "\n$indentationContent}"; + return result; + } +} + +/// List 拓展,List 转结构化字符串输出。 +extension List2StringEx on List { + String listToStructureString({int indentation = 0, String space = " "}) { + if (this == null || this.isEmpty) { + return "$this"; + } + String result = ""; + String indentationContent = space * indentation; + result += "["; + for (var value in this) { + if (value is Map) { + result += + "\n$indentationContent" + space + "${value.mapToStructureString(indentation: indentation + 1)},"; // 加空格更好看 + } else if (value is List) { + result += value.listToStructureString(indentation: indentation + 1); + } else { + result += "\n$indentationContent" + value is String ? "\"$value\"," : "$value,"; + } + } + result = result.substring(0, result.length - 1); // 去掉最后一个逗号 + result += "\n$indentationContent]"; + return result; + } +} + +/// 结果处理 +class Result { + var data; + bool success; + int code; + String msg; + var headers; + Result(this.data, this.success, this.code, this.msg, {this.headers}); +} + +class Toast { + Toast._() { + // EasyLoading 已全局初始化构建 + // EasyLoading.instance.loadingStyle = EasyLoadingStyle.custom; + // 此处可自定义风格 + } + static final Toast _instance = Toast._(); + + static const String SUCCESS = "SUCCESS"; + static const String ERROR = "ERROR"; + static const String WARNING = "WARNING"; + static const String INFO = "INFO"; + + static loading(String msg) { + EasyLoading.show(status: msg); + } + + static progeress(double value, String msg) { + EasyLoading.showProgress(value, status: msg); + } + + static show(String msg, {String? type}) { + switch (type) { + case Toast.SUCCESS: + EasyLoading.showSuccess(msg); + break; + case Toast.ERROR: + EasyLoading.showError(msg); + break; + case Toast.WARNING: + EasyLoading.showInfo(msg); + break; + case Toast.INFO: + default: + EasyLoading.showToast(msg); + break; + } + } + + static hide() { + EasyLoading.dismiss(); + } +} + +// /// 使用示例:若未设置多个 baseUrl,可省略 getInstance(),记得给 get、post 设置 static 关键字或者直接初始化多个 baseUrl 的实例。也可以参考 request 在 get、post 方法中设置 baseUrl。 +// XHttp.getInstance().post("/user/login", { +// "username": username, +// "password": password +// }).then((res) { +// // DO SOMETHING +// }).catchError((err) { +// // DO SOMETHING +// }); \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 7d65171..7a23adc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: amap_flutter_base sha256: "9ef2439b8de7100cdd1b4357701b8ca8c059c0f2d9d0257b81750bbf0c6f53bb" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" amap_flutter_location: @@ -14,7 +14,7 @@ packages: description: name: amap_flutter_location sha256: f35ff00e196d579608e0bc28ccbc1f6f53787644702f947de941f775769cc701 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" amap_flutter_map: @@ -22,7 +22,7 @@ packages: description: name: amap_flutter_map sha256: "9cebb0b2f5fc7d3ae0427e99c41edc883e8f5459f6a28bc850f0f9e16918cf2f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" async: @@ -30,7 +30,7 @@ packages: description: name: async sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.11.0" boolean_selector: @@ -38,7 +38,7 @@ packages: description: name: boolean_selector sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" characters: @@ -46,7 +46,7 @@ packages: description: name: characters sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" clock: @@ -54,7 +54,7 @@ packages: description: name: clock sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" collection: @@ -62,7 +62,7 @@ packages: description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.18.0" cupertino_icons: @@ -70,15 +70,23 @@ packages: description: name: cupertino_icons sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.4.1" fake_async: dependency: transitive description: name: fake_async sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" flutter: @@ -86,12 +94,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_easyloading: + dependency: "direct main" + description: + name: flutter_easyloading + sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" flutter_plugin_android_lifecycle: @@ -99,20 +115,36 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.17" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: b39c753e909d4796906c5696a14daf33639a76e017136c8d82bf3e620ce5bb8e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.2.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" lints: dependency: transitive description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" matcher: @@ -120,7 +152,7 @@ packages: description: name: matcher sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.16" material_color_utilities: @@ -128,7 +160,7 @@ packages: description: name: material_color_utilities sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.0" meta: @@ -136,7 +168,7 @@ packages: description: name: meta sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" path: @@ -144,7 +176,7 @@ packages: description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.3" plugin_platform_interface: @@ -152,7 +184,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" sky_engine: @@ -165,7 +197,7 @@ packages: description: name: source_span sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" stack_trace: @@ -173,7 +205,7 @@ packages: description: name: stack_trace sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.11.1" stream_channel: @@ -181,7 +213,7 @@ packages: description: name: stream_channel sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" stream_transform: @@ -189,7 +221,7 @@ packages: description: name: stream_transform sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" string_scanner: @@ -197,7 +229,7 @@ packages: description: name: string_scanner sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" term_glyph: @@ -205,7 +237,7 @@ packages: description: name: term_glyph sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" test_api: @@ -213,15 +245,23 @@ packages: description: name: test_api sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: name: vector_math sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" web: @@ -229,7 +269,7 @@ packages: description: name: web sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.0" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index 75a64e1..d78250c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,8 @@ dependencies: amap_flutter_location: ^3.0.0 amap_flutter_map: ^3.0.0 + dio: ^5.4.1 + flutter_easyloading: ^3.0.5 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.