Flutter下的一点实践
目录
- 1、背景
- 2、refena创世纪代码
- 3、localsend里refena的刷新
- 3.1 初始状态
- 3.2 发起设备扫描流程
- 3.3 扫描过程
- 3.3 刷新界面
- 4.localsend的设备扫描流程
- 4.1 UDP广播设备注册流程
- 4.2 TCP/HTTP设备注册流程
- 4.3 localsend的服务器初始化工作
- 4.4总结
1、背景
在很久以前,我曾经经历了一小段时间的Flutter开发,当时Flutter的版本才迭代到1.0,在做一个短视频应用的开发中,我曾经产生了一个巨大的疑问,就是Flutter的状态刷新怎么才能简洁、高效,如果每次及每个地方都使用setstate()来刷新界面确实显得非常笨重。
这么多年过去了,就像一个回旋镖一样,我又进了一个Flutter/kotlin混合开发的项目,项目是在开源的localsend项目上做二次开发,localsend作为跨平台传输软件,可以实现在同一局域网的端到端设备之间共享文件。本篇博客将以最简单的方式介绍refena创世纪代码、localsend里refena的刷新、localsend的设备扫描流程。
2、refena创世纪代码
由于采用了Flutter作为开发框架,状态管理必不可少,相比于市面常见的Flutter状态管理框架async_redux,localsend采用了不太常用的状态管理框架refena。作为被async_redux启迪的状态管理框架,redux中常见的store、 state、 actions 、 reducers同样适用。store保存了应用里所有的状态,而store被保存在各种provider里;action被保存在自身的reducers里,触发store状态发生变化的唯一办法是发送一个action。有关redux的工作流及基础概念可以参考这篇文章以及下面这张图:
refena官网有最小化的状态刷新介绍,先搬运介绍一下refena的创世纪代码:
final counterProvider = ReduxProvider<ReduxCounter, int>((ref) => Counter());//1.在refena中,notifier用以保存实际状态,并且可以触发监听它们的控件刷新
//2.init()方法可以定义Notifier初始化状态
//3.可以通过ref来获取定义的其他provider
//4.这里定义counter的初始状态:10
class ReduxCounter extends ReduxNotifier<int> { int init() => 10;
}//1.ReduxAction最重要的方法就是reduce()方法,用于向provider返回一个新的状态
//2.这里返回的状态为ReduxCounter现在的值加上传递过来的值
class AddAction extends ReduxAction<ReduxCounter, int> {final int amount;AddAction(this.amount); int reduce() => state + amount;
}class MyPage extends StatelessWidget {Widget build(BuildContext context) {int counterState = context.watch(counterProvider);return Scaffold(body: Center(child: Text('Counter state: $counterState'),),floatingActionButton: FloatingActionButton(//点击触发action dispatchonPressed: () => context.redux(counterProvider).dispatch(AddAction(2)),child: const Icon(Icons.add),),);}
}
总结起来,在refena下的工作流为:1、定义初始状态;2、重写reduce()方法,在ReduxAction或各个Action子类中定义要改变的状态;3、定义状态的触发条件,调用dispatch方法触发状态改变。总的来说,refena状态刷新其实和MVVM有许多相似之处。
3、localsend里refena的刷新
localsend典型的功能如下:两个接入同一个局域网的设备相互发送UDP广播或HTTP请求,确立连接之后通过HTTP协议来传输文件。下面将简单介绍localsend扫描到设备后的界面刷新流程。
3.1 初始状态
localsend的初始状态如下:
打开应用,进入发送页签,在“附件的设备”这个列表下开始扫描局域网内的设备。发送页签进行初始化工作,并且发送了一个全局异步的action——SendTabInitAction:
class SendTab extends StatelessWidget {const SendTab();Widget build(BuildContext context) {return ViewModelBuilder(provider: sendTabVmProvider,//依然是在init方法里分发初始化actioninit: (context) => context.global.dispatchAsync(SendTabInitAction(context)),......
这个SendTabInitAction的代码十分简单:
class SendTabInitAction extends AsyncGlobalAction {……Future<void> reduce() async {//从provider里边读取是否有扫描到的设备final devices = ref.read(nearbyDevicesProvider).devices;if (devices.isEmpty) {//如果没有设备触发设备扫描流程await dispatchAsync(StartSmartScan(forceLegacy: false));}}
}
根据refena工作流,发起一个action后,会直接调用它的reduce方法,在reduce方法里产生新的状态,并通过各种方式把这个新的状态同步给widget刷新界面。所以,设备是如何扫描出来的只需要跟进StartSmartScan这个action即可。
3.2 发起设备扫描流程
接下来就进入了localsend代码的核心——扫描设备流程,这个流程较为复杂,包括dart下高级网络编程及refena线程间通信等,这里只给出扫描开始及获取到扫描结果的伪代码:
//英文注释为原生代码注释
class StartSmartScan extends AsyncGlobalAction {static const maxInterfaces = 15;final bool forceLegacy;Future<void> reduce() async {// 1.Try performant Multicast/UDP method first//首先发起UDP广播,UDP广播性能比TCP/HTTP性能高。ref.redux(nearbyDevicesProvider).dispatch(StartMulticastScan());// At the same time, try to discover favorites//首先从文件里读取是否有收藏的设备final favorites = ref.read(favoritesProvider);final https = ref.read(settingsProvider).https;await ref.redux(nearbyDevicesProvider).dispatchAsync(StartFavoriteScan(devices: favorites, https: https));……// 2.If no devices has been found, then switch to legacy discovery mode// which is purely HTTP/TCP based.final stillEmpty = ref.read(nearbyDevicesProvider).devices.isEmpty;final stillInSendTab =ref.read(homePageControllerProvider).currentTab == HomeTab.send;if (forceLegacy || (stillEmpty && stillInSendTab)) {final networkInterfaces =//localIpProvider保存了从platformChannel里读取到的当前设备的IP//(这里解释一下为什么当前设备的IP需要单独用一个localIpProvider来保存,//在android设备里,根据底软实现可能有多个网卡,对应也有多个设备IP)//依然首先从provider(内存)里读取是否之前扫描到过设备ref.read(localIpProvider).localIps.take(maxInterfaces).toList();if (networkInterfaces.isNotEmpty) {//开始扫描当前设备所有IP所在局域网的设备await dispatchAsync(StartLegacySubnetScan(subnets: networkInterfaces));}} else {……}}
}
3.3 扫描过程
扫描代码主体流程主要分为两个步骤:1.通过UDP协议扫描局域网设备;2.通过TCP/HTTP协议扫描设备。扫描流程较为复杂,需要对网络编程有一定基础的了解。我们跳过具体的扫描流程,直接到获取扫描结果的部分。
//英文注释为localsend原生注释
/// HTTP based discovery on a fixed set of subnets.
class StartLegacySubnetScan extends AsyncGlobalAction {……Future<void> reduce() async {//读取配置信息,端口号、是否是HTTPS协议等final settings = ref.read(settingsProvider);final port = settings.port;final https = settings.https;// send announcement in parallel//发起设备扫描流程——UDP组播ref.redux(nearbyDevicesProvider).dispatch(StartMulticastScan());await Future.wait<void>([for (final subnet in subnets)ref.redux(nearbyDevicesProvider).dispatchAsync(//发起设备扫描流程——TCP/HTTP请求StartLegacyScan(port: port, localIp: subnet, https: https)),]);……}
}
/// It does not really "scan".
/// It just sends an announcement which will cause a response on every other LocalSend member of the network.
class StartMulticastScanextends ReduxAction<NearbyDevicesService, NearbyDevicesState> {NearbyDevicesState reduce() {external(notifier._isolateController)//开启线程发起UDP组播.dispatch(IsolateSendMulticastAnnouncementAction());return state;}
}
/// Scans one particular subnet with traditional HTTP/TCP discovery.
/// This method awaits until the scan is finished.
class StartLegacyScanextends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {final int port;final String localIp;final bool https;……Future<NearbyDevicesState> reduce() async {……// 1.Scan all IP addresses on the WLANfinal stream = external(notifier._isolateController)//开启线程发起TCP/HTTP协议设备扫描流程,扫描到的设备保存在Stream里.dispatchTakeResult(IsolateInterfaceHttpDiscoveryAction(networkInterface: localIp,port: port,https: https,));// 2.Register the device to the RegisterDeviceActionawait for (final device in stream) {//将stream里的设备赋值RegisterDeviceAction//扫描过的设备保存到nearbyDevicesProviderawait dispatchAsync(RegisterDeviceAction(device));}……}
}
3.3 刷新界面
/// Registers a device in the state.
/// It will override any existing device with the same IP.
class RegisterDeviceActionextends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {……Future<NearbyDevicesState> reduce() async {……//在RegisterDeviceAction的reduce方法里触发界面刷新var nearbyDevicesState = state.copyWith(devices: {...state.devices}..update(device.ip, (_) => device, ifAbsent: () => device),);……}
}
这样,通过扫描设备流程就将扫描到的设备更新到了界面上:
4.localsend的设备扫描流程
4.1 UDP广播设备注册流程
在前文里,我们已经提到设备扫描首先会以UDP广播来扫描设备,先来看看代码实现。
//英文注释为原生代码注释/// Binds the UDP port and listen to UDP multicast packages/// It will automatically answer announcement messagesStream<Device> startListener() async* {……final sockets = await _getSockets(syncState.multicastGroup, syncState.port);//遍历UDP组播地址的所有Socketfor (final socket in sockets) {//开始监听是否有组播socket.socket.listen((_) {final datagram = socket.socket.receive();……try {//将Socket数据转换为对象final dto = MulticastDto.fromJson(jsonDecode(utf8.decode(datagram.data)));if (dto.fingerprint == syncState.securityContext.certificateHash) {return;}……if ((dto.announcement == true || dto.announce == true) && syncState.serverRunning) {// only respond when server is running//向UDP组播广播方返回应答消息//这里业务逻辑为UDP设备注册流程//广播发送方作为server//广播应答方作为client_answerAnnouncement(peer);}} catch (e) {……}});}// Tell everyone in the network that I am online//向UDP组播地址所有成员发送UDP组播,此举可以提供设备扫描成功率sendAnnouncement(); // ignore: unawaited_futuresyield* streamController.stream;}/// Responds to an announcement.Future<void> _answerAnnouncement(Device peer) async {try {// Answer with TCP//通过dio向广播发送方发起一路HTTP请求,这里的请求接口为设备注册接口await _ref.read(dioProvider).discovery.post(ApiRoute.register.target(peer),data: _getRegisterDto().toJson(),);} catch (e) {……}}/// Sends an announcement which triggers a response on every LocalSend member of the network.//发送一个广播,在网络的每个localSend成员上触发应答广播消息Future<void> sendAnnouncement() async {final syncState = _ref.read(syncProvider);final sockets = await _getSockets(syncState.multicastGroup);final dto = _getMulticastDto(announcement: true);//分别以100ms、500ms、2000ms向发送方应答组播消息for (final wait in [100, 500, 2000]) {……for (final socket in sockets) {try {socket.socket.send(dto, InternetAddress(syncState.multicastGroup), syncState.port);socket.socket.close();} catch (e) {……}}}……}Future<List<_SocketResult>> _getSockets(String multicastGroup, [int? port]) async {//通过各个平台的platformChannel获取当前设备的IP(android设备通常是SoftAP IP)final interfaces = await NetworkInterface.list();final sockets = <_SocketResult>[];for (final interface in interfaces) {try {//根据IP地址绑定到localsend预先定义的端口号上,返回一个Socket端点final socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port ?? 0);//把这个Socket加入到UDP组播地址里socket.joinMulticast(InternetAddress(multicastGroup), interface);……} catch (e) {……}}return sockets;
}
4.2 TCP/HTTP设备注册流程
UDP设备流程结束之后,才会走到HTTP设备注册流程。HTTP设备注册流程资源占用率比UDP广播这种方式大得多。
class HttpScanDiscoveryService {……//参数名networkInterface代码当前设备的IP地址。如192.168.31.246Stream<Device> getStream({required String networkInterface, required int port, required bool https}) {//遍历197.168.31.0~197.168.31.255里的所有IP,尝试向这里面的每个IP地址发起一路HTTP请求final ipList = List.generate(256, (i) => '${networkInterface.split('.').take(3).join('.')}.$i').where((ip) => ip != networkInterface).toList();_runners[networkInterface]?.stop();_runners[networkInterface] = TaskRunner<Device?>(initialTasks: List.generate(ipList.length,//发起设备注册请求(index) => () async => _doRequest(ipList[index], port, https),),concurrency: 50,);return _runners[networkInterface]!.stream.where((device) => device != null).cast<Device>();}Future<Device?> _doRequest(String currentIp, int port, bool https) async {……final device = await _targetedDiscoveryService.state.discover(ip: currentIp,port: port,https: https,onError: null,);……}return device;}
4.3 localsend的服务器初始化工作
前面讲到了localsend的界面刷新、设备注册流程,还有个问题就是localsend到底如何处理这些广播、请求的?简单来说,localsend在进入应用的时候,跑了一个HTTP server来处理组播、HTTP请求。
/// Starts the server.Future<ServerState?> startServer({required String alias,required int port,required bool https,}) async{//1.检查用户给localsend客户端取的别名,例如:“美丽的芒果”alias = alias.trim();if (alias.isEmpty) {alias = generateRandomAlias();}……final router = SimpleServerRouteBuilder();final fingerprint = ref.read(securityProvider).certificateHash;_receiveController.installRoutes(router: router,alias: alias,port: port,https: https,fingerprint: fingerprint,showToken: ref.read(settingsProvider).showToken,);_sendController.installRoutes(router: router,alias: alias,fingerprint: fingerprint,);final HttpServer httpServer;//默认HTTPS协议,需要先安装默认证书if (https) {final securityContext = ref.read(securityProvider);httpServer = await HttpServer.bindSecure('0.0.0.0',port,SecurityContext()..usePrivateKeyBytes(securityContext.privateKey.codeUnits)..useCertificateChainBytes(securityContext.certificate.codeUnits),);} else {//HTTP协议无需证书httpServer = await HttpServer.bind('0.0.0.0',port,);}//启动服务final server = SimpleServer.start(server: httpServer, routes: router);final newServerState = ServerState(httpServer: server,alias: alias,port: port,https: https,session: null,webSendState: null,pinAttempts: {},);state = newServerState;return newServerState;}
最后一个问题,localsend作为服务器有哪些RESTful API?根据官方文档,localsend应该提供了以下这些接口:
enum ApiRoute {//早期的注册接口,现已废弃info('info'),//现在版本的注册接口,传递client端IP地址、名称等基础信息register('register'),//文件传输之前获取token的接口prepareUpload('prepare-upload', 'send-request'),//文件传输接口upload('upload', 'send'),//取消接口,包括发送取消、接收取消cancel('cancel'),……;
4.4总结
总结起来,localsend的关键原理:
- 建立一个HTTP Sever。监听相关端口接收UDP组播;初始化RESTful API接口,用于HTTP的设备注册、文件传输;
- UDP设备注册流程中,服务端监听UDP组播端口、客户端回复组播消息并在回复组播消息后发起HTTP注册流程,向服务端传输IP等关键信息;
- TCP设备注册流程中,主动作为客户端遍历当前网段的所有IP,发起一路HTTP请求,向服务端注册;
- 扫描到设备后,通过在服务端/客户端之间的HTTP协议来传输文件。传输过程中,文件发送方为client;文件接收方为server。client发起一路post请求到服务器即可完成文件传输。
相关文章:

Flutter下的一点实践
目录 1、背景2、refena创世纪代码3、localsend里refena的刷新3.1 初始状态3.2 发起设备扫描流程3.3 扫描过程3.3 刷新界面 4.localsend的设备扫描流程4.1 UDP广播设备注册流程4.2 TCP/HTTP设备注册流程4.3 localsend的服务器初始化工作4.4总结 1、背景 在很久以前,…...
Python训练营打卡 Day41
简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 卷积操作常见流程如下: 1. 输入 → 卷积层 → Batch…...
Eclipse集成lombok
一、安装 Lombok 插件(Eclipse 支持) 下载 lombok.jar: 前往官网下载页面:https://projectlombok.org/download 下载最新版本的 lombok.jar 文件。 运行 lombok.jar 配置 Eclipse: 双击下载的 lombok.jar࿰…...
什么是trace,分布式链路追踪(Distributed Tracing)
在你提到的 “个人免费版” 套餐中,“Trace 上报量:5 万条 / 月,存储 3 天” 里的 Trace 仍然是指 分布式链路追踪记录,但需要结合具体产品的场景来理解其含义和限制。以下是更贴近个人用户使用场景的解释: 一、这里的…...

VScode ios 模拟器安装cocoapods
使用 Homebrew 安装(推荐) 如果你有 Homebrew,直接用它安装更稳定: brew install cocoapods...

Redis最佳实践——安全与稳定性保障之数据持久化详解
Redis 在电商应用的安全与稳定性保障之数据持久化全面详解 一、持久化机制深度解析 1. 持久化策略矩阵 策略触发方式数据完整性恢复速度适用场景RDB定时快照分钟级快容灾备份/快速恢复AOF实时追加日志秒级慢金融交易/订单关键操作混合模式RDBAOF同时启用秒级中等高安全要求场…...
互联网大厂Java求职面试实战:Spring Boot微服务架构及Kafka消息处理示例解析
互联网大厂Java求职面试实战:Spring Boot微服务架构及Kafka消息处理示例解析 引言 在互联网大厂的Java开发岗位面试中,考察候选人对微服务架构设计、消息队列处理及高并发处理能力是重点。本文结合Spring Boot框架和Kafka消息队列,模拟一个…...

K 值选对,准确率翻倍:KNN 算法调参的黄金法则
目录 一、背景介绍 二、KNN 算法原理 2.1 核心思想 2.2 距离度量方法 2.3 算法流程 2.4算法结构: 三、KNN 算法代码实现 3.1 基于 Scikit-learn 的简单实现 3.2 手动实现 KNN(自定义代码) 四、K 值选择与可视化分析 4.1 K 值对分类…...

技术栈ES的介绍和使用
目录 1. 全文搜索引擎(Elastic Search)的由来2. Elastic Search 概述2.1 Elastic Search 介绍2.2 Elastic Search 功能2.3 Elastic Search 特点 3. 安装 Elastic Search3.1 ES 的安装3.2 安装 kibana3.3 ES 客户端的安装 4. Elastic Search 基本概念4.1 …...
跟Gemini学做PPT-模板样式的下载
好的,这里有一些推荐的网站,您可以在上面找到PPT目录样式和模板的灵感: SlideModel (slidemodel.com) 提供各种预先设计的目录幻灯片模板。这些模板100%可编辑,可用于PowerPoint和Google Slides。您可以找到不同项目数量ÿ…...

Windows版本的postgres安装插件http
1、下载安装包 这里使用安装 pgsql-http 的扩展 源码地址:GitHub - pramsey/pgsql-http: HTTP client for PostgreSQL, retrieve a web page from inside the database. 编译的安装地址:http extension for windows updated to include PostgreSQL17 …...

uni-app学习笔记十六-vue3页面生命周期(三)
uni-app官方文档页面生命周期部分位于页面 | uni-app官网。 本篇再介绍2个生命周期 1.onUnload:用于监听页面卸载。 当页面被关闭时,即页面的缓存被清掉时触发加载onUnload函数。 例如:在demo6页面点击跳转到demo4,在demo4页面回退不了到d…...
优化的两极:凸优化与非凸优化的理论、应用与挑战
在机器学习、工程设计、经济决策等众多领域,优化问题无处不在。而在优化理论的世界里,凸优化与非凸优化如同两个截然不同的 “王国”,各自有着独特的规则、挑战和应用场景。今天,就让我们深入探索这两个优化领域的核心差异、算法特…...

(五)MMA(OpenTelemetry/Rabbit MQ/ApiGateway/MongoDB)
文章目录 项目地址一、OpenTelemetry1.1 配置OpenTelemetry1. 服务添加2. 添加服务标识3. 添加请求的标识4. 添加中间价 二、Rabbit MQ2.1 配置Rabbit MQ1. docker-compose2. 添加Rabbit MQ的Connect String 2.2 替换成Rabbit MQ1. 安装所需要的包2. 使用 三、API Gateways3.1 …...

TCP通信与MQTT协议的关系
1. MQTT 处理核心(Mqtt_Pro) void Mqtt_Pro(void) { MQTT_Init(); // 初始化MQTT协议栈(连接参数、缓冲区等) MQTT_SendPro(); // 处理MQTT发送(封装消息,调用TCP发送) MQTT_RecPro();…...
AWS创建github相关的角色
创建github-actions角色 {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::11111111:oidc-provider/token.actions.githubusercontent.com…...
数据编辑器所具备的数据整理功能
在企业的数据处理过程中,数据清洗与整理是至关重要的环节,而数据编辑器在这方面发挥着关键作用。在一份包含客户信息的数据表中,常常会出现缺失值的情况。比如客户的年龄、联系方式等字段可能因为各种原因没有被记录,这就形成了缺…...
Unity网络开发实践项目
摘要:该网络通信系统基于Unity实现,包含以下几个核心模块: 协议配置:通过XML定义枚举(如玩家/英雄类型)、数据结构(如PlayerData)及消息协议(如PlayerMsg)&a…...

Jetson Orin Nano - SONY imx415 camera驱动开发
目录 前言: 调试准备工作: 修改内核默认打印等级 一、imx415驱动开发 1、硬件接线 2、设备树修改 2.1 创建 tegra234-p3767-camera-p3768-imx415-C-4lane.dtsi 文件 2.2 tegra234-p3767-camera-p3768-imx415-C-4lane.dtsi 添加到设备树 2.3 编译设备树 3、imx415驱动…...

word为跨页表格新加表头和表名
问题: 当表格过长需要跨页时(如下图所示),某些格式要求需要转页接排加续表。 方法一: 1、选中表格,在“表布局”区域点开“自动调整”,选择“固定列宽”(防止后续拆分表格后表格变…...

测试用例篇章
本节概要: 测试⽤例的概念 设计测试⽤例的万能思路 设计测试⽤例的⽅法 一、测试用例 1.1 概念 什么是测试用例? 测试⽤例(Test Case)是为了实施测试⽽向被测试的系统提供的⼀组集合,这组集合包含:测…...

2025年北京市职工职业技能大赛第六届信息通信行业网络安全技能大赛复赛CTF部分WP-哥斯拉流量分析
2025年北京市职工职业技能大赛第六届信息通信行业网络安全技能大赛复赛CTF部分WP-哥斯拉流量分析 一、流量分析 题目没有任何提示,附件gzl.pcap 解题哥斯拉流量300多KB包很多,没啥经验只能挨个看回来之后又狠狠得撸了一把哥斯拉流量分析我这里用的是哥斯拉4.0.1 测试链接…...

Django ToDoWeb 服务
我们的任务是使用 Django 创建一个简单的 ToDo 应用程序,允许用户添加、查看和删除笔记。我们将通过设置 Django 项目、创建 Todo 模型、设计表单和视图来处理用户输入以及创建模板来显示任务来构建它。我们将逐步实现核心功能以有效地管理 todo 项。 Django ToDoWeb 服务 …...
【软件】在 macOS 上安装 Postman
在 macOS 上安装 Postman 是一个简单的过程,以下是详细的步骤: 一、下载 Postman • 访问 Postman 官方网站: 打开浏览器,访问Postman 官方下载页面。 • 下载安装包: 页面会自动识别你的系统,点击“Dow…...

各种数据库,行式、列式、文档型、KV、时序、向量、图究竟怎么选?
慕然回首,发现这些年来涌现出了许多类型的数据库,今天抽空简单回顾一下,以便于后面用到时能快速选择。 1. 关系型数据库(行式) 关系型数据库(RDBMS),我们常说的数据库就是指的关系型数据库。 它的全称是关…...

全志科技携飞凌嵌入式T527核心板亮相OpenHarmony开发者大会
近日,OpenHarmony开发者大会2025(OHDC.2025,以下简称“大会”)在深圳举办,全志科技作为OpenHarmony生态的重要合作伙伴受邀参会,并进行了《全志科技行业智能芯片OpenHarmony方案适配与认证经验分享》的主题…...
AI+微信小程序:智能客服、个性化推荐等场景的落地实践
在移动互联网流量红利逐渐见顶的今天,微信小程序凭借“即用即走”的轻量化特性,已成为企业连接用户的核心阵地。而AI技术的融入,正让小程序从工具型应用进化为“懂用户、会思考”的智能服务终端。本文将结合实际案例,解析AI在微信小程序中的两大核心场景——智能客服与个性…...

事件驱动架构入门
主要参考资料: 软件架构-事件驱动架构: https://blog.csdn.net/liuxinghao/article/details/113923639 目录 简介事件队列事件日志事件收集器响应队列读事件 vs. 写事件 简介 事件驱动架构是一种系统或组件之间通过发送事件和响应事件彼此交互的架构风格。当某个事…...

基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
索引的选择与Change Buffer
1. 索引选择与Change Buffer 问题引出:普通索引 vs 唯一索引 ——如何选择? 在实际业务中,如果一个字段的值天然具有唯一性(如身份证号),并且业务代码已确保无重复写入,那就存在两种选择&…...