当前位置: 首页 > web >正文

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.xdnf.cn/news/14543.html

相关文章:

  • 内存一致性模型
  • 人工智能学习31-开发框架
  • 【技术实战】工业级设备健康管理系统搭建全栈指南:从数据采集到预测性维护
  • C++与C如何相互调用
  • 盟接之桥EDI软件:开启制造业数据对接与协同的新纪元
  • Requests源码分析01:运行tests
  • 结构学习的理论(第1、2章)
  • OpenKylin安装运行ssh及sftp服务
  • 缓冲区技术
  • SCAU大数据技术原理雨课堂测验2
  • NodeJS11和10以及之前的版本,关键差异?
  • 大模型<闲谈>
  • 6.14打卡
  • 解决虚拟环境中文绘图显示问题
  • 【DVWA系列】——SQL注入——low详细教程
  • CFD仿真硬件选型建议
  • Python高效操作MySQL数据库
  • 2025最新Nvm安装教程
  • ceil方法
  • linux多线程之可重入函数
  • 618背后的电商逻辑重构:从价格血战到价值共生
  • nlp和大模型
  • 深入剖析AI大模型:GPU在大模型训练与推理的性能抉择
  • gpfs的安装配置与部署
  • C语言:Linux libc和glibc的历史
  • Java的String
  • GitHub又打不开了怎么办?git pull push失败怎么办?
  • SpringBoot 全面深入学习指南
  • 【系统分析师】2011年真题:综合知识-答案及详解
  • k8s-pod-01的学习