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 |  129 +++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 123 insertions(+), 6 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 5f1c1f3..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
@@ -16,29 +16,55 @@
 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)
@@ -46,10 +72,12 @@
                     .persistedMessages(List.of())
                     .shortMemoryMessages(List.of())
                     .build();
+            aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, sessionId, memory);
+            return memory;
         }
         List<AiChatMessageDto> persistedMessages = listMessages(session.getId());
         List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(persistedMessages, AiDefaults.MEMORY_RECENT_ROUNDS);
-        return AiChatMemoryDto.builder()
+        memory = AiChatMemoryDto.builder()
                 .sessionId(session.getId())
                 .memorySummary(session.getMemorySummary())
                 .memoryFacts(session.getMemoryFacts())
@@ -57,12 +85,25 @@
                 .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, 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)
@@ -74,15 +115,21 @@
                 .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(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);
@@ -105,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) {
@@ -133,9 +186,11 @@
                 .setUpdateBy(userId)
                 .setUpdateTime(now);
         aiChatSessionMapper.updateById(update);
-        refreshMemoryProfile(session.getId(), userId);
+        evictConversationCaches(tenantId, userId);
+        scheduleMemoryProfileRefresh(session.getId(), userId, tenantId);
     }
 
+    /** 鍒犻櫎鏁翠釜浼氳瘽鍙婂叾娑堟伅銆� */
     @Override
     public void removeSession(Long userId, Long tenantId, Long sessionId) {
         ensureIdentity(userId, tenantId);
@@ -167,8 +222,10 @@
                     .setDeleted(1);
             aiChatMessageMapper.updateById(updateMessage);
         }
+        evictConversationCaches(tenantId, userId);
     }
 
+    /** 鏇存柊浼氳瘽鏍囬骞惰繑鍥炴渶鏂颁細璇濇憳瑕併�� */
     @Override
     public AiChatSessionDto renameSession(Long userId, Long tenantId, Long sessionId, AiChatSessionRenameRequest request) {
         ensureIdentity(userId, tenantId);
@@ -183,9 +240,12 @@
                 .setUpdateBy(userId)
                 .setUpdateTime(now);
         aiChatSessionMapper.updateById(update);
-        return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
+        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);
@@ -200,9 +260,12 @@
                 .setUpdateBy(userId)
                 .setUpdateTime(now);
         aiChatSessionMapper.updateById(update);
-        return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
+        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);
@@ -222,8 +285,10 @@
                 .setUpdateBy(userId)
                 .setUpdateTime(new Date())
                 .setLastMessageTime(session.getCreateTime()));
+        evictConversationCaches(tenantId, userId);
     }
 
+    /** 鍙繚鐣欐渶杩戜竴杞棶绛旓紝鐢ㄤ簬鎵嬪姩瑁佸壀闀夸細璇濄�� */
     @Override
     public void retainLatestRound(Long userId, Long tenantId, Long sessionId) {
         ensureIdentity(userId, tenantId);
@@ -241,7 +306,46 @@
                         .setDeleted(1));
             }
         }
-        refreshMemoryProfile(sessionId, userId);
+        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) {
@@ -307,6 +411,7 @@
     }
 
     private List<AiChatMessageDto> normalizeMessages(List<AiChatMessageDto> memoryMessages) {
+        /** 娓呮礂鍓嶇涓婁紶鐨勫唴瀛樻秷鎭紝鍙厑璁� user/assistant 涓ょ被瑙掕壊钀藉簱銆� */
         List<AiChatMessageDto> normalized = new ArrayList<>();
         if (Cools.isEmpty(memoryMessages)) {
             return normalized;
@@ -372,6 +477,10 @@
     }
 
     private String buildSessionTitle(String titleSeed) {
+        /**
+         * 鎶婇杞敤鎴烽棶棰樺帇缂╂垚閫傚悎浣滀负浼氳瘽鏍囬鐨勭煭鎽樿銆�
+         * 杩欓噷浼氬幓鎺夋崲琛屻�佽繛缁┖鐧斤紝骞朵紭鍏堝湪鑷劧璇箟鏂偣澶勬埅鏂��
+         */
         if (!StringUtils.hasText(titleSeed)) {
             throw new CoolException("AI 浼氳瘽鏍囬涓嶈兘涓虹┖");
         }
@@ -429,6 +538,11 @@
     }
 
     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()
@@ -454,6 +568,7 @@
     }
 
     private List<AiChatMessageDto> tailMessagesByRounds(List<AiChatMessageDto> source, int rounds) {
+        /** 鎸夆�滅敤鎴峰彂瑷�杞鈥濊鍓渶杩戞秷鎭紝鑰屼笉鏄畝鍗曟寜鏉℃暟鎴柇銆� */
         if (Cools.isEmpty(source) || rounds <= 0) {
             return List.of();
         }
@@ -492,6 +607,7 @@
     }
 
     private String buildMemorySummary(List<AiChatMessageDto> historyMessages) {
+        /** 涓鸿緝鏃╁巻鍙茬敓鎴愬彲鐩存帴鎻掑叆绯荤粺娑堟伅鐨勬枃鏈憳瑕併�� */
         StringBuilder builder = new StringBuilder("杈冩棭瀵硅瘽鎽樿:\n");
         for (AiChatMessageDto item : historyMessages) {
             if (item == null || !StringUtils.hasText(item.getContent())) {
@@ -511,6 +627,7 @@
     }
 
     private String buildMemoryFacts(List<AiChatMessageDto> messages) {
+        /** 浠庢渶杩戠敤鎴峰叧娉ㄧ偣涓彁鐐煎叧閿簨瀹烇紝浣滀负杞婚噺鎸佷箙璁板繂銆� */
         if (Cools.isEmpty(messages)) {
             return null;
         }

--
Gitblit v1.9.1