fix: 完成系统向智能体数据链路切换

- 切换工作台、聊天历史、资源候选与公共调用到 Agent

- 加固资源绑定、删除保护及发布运行并发控制

- 隔离旧 Bot 专属服务和组件并保留兼容入口
This commit is contained in:
2026-07-31 14:24:15 +08:00
parent f0aba1eddd
commit f872eac1f9
114 changed files with 5997 additions and 874 deletions

View File

@@ -27,6 +27,9 @@ import java.util.List;
@Service
public class SysApiKeyServiceImpl extends ServiceImpl<SysApiKeyMapper, SysApiKey> implements SysApiKeyService {
private static final String PUBLIC_AGENT_CHAT_URI = "/public-api/agent/chat";
private static final String LEGACY_PUBLIC_BOT_CHAT_URI = "/public-api/bot/chat";
@Resource
private SysApiKeyResourceMappingService mappingService;
@Resource
@@ -57,7 +60,10 @@ public class SysApiKeyServiceImpl extends ServiceImpl<SysApiKeyMapper, SysApiKey
private List<String> getCandidateRequestUris(String requestURI) {
List<String> uris = new ArrayList<>();
uris.add(requestURI);
if ("/v1/chat/completions".equals(requestURI)) {
if (PUBLIC_AGENT_CHAT_URI.equals(requestURI)) {
// 数据库权限资源暂不迁移Agent 公共接口复用旧资源记录作为兼容别名。
uris.add(LEGACY_PUBLIC_BOT_CHAT_URI);
} else if ("/v1/chat/completions".equals(requestURI)) {
uris.add("/public-api/openai/v1/chat/completions");
} else if ("/public-api/openai/v1/chat/completions".equals(requestURI)) {
uris.add("/v1/chat/completions");

View File

@@ -0,0 +1,149 @@
package tech.easyflow.system.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysApiKey;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
/**
* API Key 接口权限兼容测试。
*/
public class SysApiKeyServiceImplTest {
/**
* 验证不存在的 API Key 使用 HTTP 401 语义。
*/
@Test
public void shouldReturnUnauthorizedWhenApiKeyMissing() {
TestSysApiKeyService service = new TestSysApiKeyService(null);
BusinessException error = expectBusinessException(
() -> service.getSysApiKey("missing"));
Assert.assertEquals(401, error.getHttpStatus());
Assert.assertEquals(401, error.getErrorCode());
}
/**
* 验证状态缺失的 API Key 按禁用处理并返回 HTTP 401。
*/
@Test
public void shouldReturnUnauthorizedWhenApiKeyStatusMissing() {
SysApiKey apiKey = new SysApiKey();
TestSysApiKeyService service = new TestSysApiKeyService(apiKey);
BusinessException error = expectBusinessException(
() -> service.getSysApiKey("status-missing"));
Assert.assertEquals(401, error.getHttpStatus());
Assert.assertEquals(401, error.getErrorCode());
}
/**
* 验证过期的 API Key 使用 HTTP 401 语义。
*/
@Test
public void shouldReturnUnauthorizedWhenApiKeyExpired() {
SysApiKey apiKey = new SysApiKey();
apiKey.setStatus(1);
apiKey.setExpiredAt(new Date(System.currentTimeMillis() - 1_000L));
TestSysApiKeyService service = new TestSysApiKeyService(apiKey);
BusinessException error = expectBusinessException(
() -> service.getSysApiKey("expired"));
Assert.assertEquals(401, error.getHttpStatus());
Assert.assertEquals(401, error.getErrorCode());
}
/**
* 验证 Agent 公共聊天接口可复用未迁移的旧权限资源记录。
*
* @throws Exception 反射调用失败
*/
@Test
@SuppressWarnings("unchecked")
public void shouldResolveLegacyPermissionForPublicAgentChat() throws Exception {
SysApiKeyServiceImpl service = new SysApiKeyServiceImpl();
Method method = SysApiKeyServiceImpl.class
.getDeclaredMethod("getCandidateRequestUris", String.class);
method.setAccessible(true);
List<String> candidates =
(List<String>) method.invoke(service, "/public-api/agent/chat");
Assert.assertEquals(
List.of("/public-api/agent/chat", "/public-api/bot/chat"),
candidates
);
}
/**
* 验证旧 Bot 接口不会反向获得新 Agent 接口权限。
*
* @throws Exception 反射调用失败
*/
@Test
@SuppressWarnings("unchecked")
public void shouldKeepLegacyBotPermissionIsolated() throws Exception {
SysApiKeyServiceImpl service = new SysApiKeyServiceImpl();
Method method = SysApiKeyServiceImpl.class
.getDeclaredMethod("getCandidateRequestUris", String.class);
method.setAccessible(true);
List<String> candidates =
(List<String>) method.invoke(service, "/public-api/bot/chat");
Assert.assertEquals(List.of("/public-api/bot/chat"), candidates);
}
/**
* 执行调用并返回预期业务异常。
*
* @param action 待执行调用
* @return 捕获的业务异常
*/
private BusinessException expectBusinessException(Runnable action) {
try {
action.run();
Assert.fail("expected BusinessException");
return null;
} catch (BusinessException error) {
return error;
}
}
/**
* 固定返回 API Key 的测试服务。
*/
private static final class TestSysApiKeyService
extends SysApiKeyServiceImpl {
private final SysApiKey apiKey;
/**
* 创建测试服务。
*
* @param apiKey 查询时返回的 API Key
*/
private TestSysApiKeyService(SysApiKey apiKey) {
this.apiKey = apiKey;
}
/**
* 返回预设 API Key。
*
* @param queryWrapper 查询条件
* @return 预设 API Key
*/
@Override
public SysApiKey getOne(QueryWrapper queryWrapper) {
return apiKey;
}
}
}