0%

网络请求架构优化

记录应用内网络请求相关架构的调整,调用优化方向的思考与实践

项目中使用了dio自封装的轻量网络请求方案,但是对于新增接口的接入、Model数据结构的管理、Api接口调用等,在使用上并不太友好便捷,大大的影响了团队的开发效率。

现状

项目内当前使用的方案

  1. 底层封装
    将dio相关请求封装成底层单例类,所有的请求最后都会走到该单例内部,执行网络请求,并返回result

  2. 暴露了一个抽象的BaseRequest类给到应用层,所有的Api接口地址,参数,loading行为都放到具体的实现类中完成,实现类需要集成该BaseRequest类,并配置响应的接口参数。

  3. 静态调用,使用静态方法封装上述流程,返回裸result给到应用层

该方案的具体问题

  1. 每次需要新增接口的时候,应用层都需要新增一个类来继承BaseRequest,配置接口&参数。这个新增类只是为了配置接口,却引入了一个文件,不是很合理

  2. 调用人员通过方法只能拿到result裸返回数据,并不知道其具体的数据结构,需要自己处理,所以使用中出现了很多地方先判断返回码再解析的问题,出现很多 result[‘code’] == 200 这种东西,同时数据结构的解析也需要参考之前调用的地方,去copy过来,使用上并不方便便捷。

优化方向

  1. 根据上述分析,我希望每次新增接口只写一个静态方法即可,在该静态方法中去配置请求地址和参数,这样只需要引入一个方法,减少了文件引入的问题。

  2. 我是调用者,我只关注是否调用成功,成功后应该给我对应的数据结构,而不是让我去看别人的代码再copy代码来解析。

  3. 是否可以把调用成功的判断统一起来,不要每个调用接口的地方都去自己写判断,能否用一行代码处理呢。

  4. 是否有类似java的反射方案,自动使用泛型来处理裸返回数据的解析?

优化

配置优化

使用CommonRequest去实现BaseRequest,并把配置地址和参数作为构造参数传入,这样就不用每次引入一个新的文件去配置了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
///通用请求类
class CommonRequest extends BaseRequest {
CommonRequest(this._path, {this.isNeedLogin = true});


final String _path;

final bool isNeedLogin;

@override
bool get needLogin => isNeedLogin;

@override
String get path => _path;
}

响应返回优化

使用BaseResModel封装response裸数据返回,把返回的数据解析成BaseResModel,对返回的code和msg做统一处理,统一封装请求是否成功的判断,介于需要根据response构建数据,这里传入一个泛型T,response需要根据T来构建返回结构体。
dart中必须要运行时才能确认泛型的具体数据,所以类型T并不能被要求提供特定的构建方法,基于此,可以把构建方法传进来,返回结构体根据该构建方法来创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class BaseResModel<T> {
BaseResModel({
required this.code,
required this.msg,
required this.data,
});

///后台接口原始数据包装字段
final int code;
final String msg;
final dynamic data;

///设置解析数据结果,解析结果必须通过get方法获取,提供统一的数据获取方式
_setData(T? dataT, List<T> listT) {
_dataT = dataT;
_listT = listT;
}

///实际的解析数据实体,不提供解析方法的情况下为null
T? _dataT;
List<T> _listT = [];

///外部获取数据的方法
T? get getData => _listT.firstOrNull;

///外部获取List数据的方法
List<T> get getList => _listT;

///后台响应成功
bool get responseOk => code == ErrorCode.SUCCESS;

///请求成功且有响应数据
bool get hasResponseData =>
responseOk && data != null && (_dataT != null || _listT.isNotEmpty);

///工厂方法构造返回结构数据,参数:Map<String,dynamic> json, convertT实例转换方法,listFromData用于从返回数据内部指定一个字段数据来解析
factory BaseResModel.fromJson(dynamic json,
{T Function(dynamic jsonData)? convertT,
dynamic Function(dynamic responseData)? listFromData}) {
///先对数据判空和格式判断
if (json == null || json is! Map<String, dynamic>) {
return BaseResModel._failed();
}

///解析成T类型数据
T? parseDataT(fromArg, T Function(dynamic jsonData)? convertT) {
return (fromArg != null && fromArg is! List)
? convertT?.call(fromArg)
: null;
}

///解析成T类型数组数据
List<T> parseDataToListT(fromArg, T Function(dynamic jsonData)? convertT) {
return (fromArg != null && fromArg is List) && convertT != null
? parseList(fromArg, convertT)
: [];
}

///如果listFromData方法存在,则不是直接解析接口返回数据的data字段,而是data下的某个字段,此时dataT为空
BaseResModel<T> baseResModel = BaseResModel(
code: json["code"] ?? -1,
msg: json["msg"] ?? "",
data: json['data'],
);
bool isListFromData = listFromData != null;
final dataT = isListFromData ? null : parseDataT(json['data'], convertT);
final listT = parseDataToListT(
isListFromData ? listFromData(json['data']) : json['data'], convertT);
if (dataT != null) {
listT.add(dataT as T);
}
baseResModel._setData(dataT, listT);
return baseResModel;
}

///接口未调通的请求异常
factory BaseResModel._failed() {
return BaseResModel(code: -1, msg: '', data: null);
}

Map<String, dynamic> toJson() => {
"code": code,
"msg": msg,
"data": data,
};

///解析data数据为List的方法
static List<T> parseList<T>(
dynamic jsonDataList, T Function(dynamic itemData) itemConvert) {
return jsonDataList == null
? []
: List<T>.from((jsonDataList as List)
.map((itemMapJson) => itemConvert(itemMapJson)));
}
}

调用方法优化

BaseRequest已经封装了相关的接口&参数,那么可以直接为BaseRequest添加一个send方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension BaseRequestExtension on BaseRequest {
Future<BaseResModel<T>> send<T>(
{T Function(dynamic jsonData)? convertT,
dynamic Function(dynamic responseData)? listFromData}) async {
try {
BaseResModel<T> baseResModel = BaseResModel.fromJson(
await BaseDao.send(this),
convertT: convertT,
listFromData: listFromData,
);
return baseResModel;
} catch (e, stack) {
HiLog.e('请求异常: $e\n$stack');
}
return BaseResModel._failed();
}
}

实现方案调用

下面是一个优化后的请求构建&调用的例子

1
2
3
4
5
6
static Future<BaseResModel<MyAppState>> getMyAppStateState() async {
return await CommonRequest(UrlPath.appState)
.setHttpMethod(HttpMethod.get)
.send<MyAppState>(convertT: (value) => MyAppState.fromJson(value));
}

当新增接口接入的时候,只需要添加上诉方法并根据返回数据定义好MyAppState返回结构体即可,后续其他人的调用就不再需要考虑返回数据结构,他可以直接使用该model带的数据进行处理。