From 51877df13075ad10ef51107f15bcd21f1661febe Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 17 三月 2026 09:48:01 +0800
Subject: [PATCH] #AI

---
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java |  189 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 184 insertions(+), 5 deletions(-)

diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
index 87f9d20..f87fc84 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
@@ -1,16 +1,24 @@
 package com.vincent.rsf.server.ai.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.mapper.AiChatMessageMapper;
+import com.vincent.rsf.server.ai.mapper.AiChatSessionMapper;
 import com.vincent.rsf.server.ai.model.AiChatMessage;
 import com.vincent.rsf.server.ai.model.AiChatSession;
 import com.vincent.rsf.server.ai.service.AiRuntimeConfigService;
 import com.vincent.rsf.server.ai.service.AiSessionService;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -21,28 +29,68 @@
     private static final ConcurrentMap<String, List<AiChatSession>> LOCAL_SESSION_CACHE = new ConcurrentHashMap<>();
     private static final ConcurrentMap<String, List<AiChatMessage>> LOCAL_MESSAGE_CACHE = new ConcurrentHashMap<>();
     private static final ConcurrentMap<String, String> LOCAL_STOP_CACHE = new ConcurrentHashMap<>();
+    private static final String SESSION_TABLE_NAME = "sys_ai_chat_session";
+    private static final String MESSAGE_TABLE_NAME = "sys_ai_chat_message";
 
     @Resource
     private AiRuntimeConfigService aiRuntimeConfigService;
+    @Resource
+    private AiChatSessionMapper aiChatSessionMapper;
+    @Resource
+    private AiChatMessageMapper aiChatMessageMapper;
+    @Resource
+    private DataSource dataSource;
+
+    private volatile boolean storageReady;
+
+    @PostConstruct
+    /**
+     * 鍚姩鏃舵帰娴嬭亰澶╁瓨鍌ㄨ〃鏄惁宸插垱寤恒��
+     * 濡傛灉琛ㄥ瓨鍦ㄥ垯璧版暟鎹簱鎸佷箙鍖栵紝鍚﹀垯鍥為��鍒版湰鍦板唴瀛樼紦瀛橈紝淇濊瘉寮�鍙戝拰缂鸿〃鍦烘櫙鍙户缁繍琛屻��
+     */
+    public void initStorageMode() {
+        storageReady = detectStorageTables();
+    }
 
     @Override
+    /**
+     * 璇诲彇鐢ㄦ埛浼氳瘽鍒楄〃銆�
+     * 鏁版嵁搴撳瓨鍌ㄦā寮忕洿鎺ユ煡琛紝鍐呭瓨妯″紡鍒欎粠鏈湴缂撳瓨鍙栧嚭骞舵寜鏈�杩戞洿鏂版椂闂存帓搴忋��
+     */
     public synchronized List<AiChatSession> listSessions(Long tenantId, Long userId) {
+        if (useDatabaseStorage()) {
+            return aiChatSessionMapper.selectList(new LambdaQueryWrapper<AiChatSession>()
+                    .eq(AiChatSession::getTenantId, tenantId)
+                    .eq(AiChatSession::getUserId, userId)
+                    .orderByDesc(AiChatSession::getUpdateTime, AiChatSession::getCreateTime));
+        }
         List<AiChatSession> sessions = getSessions(tenantId, userId);
         sessions.sort(Comparator.comparing(AiChatSession::getUpdateTime, Comparator.nullsLast(Date::compareTo)).reversed());
         return sessions;
     }
 
     @Override
+    /**
+     * 鍒涘缓鏂颁細璇濓紝骞跺垵濮嬪寲鏍囬銆佹ā鍨嬪拰鏃堕棿鎴炽��
+     */
     public synchronized AiChatSession createSession(Long tenantId, Long userId, String title, String modelCode) {
-        List<AiChatSession> sessions = getSessions(tenantId, userId);
+        List<AiChatSession> sessions = useDatabaseStorage() ? listSessions(tenantId, userId) : getSessions(tenantId, userId);
         Date now = new Date();
         AiChatSession session = new AiChatSession()
                 .setId(UUID.randomUUID().toString().replace("-", ""))
+                .setTenantId(tenantId)
+                .setUserId(userId)
                 .setTitle(resolveTitle(title, sessions.size() + 1))
                 .setModelCode(resolveModelCode(modelCode))
                 .setCreateTime(now)
                 .setUpdateTime(now)
-                .setLastMessageAt(now);
+                .setLastMessageAt(now)
+                .setStatus(1)
+                .setDeleted(0);
+        if (useDatabaseStorage()) {
+            aiChatSessionMapper.insert(session);
+            return session;
+        }
         sessions.add(0, session);
         saveSessions(tenantId, userId, sessions);
         saveMessages(session.getId(), new ArrayList<>());
@@ -50,6 +98,9 @@
     }
 
     @Override
+    /**
+     * 纭繚浼氳瘽瀛樺湪锛涘鏋滀細璇濆凡瀛樺湪浣嗘ā鍨嬪彂鐢熷彉鍖栵紝浼氬悓姝ユ洿鏂颁細璇濊褰曘��
+     */
     public synchronized AiChatSession ensureSession(Long tenantId, Long userId, String sessionId, String modelCode) {
         AiChatSession session = getSession(tenantId, userId, sessionId);
         if (session == null) {
@@ -64,9 +115,22 @@
     }
 
     @Override
+    /**
+     * 瀹夊叏璇诲彇浼氳瘽锛屽苟鏍¢獙绉熸埛涓庣敤鎴峰綊灞炪��
+     */
     public synchronized AiChatSession getSession(Long tenantId, Long userId, String sessionId) {
         if (sessionId == null || sessionId.trim().isEmpty()) {
             return null;
+        }
+        if (useDatabaseStorage()) {
+            AiChatSession session = aiChatSessionMapper.selectById(sessionId);
+            if (session == null) {
+                return null;
+            }
+            if (!Objects.equals(tenantId, session.getTenantId()) || !Objects.equals(userId, session.getUserId())) {
+                return null;
+            }
+            return session;
         }
         for (AiChatSession session : getSessions(tenantId, userId)) {
             if (sessionId.equals(session.getId())) {
@@ -77,6 +141,9 @@
     }
 
     @Override
+    /**
+     * 鏇存柊浼氳瘽鏍囬銆�
+     */
     public synchronized AiChatSession renameSession(Long tenantId, Long userId, String sessionId, String title) {
         AiChatSession session = getSession(tenantId, userId, sessionId);
         if (session == null) {
@@ -89,7 +156,20 @@
     }
 
     @Override
+    /**
+     * 鍒犻櫎浼氳瘽鍙婂叾鍏宠仈娑堟伅锛屽悓鏃舵竻鐞嗗仠姝㈡爣璁扮紦瀛樸��
+     */
     public synchronized void removeSession(Long tenantId, Long userId, String sessionId) {
+        if (useDatabaseStorage()) {
+            AiChatSession session = getSession(tenantId, userId, sessionId);
+            if (session != null) {
+                aiChatMessageMapper.delete(new LambdaQueryWrapper<AiChatMessage>()
+                        .eq(AiChatMessage::getSessionId, sessionId));
+                aiChatSessionMapper.deleteById(sessionId);
+            }
+            LOCAL_STOP_CACHE.remove(sessionId);
+            return;
+        }
         List<AiChatSession> sessions = getSessions(tenantId, userId);
         sessions.removeIf(session -> sessionId.equals(session.getId()));
         saveSessions(tenantId, userId, sessions);
@@ -98,15 +178,26 @@
     }
 
     @Override
+    /**
+     * 鏌ヨ浼氳瘽鐨勫畬鏁存秷鎭巻鍙层��
+     */
     public synchronized List<AiChatMessage> listMessages(Long tenantId, Long userId, String sessionId) {
         AiChatSession session = getSession(tenantId, userId, sessionId);
         if (session == null) {
             return new ArrayList<>();
         }
+        if (useDatabaseStorage()) {
+            return aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+                    .eq(AiChatMessage::getSessionId, sessionId)
+                    .orderByAsc(AiChatMessage::getCreateTime, AiChatMessage::getId));
+        }
         return getMessages(sessionId);
     }
 
     @Override
+    /**
+     * 鎴彇鏈�杩戣嫢骞叉潯娑堟伅浣滀负妯″瀷涓婁笅鏂囷紝閬垮厤姣忔閮芥妸瀹屾暣鍘嗗彶鍙戦�佺粰妯″瀷銆�
+     */
     public synchronized List<AiChatMessage> listContextMessages(Long tenantId, Long userId, String sessionId, int maxCount) {
         List<AiChatMessage> messages = listMessages(tenantId, userId, sessionId);
         if (messages.size() <= maxCount) {
@@ -116,6 +207,9 @@
     }
 
     @Override
+    /**
+     * 杩藉姞涓�鏉℃秷鎭紝骞跺悓姝ュ埛鏂颁細璇濇憳瑕併�佹椿璺冩椂闂村拰榛樿鏍囬銆�
+     */
     public synchronized AiChatMessage appendMessage(Long tenantId, Long userId, String sessionId, String role, String content, String modelCode) {
         AiChatSession session = getSession(tenantId, userId, sessionId);
         if (session == null) {
@@ -124,13 +218,21 @@
         List<AiChatMessage> messages = getMessages(sessionId);
         AiChatMessage message = new AiChatMessage()
                 .setId(UUID.randomUUID().toString().replace("-", ""))
+                .setTenantId(tenantId)
+                .setUserId(userId)
                 .setSessionId(sessionId)
                 .setRole(role)
                 .setContent(content)
                 .setModelCode(resolveModelCode(modelCode))
-                .setCreateTime(new Date());
-        messages.add(message);
-        saveMessages(sessionId, messages);
+                .setCreateTime(new Date())
+                .setStatus(1)
+                .setDeleted(0);
+        if (useDatabaseStorage()) {
+            aiChatMessageMapper.insert(message);
+        } else {
+            messages.add(message);
+            saveMessages(sessionId, messages);
+        }
         session.setLastMessage(buildPreview(content));
         session.setLastMessageAt(message.getCreateTime());
         session.setUpdateTime(message.getCreateTime());
@@ -145,44 +247,72 @@
     }
 
     @Override
+    /**
+     * 娓呴櫎鍋滄鐢熸垚鏍囪銆�
+     */
     public void clearStopFlag(String sessionId) {
         LOCAL_STOP_CACHE.remove(sessionId);
     }
 
     @Override
+    /**
+     * 鏍囪浼氳瘽闇�瑕佸仠姝㈢敓鎴愩��
+     */
     public void requestStop(String sessionId) {
         LOCAL_STOP_CACHE.put(sessionId, "1");
     }
 
     @Override
+    /**
+     * 璇诲彇鍋滄鐢熸垚鏍囪銆�
+     */
     public boolean isStopRequested(String sessionId) {
         String stopFlag = LOCAL_STOP_CACHE.get(sessionId);
         return "1".equals(stopFlag);
     }
 
+    /**
+     * 浠庡唴瀛樼紦瀛樹腑璇诲彇褰撳墠鐢ㄦ埛鐨勪細璇濆垪琛ㄣ��
+     */
     private List<AiChatSession> getSessions(Long tenantId, Long userId) {
         String ownerKey = buildOwnerKey(tenantId, userId);
         List<AiChatSession> sessions = LOCAL_SESSION_CACHE.get(ownerKey);
         return sessions == null ? new ArrayList<>() : new ArrayList<>(sessions);
     }
 
+    /**
+     * 灏嗕細璇濆垪琛ㄥ啓鍥炴湰鍦扮紦瀛樸��
+     */
     private void saveSessions(Long tenantId, Long userId, List<AiChatSession> sessions) {
         String ownerKey = buildOwnerKey(tenantId, userId);
         List<AiChatSession> cachedSessions = new ArrayList<>(sessions);
         LOCAL_SESSION_CACHE.put(ownerKey, cachedSessions);
     }
 
+    /**
+     * 浠庡唴瀛樼紦瀛樹腑璇诲彇鎸囧畾浼氳瘽鐨勬秷鎭垪琛ㄣ��
+     */
     private List<AiChatMessage> getMessages(String sessionId) {
         List<AiChatMessage> messages = LOCAL_MESSAGE_CACHE.get(sessionId);
         return messages == null ? new ArrayList<>() : new ArrayList<>(messages);
     }
 
+    /**
+     * 灏嗘秷鎭垪琛ㄥ啓鍥炴湰鍦扮紦瀛樸��
+     */
     private void saveMessages(String sessionId, List<AiChatMessage> messages) {
         List<AiChatMessage> cachedMessages = new ArrayList<>(messages);
         LOCAL_MESSAGE_CACHE.put(sessionId, cachedMessages);
     }
 
+    /**
+     * 鎸夊瓨鍌ㄦā寮忓埛鏂板崟涓細璇濊褰曘��
+     */
     private void refreshSession(Long tenantId, Long userId, AiChatSession target) {
+        if (useDatabaseStorage()) {
+            aiChatSessionMapper.updateById(target);
+            return;
+        }
         List<AiChatSession> sessions = getSessions(tenantId, userId);
         for (int i = 0; i < sessions.size(); i++) {
             if (target.getId().equals(sessions.get(i).getId())) {
@@ -195,14 +325,23 @@
         saveSessions(tenantId, userId, sessions);
     }
 
+    /**
+     * 缁勮绉熸埛涓庣敤鎴风淮搴︾殑鏈湴缂撳瓨 key銆�
+     */
     private String buildOwnerKey(Long tenantId, Long userId) {
         return String.valueOf(tenantId) + ":" + String.valueOf(userId);
     }
 
+    /**
+     * 瑙f瀽鏈娑堟伅浣跨敤鐨勬ā鍨嬬紪鐮侊紱涓虹┖鏃跺洖閫�鍒扮郴缁熼粯璁ゆā鍨嬨��
+     */
     private String resolveModelCode(String modelCode) {
         return modelCode == null || modelCode.trim().isEmpty() ? aiRuntimeConfigService.resolveDefaultModelCode() : modelCode;
     }
 
+    /**
+     * 鐢熸垚浼氳瘽鏍囬锛屾湭鏄惧紡浼犳爣棰樻椂浣跨敤鈥滄柊瀵硅瘽 N鈥濄��
+     */
     private String resolveTitle(String title, int index) {
         if (title == null || title.trim().isEmpty()) {
             return "鏂板璇� " + index;
@@ -210,6 +349,9 @@
         return buildPreview(title);
     }
 
+    /**
+     * 灏嗙敤鎴疯緭鍏ュ帇缂╂垚閫傚悎浣滀负鏍囬鎴栨渶鍚庢秷鎭瑙堢殑鐭枃鏈��
+     */
     private String buildPreview(String content) {
         if (content == null || content.trim().isEmpty()) {
             return "鏂板璇�";
@@ -218,4 +360,41 @@
         return normalized.length() > 24 ? normalized.substring(0, 24) : normalized;
     }
 
+    /**
+     * 鍒ゆ柇褰撳墠鏄惁鍙互浣跨敤鏁版嵁搴撴寔涔呭寲鑱婂ぉ鏁版嵁銆�
+     */
+    private boolean useDatabaseStorage() {
+        return storageReady || (storageReady = detectStorageTables());
+    }
+
+    /**
+     * 妫�鏌ヨ亰澶╁瓨鍌ㄦ墍闇�琛ㄦ槸鍚﹀凡缁忓瓨鍦ㄣ��
+     */
+    private boolean detectStorageTables() {
+        try (Connection connection = dataSource.getConnection()) {
+            return tableExists(connection, SESSION_TABLE_NAME) && tableExists(connection, MESSAGE_TABLE_NAME);
+        } catch (Exception ignore) {
+            return false;
+        }
+    }
+
+    /**
+     * 鍒ゆ柇鎸囧畾琛ㄥ悕鏄惁鍦ㄥ綋鍓嶆暟鎹簱涓瓨鍦ㄣ��
+     */
+    private boolean tableExists(Connection connection, String tableName) throws Exception {
+        if (tableName == null || tableName.trim().isEmpty()) {
+            return false;
+        }
+        String[] candidates = new String[]{tableName, tableName.toUpperCase(), tableName.toLowerCase()};
+        for (String candidate : candidates) {
+            try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), null, candidate, null)) {
+                if (resultSet.next()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
 }
+

--
Gitblit v1.9.1