SpringBoot 整合 Langchain4j 实现会话记忆存储深度解析
目录
一、前言
二、AI大模型会话记忆介绍
2.1 AI 大模型的会话记忆是什么
2.2 AI 大模型为什么需要会话记忆
2.3 AI 大模型会话记忆常用实现方案
2.4 LangChain4j 会话记忆介绍
2.4.1 LangChain4j 会话记忆介绍
2.4.2 LangChain4j 会话记忆类型
三、Langchain4j 会话记忆操作案例使用
3.1 前置准备
3.1.1 导入依赖文件
3.1.2 添加配置文件
3.1.3 前置案例
3.2 会话记忆的实现
3.2.1 基于多轮对话存储结果实现会话记忆
3.2.2 基于ChatMemory 实现会话记忆
3.2.3 基于ChatMemory 会话记忆升级
3.3 会话隔离实现
3.3.1 自定义一个Assistant
3.3.2 自定义chatMemoryProvider 配置bean
3.3.3 添加测试接口
3.3.4 效果测试
四、基于Redis实现会话记忆持久化存储
4.1 前置准备
4.1.1 导入redis依赖
4.1.2 添加redis配置信息
4.1.3 自定义redis序列化类
4.2 会话记忆代码改造
4.2.1 自定义ChatMemoryStore
4.2.2 ChatMemoryProvider 配置bean改造
4.2.3 接口效果测试
五、写在文末
一、前言
在于大模型对话的时候,细心的伙伴们会发现,前面跟大模型聊的一句话,后面再基于这句话继续问问题的时候,仍然可以得到预期的回答,这就是大模型的记忆能力。什么是记忆功能?默认情况下向大模型每次发起的提问都是新的,大模型无法把每次对话形成记忆,也无法根据对话上下文给出人性化的答案。比如:我的第一次提的一个问题,大模型给出了一个回答的列表,当我再次提问这个回答列表中的一个问题时,它就不知道我在说什么了,因为大模型已经失去了上一次的提问记忆。所以让智能体(如AI助手、机器人、虚拟角色等)拥有记忆功能不仅能提升交互体验,还能增强其功能性、适应性和长期价值。
二、AI大模型会话记忆介绍
2.1 AI 大模型的会话记忆是什么
AI 大模型的会话记忆,通常指的是在对话式人工智能系统中,为了让AI能够理解并回应用户输入的信息时,考虑到之前的交互内容的能力。这种能力使得AI能够在长时间的对话中维持上下文,记住之前提到的关键信息,并根据这些信息进行更加准确和相关的回应。在下面的这2段对话中,基于第一次给出的回答内容,再次发起相关的问题时,大模型仍然能够给出预期的回答。
传统上,很多对话系统处理每个请求都是独立的,不考虑之前发生的对话内容。这种方式对于简单、直接的查询是有效的,但当涉及到需要上下文理解的复杂对话时,就显得力不从心了。为了解决这个问题,研究人员开发了不同的技术来赋予AI“记忆”功能,使它们能够在对话中保持连贯性和一致性。
2.2 AI 大模型为什么需要会话记忆
AI大模型需要会话记忆,主要是为了提升对话的质量和连贯性,使得人机交互更加自然、有效。以下是几个主要原因:
-
上下文理解:在实际对话中,人们经常依赖于之前提到的信息来构建后续的讨论。没有会话记忆的话,AI每次只能处理独立的请求,而无法理解或记住之前的对话内容,这会导致回答缺乏上下文关联性,显得机械且不自然。
-
个性化体验:通过记住用户先前提供的信息(例如偏好、历史行为等),AI可以提供更加个性化的服务和建议,增强用户体验。这种能力对于客服机器人、个人助手等应用场景尤为重要。
-
复杂问题解决:有些问题或任务需要跨多个对话回合进行,可能涉及收集额外的数据或者逐步细化需求。拥有会话记忆可以让AI更有效地管理这些复杂的交互过程,提高解决问题的效率。
-
持续学习与改进:虽然这不是传统意义上的“记忆”,但一些先进的系统能够从过去的对话中学习,识别常见模式、错误和成功案例,从而不断优化其响应策略。
-
维护对话的一致性:在长时间的对话过程中保持角色一致性、立场一致性和事实准确性是非常重要的。会话记忆帮助AI在整个对话过程中维持这些方面的一致性,避免出现自相矛盾的回答。
-
增强用户信任:当AI能够记住并正确引用早先的对话细节时,它能给用户留下深刻印象,并增加对AI系统的信任感。这对于建立长期的用户关系至关重要。
综上所述,会话记忆是使AI大模型能够在各种应用场景中实现更加智能、灵活和人性化的关键因素之一。它不仅提高了对话的质量,还为用户提供了一个更加连贯、个性化的交流体验。
2.3 AI 大模型会话记忆常用实现方案
随着大模型技术的广泛使用,AI大模型实现会话记忆的方式有多种,如下:
-
基于缓存的记忆:这种方法涉及存储最近几个回合的对话信息,并在处理新的输入时参考这些信息。这可以提供一定程度的上下文,但其容量有限,因为只能记住最近的交互。
-
外部数据库或知识图谱:在这种方法中,AI可以通过查询外部数据库或知识图谱来获取长期记忆。这种方式允许AI访问大量信息,但是如何高效地检索和利用这些信息是一个挑战。
-
长短期记忆网络(LSTM)和其他递归神经网络(RNNs):这些是专门设计用来处理序列数据的深度学习模型,能够通过内部状态保存一些关于过去事件的记忆。虽然它们能捕捉到一定的上下文信息,但在处理非常长的对话历史时仍面临挑战。
-
Transformer架构:近年来,Transformer及其变种(如BERT、GPT等)已经成为构建大规模语言模型的基础,这些模型可以同时关注多个输入部分,从而更有效地理解和记忆对话中的关键信息。特别是自注意力机制允许模型对整个对话历史进行编码,以便更好地生成响应。
随着技术的进步,现代AI大模型越来越擅长模拟人类对话,不仅能够回答问题,还能以自然流畅的方式参与复杂的对话,这部分得益于其先进的会话记忆技术。
2.4 LangChain4j 会话记忆介绍
LangChain4j 是 Java 版的 LangChain 实现,提供了构建大模型应用的组件,其中会话记忆(Memory)是核心功能之一。入口:Chat Memory | LangChain4j
2.4.1 LangChain4j 会话记忆介绍
LangChain4j 中的会话记忆是指在与大模型交互过程中,保存和管理对话历史、上下文信息的机制。它使应用能够:
-
维护多轮对话状态
-
记住用户偏好和历史交互
-
提供连贯的对话体验
2.4.2 LangChain4j 会话记忆类型
LangChain4j 提供了多种记忆实现:
1)基础记忆类型
LangChain4j 提供的基础记忆类型主要是其核心接口ChatMemory 下面的几种实现方式,如下:
-
ChatMemoryStore
-
基础接口,定义了记忆存储的基本操作
-
主要方法:
add()
,getMessages()
,clear()
-
-
MessageWindowChatMemory
-
基于滑动窗口的记忆实现
-
只保留最近N条消息(可配置)
-
防止记忆无限增长
-
-
TokenWindowChatMemory
-
基于token数量的记忆实现
-
保留最近的消息直到达到token限制
-
更适合大模型的上下文窗口限制
-
2)高级记忆类型
高级记忆主要是在实际开发中,借助外部的存储组件进行会话记忆的存储以及相关的扩展点,包括:
-
PersistentChatMemory
-
持久化记忆,可保存到数据库或文件
-
支持跨会话记忆
-
-
VectorStoreChatMemory
-
使用向量存储保存记忆
-
支持基于语义的记忆检索
-
-
SummaryChatMemory
-
自动生成对话摘要作为记忆
-
减少需要保存的原始消息数量
-
三、Langchain4j 会话记忆操作案例使用
接下来通过代码的案例操作,详细介绍Langchain4j 的会话记忆功能的使用。
3.1 前置准备
3.1.1 导入依赖文件
创建一个springboot 工程,pom中导入下面的核心依赖
<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-boot.version>3.2.6</spring-boot.version><langchain4j.version>1.0.0-beta3</langchain4j.version></properties><dependencies><!-- web应用程序核心依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 编写和运行测试用例 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 基于openai系列整合的springboot-starter --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai-spring-boot-starter</artifactId></dependency><!-- 接入阿里云百炼平台 --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId></dependency><!--langchain4j高级功能--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-spring-boot-starter</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.39</version> <!-- 使用最新安全版本 --></dependency></dependencies><dependencyManagement><dependencies><!--引入SpringBoot依赖管理清单--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!--引入langchain4j依赖管理清单--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-bom</artifactId><version>${langchain4j.version}</version><type>pom</type><scope>import</scope></dependency><!--引入百炼依赖管理清单--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain4j.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
3.1.2 添加配置文件
在工程配置文件中添加下面的配置内容
server:port: 8082#直接对接的是deepseek官网的的大模型
langchain4j:#阿里百炼平台的模型community:dashscope:chat-model:api-key: 你的apikey #这个是百炼平台的apikeymodel-name: qwen-maxlogging:level:root: debug
注意:
如果没有apikey的同学,可以前往阿里云百炼平台注册并获取一个apikey,操作入口:
大模型服务平台百炼控制台
3.1.3 前置案例
1)增加一个 Assistant接口
自定义一个Assistant,里面有一个chat方法
package com.congge.assistant;import dev.langchain4j.service.spring.AiService;@AiService
public interface Assistant {String chat(String userMessage);
}
2)增加一个测试接口
在工程中增加一个测试接口,如下代码:
@Resource
private Assistant assistant;//localhost:8082/chat/test
@GetMapping("/chat/test")
public String chat() {String result1 = assistant.chat("我是小王");System.out.println(result1);String result2 = assistant.chat("你知道我是谁吗?");System.out.println(result2);return "chat";
}
启动工程后调用该接口,通过控制台输出效果可以发现一个问题,那就是通过这种方式与大模型进行对话,大模型是没有会话记忆的。
3.2 会话记忆的实现
通过上面的接口案例不难发现,直接与大模型对话是没有会话记忆的,如果实现会话记忆功能,就需要引入其他的方式进行实现,下面分别说明。
3.2.1 基于多轮对话存储结果实现会话记忆
保存对话内容实现会话记忆是一种原始但是比较容易实现的方式,具体来说就是,在第一轮对话之后,后面的每一轮对话在调用chat方法的时候,均需要把之前的对话加入到参数里面去。
package com.congge.controller;import com.congge.assistant.Assistant;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.response.ChatResponse;import java.util.Arrays;@RestController
public class ChatMemoryTestController {@Resourceprivate Assistant assistant;@Autowiredprivate QwenChatModel qwenChatModel;//localhost:8082/chat/test@GetMapping("/chat/test")public String chat() {String result1 = assistant.chat("我是小王");System.out.println(result1);String result2 = assistant.chat("你知道我是谁吗?");System.out.println(result2);return "chat";}//localhost:8082/chat/memory/v1@GetMapping("/chat/memory/v1")public String chatMemoryV1() {//第一轮对话UserMessage userMessage1 = UserMessage.userMessage("我是小王");ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);AiMessage aiMessage1 = chatResponse1.aiMessage();//输出大语言模型回复System.out.println(aiMessage1.text());//第二轮对话UserMessage userMessage2 = UserMessage.userMessage("你知道我是谁吗");ChatResponse chatResponse2 = qwenChatModel.chat(Arrays.asList(userMessage1,aiMessage1,userMessage2));AiMessage aiMessage2 = chatResponse2.aiMessage();//输出大语言模型的回复System.out.println(aiMessage2.text());return aiMessage2.text();}}
启动项目后,调用上面的接口,通过返回结果发现,这种方式可以实现多轮会话的记忆功能
3.2.2 基于ChatMemory 实现会话记忆
Langchain4j 提供了ChatMemory 组件,基于该组件可以实现会话记忆的功能
从源码中不难发现,ChatMemory 是一个接口,理论上,只要是实现了该接口,均可以实现会话记忆功能,在接口的默认实现中,可以看到基于当前的依赖引入下,有下面两个实现
以MessageWindowChatMemory这个实现来说,顾名思义,说明这个实现情况下,会话记忆是存储在内存中,一旦程序重启了会话记忆就丢失了,使用该组件进行一个会话记忆功能,参考下面的代码
//localhost:8082/chat/memory/v2?userMessage=我是小王
@GetMapping("/chat/memory/v2")
public String chatMemoryV2(@RequestParam("userMessage") String userMessage) {//创建chatMemoryMessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);//创建AIServiceAssistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(qwenChatModel).chatMemory(chatMemory).build();//调用service的接口String answer1 = assistant.chat(userMessage);System.out.println(answer1);String answer2 = assistant.chat("我是谁");System.out.println(answer2);return answer2;
}
调用上面的接口,可以看到通过这种方式可以实现会话记忆的功能
3.2.3 基于ChatMemory 会话记忆升级
上一步基于ChatMemory 实现了会话记忆功能,但是细心的同学会发现,这种写法比较繁琐,不方便全局使用和代码复用,于是可以想到,既然上一步用到了AiServices , 那就可以借助AiServices 将会话记忆配置到全局的bean中进行管理。
1)自定义一个全局的ChatMemory的配置类
参考下面的代码,将MessageWindowChatMemory配置为全局bean
package com.congge.assistant;import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MemoryCharConfig {@Beanpublic ChatMemory chatMemory() {//设置聊天记忆记录的message数量return MessageWindowChatMemory.withMaxMessages(20);}}
2)自定义Assistant
添加一个自定义的Assistant接口,在接口的注解中,引用上一步自定义的chatMemory
package com.congge.assistant;import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "qwenChatModel",chatMemory = "chatMemory"
)
public interface ChatMemoryAssistant {String chat(String userMessage);}
3)添加测试接口
添加一个自定义接口用于效果测试,参考下面的代码
@Resource
private ChatMemoryAssistant chatMemoryAssistant;//localhost:8082/chat/memory/v3?userMessage=我是小王
@GetMapping("/chat/memory/v3")
public String chatMemoryV3(@RequestParam("userMessage") String userMessage) {//调用service的接口String answer1 = chatMemoryAssistant.chat(userMessage);System.out.println(answer1);String answer2 = chatMemoryAssistant.chat("我是谁");System.out.println(answer2);return answer2;
}
4)效果测试
调用上面的接口,通过返回结果可以看到,通过这种方式也能达到存储会话记忆的功能
3.3 会话隔离实现
在上面的基于chatMemory 实现会话记忆中,细心的同学可以发现一个问题,那就是这样的实现方式无法做到会话隔离,而在实际应用中,不同的用户进行对话,是必须要做会话隔离的,否则A用户的历史会话信息会被B用户看到了,这就出问题了,此时需要借助AiService注解中的 chatMemoryProvider 这个属性配置参数来实现,下面来看代码具体实现过程。
3.3.1 自定义一个Assistant
自定义一个Assistant 接口
-
在该chat方法中,使用了2个新增的注解@MemoryId和@UserMessage,对传入的参数类型进行了限定
-
在接口注解中,配置了chatMemoryProvider 这个属性值,在后面还需要配置一个这样的bean
package com.congge.assistant;import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "qwenChatModel",chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatAssistant {/*** 分离聊天记录* @param memoryId 聊天id* @param userMessage 用户消息* @return*/String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
3.3.2 自定义chatMemoryProvider 配置bean
自定义一个类,并配置chatMemoryProvider 的bean
package com.congge.config;import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class SeparateChatAssistantConfig {@Beanpublic ChatMemoryProvider chatMemoryProvider() {return memoryId -> MessageWindowChatMemory.builder().id(memoryId).maxMessages(20).build();}
}
3.3.3 添加测试接口
添加一个测试接口,参考下面的代码
@Resource
private SeparateChatAssistant separateChatAssistant;//localhost:8082/chat/memory/v5?userId=1&userMessage=我是小王
//localhost:8082/chat/memory/v5?userId=1&userMessage=你知道我是谁吗?
//localhost:8082/chat/memory/v5?userId=2&userMessage=我是小李
//localhost:8082/chat/memory/v5?userId=2&userMessage=你知道我是谁吗?
@GetMapping("/chat/memory/v5")
public String chatMemoryV3(@RequestParam("userId") Integer userId,@RequestParam("userMessage") String userMessage) {String answer1 = separateChatAssistant.chat(userId,userMessage);return answer1;
}
3.3.4 效果测试
依次调用上面的4个接口,可以看到下面的效果,说明上面的实现方案是正常生效了
1)第一次调用
2)第二次调用
3)第三次调用
4)第四次调用
四、基于Redis实现会话记忆持久化存储
通过上面的案例操作,我们掌握了Langchan4j中会话记忆存储的常用实现方式,但是细心的同学可以发现,这种方式是基于内存的实现,一旦服务重启,或者服务异常宕机了,历史会话就丢失了,而在实际应用开发中,这种方式是肯定不允许存在的,接下来我们使用Redis作为持久化存储数据源来实现。
4.1 前置准备
4.1.1 导入redis依赖
基于上面的工程,在pom文件中导入下redis依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.1.2 添加redis配置信息
在配置文件中添加关于redis的配置信息
spring:data:redis:host: localhostport: 6379
4.1.3 自定义redis序列化类
为了更好的管理redis中存储的数据,这里添加一个redis的自定义配置类
package com.congge.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));redisTemplate.afterPropertiesSet();return redisTemplate;}}
4.2 会话记忆代码改造
基于会话隔离中的代码我们做一些改造即可。
4.2.1 自定义ChatMemoryStore
ChatMemoryStore是存储记忆会话必须要使用的对象,如果想要实现会话记忆的自定义数据源存储,需要实现该接口,并实现里面的三个核心方法,从源码中可以清楚看到这一点
参考下面的代码
package com.congge.config;import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.data.redis.core.RedisTemplate;import java.util.List;public class RedisMemoryStoreConfig implements ChatMemoryStore {private RedisTemplate redisTemplate;public RedisMemoryStoreConfig(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic List<ChatMessage> getMessages(Object memoryId) {String value = (String) redisTemplate.opsForValue().get("chat:" + memoryId.toString());if(value == null || value.isEmpty()){return List.of();}return ChatMessageDeserializer.messagesFromJson(value);}@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> list) {String messages = ChatMessageSerializer.messagesToJson(list);redisTemplate.opsForValue().set("chat:" + memoryId.toString(), messages);}@Overridepublic void deleteMessages(Object memoryId) {redisTemplate.delete("chat:" + memoryId.toString());}
}
4.2.2 ChatMemoryProvider 配置bean改造
在上一节中我们了解到,ChatMemoryProvider 是连接大模型对话的核心配置bean,只需要在ChatMemoryProvider的配置bean中,指定chatMemoryStore使用上面自定义的这个RedisMemoryStoreConfig 即可,参考下面的代码
package com.congge.config;import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;@Configuration
public class SeparateChatAssistantConfig {@ResourceRedisTemplate redisTemplate;//基于内存的实现
// @Bean
// public ChatMemoryProvider chatMemoryProvider() {
// return memoryId -> MessageWindowChatMemory.builder()
// .id(memoryId)
// .maxMessages(20)
// .chatMemoryStore(redisMemoryStoreConfig)
// .build();
// }//基于redis的实现@Beanpublic ChatMemoryProvider chatMemoryProvider() {return memoryId -> MessageWindowChatMemory.builder().id(memoryId).maxMessages(20).chatMemoryStore(new RedisMemoryStoreConfig(redisTemplate)).build();}}
4.2.3 接口效果测试
到这一步,改造就完成了,其实核心就是将chatMemoryStore使用自定义的使用redis的自定义的类替换即可,下面通过几个接口调用验证下效果如何。
1)调用userId=1的接口
依次调用userId=1的两个接口
调用成功后,检查redis中存储的信息,可以看到redis中存储了userId=1的两个序列化之后的会话信息
2)调用userId=2的接口
依次调用userId=2的两个接口
调用成功后,检查redis中存储的信息,可以看到redis中存储了userId=2的两个序列化之后的会话信息
五、写在文末
本文通过较大的篇幅并结合实际案例,详细介绍了Langchain4j会话记忆存储的功能,最后给出了基于外部存储组件Redis实现会话存储的代码演示,有兴趣的同学还可以基于此继续深入研究,本篇到此结束,感谢观看。