package com.ruoyi.ai.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.ai.dto.AiChatMessageDto; import com.ruoyi.ai.dto.AiChatSessionDto; import com.ruoyi.ai.mapper.AiChatSessionMapper; import com.ruoyi.ai.pojo.AiChatSession; import com.ruoyi.ai.service.AiChatSessionService; import com.ruoyi.ai.store.MongoChatMemoryStore; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.security.LoginUser; import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.ToolExecutionResultMessage; import dev.langchain4j.data.message.UserMessage; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class AiChatSessionServiceImpl extends ServiceImpl implements AiChatSessionService { private static final int TITLE_MAX_LENGTH = 40; private static final int LAST_MESSAGE_MAX_LENGTH = 300; private final MongoChatMemoryStore mongoChatMemoryStore; @Override public void touchSession(String memoryId, LoginUser loginUser, String userMessage) { if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) { return; } Date now = new Date(); AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId()); if (session == null) { AiChatSession add = new AiChatSession(); add.setMemoryId(memoryId); add.setUserId(loginUser.getUserId()); add.setTenantId(loginUser.getTenantId()); add.setTitle(buildTitle(userMessage)); add.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH)); add.setMessageCount(0); add.setLastChatTime(now); add.setCreateTime(now); add.setUpdateTime(now); save(add); return; } AiChatSession update = new AiChatSession(); update.setId(session.getId()); if (!StringUtils.hasText(session.getTitle())) { update.setTitle(buildTitle(userMessage)); } update.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH)); update.setLastChatTime(now); update.setUpdateTime(now); updateById(update); } @Override public void refreshSessionStats(String memoryId, LoginUser loginUser) { if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) { return; } AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId()); if (session == null) { return; } List messages = mongoChatMemoryStore.getMessages(memoryId); AiChatSession update = new AiChatSession(); update.setId(session.getId()); update.setMessageCount(messages.size()); update.setLastMessage(trimText(lastMessageText(messages), LAST_MESSAGE_MAX_LENGTH)); update.setLastChatTime(new Date()); update.setUpdateTime(new Date()); updateById(update); } @Override public List listCurrentUserSessions(LoginUser loginUser) { if (loginUser == null || loginUser.getUserId() == null) { return new LinkedList<>(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(AiChatSession::getUserId, loginUser.getUserId()) .orderByDesc(AiChatSession::getLastChatTime); applyTenantCondition(queryWrapper, loginUser.getTenantId()); return list(queryWrapper).stream().map(item -> { AiChatSessionDto dto = new AiChatSessionDto(); dto.setMemoryId(item.getMemoryId()); dto.setTitle(item.getTitle()); dto.setLastMessage(item.getLastMessage()); dto.setMessageCount(item.getMessageCount()); dto.setLastChatTime(item.getLastChatTime()); return dto; }).collect(Collectors.toList()); } @Override public List listCurrentUserMessages(String memoryId, LoginUser loginUser) { if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) { return new LinkedList<>(); } AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId()); if (session == null) { return new LinkedList<>(); } List messages = mongoChatMemoryStore.getMessages(memoryId); return messages.stream().map(this::convertMessage).collect(Collectors.toList()); } @Override public boolean deleteCurrentUserSession(String memoryId, LoginUser loginUser) { if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) { return false; } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(AiChatSession::getMemoryId, memoryId) .eq(AiChatSession::getUserId, loginUser.getUserId()); applyTenantCondition(queryWrapper, loginUser.getTenantId()); boolean removed = remove(queryWrapper); mongoChatMemoryStore.deleteMessages(memoryId); return removed; } private AiChatSession getSession(String memoryId, Long userId, Long tenantId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(AiChatSession::getMemoryId, memoryId) .eq(AiChatSession::getUserId, userId); applyTenantCondition(queryWrapper, tenantId); return getOne(queryWrapper, false); } private void applyTenantCondition(LambdaQueryWrapper queryWrapper, Long tenantId) { if (tenantId == null) { queryWrapper.isNull(AiChatSession::getTenantId); return; } queryWrapper.eq(AiChatSession::getTenantId, tenantId); } private String buildTitle(String userMessage) { if (!StringUtils.hasText(userMessage)) { return "新会话"; } return trimText(userMessage, TITLE_MAX_LENGTH); } private String trimText(String text, int maxLength) { if (!StringUtils.hasText(text)) { return ""; } String source = text.trim(); if (source.length() <= maxLength) { return source; } return source.substring(0, maxLength) + "..."; } private String lastMessageText(List messages) { if (messages == null || messages.isEmpty()) { return ""; } ChatMessage lastMessage = messages.get(messages.size() - 1); return convertMessage(lastMessage).getContent(); } private AiChatMessageDto convertMessage(ChatMessage message) { if (message instanceof UserMessage userMessage) { return new AiChatMessageDto("user", userMessage.singleText()); } if (message instanceof AiMessage aiMessage) { return new AiChatMessageDto("assistant", aiMessage.text()); } if (message instanceof SystemMessage systemMessage) { return new AiChatMessageDto("system", systemMessage.text()); } if (message instanceof ToolExecutionResultMessage toolExecutionResultMessage) { return new AiChatMessageDto("tool", toolExecutionResultMessage.text()); } return new AiChatMessageDto("unknown", String.valueOf(message)); } }