内存存储会话记录改为mysql存储

This commit is contained in:
liukai
2025-09-05 18:00:00 +08:00
parent 897e2596b0
commit 9d5c2aaf04
12 changed files with 118 additions and 161 deletions

View File

@@ -6,7 +6,6 @@ import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.deepseek.DeepSeekChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -20,15 +19,9 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class AIConfig { public class AIConfig {
@Bean @Bean
public InMemoryChatMemoryRepository inMemoryChatMemoryRepository() { public MessageWindowChatMemory messageWindowChatMemory(MysqlChatMemoryRepository mysqlChatMemoryRepository) {
return new InMemoryChatMemoryRepository(); return MessageWindowChatMemory.builder().chatMemoryRepository(mysqlChatMemoryRepository).build();
}
@Bean
public MessageWindowChatMemory messageWindowChatMemory(InMemoryChatMemoryRepository inMemoryChatMemoryRepository) {
return MessageWindowChatMemory.builder().chatMemoryRepository(inMemoryChatMemoryRepository).build();
} }
/** /**

View File

@@ -0,0 +1,67 @@
package com.ai.app.config;
import com.ai.app.entity.ChatMessageHistory;
import com.ai.app.entity.Msg;
import com.ai.app.mapper.ChatMessageHistoryMapper;
import com.alibaba.fastjson.JSON;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @describe mysql存储会话记录
* @Author kai.liu
* @Date 2025/9/5 16:27
*/
@Component
public class MysqlChatMemoryRepository implements ChatMemoryRepository {
@Autowired
private ChatMessageHistoryMapper chatMessageHistoryMapper;
@Override
public List<String> findConversationIds() {
return List.of();
}
@Override
public List<Message> findByConversationId(String conversationId) {
List<ChatMessageHistory> messageHistorieList = chatMessageHistoryMapper.selectByConversationId(conversationId);
return messageHistorieList.stream().map(e -> new Msg(e).toMessage()).collect(Collectors.toList());
}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void saveAll(String conversationId, List<Message> messages) {
chatMessageHistoryMapper.deleteByConversationId(conversationId);
List<ChatMessageHistory> messageHistoryList = messages.stream().map(message -> {
ChatMessageHistory chatMessageHistory = new ChatMessageHistory();
chatMessageHistory.setId(UUID.randomUUID().toString());
chatMessageHistory.setConversationId(conversationId);
chatMessageHistory.setMessageType(message.getMessageType().getValue());
chatMessageHistory.setMessageContent(message.getText());
chatMessageHistory.setMetadata(JSON.toJSONString(message.getMetadata()));
if (message instanceof AssistantMessage am) {
chatMessageHistory.setToolCalls(JSON.toJSONString(am.getToolCalls()));
}
chatMessageHistory.setCreateTime(LocalDateTime.now());
return chatMessageHistory;
}).collect(Collectors.toList());
chatMessageHistoryMapper.insert(messageHistoryList);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void deleteByConversationId(String conversationId) {
chatMessageHistoryMapper.deleteByConversationId(conversationId);
}
}

View File

@@ -1,11 +1,11 @@
package com.ai.app.controller; package com.ai.app.controller;
import com.ai.app.config.MysqlChatMemoryRepository;
import com.ai.app.constant.ChatTypeEnum; import com.ai.app.constant.ChatTypeEnum;
import com.ai.app.dto.MessageDTO; import com.ai.app.dto.MessageDTO;
import com.ai.app.service.ChatHistoryService; import com.ai.app.service.ChatHistoryService;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@@ -44,7 +44,7 @@ public class DashScopeController {
private ChatHistoryService chatHistoryService; private ChatHistoryService chatHistoryService;
@Autowired @Autowired
private InMemoryChatMemoryRepository inMemoryChatMemoryRepository; private MysqlChatMemoryRepository mysqlChatMemoryRepository;
/** /**
* 对话聊天 * 对话聊天
@@ -88,8 +88,9 @@ public class DashScopeController {
*/ */
@RequestMapping("/getChatHistory/{chatId}") @RequestMapping("/getChatHistory/{chatId}")
public List<MessageDTO> getChatHistory(@PathVariable("chatId") String chatId) { public List<MessageDTO> getChatHistory(@PathVariable("chatId") String chatId) {
List<Message> messages = inMemoryChatMemoryRepository.findByConversationId(chatId); List<Message> messages = mysqlChatMemoryRepository.findByConversationId(chatId);
return messages.stream().map(e -> new MessageDTO(e.getMessageType().getValue(), e.getText())).collect(Collectors.toList()); return messages.stream().map(e -> new MessageDTO(e.getMessageType().getValue(), e.getText()))
.collect(Collectors.toList());
} }

View File

@@ -9,22 +9,24 @@ import java.time.LocalDateTime;
/** /**
* @describe * @describe
* @Author kai.liu * @Author kai.liu
* @Date 2025/8/29 9:20 * @Date 2025/9/5 10:23
*/ */
@Data @Data
@TableName("ai_chat_message") @TableName("chat_message_history")
public class AIChatMessage { public class ChatMessageHistory {
@TableId @TableId
private String id; private String id;
private String conversationId; private String conversationId;
private String conversationType;
private String messageType; private String messageType;
private String message; private String messageContent;
private String metadata;
private String toolCalls;
private LocalDateTime createTime; private LocalDateTime createTime;
} }

View File

@@ -1,5 +1,7 @@
package com.ai.app.entity; package com.ai.app.entity;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -26,6 +28,16 @@ public class Msg {
} }
} }
public Msg(ChatMessageHistory message) {
this.messageType = MessageType.fromValue(message.getMessageType());
this.text = message.getMessageContent();
this.metadata = JSONObject.parseObject(message.getMetadata());
if (MessageType.ASSISTANT.equals(this.messageType)) {
this.toolCalls = JSONObject.parseObject(message.getToolCalls(), new TypeReference<>() {
});
}
}
public Message toMessage() { public Message toMessage() {
return switch (messageType) { return switch (messageType) {
case SYSTEM -> new SystemMessage(text); case SYSTEM -> new SystemMessage(text);

View File

@@ -1,29 +0,0 @@
package com.ai.app.mapper;
import com.ai.app.entity.AIChatMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @describe
* @Author kai.liu
* @Date 2025/8/29 9:18
*/
@Mapper
public interface AIChatMessageMapper extends BaseMapper<AIChatMessage> {
@Select("SELECT DISTINCT conversation_id FROM ai_chat_message ")
List<String> selectConversationIds();
@Select("SELECT * FROM ai_chat_message WHERE conversation_id = #{conversationId}")
List<AIChatMessage> selectByConversationId(String conversationId);
void batchInsert(List<AIChatMessage> messageList);
@Select("DELETE FROM ai_chat_message WHERE conversation_id = #{conversationId}")
void deleteByConversationId(String conversationId);
}

View File

@@ -0,0 +1,22 @@
package com.ai.app.mapper;
import com.ai.app.entity.ChatMessageHistory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @describe
* @Author kai.liu
* @Date 2025/9/5 10:43
*/
public interface ChatMessageHistoryMapper extends BaseMapper<ChatMessageHistory> {
@Select("select * from chat_message_history where conversation_id = #{conversationId}")
List<ChatMessageHistory> selectByConversationId(String conversationId);
@Delete("delete from chat_message_history where conversation_id = #{conversationId}")
void deleteByConversationId(String conversationId);
}

View File

@@ -1,23 +0,0 @@
package com.ai.app.service;
import org.springframework.ai.chat.messages.Message;
import java.util.List;
/**
* @describe
* @Author kai.liu
* @Date 2025/8/29 9:27
*/
public interface AIChatMessageService {
List<String> getConversationIds(String type);
List<String> getConversationIds();
List<Message> getMessage(String conversationId);
void saveAll(String conversationId, List<Message> messages);
void delete(String conversationId);
}

View File

@@ -1,72 +0,0 @@
package com.ai.app.service.impl;
import com.ai.app.service.AIChatMessageService;
import com.ai.app.service.RedisTemplateService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.Message;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @describe
* @Author kai.liu
* @Date 2025/8/29 9:27
*/
@Slf4j
@Service
public class AIChatMessageServiceImpl implements AIChatMessageService {
@Resource
private RedisTemplateService redisTemplateService;
public static final String AI_MESSAGE_CONVERSATION_ID = "ai:message:conversationId:";
public static final String AI_MESSAGE_CONVERSATION_TYPE = "ai:message:conversationType:";
@Override
public List<String> getConversationIds(String type) {
String conversationTypeKey = getConversationTypeKey(type);
if (redisTemplateService.hasKey(conversationTypeKey)) {
return (List<String>) redisTemplateService.get(conversationTypeKey);
}
return List.of();
}
@Override
public List<String> getConversationIds() {
Set<String> keys = redisTemplateService.getKeys(AI_MESSAGE_CONVERSATION_ID);
return new ArrayList<>(keys);
}
@Override
public List<Message> getMessage(String conversationId) {
String conversationIdKey = getConversationIdKey(conversationId);
if (redisTemplateService.hasKey(conversationIdKey)) {
return (List<Message>) redisTemplateService.get(conversationIdKey);
}
return List.of();
}
@Override
public void saveAll(String conversationId, List<Message> messages) {
String conversationIdKey = getConversationIdKey(conversationId);
redisTemplateService.set(conversationIdKey, messages);
}
@Override
public void delete(String conversationId) {
String conversationIdKey = getConversationIdKey(conversationId);
redisTemplateService.delete(conversationIdKey);
}
private String getConversationIdKey(String conversationId) {
return AI_MESSAGE_CONVERSATION_ID + conversationId;
}
private String getConversationTypeKey(String type) {
return AI_MESSAGE_CONVERSATION_TYPE + type;
}
}

View File

@@ -20,12 +20,8 @@ import java.util.Map;
@Service @Service
public class ChatHistoryServiceImpl implements ChatHistoryService { public class ChatHistoryServiceImpl implements ChatHistoryService {
/*@Autowired
private RedisTemplateService redisTemplateService;*/
private final Map<String,List<String>> chatStore = new HashMap<>(); private final Map<String,List<String>> chatStore = new HashMap<>();
/** /**
* 保存会话ID * 保存会话ID
* *

View File

@@ -45,7 +45,8 @@ spring:
options: options:
model: deepseek-reasoner model: deepseek-reasoner
dashscope: dashscope:
api-key: ${DASH_SCOPE_API_KEY} # api-key: ${DASH_SCOPE_API_KEY}
api-key: sk-1acdcf6f47e7480c9e6bcc5624d4cbfc
chat: chat:
options: options:
model: qwen-max model: qwen-max

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ai.app.mapper.AIChatMessageMapper">
<insert id="batchInsert" parameterType="com.ai.app.entity.AIChatMessage">
INSERT INTO ai_chat_message (id,conversation_id,conversation_type,message_type,message,create_time)
VALUES
<foreach collection="messageList" item="message" separator=",">
(#{message.id}, #{message.conversationId}, #{message.conversationType}, #{message.messageType},#{message.message}, #{message.createTime})
</foreach>
</insert>
</mapper>