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 背景。这些限制有助于最大限度地减少对用户的干扰, 让用户能够更好地控制其屏幕上显示的内容。本文以此为出发点,基于展锐平台对系统代码进行…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...