SpringAI+DeepSeek大模型应用开发——6基于MongDB持久化对话

持久化对话

默认情况下,聊天记忆存储在内存中ChatMemory chatMemory = new InMemoryChatMemory()

如果需要持久化存储,可以实现一个自定义的聊天记忆存储类,以便将聊天消息存储在你选择的任何持久化存储介质中。

MongoDB

文档型数据库,数据以JSON - like的文档形式存储,具有高度的灵活性和可扩展性。它不需要预先定义严格的表结构,适合存储半结构化或非结构化的数据。

当聊天记忆中包含多样化的信息,如文本消息、图片、语音等多媒体数据,或者消息格式可能会频繁变化时,MongoDB 能很好地适应这种灵活性。例如,一些社交应用中用户可能会发送各种格式的消息,使用 MongoDB 可以方便地存储和管理这些不同类型的数据。

整合SpringBoot

引入MongoDB依赖:

<!-- Spring Boot Starter Data MongoDB -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

添加远程连接配置:

#MongoDB连接配置
spring:data:mongodb:uri: mongodb://localhost:27017/chat_memory_dbusername: rootpassword: xxx
实体类

映射MongoDB中的文档(相当与MySQL的表)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;@Data
@AllArgsConstructor
@NoArgsConstructor
@Document("chatMessages")
public class ChatMessages {//唯一标识,映射到 MongoDB 文档的 _id 字段@Idprivate ObjectId id;private String conversationId;  //会话IDprivate String messagesJson;  //消息JSON}
消息序列化器

对聊天消息message进行 序列化 和 反序列化 操作

消息序列化(messagesToJson 方法):

将一组 Message 对象(如 UserMessage、AssistantMessage)转换为 JSON 字符串。

用于将内存中的聊天记录保存到存储介质(如数据库、文件)或通过网络传输。

消息反序列化(messagesFromJson 方法):

将 JSON 字符串还原为 Message 对象列表。
用于从持久化存储或网络接收的数据中恢复聊天消息对象。

支持多态反序列化(MessageDeserializer 类)

根据 JSON 中的 messageType 字段判断消息类型(如 “USER” 或 “ASSISTANT”),并创建对应的子类实例。

解决了 Jackson 默认无法识别接口或抽象类具体实现的问题。

格式美化(可选)

启用了 SerializationFeature.INDENT_OUTPUT,使输出的 JSON 更具可读性(适合调试和日志输出)。

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;import java.io.IOException;
import java.util.List;
import java.util.Map;
//聊天消息序列化器
public class MessageSerializer {private static final ObjectMapper objectMapper = new ObjectMapper();static {objectMapper.enable(SerializationFeature.INDENT_OUTPUT);SimpleModule module = new SimpleModule();module.addDeserializer(Message.class, new MessageDeserializer());objectMapper.registerModule(module);}public static String messagesToJson(List<Message> messages) throws JsonProcessingException {return objectMapper.writeValueAsString(messages);}public static List<Message> messagesFromJson(String json) throws JsonProcessingException {return objectMapper.readValue(json, new TypeReference<List<Message>>() {});}private static class MessageDeserializer extends JsonDeserializer<Message> {@Overridepublic Message deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {Map<String, Object> node = p.readValueAs(Map.class);String type = (String) node.get("messageType");switch (type) {case "USER":return new UserMessage((String) node.get("text"));case "ASSISTANT":return new AssistantMessage((String) node.get("text"));default:throw new IOException("未知消息类型: " + type);}}}
}
持久化类
@Component
@RequiredArgsConstructor
public class MongoChatMemory implements ChatMemory {@Resourceprivate MongoTemplate mongoTemplate;@Overridepublic void add(String conversationId, List<Message> messages) {Query query = new Query(Criteria.where("conversationId").is(conversationId));ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);List<Message> updatedMessages;if (chatMessages != null) {try {updatedMessages = new java.util.ArrayList<>(chatMessages.getMessagesJson() != null? MessageSerializer.messagesFromJson(chatMessages.getMessagesJson()) : Collections.emptyList());} catch (JsonProcessingException e) {throw new RuntimeException("序列化消息失败", e);}updatedMessages.addAll(messages);} else {updatedMessages = new java.util.ArrayList<>(messages);}try {String json = MessageSerializer.messagesToJson(updatedMessages);if (chatMessages != null) {Update update = new Update().set("messagesJson", json);mongoTemplate.updateFirst(query, update, ChatMessages.class);} else {ChatMessages newChatMessages = new ChatMessages();newChatMessages.setConversationId(conversationId);newChatMessages.setMessagesJson(json);mongoTemplate.insert(newChatMessages);}} catch (JsonProcessingException e) {throw new RuntimeException("序列化消息失败", e);}}@Overridepublic List<Message> get(String conversationId, int lastN) {Query query = new Query(Criteria.where("conversationId").is(conversationId));ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);if (chatMessages == null || chatMessages.getMessagesJson() == null) {return Collections.emptyList();}try {List<Message> allMessages = MessageSerializer.messagesFromJson(chatMessages.getMessagesJson());int size = allMessages.size();int fromIndex = Math.max(0, size - lastN);return allMessages.subList(fromIndex, size);} catch (JsonProcessingException e) {throw new RuntimeException("反序列化消息失败", e);}}@Overridepublic void clear(String conversationId) {Query query = new Query(Criteria.where("conversationId").is(conversationId));mongoTemplate.remove(query, ChatMessages.class);}
}
测试

初始化ChatClient时,注入MongoChatMemory

//健康报告对话
@Component
@Slf4j
public class HealthReportApp {private final ChatClient chatClient1;private static final String SYSTEM_PROMPT = "你的名字是“小鹿”,你是一家名为“北京协和医院”的智能客服。你是一个训练有素的医疗顾问和医疗伴诊助手。你态度友好、礼貌且言辞简洁。\n" +"1、请仅在用户发起第一次会话时,和用户打个招呼,并介绍你是谁。\n" ;//初始化ChatClientpublic HealthReportApp(ChatModel dashscopeChatModel,MongoChatMemory mongoChatMemory) throws IOException {chatClient1 = ChatClient.builder(dashscopeChatModel).defaultSystem(SYSTEM_PROMPT)  //系统预设.defaultAdvisors(new MessageChatMemoryAdvisor(mongoChatMemory), //对话记忆//自定义日志  Advisor,可按需开启new MyLoggerAdvisor(),// 自定义违禁词 Advisor,可按需开启new ProhibitedWordAdvisor()//自定义推理增强,可按需开启//new ReReadingAdvisor()).build();}public record HealthReport(String title, List<String> suggestions) { }//生成健康报告对话public HealthReport doChatWithReport(String message, String chatId) {HealthReport healthReport = chatClient1.prompt().system(SYSTEM_PROMPT + "分析用户提供的信息,每次对话后都要生成健康报告,标题为{用户名}的健康报告,内容为建议列表").user(message).advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)).call().entity(HealthReport.class);log.info("healthReport: {}", healthReport);return  healthReport;}}       

mongodb记录如下

[ {"messageType" : "USER","metadata" : {"messageType" : "USER"},"media" : [ ],"text" : "你好,我是程序员kk"
}, {"messageType" : "ASSISTANT","metadata" : {"messageType" : "ASSISTANT"},"toolCalls" : [ ],"media" : [ ],"text" : "{\n  \"suggestions\": [\n    \"您好,程序员kk,我是北京协和医院的智能客服小鹿,很高兴为您服务。\",\n    \"长期从事编程工作可能导致久坐,建议您定时起身活动,保持良好姿势。\",\n    \"注意用眼卫生,每工作40-50分钟休息一下眼睛。\",\n    \"合理安排作息时间,保证充足睡眠以维持身体和心理健康。\"\n  ],\n  \"title\": \"程序员kk的健康报告\"\n}"
}, {"messageType" : "USER","metadata" : {"messageType" : "USER"},"media" : [ ],"text" : "你是谁"
}, {"messageType" : "ASSISTANT","metadata" : {"finishReason" : "STOP","id" : "0110f881-f29f-9cef-b142-7a7cf9e3e76c","role" : "ASSISTANT","messageType" : "ASSISTANT","reasoningContent" : ""},"toolCalls" : [ ],"media" : [ ],"text" : "{\n  \"suggestions\": [\n    \"您好,我是北京协和医院的智能客服小鹿,很高兴为您服务。\",\n    \"作为您的医疗顾问和伴诊助手,我将为您提供专业建议。\",\n    \"请告诉我您的需求或问题,我会尽力帮助您。\"\n  ],\n  \"title\": \"程序员kk的健康报告\"\n}"
} ]

接口开发

为了在Controller层实现AI对话历史记录的功能,将添加两个接口:根据chatId查询特定对话历史记录和查询所有对话的摘要列表。

//获取所有conversationId
public List<String> findAllConversationIds(String userId) {if (userId == null || userId.isEmpty()) {return Collections.emptyList(); // 或抛出异常}// 构建正则表达式:以 userId + "_" 开头Pattern pattern = Pattern.compile("^" + Pattern.quote(userId) + "_");// 使用 regex 替代 matchesQuery query = new Query(Criteria.where("conversationId").regex(pattern));return mongoTemplate.findDistinct(query,"conversationId",ChatMessages.class,String.class);
}
/*** 根据 chatId 获取历史聊天记录* 支持参数 lastN 控制获取最近 N 条消息(默认获取全部)*/@GetMapping("/history/{chatId}")public BaseResponse<List<Message>> getChatHistory(@PathVariable String chatId,@RequestParam(defaultValue = "-1") int lastN) {int effectiveLastN = lastN <= 0 ? Integer.MAX_VALUE : lastN;List<Message> history = chatMemory.get(chatId, effectiveLastN);return ResultUtils.success(history);}/*** 获取所有 chatId 列表(用于展示对话历史页面)*/@GetMapping("/conversations")public BaseResponse<List<String>> getAllConversations() {Long currentUserId= BaseContext.getCurrentId();String userId = currentUserId.toString();List<String> conversationIds =  chatMemory.findAllConversationIds(userId);return ResultUtils.success(conversationIds);}/*** 新建对话:生成新的 chatId,并在 MongoDB 中插入一条空记录*/@PostMapping("/conversations/add")public BaseResponse<String> createNewConversation() {Long currentUserId= BaseContext.getCurrentId();String conversationId = currentUserId + "_" + UUID.randomUUID().toString();chatMemory.add(conversationId, Collections.emptyList()); // 插入空消息记录return ResultUtils.success(conversationId);}/*** 删除指定 chatId 的对话记录*/@DeleteMapping("/conversations/{chatId}")public BaseResponse<Boolean> deleteConversation(@PathVariable String chatId) {chatMemory.clear(chatId);return ResultUtils.success(true);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87596.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87596.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac电脑-音视频剪辑编辑-Final Cut Pro X(fcpx)

Final Cut Pro Mac是一款专业的视频剪辑工具&#xff0c;专为苹果用户设计。 它具备强大的视频剪辑、音轨、图形特效和调色功能&#xff0c;支持整片输出&#xff0c;提升创作效率。 经过Apple芯片优化&#xff0c;利用Metal引擎动力&#xff0c;可处理更复杂的项目&#xff…

不同程度多径效应影响下的无线通信网络电磁信号仿真数据生成程序

生成.mat数据&#xff1a; %创建时间&#xff1a;2025年6月19日 %zhouzhichao %遍历生成不同程度多径效应影响的无线通信网络拓扑推理数据用于测试close all clearsnr 40; n 30;dataset_n 100;for bias 0.1:0.1:0.9nodes_P ones(n,1);Sampling_M 3000;%获取一帧信号及对…

Eureka 和 Feign(二)

Eureka 和 Feign 是 Spring Cloud 微服务架构中协同工作的两个核心组件&#xff0c;它们的关系可以通过以下比喻和详解来说明&#xff1a; 关系核心&#xff1a;服务发现 → 动态调用 组件角色核心功能Eureka服务注册中心服务实例的"电话簿"Feign声明式HTTP客户端根…

Springboot仿抖音app开发之RabbitMQ 异步解耦(进阶)

Springboot仿抖音app开发之评论业务模块后端复盘及相关业务知识总结 Springboot仿抖音app开发之粉丝业务模块后端复盘及相关业务知识总结 Springboot仿抖音app开发之用短视频务模块后端复盘及相关业务知识总结 Springboot仿抖音app开发之用户业务模块后端复盘及相关业务知识…

1.部署KVM虚拟化平台

一.KVM原理简介 广义的KVM实际上包含两部分&#xff0c;一部分是基于Linux内核支持的KVM内核模块&#xff0c;另一部分就是经过简化和修改的Qemuo KVM内核模块是模拟处理器和内存以支持虚拟机的运行&#xff0c;Qemu主要处理丨℃以及为用户提供一个用户空间工具来进行虚拟机的…

优化与管理数据库连接池

优化与管理数据库连接池 在现代高并发系统中,数据库连接池是保障数据库访问性能的核心组件之一。合理配置、优化和管理连接池,可以有效缓解连接创建成本高、连接频繁断开重连等问题,从而提升系统整体的响应速度与稳定性。 数据库连接池的作用与价值 数据库连接池的核心思…

实现回显服务器(基于UDP)

目录 一.回显服务器的基本概念 二.回显服务器的简单示意图 三.实现回显服务器&#xff08;基于UDP&#xff09;必须要知道的API 1.DatagramSocket 2.DatagramPacket 3.InetSocketAddress 4.二者区别 1. 功能职责 2. 核心作用 3. 使用场景流程 四.实现服务器端的主…

LabVIEW电液伺服阀自动测试

针对航空航天及工业液压领域电液伺服阀测试需求&#xff0c;采用 LabVIEW 图形化编程平台&#xff0c;集成 NI、GE Druck 等品牌硬件&#xff0c;构建集静态特性&#xff08;流量/ 压力 / 泄漏&#xff09;与动态特性&#xff08;频率响应&#xff09;测试于一体的自动化系统&a…

性能优化 - 高级进阶: Spring Boot服务性能优化

文章目录 Pre引言&#xff1a;为何提前暴露指标与分析的重要性指标暴露与监控接入Prometheus 集成 性能剖析工具&#xff1a;火焰图与 async-profilerasync-profiler 下载与使用结合 Flame 图优化示例 HTTP 及 Web 层优化CDN 与静态资源加速Cache-Control/Expires 在 Nginx 中配…

力扣网C语言编程题:除自身以外数组的乘积

一. 简介 本文记录力扣网上涉及数组方面的编程题&#xff0c;主要以 C语言实现。 二. 力扣上C语言编程题&#xff1a;涉及数组 题目&#xff1a;除自身以外数组的乘积 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i…

SpringBoot扩展——发送邮件!

发送邮件 在日常工作和生活中经常会用到电子邮件。例如&#xff0c;当注册一个新账户时&#xff0c;系统会自动给注册邮箱发送一封激活邮件&#xff0c;通过邮件找回密码&#xff0c;自动批量发送活动信息等。邮箱的使用基本包括这几步&#xff1a;先打开浏览器并登录邮箱&…

【html】iOS26 液态玻璃实现效果

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>液体玻璃效果演示</title><style>bo…

探索算法秘境:量子随机游走算法及其在图论问题中的创新应用

目录 ​编辑 一、量子随机游走算法的起源与原理 二、量子随机游走算法在图论问题中的创新应用 三、量子随机游走算法的优势与挑战 四、结语 在算法研究的浩瀚星空中&#xff0c;总有一些领域如同遥远星系&#xff0c;闪烁着神秘而诱人的光芒。今天&#xff0c;我们将一同深…

C# 一维数组和矩形数组全解析

在编程的世界里&#xff0c;数组是一种非常重要的数据结构。今天&#xff0c;我们就来详细了解一下一维数组和矩形数组。 数组基础认知 数组实例是从 System.Array 继承类型的对象。由于它从 BCL 基类派生而来&#xff0c;所以继承了许多有用的成员&#xff1a; Rank 属性&a…

WebStorm编辑器侧边栏

目录 编辑器侧边栏行号配置行号隐藏行号 代码折叠侧边栏图标书签添加匿名书签添加助记符书签 运行和调试管理断点配置断点图标 版本控制配置Git Blame注释 编辑器侧边栏 编辑器左侧的垂直区域。当编写代码时&#xff0c;提供重要信息和操作图标。外观和行为可以根据你的喜好进…

腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(PostgreSQL版)

数据库交付运维工程师-腾讯云TDSQL(PostgreSQL版)认证 适合人群&#xff1a; 适合从事TDSQL(PostgreSQL版)交付、运维、售前咨询以及TDSQL(PostgreSQL版)相关项目的管理人员。 认证考试 单选*40道多选*20道 成绩查询 70分及以上通过认证&#xff0c;官网个人中心->认证考…

attn_mask 为 (1, 1) 时什么意思? 7,7又是什么意思?

在深度学习中&#xff0c;特别是在 Transformer 模型和注意力机制&#xff08;Attention Mechanism&#xff09;中&#xff0c;attn_mask&#xff08;注意力掩码&#xff09;是一个用于控制注意力计算的张量。它决定了在计算注意力分数时&#xff0c;哪些位置应该被关注&#x…

Qt联合Halcon开发二:Halcon窗口绑定Qt控件显示Hobject图像【详细图解流程】

1. 项目准备 在本项目中&#xff0c;我们将使用Qt框架与Halcon库结合&#xff0c;展示图像并进行图像处理。首先&#xff0c;确保你已经配置好Qt和Halcon的开发环境。 环境配置可查看上篇文章 2. 创建Qt界面 在Qt中&#xff0c;创建一个窗口并拖入按钮和Graphics View控件。G…

Redis 持久化机制详解:RDB、AOF 原理与面试最佳实践(AOF篇)

在上一章我们深入学习了 Redis 中重要的数据持久化机制 ——RDB&#xff08;Redis Database&#xff09;&#xff0c;了解了其通过周期性快照将数据以二进制文件形式保存到磁盘的原理&#xff0c;包括触发条件、文件结构以及优缺点等核心内容。 Redis 持久化机制详解&#xff…

【GateWay】和权限验证

【GateWay】网关详解和权限验证 一、Gateway 核心概念与架构二、路由断言&#xff08;Route Predicates&#xff09;详解三、过滤器&#xff08;Filters&#xff09;机制四、权限认证的核心理论模型五、Spring Cloud Gateway Security OAuth2 集成方案六、OAuth2.0 集成 一、…