内存存储会话记录改为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.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
@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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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