feat: 接入聊天历史界面与外链会话恢复
- 新增管理端与用户端聊天历史接口和页面 - 外链聊天支持访问令牌登录、身份保活与当前会话恢复 - 聊天执行链路切到统一 runtime 与 chatlog 查询接口
This commit is contained in:
@@ -18,7 +18,12 @@ import tech.easyflow.core.chat.protocol.ChatType;
|
||||
import tech.easyflow.core.chat.protocol.MessageRole;
|
||||
import tech.easyflow.core.chat.protocol.payload.ErrorPayload;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
|
||||
import tech.easyflow.core.runtime.ChatAssistantAccumulator;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeContext;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeManager;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeMessage;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,6 +38,9 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
private final MemoryPrompt memoryPrompt;
|
||||
private final ChatSseEmitter sseEmitter;
|
||||
private final ChatOptions chatOptions;
|
||||
private final ChatRuntimeManager chatRuntimeManager;
|
||||
private final ChatRuntimeContext runtimeContext;
|
||||
private final ChatAssistantAccumulator assistantAccumulator;
|
||||
// 核心标记:是否允许执行onStop业务逻辑(仅最后一次无后续工具调用时为true)
|
||||
private boolean canStop = true;
|
||||
// 辅助标记:是否进入过工具调用(避免重复递归判断)
|
||||
@@ -40,12 +48,17 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
// 流式响应只能结束一次,避免重复发送导致 IllegalStateException
|
||||
private final AtomicBoolean completed = new AtomicBoolean(false);
|
||||
|
||||
public ChatStreamListener(String conversationId, ChatModel chatModel, MemoryPrompt memoryPrompt, ChatSseEmitter sseEmitter, ChatOptions chatOptions) {
|
||||
public ChatStreamListener(String conversationId, ChatModel chatModel, MemoryPrompt memoryPrompt, ChatSseEmitter sseEmitter,
|
||||
ChatOptions chatOptions, ChatRuntimeManager chatRuntimeManager,
|
||||
ChatRuntimeContext runtimeContext, ChatAssistantAccumulator assistantAccumulator) {
|
||||
this.conversationId = conversationId;
|
||||
this.chatModel = chatModel;
|
||||
this.memoryPrompt = memoryPrompt;
|
||||
this.sseEmitter = sseEmitter;
|
||||
this.chatOptions = chatOptions;
|
||||
this.chatRuntimeManager = chatRuntimeManager;
|
||||
this.runtimeContext = runtimeContext;
|
||||
this.assistantAccumulator = assistantAccumulator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,6 +83,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
List<ToolCall> toolCalls = aiMessage.getToolCalls();
|
||||
if (toolCalls != null) {
|
||||
for (ToolCall toolCall : toolCalls) {
|
||||
assistantAccumulator.appendToolCall(toolCall.getId(), toolCall.getName(), toolCall.getArguments());
|
||||
sendToolCallEnvelope(toolCall);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +92,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
List<ToolMessage> toolMessages = aiMessageResponse.executeToolCallsAndGetToolMessages();
|
||||
for (ToolMessage toolMessage : toolMessages) {
|
||||
memoryPrompt.addMessage(toolMessage);
|
||||
assistantAccumulator.appendToolResult(toolMessage.getToolCallId(), null, toolMessage.getContent());
|
||||
sendToolResultEnvelope(toolMessage);
|
||||
}
|
||||
chatModel.chatStream(memoryPrompt, this, chatOptions);
|
||||
@@ -87,10 +102,14 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
}
|
||||
String reasoningContent = aiMessage.getReasoningContent();
|
||||
if (reasoningContent != null && !reasoningContent.isEmpty()) {
|
||||
assistantAccumulator.appendReasoning(reasoningContent);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext, buildAssistantDeltaMessage(reasoningContent, ChatType.THINKING));
|
||||
sendChatEnvelope(sseEmitter, reasoningContent, ChatType.THINKING);
|
||||
} else {
|
||||
String delta = aiMessage.getContent();
|
||||
if (delta != null && !delta.isEmpty()) {
|
||||
assistantAccumulator.appendContent(delta);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext, buildAssistantDeltaMessage(delta, ChatType.MESSAGE));
|
||||
sendChatEnvelope(sseEmitter, delta, ChatType.MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -111,10 +130,13 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
// 仅当canStop为true(最后一次无后续工具调用的响应)时,执行业务逻辑
|
||||
if (this.canStop && completed.compareAndSet(false, true)) {
|
||||
if (context.getThrowable() != null) {
|
||||
chatRuntimeManager.recordFailure(runtimeContext, context.getThrowable());
|
||||
sendSystemError(sseEmitter, context.getThrowable().getMessage(), context.getThrowable());
|
||||
return;
|
||||
}
|
||||
memoryPrompt.addMessage(context.getFullMessage());
|
||||
chatRuntimeManager.recordAssistantCompleted(runtimeContext, buildAssistantCompletedMessage(context));
|
||||
chatRuntimeManager.recordCompleted(runtimeContext);
|
||||
ChatEnvelope<Map<String, String>> chatEnvelope = new ChatEnvelope<>();
|
||||
chatEnvelope.setDomain(ChatDomain.SYSTEM);
|
||||
boolean doneSent = sseEmitter.sendDone(chatEnvelope);
|
||||
@@ -133,6 +155,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
conversationId, throwable.getMessage(), throwable.toString(), throwable);
|
||||
}
|
||||
if (throwable != null && completed.compareAndSet(false, true)) {
|
||||
chatRuntimeManager.recordFailure(runtimeContext, throwable);
|
||||
sendSystemError(sseEmitter, throwable.getMessage(), throwable);
|
||||
}
|
||||
stopStreamClient(context, "on_failure", throwable);
|
||||
@@ -235,4 +258,28 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
}
|
||||
}
|
||||
|
||||
private ChatRuntimeMessage buildAssistantDeltaMessage(String delta, ChatType chatType) {
|
||||
ChatRuntimeMessage message = new ChatRuntimeMessage();
|
||||
message.setRole("assistant");
|
||||
message.setContentType(chatType == ChatType.THINKING ? "THINKING" : "TEXT");
|
||||
message.setContentText(delta);
|
||||
message.setCreatedAt(new Date());
|
||||
message.setSenderId(runtimeContext.getAssistantId());
|
||||
message.setSenderName(runtimeContext.getAssistantName());
|
||||
return message;
|
||||
}
|
||||
|
||||
private ChatRuntimeMessage buildAssistantCompletedMessage(StreamContext context) {
|
||||
ChatRuntimeMessage message = new ChatRuntimeMessage();
|
||||
message.setRole("assistant");
|
||||
message.setContentType("TEXT");
|
||||
String fullContent = context != null && context.getFullMessage() != null ? context.getFullMessage().getContent() : null;
|
||||
message.setContentText(StringUtil.hasText(fullContent) ? fullContent : assistantAccumulator.getContent());
|
||||
message.setContentPayload(assistantAccumulator.buildPayload());
|
||||
message.setCreatedAt(new Date());
|
||||
message.setSenderId(runtimeContext.getAssistantId());
|
||||
message.setSenderName(runtimeContext.getAssistantName());
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package tech.easyflow.ai.easyagents.memory;
|
||||
|
||||
import com.easyagents.core.memory.ChatMemory;
|
||||
import com.easyagents.core.message.AiMessage;
|
||||
import com.easyagents.core.message.Message;
|
||||
import com.easyagents.core.message.SystemMessage;
|
||||
import com.easyagents.core.message.UserMessage;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RuntimeChatMemory implements ChatMemory {
|
||||
|
||||
private final Object id;
|
||||
private final List<Message> messages = new ArrayList<>();
|
||||
|
||||
public RuntimeChatMemory(Object id, List<ChatRuntimeMessage> runtimeMessages) {
|
||||
this.id = id;
|
||||
if (runtimeMessages != null) {
|
||||
for (ChatRuntimeMessage runtimeMessage : runtimeMessages) {
|
||||
Message message = toMessage(runtimeMessage);
|
||||
if (message != null) {
|
||||
this.messages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Message> getMessages(int count) {
|
||||
if (messages.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (count <= 0 || messages.size() <= count) {
|
||||
return new ArrayList<>(messages);
|
||||
}
|
||||
return new ArrayList<>(messages.subList(Math.max(messages.size() - count, 0), messages.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMessage(Message message) {
|
||||
if (message != null) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private Message toMessage(ChatRuntimeMessage runtimeMessage) {
|
||||
if (runtimeMessage == null || runtimeMessage.getContentText() == null || runtimeMessage.getContentText().isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String role = runtimeMessage.getRole();
|
||||
if ("assistant".equalsIgnoreCase(role)) {
|
||||
return new AiMessage(runtimeMessage.getContentText());
|
||||
}
|
||||
if ("system".equalsIgnoreCase(role)) {
|
||||
return new SystemMessage(runtimeMessage.getContentText());
|
||||
}
|
||||
if ("tool".equalsIgnoreCase(role)) {
|
||||
return new SystemMessage(runtimeMessage.getContentText());
|
||||
}
|
||||
return new UserMessage(runtimeMessage.getContentText());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import tech.easyflow.ai.entity.Bot;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import tech.easyflow.ai.service.impl.BotServiceImpl;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeContext;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
@@ -31,8 +32,10 @@ public interface BotService extends IService<Bot> {
|
||||
|
||||
SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, BotServiceImpl.ChatCheckResult chatCheckResult);
|
||||
|
||||
SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List<Map<String, String>> messages, BotServiceImpl.ChatCheckResult chatCheckResult, List<String> attachments);
|
||||
SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List<Map<String, String>> messages,
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult, List<String> attachments, ChatRuntimeContext runtimeContext);
|
||||
|
||||
SseEmitter startPublicChat(BigInteger botId, String prompt, List<Message> messages, BotServiceImpl.ChatCheckResult chatCheckResult);
|
||||
SseEmitter startPublicChat(BigInteger botId, String prompt, List<Message> messages,
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult, ChatRuntimeContext runtimeContext);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import tech.easyflow.ai.easyagents.listener.ChatStreamListener;
|
||||
import tech.easyflow.ai.easyagents.memory.BotMessageMemory;
|
||||
import tech.easyflow.ai.easyagents.memory.DefaultBotMessageMemory;
|
||||
import tech.easyflow.ai.easyagents.memory.PublicBotMessageMemory;
|
||||
import tech.easyflow.ai.easyagents.memory.RuntimeChatMemory;
|
||||
import tech.easyflow.ai.entity.*;
|
||||
import tech.easyflow.ai.mapper.BotMapper;
|
||||
import tech.easyflow.ai.service.*;
|
||||
@@ -41,11 +41,17 @@ import tech.easyflow.common.util.UrlEncoderUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseUtil;
|
||||
import tech.easyflow.core.runtime.ChatAssistantAccumulator;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeContext;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeManager;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeMessage;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -87,9 +93,6 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
public void setConversationIdStr(String conversationIdStr) {this.conversationIdStr = conversationIdStr;}
|
||||
}
|
||||
|
||||
@Resource
|
||||
private BotMessageService botMessageService;
|
||||
|
||||
@Resource(name = "sseThreadPool")
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
@Resource
|
||||
@@ -110,6 +113,8 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
FileStorageService storageService;
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
@Resource
|
||||
private ChatRuntimeManager chatRuntimeManager;
|
||||
|
||||
@Override
|
||||
public Bot getDetail(String id) {
|
||||
@@ -189,7 +194,7 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
|
||||
@Override
|
||||
public SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List<Map<String, String>> messages,
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult, List<String> attachments) {
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult, List<String> attachments, ChatRuntimeContext runtimeContext) {
|
||||
Map<String, Object> modelOptions = chatCheckResult.getModelOptions();
|
||||
ChatModel chatModel = chatCheckResult.getChatModel();
|
||||
final MemoryPrompt memoryPrompt = new MemoryPrompt();
|
||||
@@ -214,19 +219,33 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
chatOptions.setThinkingEnabled(enableDeepThinking);
|
||||
ChatSseEmitter chatSseEmitter = new ChatSseEmitter();
|
||||
SseEmitter emitter = chatSseEmitter.getEmitter();
|
||||
int historyLimit = resolveHistoryLimit(maxMessageCount);
|
||||
if (messages != null && !messages.isEmpty()) {
|
||||
ChatMemory defaultChatMemory = new DefaultBotMessageMemory(conversationId, chatSseEmitter, messages);
|
||||
memoryPrompt.setMemory(defaultChatMemory);
|
||||
} else {
|
||||
BotMessageMemory memory = new BotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), conversationId, botMessageService);
|
||||
memoryPrompt.setMemory(memory);
|
||||
memoryPrompt.setMemory(new RuntimeChatMemory(
|
||||
conversationId,
|
||||
chatRuntimeManager.loadMessages(runtimeContext, historyLimit)
|
||||
));
|
||||
}
|
||||
chatRuntimeManager.prepareSession(runtimeContext);
|
||||
chatRuntimeManager.recordUserMessage(runtimeContext, buildUserRuntimeMessage(runtimeContext, prompt, attachments));
|
||||
memoryPrompt.addMessage(userMessage);
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
threadPoolTaskExecutor.execute(() -> {
|
||||
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
|
||||
RequestContextHolder.setRequestAttributes(sra, true);
|
||||
StreamResponseListener streamResponseListener = new ChatStreamListener(conversationId.toString(), chatModel, memoryPrompt, chatSseEmitter, chatOptions);
|
||||
StreamResponseListener streamResponseListener = new ChatStreamListener(
|
||||
conversationId.toString(),
|
||||
chatModel,
|
||||
memoryPrompt,
|
||||
chatSseEmitter,
|
||||
chatOptions,
|
||||
chatRuntimeManager,
|
||||
runtimeContext,
|
||||
new ChatAssistantAccumulator()
|
||||
);
|
||||
chatModel.chatStream(memoryPrompt, streamResponseListener, chatOptions);
|
||||
});
|
||||
|
||||
@@ -239,7 +258,8 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public SseEmitter startPublicChat(BigInteger botId, String prompt, List<Message> messages, BotServiceImpl.ChatCheckResult chatCheckResult) {
|
||||
public SseEmitter startPublicChat(BigInteger botId, String prompt, List<Message> messages,
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult, ChatRuntimeContext runtimeContext) {
|
||||
Map<String, Object> modelOptions = chatCheckResult.getModelOptions();
|
||||
ChatOptions chatOptions = getChatOptions(modelOptions);
|
||||
ChatModel chatModel = chatCheckResult.getChatModel();
|
||||
@@ -260,11 +280,22 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
memoryPrompt.setSystemMessage(SystemMessage.of(systemPrompt));
|
||||
}
|
||||
memoryPrompt.addMessage(userMessage);
|
||||
chatRuntimeManager.prepareSession(runtimeContext);
|
||||
chatRuntimeManager.recordUserMessage(runtimeContext, buildUserRuntimeMessage(runtimeContext, prompt, null));
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
|
||||
threadPoolTaskExecutor.execute(() -> {
|
||||
RequestContextHolder.setRequestAttributes(sra, true);
|
||||
StreamResponseListener streamResponseListener = new ChatStreamListener(chatCheckResult.getConversationIdStr(), chatModel, memoryPrompt, chatSseEmitter, chatOptions);
|
||||
StreamResponseListener streamResponseListener = new ChatStreamListener(
|
||||
chatCheckResult.getConversationIdStr(),
|
||||
chatModel,
|
||||
memoryPrompt,
|
||||
chatSseEmitter,
|
||||
chatOptions,
|
||||
chatRuntimeManager,
|
||||
runtimeContext,
|
||||
new ChatAssistantAccumulator()
|
||||
);
|
||||
chatModel.chatStream(memoryPrompt, streamResponseListener, chatOptions);
|
||||
});
|
||||
|
||||
@@ -425,6 +456,36 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
return messageBuilder.toString();
|
||||
}
|
||||
|
||||
private int resolveHistoryLimit(Integer maxMessageCount) {
|
||||
if (maxMessageCount == null || maxMessageCount <= 0) {
|
||||
return 20;
|
||||
}
|
||||
return Math.min(maxMessageCount, 200);
|
||||
}
|
||||
|
||||
private ChatRuntimeMessage buildUserRuntimeMessage(ChatRuntimeContext context, String prompt, List<String> attachments) {
|
||||
ChatRuntimeMessage runtimeMessage = new ChatRuntimeMessage();
|
||||
runtimeMessage.setRole("user");
|
||||
runtimeMessage.setContentType("TEXT");
|
||||
runtimeMessage.setContentText(prompt);
|
||||
runtimeMessage.setCreatedAt(new Date());
|
||||
runtimeMessage.setSenderId(context.getUserId());
|
||||
runtimeMessage.setSenderName(resolveUserDisplayName(context));
|
||||
if (attachments != null && !attachments.isEmpty()) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("attachments", attachments);
|
||||
runtimeMessage.setContentPayload(payload);
|
||||
}
|
||||
return runtimeMessage;
|
||||
}
|
||||
|
||||
private String resolveUserDisplayName(ChatRuntimeContext context) {
|
||||
if (context.getUserName() != null && !context.getUserName().isBlank()) {
|
||||
return context.getUserName();
|
||||
}
|
||||
return context.getUserAccount();
|
||||
}
|
||||
|
||||
private String buildSystemPromptWithFaqImageRule(String systemPrompt) {
|
||||
if (!StringUtils.hasLength(systemPrompt)) {
|
||||
return FAQ_IMAGE_SYSTEM_RULE;
|
||||
|
||||
@@ -3,6 +3,8 @@ package tech.easyflow.auth.service;
|
||||
import tech.easyflow.auth.entity.LoginDTO;
|
||||
import tech.easyflow.auth.entity.LoginVO;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public interface AuthService {
|
||||
/**
|
||||
* 登录
|
||||
@@ -13,4 +15,14 @@ public interface AuthService {
|
||||
* 开发模式免登录
|
||||
*/
|
||||
LoginVO devLogin(String account);
|
||||
|
||||
/**
|
||||
* 通过访问令牌登录
|
||||
*/
|
||||
LoginVO loginByApiKey(String apiKey);
|
||||
|
||||
/**
|
||||
* 通过账号ID登录
|
||||
*/
|
||||
LoginVO loginByAccountId(BigInteger accountId, Long timeoutSeconds);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package tech.easyflow.auth.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.tenant.TenantManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.auth.entity.LoginDTO;
|
||||
import tech.easyflow.auth.entity.LoginVO;
|
||||
import tech.easyflow.auth.service.AuthService;
|
||||
@@ -7,22 +14,19 @@ import tech.easyflow.common.constant.Constants;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysMenu;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.service.SysApiKeyService;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
import tech.easyflow.system.service.SysMenuService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.tenant.TenantManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,6 +39,8 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
private SysRoleService sysRoleService;
|
||||
@Resource
|
||||
private SysMenuService sysMenuService;
|
||||
@Resource
|
||||
private SysApiKeyService sysApiKeyService;
|
||||
|
||||
@Override
|
||||
public LoginVO login(LoginDTO loginDTO) {
|
||||
@@ -63,6 +69,29 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginVO loginByApiKey(String apiKey) {
|
||||
try {
|
||||
TenantManager.ignoreTenantCondition();
|
||||
sysApiKeyService.checkApikeyPermission(apiKey, "/public-api/bot/chat");
|
||||
SysApiKey sysApiKey = sysApiKeyService.getSysApiKey(apiKey);
|
||||
BigInteger accountId = sysApiKey.getCreatedBy();
|
||||
if (accountId == null) {
|
||||
throw new BusinessException("访问令牌未绑定创建用户");
|
||||
}
|
||||
Long timeoutSeconds = resolveApiKeyLoginTimeoutSeconds(sysApiKey);
|
||||
return loginByAccountId(accountId, timeoutSeconds);
|
||||
} finally {
|
||||
TenantManager.restoreTenantCondition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginVO loginByAccountId(BigInteger accountId, Long timeoutSeconds) {
|
||||
SysAccount record = getAvailableAccount(accountId, "账号不存在或不可用");
|
||||
return createLoginVO(record, timeoutSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
List<SysMenu> menus = sysMenuService.getMenusByAccountId(new SysMenu(), BigInteger.valueOf(Long.parseLong(loginId.toString())));
|
||||
@@ -79,7 +108,17 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
}
|
||||
|
||||
private LoginVO createLoginVO(SysAccount record) {
|
||||
StpUtil.login(record.getId());
|
||||
return createLoginVO(record, null);
|
||||
}
|
||||
|
||||
private LoginVO createLoginVO(SysAccount record, Long timeoutSeconds) {
|
||||
if (timeoutSeconds != null) {
|
||||
SaLoginModel loginModel = new SaLoginModel();
|
||||
loginModel.setTimeout(timeoutSeconds);
|
||||
StpUtil.login(record.getId(), loginModel);
|
||||
} else {
|
||||
StpUtil.login(record.getId());
|
||||
}
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
BeanUtil.copyProperties(record, loginAccount);
|
||||
StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount);
|
||||
@@ -92,10 +131,32 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
return res;
|
||||
}
|
||||
|
||||
private Long resolveApiKeyLoginTimeoutSeconds(SysApiKey sysApiKey) {
|
||||
Date expiredAt = sysApiKey.getExpiredAt();
|
||||
if (expiredAt == null) {
|
||||
return null;
|
||||
}
|
||||
long remainingMs = expiredAt.getTime() - System.currentTimeMillis();
|
||||
if (remainingMs <= 0) {
|
||||
throw new BusinessException("apiKey 已过期");
|
||||
}
|
||||
long timeoutSeconds = (remainingMs + 999) / 1000;
|
||||
return Math.max(timeoutSeconds, 1L);
|
||||
}
|
||||
|
||||
private SysAccount getAvailableAccount(String account, String accountNotFoundMessage) {
|
||||
QueryWrapper w = QueryWrapper.create();
|
||||
w.eq(SysAccount::getLoginName, account);
|
||||
SysAccount record = sysAccountService.getOne(w);
|
||||
return validateAvailableAccount(record, accountNotFoundMessage);
|
||||
}
|
||||
|
||||
private SysAccount getAvailableAccount(BigInteger accountId, String accountNotFoundMessage) {
|
||||
SysAccount record = sysAccountService.getById(accountId);
|
||||
return validateAvailableAccount(record, accountNotFoundMessage);
|
||||
}
|
||||
|
||||
private SysAccount validateAvailableAccount(SysAccount record, String accountNotFoundMessage) {
|
||||
if (record == null) {
|
||||
throw new BusinessException(accountNotFoundMessage);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user