From 4954d3978cf1967729a5a2d5b90f6baef18974da Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 23 三月 2026 09:35:10 +0800
Subject: [PATCH] #ai redis+页面优化
---
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 402 insertions(+), 17 deletions(-)
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
index b663a6a..a7210c3 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
@@ -3,8 +3,11 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.dto.AiChatMemoryDto;
import com.vincent.rsf.server.ai.dto.AiChatMessageDto;
+import com.vincent.rsf.server.ai.dto.AiChatSessionPinRequest;
+import com.vincent.rsf.server.ai.dto.AiChatSessionRenameRequest;
import com.vincent.rsf.server.ai.dto.AiChatSessionDto;
import com.vincent.rsf.server.ai.entity.AiChatMessage;
import com.vincent.rsf.server.ai.entity.AiChatSession;
@@ -13,66 +16,120 @@
import com.vincent.rsf.server.ai.service.AiChatMemoryService;
import com.vincent.rsf.server.system.enums.StatusType;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ConcurrentHashMap;
@Service
+@Slf4j
@RequiredArgsConstructor
public class AiChatMemoryServiceImpl implements AiChatMemoryService {
private final AiChatSessionMapper aiChatSessionMapper;
private final AiChatMessageMapper aiChatMessageMapper;
+ private final AiRedisSupport aiRedisSupport;
+ @Qualifier("aiMemoryTaskExecutor")
+ private final Executor aiMemoryTaskExecutor;
+ /**
+ * 鐢ㄤ袱涓湰鍦伴泦鍚堟妸鈥滃悓涓�涓細璇濈殑鎽樿鍒锋柊鈥濆悎骞舵垚涓茶浠诲姟锛岄伩鍏嶈繛缁秷鎭妸閲嶅浠诲姟濉炴弧绾跨▼姹犮��
+ */
+ private final Set<Long> refreshingSessionIds = ConcurrentHashMap.newKeySet();
+ private final Set<Long> pendingRefreshSessionIds = ConcurrentHashMap.newKeySet();
+
+ /**
+ * 璇诲彇浼氳瘽璁板繂蹇収銆�
+ * 杩斿洖缁撴灉鍚屾椂鍖呭惈瀹屾暣钀藉簱鍘嗗彶銆佺煭鏈熻蹇嗙獥鍙d互鍙婃憳瑕�/浜嬪疄璁板繂锛�
+ * 渚夸簬璋冪敤鏂规寜涓嶅悓鐢ㄩ�旈�夋嫨鏁版嵁绮掑害銆�
+ */
@Override
public AiChatMemoryDto getMemory(Long userId, Long tenantId, String promptCode, Long sessionId) {
ensureIdentity(userId, tenantId);
String resolvedPromptCode = requirePromptCode(promptCode);
+ // 浼氳瘽璁板繂灞炰簬鍏稿瀷鈥滆澶氬啓灏戔�濇暟鎹紝鍏堣蛋鐭� TTL 缂撳瓨鑳芥槑鏄惧噺杞绘娊灞夊垵濮嬪寲鍜屽垏浼氳瘽鍘嬪姏銆�
+ AiChatMemoryDto cached = aiRedisSupport.getMemory(tenantId, userId, resolvedPromptCode, sessionId);
+ if (cached != null) {
+ return cached;
+ }
AiChatSession session = sessionId == null
? findLatestSession(userId, tenantId, resolvedPromptCode)
: getSession(sessionId, userId, tenantId, resolvedPromptCode);
+ AiChatMemoryDto memory;
if (session == null) {
- return AiChatMemoryDto.builder()
+ memory = AiChatMemoryDto.builder()
.sessionId(null)
+ .memorySummary(null)
+ .memoryFacts(null)
+ .recentMessageCount(0)
.persistedMessages(List.of())
+ .shortMemoryMessages(List.of())
.build();
+ aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, sessionId, memory);
+ return memory;
}
- return AiChatMemoryDto.builder()
+ List<AiChatMessageDto> persistedMessages = listMessages(session.getId());
+ List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(persistedMessages, AiDefaults.MEMORY_RECENT_ROUNDS);
+ memory = AiChatMemoryDto.builder()
.sessionId(session.getId())
- .persistedMessages(listMessages(session.getId()))
+ .memorySummary(session.getMemorySummary())
+ .memoryFacts(session.getMemoryFacts())
+ .recentMessageCount(shortMemoryMessages.size())
+ .persistedMessages(persistedMessages)
+ .shortMemoryMessages(shortMemoryMessages)
.build();
+ aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, session.getId(), memory);
+ if (sessionId == null || !session.getId().equals(sessionId)) {
+ aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, null, memory);
+ }
+ return memory;
}
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鍦ㄦ煇涓� Prompt 涓嬬殑浼氳瘽鍒楄〃銆�
+ * 鍒楄〃鍙繑鍥炵敤浜庝晶杈规爮灞曠ず鐨勬憳瑕佷俊鎭紝涓嶈繑鍥炲畬鏁村璇濆唴瀹广��
+ */
@Override
- public List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode) {
+ public List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode, String keyword) {
ensureIdentity(userId, tenantId);
String resolvedPromptCode = requirePromptCode(promptCode);
+ List<AiChatSessionDto> cached = aiRedisSupport.getSessionList(tenantId, userId, resolvedPromptCode, keyword);
+ if (cached != null) {
+ return cached;
+ }
List<AiChatSession> sessions = aiChatSessionMapper.selectList(new LambdaQueryWrapper<AiChatSession>()
.eq(AiChatSession::getUserId, userId)
.eq(AiChatSession::getTenantId, tenantId)
.eq(AiChatSession::getPromptCode, resolvedPromptCode)
.eq(AiChatSession::getDeleted, 0)
.eq(AiChatSession::getStatus, StatusType.ENABLE.val)
+ .like(StringUtils.hasText(keyword), AiChatSession::getTitle, keyword == null ? null : keyword.trim())
+ .orderByDesc(AiChatSession::getPinned)
.orderByDesc(AiChatSession::getLastMessageTime)
.orderByDesc(AiChatSession::getId));
if (Cools.isEmpty(sessions)) {
+ aiRedisSupport.cacheSessionList(tenantId, userId, resolvedPromptCode, keyword, List.of());
return List.of();
}
List<AiChatSessionDto> result = new ArrayList<>();
for (AiChatSession session : sessions) {
- result.add(AiChatSessionDto.builder()
- .sessionId(session.getId())
- .title(session.getTitle())
- .promptCode(session.getPromptCode())
- .lastMessageTime(session.getLastMessageTime())
- .build());
+ result.add(buildSessionDto(session));
}
+ aiRedisSupport.cacheSessionList(tenantId, userId, resolvedPromptCode, keyword, result);
return result;
}
+ /**
+ * 瑙f瀽鏈疆璇锋眰搴旇钀藉埌鍝釜浼氳瘽銆�
+ * 濡傛灉鍓嶇甯︿簡 sessionId 鍒欏仛褰掑睘鏍¢獙骞跺鐢紱鍚﹀垯鑷姩鍒涘缓鏂颁細璇濄��
+ */
@Override
public AiChatSession resolveSession(Long userId, Long tenantId, String promptCode, Long sessionId, String titleSeed) {
ensureIdentity(userId, tenantId);
@@ -87,6 +144,7 @@
.setUserId(userId)
.setTenantId(tenantId)
.setLastMessageTime(now)
+ .setPinned(0)
.setStatus(StatusType.ENABLE.val)
.setDeleted(0)
.setCreateBy(userId)
@@ -94,9 +152,15 @@
.setUpdateBy(userId)
.setUpdateTime(now);
aiChatSessionMapper.insert(session);
+ evictConversationCaches(tenantId, userId);
return session;
}
+ /**
+ * 钀藉簱淇濆瓨涓�鏁磋疆瀵硅瘽銆�
+ * 杩欓噷浼氶『搴忓啓鍏ユ湰杞敤鎴锋秷鎭拰妯″瀷鍥炲锛屽苟鍦ㄦ渶鍚庡埛鏂颁細璇濇爣棰樺拰娲昏穬鏃堕棿銆�
+ * 璁板繂鐢诲儚鏀逛负鍚庡彴寮傛鍒锋柊锛岄伩鍏嶆妸鎽樿閲嶇畻鑰楁椂鍘嬪湪鐢ㄦ埛鏈疆鍝嶅簲灏鹃儴銆�
+ */
@Override
public void saveRound(AiChatSession session, Long userId, Long tenantId, List<AiChatMessageDto> memoryMessages, String assistantContent) {
if (session == null || session.getId() == null) {
@@ -122,8 +186,11 @@
.setUpdateBy(userId)
.setUpdateTime(now);
aiChatSessionMapper.updateById(update);
+ evictConversationCaches(tenantId, userId);
+ scheduleMemoryProfileRefresh(session.getId(), userId, tenantId);
}
+ /** 鍒犻櫎鏁翠釜浼氳瘽鍙婂叾娑堟伅銆� */
@Override
public void removeSession(Long userId, Long tenantId, Long sessionId) {
ensureIdentity(userId, tenantId);
@@ -155,6 +222,130 @@
.setDeleted(1);
aiChatMessageMapper.updateById(updateMessage);
}
+ evictConversationCaches(tenantId, userId);
+ }
+
+ /** 鏇存柊浼氳瘽鏍囬骞惰繑鍥炴渶鏂颁細璇濇憳瑕併�� */
+ @Override
+ public AiChatSessionDto renameSession(Long userId, Long tenantId, Long sessionId, AiChatSessionRenameRequest request) {
+ ensureIdentity(userId, tenantId);
+ if (request == null || !StringUtils.hasText(request.getTitle())) {
+ throw new CoolException("浼氳瘽鏍囬涓嶈兘涓虹┖");
+ }
+ AiChatSession session = requireOwnedSession(sessionId, userId, tenantId);
+ Date now = new Date();
+ AiChatSession update = new AiChatSession()
+ .setId(sessionId)
+ .setTitle(buildSessionTitle(request.getTitle()))
+ .setUpdateBy(userId)
+ .setUpdateTime(now);
+ aiChatSessionMapper.updateById(update);
+ AiChatSessionDto sessionDto = buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
+ evictConversationCaches(tenantId, userId);
+ return sessionDto;
+ }
+
+ /** 鏇存柊浼氳瘽缃《鐘舵�併�� */
+ @Override
+ public AiChatSessionDto pinSession(Long userId, Long tenantId, Long sessionId, AiChatSessionPinRequest request) {
+ ensureIdentity(userId, tenantId);
+ if (request == null || request.getPinned() == null) {
+ throw new CoolException("缃《鐘舵�佷笉鑳戒负绌�");
+ }
+ AiChatSession session = requireOwnedSession(sessionId, userId, tenantId);
+ Date now = new Date();
+ AiChatSession update = new AiChatSession()
+ .setId(sessionId)
+ .setPinned(Boolean.TRUE.equals(request.getPinned()) ? 1 : 0)
+ .setUpdateBy(userId)
+ .setUpdateTime(now);
+ aiChatSessionMapper.updateById(update);
+ AiChatSessionDto sessionDto = buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
+ evictConversationCaches(tenantId, userId);
+ return sessionDto;
+ }
+
+ /** 娓呯┖鏌愪釜浼氳瘽鐨勫叏閮ㄦ秷鎭拰娲剧敓璁板繂瀛楁銆� */
+ @Override
+ public void clearSessionMemory(Long userId, Long tenantId, Long sessionId) {
+ ensureIdentity(userId, tenantId);
+ AiChatSession session = requireOwnedSession(sessionId, userId, tenantId);
+ List<AiChatMessage> messages = aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, sessionId)
+ .eq(AiChatMessage::getDeleted, 0));
+ for (AiChatMessage message : messages) {
+ aiChatMessageMapper.updateById(new AiChatMessage()
+ .setId(message.getId())
+ .setDeleted(1));
+ }
+ aiChatSessionMapper.updateById(new AiChatSession()
+ .setId(sessionId)
+ .setMemorySummary(null)
+ .setMemoryFacts(null)
+ .setUpdateBy(userId)
+ .setUpdateTime(new Date())
+ .setLastMessageTime(session.getCreateTime()));
+ evictConversationCaches(tenantId, userId);
+ }
+
+ /** 鍙繚鐣欐渶杩戜竴杞棶绛旓紝鐢ㄤ簬鎵嬪姩瑁佸壀闀夸細璇濄�� */
+ @Override
+ public void retainLatestRound(Long userId, Long tenantId, Long sessionId) {
+ ensureIdentity(userId, tenantId);
+ requireOwnedSession(sessionId, userId, tenantId);
+ List<AiChatMessage> records = listMessageRecords(sessionId);
+ if (records.isEmpty()) {
+ return;
+ }
+ List<AiChatMessage> retained = tailMessageRecordsByRounds(records, 1);
+ for (AiChatMessage message : records) {
+ boolean shouldKeep = retained.stream().anyMatch(item -> item.getId().equals(message.getId()));
+ if (!shouldKeep) {
+ aiChatMessageMapper.updateById(new AiChatMessage()
+ .setId(message.getId())
+ .setDeleted(1));
+ }
+ }
+ evictConversationCaches(tenantId, userId);
+ scheduleMemoryProfileRefresh(sessionId, userId, tenantId);
+ }
+
+ private void evictConversationCaches(Long tenantId, Long userId) {
+ // 浼氳瘽鏍囬銆佹憳瑕併�佹渶杩戞秷鎭拰 runtime 閮戒細浜掔浉褰卞搷锛岀粺涓�鎸夌敤鎴风淮搴︿竴璧峰け鏁堟洿绋冲Ε銆�
+ aiRedisSupport.evictUserConversationCaches(tenantId, userId);
+ }
+
+ private void scheduleMemoryProfileRefresh(Long sessionId, Long userId, Long tenantId) {
+ if (sessionId == null) {
+ return;
+ }
+ if (!refreshingSessionIds.add(sessionId)) {
+ pendingRefreshSessionIds.add(sessionId);
+ return;
+ }
+ aiMemoryTaskExecutor.execute(() -> runMemoryProfileRefreshLoop(sessionId, userId, tenantId));
+ }
+
+ private void runMemoryProfileRefreshLoop(Long sessionId, Long userId, Long tenantId) {
+ try {
+ boolean shouldContinue;
+ do {
+ pendingRefreshSessionIds.remove(sessionId);
+ try {
+ refreshMemoryProfile(sessionId, userId);
+ evictConversationCaches(tenantId, userId);
+ } catch (Exception e) {
+ log.warn("AI memory profile refresh failed, sessionId={}, userId={}, tenantId={}, message={}",
+ sessionId, userId, tenantId, e.getMessage(), e);
+ }
+ shouldContinue = pendingRefreshSessionIds.remove(sessionId);
+ } while (shouldContinue);
+ } finally {
+ refreshingSessionIds.remove(sessionId);
+ if (pendingRefreshSessionIds.remove(sessionId) && refreshingSessionIds.add(sessionId)) {
+ aiMemoryTaskExecutor.execute(() -> runMemoryProfileRefreshLoop(sessionId, userId, tenantId));
+ }
+ }
}
private AiChatSession findLatestSession(Long userId, Long tenantId, String promptCode) {
@@ -184,12 +375,25 @@
return session;
}
+ private AiChatSession requireOwnedSession(Long sessionId, Long userId, Long tenantId) {
+ if (sessionId == null) {
+ throw new CoolException("AI 浼氳瘽 ID 涓嶈兘涓虹┖");
+ }
+ AiChatSession session = aiChatSessionMapper.selectOne(new LambdaQueryWrapper<AiChatSession>()
+ .eq(AiChatSession::getId, sessionId)
+ .eq(AiChatSession::getUserId, userId)
+ .eq(AiChatSession::getTenantId, tenantId)
+ .eq(AiChatSession::getDeleted, 0)
+ .eq(AiChatSession::getStatus, StatusType.ENABLE.val)
+ .last("limit 1"));
+ if (session == null) {
+ throw new CoolException("AI 浼氳瘽涓嶅瓨鍦ㄦ垨鏃犳潈璁块棶");
+ }
+ return session;
+ }
+
private List<AiChatMessageDto> listMessages(Long sessionId) {
- List<AiChatMessage> records = aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
- .eq(AiChatMessage::getSessionId, sessionId)
- .eq(AiChatMessage::getDeleted, 0)
- .orderByAsc(AiChatMessage::getSeqNo)
- .orderByAsc(AiChatMessage::getId));
+ List<AiChatMessage> records = listMessageRecords(sessionId);
if (Cools.isEmpty(records)) {
return List.of();
}
@@ -207,6 +411,7 @@
}
private List<AiChatMessageDto> normalizeMessages(List<AiChatMessageDto> memoryMessages) {
+ /** 娓呮礂鍓嶇涓婁紶鐨勫唴瀛樻秷鎭紝鍙厑璁� user/assistant 涓ょ被瑙掕壊钀藉簱銆� */
List<AiChatMessageDto> normalized = new ArrayList<>();
if (Cools.isEmpty(memoryMessages)) {
return normalized;
@@ -227,6 +432,14 @@
return normalized;
}
+ private List<AiChatMessage> listMessageRecords(Long sessionId) {
+ return aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, sessionId)
+ .eq(AiChatMessage::getDeleted, 0)
+ .orderByAsc(AiChatMessage::getSeqNo)
+ .orderByAsc(AiChatMessage::getId));
+ }
+
private int findNextSeqNo(Long sessionId) {
AiChatMessage lastMessage = aiChatMessageMapper.selectOne(new LambdaQueryWrapper<AiChatMessage>()
.eq(AiChatMessage::getSessionId, sessionId)
@@ -243,6 +456,7 @@
.setSeqNo(seqNo)
.setRole(role)
.setContent(content)
+ .setContentLength(content == null ? 0 : content.length())
.setUserId(userId)
.setTenantId(tenantId)
.setDeleted(0)
@@ -263,11 +477,182 @@
}
private String buildSessionTitle(String titleSeed) {
+ /**
+ * 鎶婇杞敤鎴烽棶棰樺帇缂╂垚閫傚悎浣滀负浼氳瘽鏍囬鐨勭煭鎽樿銆�
+ * 杩欓噷浼氬幓鎺夋崲琛屻�佽繛缁┖鐧斤紝骞朵紭鍏堝湪鑷劧璇箟鏂偣澶勬埅鏂��
+ */
if (!StringUtils.hasText(titleSeed)) {
throw new CoolException("AI 浼氳瘽鏍囬涓嶈兘涓虹┖");
}
- String title = titleSeed.trim().replace("\r", " ").replace("\n", " ");
- return title.length() > 60 ? title.substring(0, 60) : title;
+ String title = titleSeed.trim()
+ .replace("\r", " ")
+ .replace("\n", " ")
+ .replaceAll("\\s+", " ");
+ int punctuationIndex = findSummaryBreakIndex(title);
+ if (punctuationIndex > 0) {
+ title = title.substring(0, punctuationIndex).trim();
+ }
+ return title.length() > 48 ? title.substring(0, 48) : title;
+ }
+
+ private int findSummaryBreakIndex(String title) {
+ String[] separators = {"銆�", "锛�", "锛�", ".", "!", "?"};
+ int result = -1;
+ for (String separator : separators) {
+ int index = title.indexOf(separator);
+ if (index > 0 && (result < 0 || index < result)) {
+ result = index;
+ }
+ }
+ return result;
+ }
+
+ private AiChatSessionDto buildSessionDto(AiChatSession session) {
+ AiChatMessage lastMessage = aiChatMessageMapper.selectOne(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, session.getId())
+ .eq(AiChatMessage::getDeleted, 0)
+ .orderByDesc(AiChatMessage::getSeqNo)
+ .orderByDesc(AiChatMessage::getId)
+ .last("limit 1"));
+ return AiChatSessionDto.builder()
+ .sessionId(session.getId())
+ .title(session.getTitle())
+ .promptCode(session.getPromptCode())
+ .pinned(session.getPinned() != null && session.getPinned() == 1)
+ .lastMessagePreview(buildLastMessagePreview(lastMessage))
+ .lastMessageTime(session.getLastMessageTime())
+ .build();
+ }
+
+ private String buildLastMessagePreview(AiChatMessage message) {
+ if (message == null || !StringUtils.hasText(message.getContent())) {
+ return null;
+ }
+ String preview = message.getContent().trim()
+ .replace("\r", " ")
+ .replace("\n", " ")
+ .replaceAll("\\s+", " ");
+ String prefix = "assistant".equalsIgnoreCase(message.getRole()) ? "AI: " : "浣�: ";
+ String normalized = prefix + preview;
+ return normalized.length() > 80 ? normalized.substring(0, 80) : normalized;
+ }
+
+ private void refreshMemoryProfile(Long sessionId, Long userId) {
+ /**
+ * 閲嶆柊璁$畻浼氳瘽鐨勬憳瑕佽蹇嗗拰鍏抽敭浜嬪疄銆�
+ * 杩欐槸鈥滄寔涔呭寲娑堟伅鈥濆拰鈥滄ā鍨嬩笂涓嬫枃娌荤悊鈥濅箣闂寸殑妗ユ鏂规硶銆�
+ * 鐜板湪瀹冭繍琛屽湪鍚庡彴绾跨▼閲岋紝鍥犳鍏佽鐭椂闂存渶缁堜竴鑷达紝鑰屼笉鏄己鍒舵湰杞悓姝ュ畬鎴愩��
+ */
+ List<AiChatMessageDto> messages = listMessages(sessionId);
+ List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(messages, AiDefaults.MEMORY_RECENT_ROUNDS);
+ List<AiChatMessageDto> historyMessages = messages.size() > shortMemoryMessages.size()
+ ? messages.subList(0, messages.size() - shortMemoryMessages.size())
+ : List.of();
+ String memorySummary = historyMessages.size() >= AiDefaults.MEMORY_SUMMARY_TRIGGER_MESSAGES
+ ? buildMemorySummary(historyMessages)
+ : null;
+ String memoryFacts = buildMemoryFacts(messages);
+ AiChatMessage lastMessage = aiChatMessageMapper.selectOne(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, sessionId)
+ .eq(AiChatMessage::getDeleted, 0)
+ .orderByDesc(AiChatMessage::getSeqNo)
+ .orderByDesc(AiChatMessage::getId)
+ .last("limit 1"));
+ aiChatSessionMapper.updateById(new AiChatSession()
+ .setId(sessionId)
+ .setMemorySummary(memorySummary)
+ .setMemoryFacts(memoryFacts)
+ .setLastMessageTime(lastMessage == null ? null : lastMessage.getCreateTime())
+ .setUpdateBy(userId)
+ .setUpdateTime(new Date()));
+ }
+
+ private List<AiChatMessageDto> tailMessagesByRounds(List<AiChatMessageDto> source, int rounds) {
+ /** 鎸夆�滅敤鎴峰彂瑷�杞鈥濊鍓渶杩戞秷鎭紝鑰屼笉鏄畝鍗曟寜鏉℃暟鎴柇銆� */
+ if (Cools.isEmpty(source) || rounds <= 0) {
+ return List.of();
+ }
+ int userCount = 0;
+ int startIndex = source.size();
+ for (int i = source.size() - 1; i >= 0; i--) {
+ AiChatMessageDto item = source.get(i);
+ startIndex = i;
+ if (item != null && "user".equalsIgnoreCase(item.getRole())) {
+ userCount++;
+ if (userCount >= rounds) {
+ break;
+ }
+ }
+ }
+ return new ArrayList<>(source.subList(Math.max(0, startIndex), source.size()));
+ }
+
+ private List<AiChatMessage> tailMessageRecordsByRounds(List<AiChatMessage> source, int rounds) {
+ if (Cools.isEmpty(source) || rounds <= 0) {
+ return List.of();
+ }
+ int userCount = 0;
+ int startIndex = source.size();
+ for (int i = source.size() - 1; i >= 0; i--) {
+ AiChatMessage item = source.get(i);
+ startIndex = i;
+ if (item != null && "user".equalsIgnoreCase(item.getRole())) {
+ userCount++;
+ if (userCount >= rounds) {
+ break;
+ }
+ }
+ }
+ return new ArrayList<>(source.subList(Math.max(0, startIndex), source.size()));
+ }
+
+ private String buildMemorySummary(List<AiChatMessageDto> historyMessages) {
+ /** 涓鸿緝鏃╁巻鍙茬敓鎴愬彲鐩存帴鎻掑叆绯荤粺娑堟伅鐨勬枃鏈憳瑕併�� */
+ StringBuilder builder = new StringBuilder("杈冩棭瀵硅瘽鎽樿:\n");
+ for (AiChatMessageDto item : historyMessages) {
+ if (item == null || !StringUtils.hasText(item.getContent())) {
+ continue;
+ }
+ String prefix = "assistant".equalsIgnoreCase(item.getRole()) ? "- AI: " : "- 鐢ㄦ埛: ";
+ String content = compactText(item.getContent(), 120);
+ if (!StringUtils.hasText(content)) {
+ continue;
+ }
+ builder.append(prefix).append(content).append("\n");
+ if (builder.length() >= AiDefaults.MEMORY_SUMMARY_MAX_LENGTH) {
+ break;
+ }
+ }
+ return compactText(builder.toString(), AiDefaults.MEMORY_SUMMARY_MAX_LENGTH);
+ }
+
+ private String buildMemoryFacts(List<AiChatMessageDto> messages) {
+ /** 浠庢渶杩戠敤鎴峰叧娉ㄧ偣涓彁鐐煎叧閿簨瀹烇紝浣滀负杞婚噺鎸佷箙璁板繂銆� */
+ if (Cools.isEmpty(messages)) {
+ return null;
+ }
+ StringBuilder builder = new StringBuilder("鍏抽敭浜嬪疄:\n");
+ int userFacts = 0;
+ for (int i = messages.size() - 1; i >= 0 && userFacts < 4; i--) {
+ AiChatMessageDto item = messages.get(i);
+ if (item == null || !"user".equalsIgnoreCase(item.getRole()) || !StringUtils.hasText(item.getContent())) {
+ continue;
+ }
+ builder.append("- 鐢ㄦ埛鍏虫敞: ").append(compactText(item.getContent(), 100)).append("\n");
+ userFacts++;
+ }
+ return userFacts == 0 ? null : compactText(builder.toString(), AiDefaults.MEMORY_FACTS_MAX_LENGTH);
+ }
+
+ private String compactText(String content, int maxLength) {
+ if (!StringUtils.hasText(content)) {
+ return null;
+ }
+ String normalized = content.trim()
+ .replace("\r", " ")
+ .replace("\n", " ")
+ .replaceAll("\\s+", " ");
+ return normalized.length() > maxLength ? normalized.substring(0, maxLength) : normalized;
}
private void ensureIdentity(Long userId, Long tenantId) {
--
Gitblit v1.9.1