Flutter 13 网络层框架架构设计,支持dio等框架。
在移动APP开发过程中,进行数据交互时,大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层,我们该如何通过网络层架构设计来有效解决这些问题,这便是网络层框架架构设计的初衷。
设计要求:
1. 支持网络库插拔设计,且不干扰业务层
2. 简洁易用,支持配置来进行请求
3. Adapter设计,扩展性强
4. 统一异常和返回处理
解决问题:
切换成本高:网络操作使用的三方库存在不维护切换成本高的风险;
接口管理不便:对于大中型APP接口众多,不方便管理;
重复代码多:APP中进行数据交互的场景很多,网络请求存在大量的重复代码;
扩展性差:网络操作和业务代码耦合严重,不利于扩展;
开发效率低:不同三方库使用方式不统一,步骤繁琐开发效率低;
一、搭建基础的网络请求框架HiNet
1)创建基础请求
创建抽象的基础请求类BaseRequest,并向上层提供获取请求路径,请求方式等抽象方法,及添加参数和请求头等能力。
base_request.dart
enum HttpMethod { GET, POST, DELETE }/// 基础请求
abstract class BaseRequest {var pathParams;var userHttps = true;/// 域名String authority() {return "api.devio.org";}HttpMethod httpMethod();String path();String url() {Uri uri;var pathStr = path();// 拼接路径参数if (pathParams != null) {if (pathStr.endsWith("/")) {pathStr = "$pathStr$pathParams";} else {pathStr = "$pathStr/$pathParams";}}// http和https的切换if (userHttps) {uri = Uri.https(authority(), pathStr, params);} else {uri = Uri.http(authority(), pathStr, params);}print("url:${uri.toString()}");return uri.toString();}bool needLogin();Map<String, String> params = {};/// 添加参数BaseRequest add(String k, Object v) {params[k] = v.toString();return this;}Map<String, dynamic> header = {};/// 添加请求头BaseRequest addHeader(String k, Object v) {header[k] = v.toString();return this;}
}
2)创建测试请求
创建测试请求类TextRequest继承自 基础请求抽象类BaseRequest,实现请求路径和请求方式等。
test_request.dart
import 'package:hi_net/http/request/base_request.dart';class TestRequest extends BaseRequest{@overrideHttpMethod httpMethod() {return HttpMethod.GET;}@overridebool needLogin() {return false;}@overrideString path() {return "uapi/test/test";}
}
3)创建核心网络请求类
创建核心网络请求类HiNet,提供发送请求,接收响应,解析返回数据,统一错误处理等功能。当前使用模拟响应请求的方式,下面内容会带着大家一步一步进行完善。
hi_net.dart
import 'package:hi_net/http/request/base_request.dart';class HiNet {HiNet._internal();static HiNet? _instance;static HiNet get getInstance {_instance ??= HiNet._internal();return _instance!;}Future fire(BaseRequest request) async {var respones = await send(request);var result = respones['data'];print("result:$result");return result;}Future<dynamic> send<T>(BaseRequest request) {print("url:${request.url()}");print("httpMethod:${request.httpMethod()}");request.addHeader("aaaa", "bbbb");print("header:${request.header}");return Future.value({"statusCode": 200,"data": {"code": 0, "message": "success"}});}
}
4)发送测试请求
创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行模拟Http请求。
TestRequest testRequest = TestRequest();
testRequest.add("1111", "2222").add("3333", "4444");
HiNet.getInstance.fire(testRequest);
模拟接口请求成功,输出log:
二、增加统一异常和响应数据处理,及Adapter模式设计
1)创建网络异常统一格式类
创建网络异常统一格式类HiNetError,包含code、message和data信息。
创建登录异常NeedLogin 和 授权异常NeedAuth 继承自HiNetError。
hi_net_error.dart
/// 需要登录的异常
class NeedLogin extends HiNetError {NeedLogin({int code = 401, String message = "请先登录"}) : super(code, message);
}/// 需要授权的异常
class NeedAuth extends HiNetError {NeedAuth(String message, {int code = 403, dynamic data}): super(code, message, data: data);
}/// 网络异常统一格式类
class HiNetError implements Exception {final int code;final String message;final dynamic data;HiNetError(this.code, this.message, {this.data});
}
2)创建统一网络层返回格式
创建统一网络层返回格式HiNetResponse,包含request、statusCode、statusMessage和data等信息。
hi_net_adapter.dart
/// 统一网络层返回格式
class HiNetResponse<T> {HiNetResponse({this.data,this.request,this.statusCode,this.statusMessage,this.extra});T? data;BaseRequest? request;int? statusCode;String? statusMessage;dynamic extra;@overrideString toString() {if (data is Map) {return json.encode(data);}return data.toString();}
}
3)创建网络请求抽象类
网络请求抽象类HiNetAdapter,提供发送请求能力。
hi_net_adapter.dart
import 'dart:convert';import 'package:hi_net/http/request/base_request.dart';/// 网络请求抽象类
abstract class HiNetAdapter {Future<HiNetResponse<T>> send<T>(BaseRequest request);
}/// 统一网络层返回格式
class HiNetResponse<T> {HiNetResponse({this.data,this.request,this.statusCode,this.statusMessage,this.extra});T? data;BaseRequest? request;int? statusCode;String? statusMessage;dynamic extra;@overrideString toString() {if (data is Map) {return json.encode(data);}return data.toString();}
}
4)创建测试适配器
创建测试适配器MockAdapter ,mock数据。
mock_adapter.dart
import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';/// 测试适配器,mock数据
class MockAdapter extends HiNetAdapter {@overrideFuture<HiNetResponse<T>> send<T>(BaseRequest request) {return Future.delayed(const Duration(milliseconds: 1000), () {return HiNetResponse(data: {"code": 0, "message": "success"} as T, statusCode: 200);});}
}
5)完善核心网络请求类
完善核心网络请求类HiNet,使用mock适配器MockAdapter发送请求,使用HiNetResponse接收请求响应数据,增加统一错误处理。
hi_net.dart
import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/core/mock_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';class HiNet {HiNet._internal();static HiNet? _instance;static HiNet get getInstance {_instance ??= HiNet._internal();return _instance!;}Future fire(BaseRequest request) async {HiNetResponse? response;var error;try {response = await send(request);} on HiNetError catch (e) {error = e;response = e.data;print("HiNetError:${e.message}");} catch (e) {// 其他错误error = e;print("OtherError:$e");}if (response == null) {print("error:$error");}var result = response?.data;print("result:$result");var status = response?.statusCode ?? 0;switch (status) {case 200:return result;case 401:throw NeedLogin();case 403:throw NeedAuth(result.toString(), data: result);default:throw HiNetError(status, result.toString(), data: result);}}Future<dynamic> send<T>(BaseRequest request) {print("url:${request.url()}");/// 使用mock发送请求HiNetAdapter adapter = MockAdapter();return adapter.send(request);}
}
6)发送测试请求
创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行模拟Http请求。
TestRequest testRequest = TestRequest();testRequest.add("1111", "2222").add("3333", "4444");try{var result = await HiNet.getInstance.fire(testRequest);print(result);} on NeedAuth catch (e) {print(e);} on NeedAuth catch (e) {print(e);} on HiNetError catch (e) {print(e);}
模拟接口请求成功,输出log:
三、 扩展hi_net添加对dio的支持
1)创建dio适配器
创建dio适配器DioAdapter,重写发送请求的send方法,采用dio框架进行真正的Http网络请求;根据服务器响应数据构建HiNetError 和 HiNetResponse。
添加dio依赖:
pubspec.yaml
dio: ^5.7.0
dio_adapter.dart
import 'package:dio/dio.dart';
import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/request/base_request.dart';/// Dio适配器
class DioAdapter extends HiNetAdapter {@overrideFuture<HiNetResponse<T>> send<T>(BaseRequest request) async {Response? response;var error, options = Options(headers: request.header);try {if (request.httpMethod() == HttpMethod.GET) {response = await Dio().get(request.url(), options: options);} else if (request.httpMethod() == HttpMethod.POST) {response = await Dio().post(request.url(), data: request.params, options: options);} else if (request.httpMethod() == HttpMethod.DELETE) {response = await Dio().delete(request.url(), data: request.params, options: options);}} on DioError catch (e) {error = e;response = e.response;}if (error != null) {/// 抛出HiNetError异常throw HiNetError(response?.statusCode ?? -1, error.toString(),data: buildResponse(response, request));}return buildResponse(response, request);}/// 构建HiNetResponseHiNetResponse<T> buildResponse<T>(Response? response, BaseRequest request) {return HiNetResponse(data: response?.data as T,request: request,statusCode: response?.statusCode,statusMessage: response?.statusMessage,extra: response);}
}
2)使用dio发送请求
修改HiNet 的send方法,使用dio适配器发送请求;Adapter设计,可轻便的更换三方网络请求库,加强了网络请求架构的扩展性。
hi_net.dart
import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/core/adapter/mock_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';import 'adapter/dio_adapter.dart';///1.支持网络库插拔设计,且不干扰业务层
///2.基于配置请求请求,简洁易用
///3.Adapter设计,扩展性强
///4.统一异常和返回处理
class HiNet {Future<dynamic> send<T>(BaseRequest request) {print("url:${request.url()}");/// 使用mock发送请求/// HiNetAdapter adapter = MockAdapter();/// 使用dio发送请求HiNetAdapter adapter = DioAdapter();return adapter.send(request);}
}
3)发送测试请求
创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行Http请求。
注意:该测试接口requestPrams是必传字段,不传接口会返回失败。
TestRequest testRequest = TestRequest();testRequest.add("1111", "2222").add("3333", "4444").add("requestPrams", "5555");try{var result = await HiNet.getInstance.fire(testRequest);print("testRequest result: $result");} on NeedAuth catch (e) {print(e);} on NeedAuth catch (e) {print(e);} on HiNetError catch (e) {print(e);}
Http接口请求成功,输出log:
Http接口请求失败,输出log:
四、JSON编码器和解码器
1)使用 json_serializable 框架
使用 json_serializable 框架,对JSON数据进行解析。
添加 json_serializable 依赖:
pubspec.yaml
json_serializable: ^6.8.0json_annotation: ^4.9.0build_runner: ^2.1.11
2)创建测试接口返回数据bean
测试接口返回接送数据:
{code: 0, method: GET, requestPrams: 5555}
创建TestModel,编写好属性、构造方法、TestModel.fromJson() 和 toJson();增加注解:@JsonSerializable()
test_model.dart
import 'package:json_annotation/json_annotation.dart';part 'test_model.g.dart';/// 测试接口返回数据bean
/// {code: 0, method: GET, requestPrams: 5555}
@JsonSerializable()
class TestModel {int code;String method;String requestPrams;TestModel(this.code, this.method, this.requestPrams);//固定格式,不同的类使用不同的mixin即可factory TestModel.fromJson(Map<String, dynamic> json) => _$TestModelFromJson(json);//固定格式,Map<String, dynamic> toJson() => _$TestModelToJson(this);
}
执行 :dart run build_runner build
提示:需要在 TestModel 增加 part 'test_model.g.dart';
加上后再次执行 :dart run build_runner build ,接口自动生成 test_model.g.dart 文件
总结:使用 json_serializable 框架构建JSON解析类时,手动创建的基类Model(TestModel),必须满足以下要求:
1. 在Model类编写 part '同Model类文件名.g.dart'; 如 TestModel类文件名est_model.dart ,则编写 part 'test_model.g.dart';
2.增加注解: @JsonSerializable()
3.编写固定格式的代码,名字换成基类名字,这里是TestModel:
//固定格式,不同的类使用不同的mixin即可factory TestModel.fromJson(Map<String, dynamic> json) => _$TestModelFromJson(json);//固定格式,Map<String, dynamic> toJson() => _$TestModelToJson(this);
3)使用
TestRequest testRequest = TestRequest();testRequest.add("1111", "2222").add("3333", "4444").add("requestPrams", "5555");try{var result = await HiNet.getInstance.fire(testRequest);var testModel = TestModel.fromJson(result['data']);print("testRequest requestPrams: ${testModel.requestPrams}");} on NeedAuth catch (e) {print(e);} on NeedAuth catch (e) {print(e);} on HiNetError catch (e) {print(e);}
JSON解析,输出log:
至此,完成了网络请求框架的基本功能,持续完善。。。
相关文章:

Flutter 13 网络层框架架构设计,支持dio等框架。
在移动APP开发过程中,进行数据交互时,大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层,我们该如何通过网络层架构设计来有效解决这些问题,这便是网络层框架架构设计的初…...
Python小白学习教程从入门到入坑------第二十课 闭包修饰器(语法基础)
一、递归函数 1.1 基本信息 递归函数是指一个函数在其定义中直接或间接地调用了自身 递归在解决许多问题(如树的遍历、图的搜索、数学中的分治算法等)时非常有用 在Python中,递归函数可以通过简单的语法来实现 然而,使用递归…...

Vue+element-ui实现网页右侧快捷导航栏 Vue实现全局右侧快捷菜单功能组件
Vue+element-ui实现网页右侧快捷导航栏 Vue实现全局右侧快捷菜单功能组件 可视区域没超过当前屏幕高度时候只显示三个菜单效果 可视区域超过当前屏幕高度时,显示可回到顶部菜单的,当然这个菜单显示条件可以自定义,根据需求设置 然后将这个整体功能创建为一个全局组件 代…...
如何配置,npm install 是从本地安装依赖
在 Node.js 中,要使npm install从本地安装依赖,可以按照以下步骤进行配置: 一、准备本地依赖包 确保你有本地的依赖包。这个依赖包可以是一个包含package.json文件的文件夹,或者是一个已经打包好的.tgz文件。 二、使用相对路径…...

Python画图3个小案例之“一起看流星雨”、“爱心跳动”、“烟花绚丽”
源码如下: import turtle # 导入turtle库,用于图形绘制 import random # 导入random库,生成随机数 import math # 导入math库,进行数学计算turtle.setup(1.0, 1.0) # 设置窗口大小为屏幕大小 turtle.title("流星雨动画&…...

Knife4j配置 ▎使用 ▎教程 ▎实例
knife4j简介 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试.参数和格式都…...

电子电气架构 --- 车载芯片现状
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧…...

Unity 二次元三渲二
三渲二 注意:Unity必须是2022.3LTS及以上和URP项目!!! 下载三渲二插件 【如何将原神的角色导入Unity】全网最细致教程,全程干货。不使用任何收费插件,使用Spring Bone对头发和衣服进行物理模拟。_原神 步…...

echart实现地图数据可视化
文章目录 [TOC](文章目录) 前言一、基本地图展示2.数据可视化 总结 前言 最近工作安排使用echarts来制作图形报表,记录一下我的步骤,需求呈现一个地图,地图显示标签,根据业务指标值给地图不同省市填充不同颜色,鼠标放…...

网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?
文章整体介绍 本文旨在解答关于微服务网关的三个核心问题: 1)为什么需要网关?也即在何种场景下应采用微服务网关以优化系统架构; 2)什么是微服务网关?主要讲构成微服务网关的关键能力,包括但…...
Mybatis-plus解决兼容oracle批量插入
本博客借鉴网上很多大佬的答案,东拼西凑,最终在项目中完成批量插入,仅供参考~~~ 1. 自定义SQL注入器 新建一个名为EasySqlInjector的类,继承DefaultSqlInjector。 public class EasySqlInjector extends DefaultSqlInjector {O…...

Kaggle竞赛——灾难推文分类(Disaster Tweets)
目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测…...

SC2601音频编解码器可pin to pin兼容ES8311
SC2601 是一款低功耗单声道音频编解码器,具有全差分输出,支持在全差分配置下可编程模拟输入。可pin to pin兼容ES8311。 录音路径包含一个全差分输入,低噪声可编程增益放大器和自动增益控制(ALC)。在录音过程中,通过内…...
通用AT指令
1、查询SIM卡状态 ATCPIN?2、查询信号强度 ATCSQ //99,99 表示无信号3、查询IMEI ATCGSN4、查询4G/5G模式 ATCOPS? //7表示在4G模式,13表示在5G模式5、设置接入点 ATCGDCONT1,"IP","uninet" //联通 ATCGDCONT1,"IP","…...
二进制狼群算法
本文所涉及所有资源均在 传知代码平台 可获取。 目录 一、背景及意义介绍 背景 意义...
STL——list的介绍和使用
前言 本篇博客我们继续来介绍STL的内容,这次我们要介绍的是list这个容器,可以简单地理解为顺序表,当然和我们之前学过顺序表还是有区别的,具体内容大家可以继续往下阅读,下面进入正文。 1. list简介 1.list是一种可…...
二百七十六、ClickHouse——Hive和ClickHouse非常不同的DWS指标数据SQL语句
一、目的 在完成数据之后对业务指标进行分析,Hive和ClickHouseSQL真不一样 二、部分业务指标表 2.1 统计数据流量表1天周期 2.1.1 Hive中原有代码 2.1.1.1 Hive中建表语句 --1、统计数据流量表——动态分区——1天周期 create table if not exists hurys_d…...
Elasticsearch Date类型,时间存储相关说明
本文介绍了在SpringBoot中处理Elasticsearch中日期时间格式的问题。当时间输出为UTC格式并存在时区差异时,可通过设置字段格式如yyyy-MM-dd HH:mm:ss并指定时区为GMT8来解决。存储Date类型数据时,可以使用JSON库如json-lib, fastjson, Jackson或gson进行…...

mathorcup2024台风 我all in ai
三个问题,力大砖飞。 不建物理模型,直接all in好吧 第一个故意无监督 第二个LSTMCNN注意力,刚好时间空间 第三个在第二个上加了个transfomer ,然后LSTM变双向,增加层数(基线模型选的经验公式,少…...
android 10 后台启动activity
摘要:Android 10(API 级别 29)及更高版本会限制应用何时可以启动 activity 背景。这些限制有助于最大限度地减少对用户的干扰, 让用户能够更好地控制其屏幕上显示的内容。本文以此为出发点,基于展锐平台对系统代码进行…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...