背景
我们知道dio
是一个非常强大的Dart
Http
请求库,支持非常多的功能,如果我们单单只是在外层包裹一层业务相关的封装,如果业务场景稍微复杂一点,这一层封装就显得臃肿和冗余了,当然我们的网络层本身也是为业务层服务,业务层有了需求,网络层自然得有响应了,只是响应的方式应该遵循低耦合、可用性、可维护性、可扩展性等原则。
设计
我在做iOS原生开发的时候深受Casa大神网络层设计的影响,这里将服务层(service
)和接口层(api
)划分开,这样做的好处有:
- 在面对不同服务时可单独配置相关的
http
强求配置,通常我们的应用在开发的过程中都不会只和一个服务交互,所以在这种场景下是非常有必要的。 - 服务层和接口层职责拆分,让他们做自己该做的事情。
实现
服务层
服务层的职责包括:baseUrl
的配置、服务公共请求头的配置、服务公共请求参数的配置、请求结果的加工、请求错误的统一处理等,除此之外还有token
验证、log
输出等。这里只提供一种封装思路具体的实现可以根据业务场景自行实现。
首先我们会创建一个抽象的基类,这里命名为Service
,因为每一次请求都应该有一个全新的dio
实例,Service
只需要记录一个配置信息
import 'package:dio/dio.dart';
abstract class ServiceConfig {
BaseOptions? options;
void initOptions();
Dio getDio() {
Dio dio = Dio(options);
initDio(dio);
//可以继续实现一些基础设置 如添加全局的插件、代理等
//dio.interceptors.add(),
//if (httpProxyHost != null && httpProxyPort != null) {
// dioSetHttpProxy(dio, httpProxyHost!, httpProxyPort!);
// }
);
return dio;
}
}
void initDio(Dio dio) {}
管理service配置
初始化service
后就需要保存ServiceConfig
的实例。这里有两种方案,之前的版本是通过维护一个serviceKey
来关联ServiceConfig
吗,然后在程序启动时去注册一下。在使用的过程发现注册一下确实比较麻烦,所以在后面的版本直接是将ServiceConfig
写成单例,效果是一样的,减少了serviceKey
的维护。
添加serviceKey
(老版本)
为了更好管理service
,这里引入一个service
来作为唯一标识。
import 'package:dio/dio.dart';
class Service {
final Dio dio = Dio();
}
配置options
在使用dio
时是需要配置options
的,在初始化实现ServiceConfig
时需要实现void initOptions();
方法。可以在此方法下可以配置Service
特有的属性,如下面的例子:
class MyService extends ServiceConfig {
static final MyService _singleton = MyService._internal();
factory MyService() {
return _singleton;
}
MyService._internal() {
initOptions();
}
@override
void initOptions() {
options = BaseOptions(
baseUrl: "http://baseurl",
connectTimeout: const Duration(milliseconds: 10000),
receiveTimeout: const Duration(milliseconds: 80000),
headers: {"Content-Type": 'application/json'},
contentType: "application/json");
}
}
添加公共请求头、公共参数
通过子类实现serviceHeader
、serviceQuery
、serviceBody
三个方法来配置Service
的配置。
import 'package:dio/dio.dart';
abstract class ServiceConfig {
BaseOptions? options;
Map<String, dynamic>? serviceHeader();
Map<String, dynamic>? serviceQuery();
Map<String, dynamic>? serviceBody();
}
添加公共结果处理、错误处理
在请求结果到达后可对Api
结果进行统一处理
import 'package:dio/dio.dart';
class ServiceConfig {
BaseOptions? options;
/*...省略其他设置...*/
Map<String, dynamic> responseFactory(Map<String, dynamic> dataMap) {
/*...可对请求结果进行加工...*/
return dataMap;
}
String errorFactory(DioError error) {
// 请求错误处理
String errorMessage = error.message;
/*...具体的错误处理...*/
return errorMessage;
}
}
Service
的管理(老版本)
这里是引入了一个manager
来管理多个Service
,manager
为一个单例,用于注册和管理Service
,实现也很简单,就使用一个serviceMap
来存放Service
,在使用服务前调用方法registeredService()
注册需要服务即可,如果需要批量注册或者注销等功能自行添加即可。
import 'service.dart';
class ServiceManager {
static final ServiceManager _instance = ServiceManager._internal();
factory ServiceManager() {
return _instance;
}
ServiceManager._internal() {
init();
}
Map serviceMap = {};
void init() {}
void registeredService(Service service) {
service.initDio();
String key = service.serviceKey();
serviceMap[key] = service;
}
}
Api层
Api
层的职责包括设置path
、请求参数的组装、请求method
配置等功能了。
定义一个自定义的method
enum RequestMethod { get, post, put, delete, patch, copy }
serviceKey的配置(老版本)
设置serviceKey
是为了指定该Api
属于哪一个服务的:
class BaseApi {
String serviceKey() {
return "";
}
}
加上path和method配置
abstract class BaseApi {
String path();
RequestMethod method();
ServiceConfig getService();
}
核心request方法
此处只实现了部分method
,如有需求自行实现即可:
class BaseApi {
/*...省略其他部分...*/
void request(
{Map<String, dynamic>? query,
Map<String, dynamic>? body,
Map<String, dynamic>? header,
required Function successCallBack,
required Function errorCallBack}) async {
//获取到对应的服务
Service service;
if (ServiceManager().serviceMap.containsKey(serviceKey())) {
service = ServiceManager().serviceMap[serviceKey()];
} else {
throw Exception('服务尚未注册');
}
Dio dio = service.dio;
Response? response;
Map<String, dynamic>? queryParams = {};
var globalQueryParams = service.serviceQuery();
if (globalQueryParams != null) {
queryParams.addAll(globalQueryParams);
}
if (query != null) {
queryParams.addAll(query);
}
Map<String, dynamic>? headerParams = {};
var globalHeaderParams = service.serviceHeader();
if (globalHeaderParams != null) {
headerParams.addAll(globalHeaderParams);
}
if (header != null) {
headerParams.addAll(header);
}
Map<String, dynamic>? bodyParams = {};
var globalBodyParams = service.serviceBody();
if (globalBodyParams != null) {
bodyParams.addAll(globalBodyParams);
}
if (body != null) {
bodyParams.addAll(body);
}
String url = path();
Options options = Options(headers: headerParams);
try {
switch (method()) {
case RequestMethod.get:
if (queryParams.isNotEmpty) {
response = await dio.get(url,
queryParameters: queryParams, options: options);
} else {
response = await dio.get(url, options: options);
}
break;
case RequestMethod.post:
if (body != null && body.isNotEmpty) {
response = await dio.post(url, data: body, options: options);
} else {
response = await dio.post(url, options: options);
}
break;
default:
}
} on DioError catch (error) {
errorCallBack(service.errorFactory(error));
}
if (response != null && response.data != null) {
String dataStr = json.encode(response.data);
Map<String, dynamic> dataMap = json.decode(dataStr);
dataMap = service.responseFactory(dataMap);
successCallBack(dataMap);
}
}
}
使用
实现服务层
继承于ServiceConfig
实现单例并完成相应的父类方法重写即可。
import 'package:dio/dio.dart';
import 'package:rm_http_request/request/service_config.dart';
class MyService extends ServiceConfig {
static final MyService _singleton = MyService._internal();
factory MyService() {
return _singleton;
}
MyService._internal() {
initOptions();
}
@override
Map<String, dynamic>? serviceBody() {
return null;
}
@override
Map<String, dynamic>? serviceHeader() {
Map<String, dynamic> header = <String, dynamic>{};
header["_langtype"] = "zh_CN";
return header;
}
@override
Map<String, dynamic>? serviceQuery() {
return null;
}
@override
void initOptions() {
options = BaseOptions(
baseUrl: "http://baseurl",
connectTimeout: const Duration(milliseconds: 10000),
receiveTimeout: const Duration(milliseconds: 80000),
headers: {"Content-Type": 'application/json'},
contentType: "application/json");
}
@override
String errorFactory(DioError error) {
// 请求错误处理
return "";
}
@override
Map<String, dynamic> responseFactory(
Map<String, dynamic> dataMap, api, response) {
return dataMap;
}
}
注册(老版本)
在网络请求前需要完成服务的注册:
void main() {
runApp(MyApp());
//注册服务
ServiceManager().registeredService(CustomService());
}
Api的实现
Api
的实现也是继承于BaseApi
,然后实现相关的父类方法重写即可:
class LoginApi extends BaseApi {
@override
RequestMethod method() => RequestMethod.post;
@override
String path() => "/user/login";
@override
ServiceConfig getService() => MyService();
void login(SuccessCallback successCallback, ErrorCallBack errorCallBack,
{required String account, required String password}) {
request(successCallback, errorCallBack,
body: {'account': account, 'password': password});
}
}
Api的调用
对上面的Api
发起请求例子:
void requestApi() {
LoginApi api = LoginApi();
api.login((json) {
print(json);
}, (error, code) {
print(error);
}, account: '1', password: 'w');
api.request(
body: {"account": "1", "password": 2},
(json) {
print(json);
},
(error, code) {
print(error);
},
);
}
ServiceConfig全部实现:
可根据需求自己去修改。
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:rm_http_request/request/base_api.dart';
abstract class ServiceConfig {
BaseOptions? options;
String? httpProxyHost;
String? httpProxyPort;
Map<String, dynamic>? serviceHeader();
Map<String, dynamic>? serviceQuery();
Map<String, dynamic>? serviceBody();
///获取到业务数据后的 数据加工
Map<String, dynamic> responseFactory(
Map<String, dynamic> dataMap, BaseApi api, Response response);
void initOptions();
Dio getDio() {
Dio dio = Dio(options);
initDio(dio);
if (httpProxyHost != null && httpProxyPort != null) {
dioSetHttpProxy(dio, httpProxyHost!, httpProxyPort!);
}
// dio.interceptors.add(
// LoggyDioInterceptor(
// requestHeader: true,
// requestBody: true,
// requestLevel: LogLevel.debug,
// responseHeader: true,
// responseBody: true,
// responseLevel: LogLevel.info)
// );
dio.interceptors.add(
LogInterceptor(
request: true,
requestBody: true,
requestHeader: true,
responseBody: true,
responseHeader: true,
logPrint: (object) {
log(object.toString());
},
),
);
return dio;
}
void dioSetHttpProxy(Dio dio, String host, String port) {
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
//proxy all request to localhost:8888
return "PROXY $host:$port"; //这里将localhost设置为自己电脑的IP,其他不变,注意上线的时候一定记得把代理去掉
};
return null;
};
}
void initDio(Dio dio) {}
String errorFactory(DioError error) {
// 请求错误处理
String errorMessage = error.response?.statusMessage ?? "";
switch (error.type) {
case DioErrorType.connectionTimeout:
errorMessage = "Connection timeout";
break;
case DioErrorType.receiveTimeout:
errorMessage = "Receive timeout";
break;
case DioErrorType.sendTimeout:
errorMessage = "Send timeout";
break;
case DioErrorType.badResponse:
try {
int? errCode = error.response?.statusCode;
switch (errCode) {
case 400:
errorMessage = "语法错误";
}
} on Exception catch (_) {
errorMessage = error.response?.statusMessage ?? "";
}
break;
// case DioErrorType.:
// errorMessage = error.response?.statusMessage ??"";
// break;
default:
errorMessage = error.response?.statusMessage ?? "";
}
return errorMessage;
}
}
BaseApi实现
import 'dart:convert';
import 'service_config.dart';
import 'package:dio/dio.dart';
enum RequestMethod {
get('GET'),
post('POST'),
put('PUT'),
delete('DELETE'),
patch('PATCH'),
copy('COPY');
final String value;
const RequestMethod(this.value);
}
typedef SuccessCallback = void Function(Map<String, dynamic> json);
typedef ErrorCallBack = void Function(String error, int code);
abstract class BaseApi {
String path();
RequestMethod method();
ServiceConfig getService();
void request(SuccessCallback successCallBack, ErrorCallBack errorCallBack,
{Map<String, dynamic>? query,
Map<String, dynamic>? body,
Map<String, dynamic>? header}) async {
Response? response;
ServiceConfig service = getService();
try {
response = await _baseRequest(query: query, body: body, header: header);
} on DioError catch (error) {
errorCallBack(service.errorFactory(error), 0);
}
if (response != null && response.data != null) {
Map<String, dynamic> dataMap = _responseFactory(service, response);
successCallBack(dataMap);
}
}
Future<Map<String, dynamic>> requestAsync(
{Map<String, dynamic>? query,
Map<String, dynamic>? body,
Map<String, dynamic>? header}) async {
Response? response;
ServiceConfig service = getService();
try {
response = await _baseRequest(query: query, body: body, header: header);
} on DioError catch (error) {
return {"success": false, "message": service.errorFactory(error)};
}
if (response.data != null) {
Map<String, dynamic> dataMap = _responseFactory(service, response);
return dataMap;
}
return {"success": false, "message": "请求失败"};
}
Future<Response<T>> _baseRequest<T>(
{Map<String, dynamic>? query,
Map<String, dynamic>? body,
Map<String, dynamic>? header}) async {
ServiceConfig service = getService();
Dio dio = service.getDio();
Map<String, dynamic> queryParams = _getQueryParams(service, query);
Map<String, dynamic> headerParams = _getHeaderParams(service, header);
Map<String, dynamic>? bodyParams = _getBodyParams(service, body);
String url = path();
Options options = Options(headers: headerParams, method: method().value);
return dio.request(url,
data: bodyParams, queryParameters: queryParams, options: options);
}
dynamic _responseFactory(ServiceConfig service, Response response) {
String dataStr = json.encode(response.data);
Map<String, dynamic> dataMap = json.decode(dataStr);
dataMap = service.responseFactory(dataMap, this, response);
return dataMap;
}
Map<String, dynamic> _getHeaderParams(
ServiceConfig service, Map<String, dynamic>? header) {
Map<String, dynamic> headerParams = {};
var globalHeaderParams = service.serviceHeader();
if (globalHeaderParams != null) {
headerParams.addAll(globalHeaderParams);
}
if (header != null) {
headerParams.addAll(header);
}
return headerParams;
}
Map<String, dynamic> _getQueryParams(
ServiceConfig service, Map<String, dynamic>? query) {
Map<String, dynamic>? queryParams = {};
var globalQueryParams = service.serviceQuery();
if (globalQueryParams != null) {
queryParams.addAll(globalQueryParams);
}
if (query != null) {
queryParams.addAll(query);
}
return queryParams;
}
Map<String, dynamic> _getBodyParams(
ServiceConfig service, Map<String, dynamic>? body) {
Map<String, dynamic>? bodyParams = {};
var globalBodyParams = service.serviceBody();
if (globalBodyParams != null) {
bodyParams.addAll(globalBodyParams);
}
if (body != null) {
bodyParams.addAll(body);
}
return bodyParams;
}
}
demo(demo未更新)