package com.vincent.rsf.server.ai.service.impl; 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.Resource; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Service public class AiSessionServiceImpl implements AiSessionService { private static final ConcurrentMap> LOCAL_SESSION_CACHE = new ConcurrentHashMap<>(); private static final ConcurrentMap> LOCAL_MESSAGE_CACHE = new ConcurrentHashMap<>(); private static final ConcurrentMap LOCAL_STOP_CACHE = new ConcurrentHashMap<>(); @Resource private AiRuntimeConfigService aiRuntimeConfigService; @Override public synchronized List listSessions(Long tenantId, Long userId) { List 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 sessions = getSessions(tenantId, userId); Date now = new Date(); AiChatSession session = new AiChatSession() .setId(UUID.randomUUID().toString().replace("-", "")) .setTitle(resolveTitle(title, sessions.size() + 1)) .setModelCode(resolveModelCode(modelCode)) .setCreateTime(now) .setUpdateTime(now) .setLastMessageAt(now); sessions.add(0, session); saveSessions(tenantId, userId, sessions); saveMessages(session.getId(), new ArrayList<>()); return session; } @Override public synchronized AiChatSession ensureSession(Long tenantId, Long userId, String sessionId, String modelCode) { AiChatSession session = getSession(tenantId, userId, sessionId); if (session == null) { return createSession(tenantId, userId, null, modelCode); } if (modelCode != null && !modelCode.trim().isEmpty() && !modelCode.equals(session.getModelCode())) { session.setModelCode(modelCode); session.setUpdateTime(new Date()); refreshSession(tenantId, userId, session); } return session; } @Override public synchronized AiChatSession getSession(Long tenantId, Long userId, String sessionId) { if (sessionId == null || sessionId.trim().isEmpty()) { return null; } for (AiChatSession session : getSessions(tenantId, userId)) { if (sessionId.equals(session.getId())) { return session; } } return null; } @Override public synchronized AiChatSession renameSession(Long tenantId, Long userId, String sessionId, String title) { AiChatSession session = getSession(tenantId, userId, sessionId); if (session == null) { return null; } session.setTitle(resolveTitle(title, 1)); session.setUpdateTime(new Date()); refreshSession(tenantId, userId, session); return session; } @Override public synchronized void removeSession(Long tenantId, Long userId, String sessionId) { List sessions = getSessions(tenantId, userId); sessions.removeIf(session -> sessionId.equals(session.getId())); saveSessions(tenantId, userId, sessions); LOCAL_MESSAGE_CACHE.remove(sessionId); LOCAL_STOP_CACHE.remove(sessionId); } @Override public synchronized List listMessages(Long tenantId, Long userId, String sessionId) { AiChatSession session = getSession(tenantId, userId, sessionId); if (session == null) { return new ArrayList<>(); } return getMessages(sessionId); } @Override public synchronized List listContextMessages(Long tenantId, Long userId, String sessionId, int maxCount) { List messages = listMessages(tenantId, userId, sessionId); if (messages.size() <= maxCount) { return messages; } return new ArrayList<>(messages.subList(messages.size() - maxCount, messages.size())); } @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) { return null; } List messages = getMessages(sessionId); AiChatMessage message = new AiChatMessage() .setId(UUID.randomUUID().toString().replace("-", "")) .setSessionId(sessionId) .setRole(role) .setContent(content) .setModelCode(resolveModelCode(modelCode)) .setCreateTime(new Date()); messages.add(message); saveMessages(sessionId, messages); session.setLastMessage(buildPreview(content)); session.setLastMessageAt(message.getCreateTime()); session.setUpdateTime(message.getCreateTime()); if (modelCode != null && !modelCode.trim().isEmpty()) { session.setModelCode(modelCode); } if ((session.getTitle() == null || session.getTitle().startsWith("新对话")) && "user".equalsIgnoreCase(role)) { session.setTitle(buildPreview(content)); } refreshSession(tenantId, userId, session); return message; } @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 getSessions(Long tenantId, Long userId) { String ownerKey = buildOwnerKey(tenantId, userId); List sessions = LOCAL_SESSION_CACHE.get(ownerKey); return sessions == null ? new ArrayList<>() : new ArrayList<>(sessions); } private void saveSessions(Long tenantId, Long userId, List sessions) { String ownerKey = buildOwnerKey(tenantId, userId); List cachedSessions = new ArrayList<>(sessions); LOCAL_SESSION_CACHE.put(ownerKey, cachedSessions); } private List getMessages(String sessionId) { List messages = LOCAL_MESSAGE_CACHE.get(sessionId); return messages == null ? new ArrayList<>() : new ArrayList<>(messages); } private void saveMessages(String sessionId, List messages) { List cachedMessages = new ArrayList<>(messages); LOCAL_MESSAGE_CACHE.put(sessionId, cachedMessages); } private void refreshSession(Long tenantId, Long userId, AiChatSession target) { List sessions = getSessions(tenantId, userId); for (int i = 0; i < sessions.size(); i++) { if (target.getId().equals(sessions.get(i).getId())) { sessions.set(i, target); saveSessions(tenantId, userId, sessions); return; } } sessions.add(target); saveSessions(tenantId, userId, sessions); } private String buildOwnerKey(Long tenantId, Long userId) { return String.valueOf(tenantId) + ":" + String.valueOf(userId); } private String resolveModelCode(String modelCode) { return modelCode == null || modelCode.trim().isEmpty() ? aiRuntimeConfigService.resolveDefaultModelCode() : modelCode; } private String resolveTitle(String title, int index) { if (title == null || title.trim().isEmpty()) { return "新对话 " + index; } return buildPreview(title); } private String buildPreview(String content) { if (content == null || content.trim().isEmpty()) { return "新对话"; } String normalized = content.replace("\r", " ").replace("\n", " ").trim(); return normalized.length() > 24 ? normalized.substring(0, 24) : normalized; } }