SpringAI聊天记忆ChatMemory

SpringAI聊天记忆ChatMemory

目录

1、概述

2、基于内存存储聊天记忆

2.1 导入依赖

2.2 创建基于内存的 chatMemory

2.3 简单的代码示例

2.4 结果示例

3、基于Mysql jdbc方式存储聊天记忆

3.1 环境配置

3.2 导入mysql jdbc相关依赖

3.3 配置文件配置ChatMemory初始化配置

3.4 配置ChatMemory

3.4.1 bean 配置mysql 和chat memory

3.4.2 starter-jdbc 进行配置

3.5 支持的数据库JdbcChatMemoryRepositoryDialect

3.6 使用

3.7 结果

4、基于redis 存储聊天记忆

4.1 环境准备

4.2 导入依赖

4.3 bean 配置

4.4 基础使用

4.5 redis存储结果


1、概述

SpringAI的版本查看 https://blog.ZEEKLOG.net/weixin_45948519/article/details/156327249?spm=1011.2415.3001.5331

Spring AI 的 chat-memory 是支撑多轮连贯对话的核心组件,核心解决大语言模型本身的无状态痛点。它的核心作用是存储多轮对话的交互记录,并在后续请求中把对话历史与新请求合并后发送给模型,实现连贯响应。

2、基于内存存储聊天记忆

2.1 导入依赖

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId> </dependency>

2.2 创建基于内存的 chatMemory

import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ChatMemoryConfig { /** * 聊天内存仓库 */ @Bean("inMemoryChatMemoryRepository") ChatMemoryRepository chatMemoryRepository() { return new InMemoryChatMemoryRepository(); } /** * 聊内存储 */ @Bean("inMemoryChatMemory") ChatMemory chatMemory(@Qualifier("inMemoryChatMemoryRepository") ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder() .chatMemoryRepository(chatMemoryRepository) // 设置最大消息 默认 20条 也就是10轮对话 .maxMessages(20) .build(); } }

2.3 简单的代码示例

import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("chat/memory") public class ChatMemoryController { private final ChatClient ollamaChatClient; // 基于内存的 ChatMemory private final ChatMemory inMemoryChatMemory; @GetMapping("memory") public String memory() { // 会话编号 每个会话一个会话编号 用于会话记忆隔离 String conversationId = UUID.randomUUID().toString(); String one = ollamaChatClient.prompt() .user("请记住黄霸天是女生") .advisors( PromptChatMemoryAdvisor.builder(inMemoryChatMemory) .conversationId(conversationId) .build(), // MessageChatMemoryAdvisor.builder(inMemoryChatMemory) // .conversationId(conversationId) // .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第一次回复消息 {}", one); String two = ollamaChatClient.prompt() .user("黄霸天是男生还是女生") .advisors( PromptChatMemoryAdvisor.builder(inMemoryChatMemory) .conversationId(conversationId) .build(), // MessageChatMemoryAdvisor.builder(inMemoryChatMemory) // .conversationId(conversationId) // .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第二次回复消息 {}", two); return "ok"; } }

2.4 结果示例

  • 通过日志可看出已经将数据传递给大模型了 此模式为PromptChatMemoryAdvisor 兼容性最好。检索对话历史后,将其整合为一段文本,嵌入到 Prompt 的 “系统文本”(System Prompt)中;不保留对话的原始消息结构,而是把历史转成描述性文本
  • 以下为MessageChatMemoryAdvisor的抓包示例 可以看出 MessageChatMemoryAdvisor是将历史对话信息保留原始结构发送,检索对话历史后,以「消息组」的形式直接添加到 Prompt 中。但是注意并非所有 AI 模型都支持这种 “多消息结构” 的 Prompt(部分模型仅接受单段文本 Prompt)

3、基于Mysql jdbc方式存储聊天记忆

这里拦截器使用的是PromptChatMemoryAdvisor。与MessageChatMemoryAdvisor区别可以看内存存储聊天示例

3.1 环境配置

  • 安装mysql 这里使用docker 的方式快速布置 如果未安装的可以百度一下如何安装docker。建议使用win11进行开发的话都安装一下docker 很方便
# 这个命令为 创建一个mysql8 密码是123456 端口是 16010 的mysql容器 docker run -d --name mysql8.0.20 -p 16010:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_ROOT_HOST=% mysql:8.0.20 # 使用命令 创建数据库 test_db docker exec -it mysql8.0.20 mysql -u root -p123456 -e "CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;"

3.2 导入mysql jdbc相关依赖

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId> </dependency> <!--mysql驱动--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

3.3 配置文件配置ChatMemory初始化配置

# 配置数据库初始化为 always spring.ai.chat.memory.repository.jdbc.initialize-schema=always # 配置数据库初始化表的sql文件 可以使用默认值 spring.ai.chat.memory.repository.jdbc.schema=classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql # 配置数据库名称 spring.ai.chat.memory.repository.jdbc.platform=mysql
  • IDEA 按两下 shift 搜索 schema-mysql.sql 可找到默认的数据库文件
  • schema-mysql.sql
CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY ( `conversation_id` VARCHAR(36) NOT NULL, `content` TEXT NOT NULL, `type` ENUM('USER', 'ASSISTANT', 'SYSTEM', 'TOOL') NOT NULL, `timestamp` TIMESTAMP NOT NULL, INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`) );

3.4 配置ChatMemory

3.4.1 bean 配置mysql 和chat memory

import com.zaxxer.hikari.HikariDataSource; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.memory.repository.jdbc.MysqlChatMemoryRepositoryDialect; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; @Configuration public class MysqlChatMemoryConfig { // 创建数据源 @Bean(name = "mysqlChatDataSource") DataSource getDateSource() { HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build(); String url = "jdbc:mysql://%s:%s/%s" + "?useUnicode=true&characterEncoding=utf-8&useSSL=false" + "&allowPublicKeyRetrieval=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai"; // 数据库相关配置 dataSource.setJdbcUrl(String.format(url, "127.0.0.1", 16010, "test_db")); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } // 创建jdbc模版 @Bean(name = "mysqlChatJdbcTemplate") JdbcTemplate mysqlChatJdbcTemplate(@Qualifier("mysqlChatDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } // 创建事务管理器 @Bean(name = "mysqlChatTransactionManager") PlatformTransactionManager mysqlChatTransactionManager(@Qualifier("mysqlChatDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } // 创建 chat 数据库存储 @Bean(name = "mysqlChatMemoryRepository") JdbcChatMemoryRepository mysqlChatMemoryRepository( @Qualifier("mysqlChatJdbcTemplate") JdbcTemplate jdbcTemplate, @Qualifier("mysqlChatDataSource") DataSource dataSource, @Qualifier("mysqlChatTransactionManager") PlatformTransactionManager transactionManager) { MysqlChatMemoryRepositoryDialect dialect = new MysqlChatMemoryRepositoryDialect(); return JdbcChatMemoryRepository.builder() .jdbcTemplate(jdbcTemplate) .transactionManager(transactionManager) .dialect(dialect) .dataSource(dataSource) .build(); } // 创建 chatMemory @Bean("mysqlChatMemory") ChatMemory mysqlChatMemory(@Qualifier("mysqlChatMemoryRepository") JdbcChatMemoryRepository repository) { return MessageWindowChatMemory.builder() // 使用数据库存储 .chatMemoryRepository(repository) // 保留最多10条记录 .maxMessages(10) .build(); } }

3.4.2 starter-jdbc 进行配置

  • 导入依赖
<!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
  • 配置文件配置
# 可使用spring 默认数据库配置信息配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:16010/test_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456
  • bean配置
@Bean("mysqlChatMemory") ChatMemory mysqlChatMemory(JdbcChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory .builder() .chatMemoryRepository(chatMemoryRepository) .maxMessages(10) .build(); }

3.5 支持的数据库JdbcChatMemoryRepositoryDialect

3.6 使用

import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("chat/memory/mysql") public class MysqlChatMemoryController { private final ChatClient dashscopeChatClient; private final ChatMemory mysqlChatMemory; @GetMapping("base") public String base() { // 会话编号 每个会话一个会话编号 用于会话记忆隔离 String conversationId = UUID.randomUUID().toString().replace("-", ""); log.info("conversationId: {}", conversationId); String one = dashscopeChatClient.prompt() .user("记住北京今天的天气是晴天,26摄氏度") .advisors( PromptChatMemoryAdvisor.builder(mysqlChatMemory) .conversationId(conversationId) .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第一次回复消息 {}", one); String two = dashscopeChatClient.prompt() .user("北京今天的天气是什么样的") .advisors( PromptChatMemoryAdvisor.builder(mysqlChatMemory) .conversationId(conversationId) .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第二次回复消息 {}", two); return "ok"; } }

3.7 结果

  • 执行后可查看数据库已经存储到了历史聊天记录了
  • 目前最大聊天记录超过了设定的值就会删除,我们想的是最近的数据传递给大模型,而历史的聊天记录保存下来的话可以自己写一个增强器对历史的数据进行保留。并且如图,目前JdbcChatMemoryRepository里面的做法是直接删除所有的然后添加进去。这种方式里面的时间戳也不是用户真实添加的时间戳。而应该算是最后使用时间而不是创建时间。用户所有的历史聊天记录还是推荐使用自定义增强器对历史记录保存。然后短期的历史记录则通过redis或者内存进行存储发送给大模型
  • 提供一个简单保存增强器示例
 import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.ai.chat.client.ChatClientMessageAggregator; import org.springframework.ai.chat.client.ChatClientRequest; import org.springframework.ai.chat.client.ChatClientResponse; import org.springframework.ai.chat.client.advisor.api.CallAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain; import org.springframework.ai.chat.client.advisor.api.StreamAdvisor; import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain; import org.springframework.ai.chat.messages.Message; import reactor.core.publisher.Flux; import java.util.Date; @Slf4j public class SaveAdvisor implements CallAdvisor, StreamAdvisor { private int order = 0; private String conversationId = "def"; public SaveAdvisor(int order, String conversationId) { this.order = order; this.conversationId = conversationId; } @NotNull @Override public ChatClientResponse adviseCall(@NotNull ChatClientRequest chatClientRequest, @NotNull CallAdvisorChain callAdvisorChain) { this.saveRequest(chatClientRequest); ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest); this.saveResponse(chatClientResponse); return chatClientResponse; } @NotNull @Override public Flux<ChatClientResponse> adviseStream(@NotNull ChatClientRequest chatClientRequest, @NotNull StreamAdvisorChain streamAdvisorChain) { this.saveRequest(chatClientRequest); Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest); return (new ChatClientMessageAggregator()).aggregateChatClientResponse(chatClientResponses, this::saveResponse); } public void saveRequest(ChatClientRequest request) { saveMessage(request.prompt().getUserMessage()); } public void saveResponse(ChatClientResponse chatClientResponse) { saveMessage(chatClientResponse.chatResponse().getResult().getOutput()); } public void saveMessage(Message message) { // 这里可以使用MQ异步保存 log.info("保存消息 {} {} {} {}", this.conversationId, message.getMessageType(), message.getText(), new Date()); } @Override public int getOrder() { return this.order; } @NotNull @Override public String getName() { return "SaveAdvisor"; } public static SaveAdvisor.Builder builder() { return new SaveAdvisor.Builder(); } public static final class Builder { private int order = 0; private String conversationId = "def"; public Builder() { } public SaveAdvisor.Builder conversationId(String conversationId) { this.conversationId = conversationId; return this; } public SaveAdvisor.Builder order(int order) { this.order = order; return this; } public SaveAdvisor build() { return new SaveAdvisor(this.order, this.conversationId); } } }

4、基于redis 存储聊天记忆

这里使用的是spring Alibaba 提供的ChatMemoryRepository。

文档所在地址 http://github地址 https://github.com/alibaba/spring-ai-alibaba/tree/1.0.0.3-retriever/community/memories/spring-ai-alibaba-starter-memory-redis

使用版本是1.1.0.0 但是可以查看分支1.0.0.3的文档README

4.1 环境准备

  • 安装redis
docker run -d --name redis-simple -p 16379:6379 redis:7.2.4 redis-server --bind 0.0.0.0 --requirepass "123456" --appendonly yes

4.2 导入依赖

<properties> <java.version>17</java.version> <spring-ai.version>1.1.2</spring-ai.version> <spring-ai-alibaba.version>1.1.0.0</spring-ai-alibaba.version> </properties> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId> <version>${spring-ai-alibaba.version}</version> </dependency>

4.3 bean 配置

import com.alibaba.cloud.ai.memory.redis.LettuceRedisChatMemoryRepository; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedisChatMemoryConfig { @Bean(name = "lettuceRedisChatMemoryRepository") public LettuceRedisChatMemoryRepository lettuceRedisChatMemoryRepository() { // 创建连接池配置对象 GenericObjectPoolConfig<Object> poolConfig = new GenericObjectPoolConfig<>(); // 最大活跃连接数 poolConfig.setMaxTotal(8); // 最大空闲连接数 poolConfig.setMaxIdle(8); // 最小空闲连接数 poolConfig.setMinIdle(2); return LettuceRedisChatMemoryRepository.builder() // redis 地址 .host("127.0.0.1") // 端口 .port(16379) // 密码 .password("123456") // 连接超时时间 .timeout(10000) // 连接池配置对象 .poolConfig(poolConfig) .build(); } @Bean("redisChatMemory") ChatMemory redisChatMemory(@Qualifier("lettuceRedisChatMemoryRepository") LettuceRedisChatMemoryRepository repository) { return MessageWindowChatMemory.builder() // 使用数据库存储 .chatMemoryRepository(repository) // 保留最多10条记录 .maxMessages(10) .build(); } }

4.4 基础使用

import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("chat/memory/redis") public class RedisChatMemoryController { private final ChatClient dashscopeChatClient; private final ChatMemory redisChatMemory; @GetMapping("base") public String base() { // 会话编号 每个会话一个会话编号 用于会话记忆隔离 String conversationId = UUID.randomUUID().toString().replace("-", ""); log.info("conversationId: {}", conversationId); String one = dashscopeChatClient.prompt() .user("记住北京今天的天气是晴天,26摄氏度") .advisors( MessageChatMemoryAdvisor.builder(redisChatMemory) .conversationId(conversationId) .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第一次回复消息 {}", one); String two = dashscopeChatClient.prompt() .user("北京今天的天气是什么样的") .advisors( MessageChatMemoryAdvisor.builder(redisChatMemory) .conversationId(conversationId) .build(), SimpleLoggerAdvisor.builder().build() ).call().content(); log.info("第二次回复消息 {}", two); return "ok"; } }

4.5 redis存储结果

Read more

鸿蒙金融理财全栈项目——运维监控、性能优化、安全加固

鸿蒙金融理财全栈项目——运维监控、性能优化、安全加固

《鸿蒙APP开发从入门到精通》第20篇:鸿蒙金融理财全栈项目——运维监控、性能优化、安全加固 📊🔧🛡️ 内容承接与核心价值 这是《鸿蒙APP开发从入门到精通》的第20篇——运维监控、性能优化、安全加固篇,100%承接第19篇的生态合作、用户运营、数据变现架构,并基于金融场景的运维监控、性能优化、安全加固要求,设计并实现鸿蒙金融理财全栈项目的运维监控、性能优化、安全加固功能。 学习目标: * 掌握鸿蒙金融理财项目的运维监控设计与实现; * 实现应用监控、服务器监控、数据库监控; * 理解性能优化在金融场景的核心设计与实现; * 实现前端优化、后端优化、数据库优化; * 掌握安全加固在金融场景的设计与实现; * 实现代码加固、数据加密、安全审计; * 优化金融理财项目的用户体验(运维监控、性能优化、安全加固)。 学习重点: * 鸿蒙金融理财项目的运维监控设计原则; * 性能优化在金融场景的应用; * 安全加固在金融场景的设计要点。 一、 运维监控基础 🎯 1.1 运维监控定义 运维监控是指对金融理财项目的应用、

By Ne0inhk
Flutter for OpenHarmony:cider 自动化版本管理与变更日志生成器(发布流程标准化的瑞士军刀) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:cider 自动化版本管理与变更日志生成器(发布流程标准化的瑞士军刀) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 App 的迭代过程中,维护 pubspec.yaml 中的版本号和编写 CHANGELOG.md 是一件既繁琐又容易出错的事情。 * “这次发布是 1.0.1 还是 1.1.0?” * “昨天的 bug fix 有没有写进变更日志?” * “谁不小心把 build number 搞错了,导致应用商店上传失败?” 对于 OpenHarmony 应用来说,更加严格的版本管控(如 HAP 包的版本对应)使得这一环节尤为重要。 Cider 是一个专为 Dart/Flutter 项目设计的命令行工具,它可以自动化地处理版本升级、变更日志维护以及发布的检查。它就像是你的“发布管家”

By Ne0inhk

Flutter for OpenHarmony: Flutter 三方库 plugin_platform_interface 规范鸿蒙插件跨端接口契约(插件开发标准指南)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 插件开发时,一个核心挑战是如何确保你的插件在 Android、iOS 和鸿蒙等多端表现一致。为了保证扩展的可测试性和规范性,Flutter 团队提出了一套“基于接口”的插件架构规范。 plugin_platform_interface 正是实现这一架构的官方基石。它通过强行校验开发者是否继承了特定的基类,确保任何三方开发者(或你自己在进行鸿蒙适配时)在模拟或重写平台库时,都能遵循严格的协议契约,防止因漏写方法而导致的运行时崩溃。 一、标准分层插件架构 该库致力于定义中间的“平台接口层(Platform Interface)”。 注册实现 注册实现 通过校验器 Flutter App 插件 API (面向用户) Platform Interface (定义契约) 鸿蒙特定实现 (ArkTS 交互) Android 特定实现

By Ne0inhk
Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座

Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座 在大数据传输、大型游戏资源更新以及分布式固件升级场景中,传统的单点 HTTP 下载往往面临带宽压力和校验失效的风险。metalink 协议(RFC 5854)通过整合多个源地址与强校验机制,提供了一套工业级的资源分发方案。metalink_advanced_final 库为 Flutter 开发者提供了该协议的终极解析与执行引擎。本文将深度解析如何在 OpenHarmony(鸿蒙)环境下,结合鸿蒙的并发架构,完美适配这一强大的下载工具。 前言 随着鸿蒙系统(HarmonyOS)跨终端特性的普及,应用在不同设备间流转时的资源同步速度成为了用户体验的“胜负手”。metalink_advanced_final

By Ne0inhk