内存存储会话记录改为mysql存储
This commit is contained in:
@@ -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.advisor.MessageChatMemoryAdvisor;
|
||||
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.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -20,15 +19,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class AIConfig {
|
||||
|
||||
|
||||
@Bean
|
||||
public InMemoryChatMemoryRepository inMemoryChatMemoryRepository() {
|
||||
return new InMemoryChatMemoryRepository();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageWindowChatMemory messageWindowChatMemory(InMemoryChatMemoryRepository inMemoryChatMemoryRepository) {
|
||||
return MessageWindowChatMemory.builder().chatMemoryRepository(inMemoryChatMemoryRepository).build();
|
||||
public MessageWindowChatMemory messageWindowChatMemory(MysqlChatMemoryRepository mysqlChatMemoryRepository) {
|
||||
return MessageWindowChatMemory.builder().chatMemoryRepository(mysqlChatMemoryRepository).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.ai.app.controller;
|
||||
|
||||
import com.ai.app.config.MysqlChatMemoryRepository;
|
||||
import com.ai.app.constant.ChatTypeEnum;
|
||||
import com.ai.app.dto.MessageDTO;
|
||||
import com.ai.app.service.ChatHistoryService;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -44,7 +44,7 @@ public class DashScopeController {
|
||||
private ChatHistoryService chatHistoryService;
|
||||
|
||||
@Autowired
|
||||
private InMemoryChatMemoryRepository inMemoryChatMemoryRepository;
|
||||
private MysqlChatMemoryRepository mysqlChatMemoryRepository;
|
||||
|
||||
/**
|
||||
* 对话聊天
|
||||
@@ -88,8 +88,9 @@ public class DashScopeController {
|
||||
*/
|
||||
@RequestMapping("/getChatHistory/{chatId}")
|
||||
public List<MessageDTO> getChatHistory(@PathVariable("chatId") String chatId) {
|
||||
List<Message> messages = inMemoryChatMemoryRepository.findByConversationId(chatId);
|
||||
return messages.stream().map(e -> new MessageDTO(e.getMessageType().getValue(), e.getText())).collect(Collectors.toList());
|
||||
List<Message> messages = mysqlChatMemoryRepository.findByConversationId(chatId);
|
||||
return messages.stream().map(e -> new MessageDTO(e.getMessageType().getValue(), e.getText()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,22 +9,24 @@ import java.time.LocalDateTime;
|
||||
/**
|
||||
* @describe
|
||||
* @Author kai.liu
|
||||
* @Date 2025/8/29 9:20
|
||||
* @Date 2025/9/5 10:23
|
||||
*/
|
||||
@Data
|
||||
@TableName("ai_chat_message")
|
||||
public class AIChatMessage {
|
||||
@TableName("chat_message_history")
|
||||
public class ChatMessageHistory {
|
||||
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
private String conversationId;
|
||||
|
||||
private String conversationType;
|
||||
|
||||
private String messageType;
|
||||
|
||||
private String message;
|
||||
private String messageContent;
|
||||
|
||||
private String metadata;
|
||||
|
||||
private String toolCalls;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ai.app.entity;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
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() {
|
||||
return switch (messageType) {
|
||||
case SYSTEM -> new SystemMessage(text);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,8 @@ import java.util.Map;
|
||||
@Service
|
||||
public class ChatHistoryServiceImpl implements ChatHistoryService {
|
||||
|
||||
/*@Autowired
|
||||
private RedisTemplateService redisTemplateService;*/
|
||||
|
||||
private final Map<String,List<String>> chatStore = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 保存会话ID
|
||||
*
|
||||
|
||||
@@ -45,7 +45,8 @@ spring:
|
||||
options:
|
||||
model: deepseek-reasoner
|
||||
dashscope:
|
||||
api-key: ${DASH_SCOPE_API_KEY}
|
||||
# api-key: ${DASH_SCOPE_API_KEY}
|
||||
api-key: sk-1acdcf6f47e7480c9e6bcc5624d4cbfc
|
||||
chat:
|
||||
options:
|
||||
model: qwen-max
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user