实现思想
自己在工作中长期使用Swift,网络库经常使用Moya+Alamofire框架,对于Moya的设计理念很喜欢,学了Flutter后一直没有找到比较喜欢的网络框架,因此就像写一个使用Moya思想的框架
仓库地址
https://github.com/manfengjun/flutter_spi
使用
# add this line to your dependencies
flutter_spi: ^0.1.1
引入
import 'package:flutter_spi/flutter_spi.dart';
目录
| class | description |
|---|---|
| pg_spi | Spi |
| pg_spi_dio | Dio Response Convert |
| pg_spi_error | Error |
| pg_spi_logger | Logger |
| pg_spi_manager | NetWork Manage |
| pg_spi_response | Response Convert |
| pg_spi_target | Api Enum |
详解
Api Enum
在这里,我是用了Dart的枚举和抽象类(类似Swift的Protocol)
// 请求类型
enum HTTPMethod {
get,
post,
put,
patch,
delete,
trace,
connect,
}
// value Extension
extension HTTPMethodEx on HTTPMethod {
String get value =>
['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT'][this.index];
}
// PGSpiTarget
abstract class PGSpiTarget {
// 发出网络请求的基础地址字符串
String get baseUrl;
/// 网络请求的路径字符串
String get path;
/// 网络请求的方式,默认返回get
HTTPMethod get method => HTTPMethod.post;
/// 网络请求参数
Map<String, dynamic> get parameters => null;
/// 网络请求头
Map<String, String> get headers => null;
/// 日志输出
bool get logEnable => true;
}
示例
enum Account { login }
// 由于Dart的枚举不支持关联参数,因此使用了扩展代替,基本实现了功能,使用相对繁琐一点
extension AccountAPI on Account {
static login(Map<String, dynamic> params) =>
AccountTarget(Account.login, params: params);
}
class AccountTarget with PGSpiTarget {
final Account type;
Map<String, dynamic> params = {};
@override
String get baseUrl => Constant.baseUrl;
@override
String get path {
switch (type) {
case Account.login:
return Constant.api + '/toutiao/index';
break;
default:
return '';
}
}
@override
HTTPMethod get method {
switch (type) {
case Account.login:
return HTTPMethod.get;
break;
default:
return HTTPMethod.post;
}
}
@override
Map<String, dynamic> get parameters => params;
@override
Map<String, String> get headers => {"version": "1.0"};
AccountTarget(this.type, {this.params});
}
错误处理
enum Exception {
// 网络异常
networkException,
// invalidURL
invalidURL,
// 服务器异常 500
serverException,
// 方法不存在 404
notFound,
// ContentType 不被接受
unacceptableContentType,
// 响应状态异常
unacceptableStatusCode,
// data缺失
dataNotFound,
// JSON序列化异常
jsonSerializationFailed,
// 对象转换失败
objectFailed,
// 执行结果状态吗不合理
unlegal,
// 执行结果异常,操作失败
executeFail,
}
extension ExceptionEx on Exception {
int get value => index + 10000;
}
class PGSpiError extends Error {
Exception exception;
int status;
String message = '请求失败';
PGSpiError._init(this.status, {this.message});
factory PGSpiError.exception(Exception exception,
{int status, String message}) {
int code = status != null ? status : exception.value;
String msg = message != null ? message : spiCode[exception.value].msg;
return PGSpiError._init(code, message: msg);
}
}
Map<int, Tuple> spiCode = {
10000: Tuple(10000, '网络异常,请稍后重试!'),
10001: Tuple(10001, '请求地址异常'),
10002: Tuple(10002, '服务器异常,请稍后重试!'),
10003: Tuple(10003, '接口未找到'),
10004: Tuple(10004, 'Content-Type异常'),
10005: Tuple(10005, '响应状态异常'),
10006: Tuple(10006, '没有数据节点'),
10007: Tuple(10007, 'JSON序列化异常'),
10008: Tuple(10008, '对象序列化异常'),
10009: Tuple(10009, '缺省状态节点'),
};
class Tuple<T> {
final int status;
final T msg;
Tuple(this.status, this.msg);
}
日志
// 拦截器
class LogsInterceptors extends InterceptorsWrapper {
@override
Future onRequest(RequestOptions options) {
print("✅ " + '${options.path}');
print('✅ METHOD:${options.method}');
if (options.method == 'GET') {
print('✅ Body:${options.queryParameters}');
} else {
print('✅ Body:${options.data}');
}
return super.onRequest(options);
}
@override
Future onResponse(Response response) {
if (response != null) {
var json = jsonDecode(response.data);
if (json != null) {
print('🇨🇳 Return Data:');
print('🇨🇳 $json');
} else {
print('🇨🇳 JSON 解析异常');
}
} else {
print('🇨🇳 response 不存在');
}
return super.onResponse(response);
}
@override
Future onError(DioError e) {
if (e.error is PGSpiError) {
print('❌ ${e.error.status} ---- ${e.error.message}');
} else {
print('❌ ${e.toString() ?? "无错误描述"})');
}
return super.onError(e);
}
}
Response的解析
在Swift中一般请求结果都通过泛型来进行类型的传递,这样就可以直接将返回信息直接处理,就可以直接得到对象,在Dart中遇到了不少问题,Dart的泛型功能相对Swift弱了一点,无法通过泛型去对应解析关系,最后遭到了一种使用字符串对应解析关系的方法,目前的问题是使用时需要对应类和字符串的关系
// News 为类名,需要通过字符串找到对应关系
class EntityFactory {
static T generateOBJ<T>(json) {
if (1 == 0) {
return null;
} else if (T.toString() == "News") {
return News.fromJson(json) as T;
} else {
return null;
}
}
}
Bean类解析
/// 解析基类
class BaseBeanEntity<T> {
Map<String, dynamic> results;
List<dynamic> resultsList = [];
BaseBeanEntity({this.results, this.resultsList});
/// 处理results为对象的情况
BaseBeanEntity.fromJson(Map<String, dynamic> json) {
results = json;
}
/// 处理results为数组的情况
BaseBeanEntity.fromJsonList(List<Map<String, dynamic>> json) {
resultsList = json;
}
/// 获取results对象
T object<T>() {
return EntityFactory.generateOBJ<T>(results); //使用EntityFactory解析对象
}
/// 获取results数组
List<T> objects<T>() {
var list = new List<T>();
if (resultsList != null) {
resultsList.forEach((v) {
//拼装List
list.add(EntityFactory.generateOBJ<T>(v)); //使用EntityFactory解析对象
});
}
return list;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = new Map<String, dynamic>();
if (this.results != null) {
data = this.results;
}
return data;
}
}
这里是一个对象数组的解析类,通过泛型传递对象类型
// 返回结果为 List
Future<List<dynamic>> mapSpiJsons<T>(
PGSpiTarget target, {
String designatedPath,
Options options,
CancelToken cancelToken,
ProgressCallback onSendProgress,
onReceiveProgress,
}) async {
Response response = await request(
target.baseUrl + target.path,
data: target.method == HTTPMethod.get ? {} : target.parameters,
options: checkOptions(target.method.value, options),
queryParameters: target.method != HTTPMethod.get ? {} : target.parameters,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
var json = response.data;
var status = json[PGSpiManager.shared.key.status];
if (status == PGSpiManager.shared.key.success) {
if (json[PGSpiManager.shared.key.data] == null) {
return [];
}
if (designatedPath == null || designatedPath.length <= 0) {
return json[PGSpiManager.shared.key.data];
}
if (json[PGSpiManager.shared.key.data][designatedPath] == null) {
throw DioError(
error: PGSpiError.exception(Exception.objectFailed),
);
}
return json[PGSpiManager.shared.key.data][designatedPath];
} else {
if (status != null && status is int) {
// 请求正常,操作失败
throw DioError(
error: PGSpiError.exception(
Exception.executeFail,
status: status,
message: json[PGSpiManager.shared.key.msg],
),
);
}
// 请求结果状态码不合法
throw DioError(
error: PGSpiError.exception(Exception.unlegal),
);
}
}
使用
使用时如果需要结合RxDart使用,需要增加一个Extension
extension PGSpiRx on PGSpi {
// object stream
Stream<T> mapSpiObject<T>({String path}) => this
.responseSpiJson(path: path)
.asStream()
.map((value) => BaseBeanEntity.fromJson(value).object<T>());
// objects stream
Stream<List<T>> mapSpiObjects<T>({String path}) => this
.responseSpiJsons(path: path)
.asStream()
.map((value) => BaseBeanEntity.fromJsonList(value).objects<T>());
}
实际使用示例
PGSpi(AccountAPI.login(
{"type": "top", "key": "8093f06289133b469be6ff7ab6af1aa9"}))
.mapSpiObjects<News>(path: "data")
.listen(
(value) => print(value[0].authorName),
onError: (e) {
//获取失败
print('错误:' + (e.error as PGSpiError).message);
},
);
思考
目前功能基本实现了,使用上也还不错,主要问题是解析时实体类和泛型的结合有一定问题,通过字符串的方式后续维护可能和麻烦。不过目前没有找到一个不错的解决方案