feat: 接入聊天历史界面与外链会话恢复

- 新增管理端与用户端聊天历史接口和页面

- 外链聊天支持访问令牌登录、身份保活与当前会话恢复

- 聊天执行链路切到统一 runtime 与 chatlog 查询接口
This commit is contained in:
2026-04-05 11:37:25 +08:00
parent 25e80433a5
commit a4f75a5e4c
48 changed files with 3724 additions and 972 deletions

View File

@@ -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);
}

View File

@@ -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);
}