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