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

@@ -12,6 +12,10 @@
<artifactId>easyflow-api-usercenter</artifactId>
<dependencies>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-agent</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-auth</artifactId>

View File

@@ -0,0 +1,99 @@
package tech.easyflow.usercenter.controller.agent;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.security.AgentVisibilityQueryHelper;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.usercenter.model.agent.UcAgentListItemVo;
import java.util.Collections;
import java.util.List;
/**
* 用户中心 Agent 查询接口。
*/
@RestController
@RequestMapping("/userCenter/agent")
@UsePermission(moduleName = "/api/v1/agent")
public class UcAgentController {
private final AgentService agentService;
private final AgentVisibilityQueryHelper agentVisibilityQueryHelper;
/**
* 创建用户中心 Agent 控制器。
*
* @param agentService Agent 服务
* @param agentVisibilityQueryHelper Agent 可见性查询助手
*/
public UcAgentController(AgentService agentService,
AgentVisibilityQueryHelper agentVisibilityQueryHelper) {
this.agentService = agentService;
this.agentVisibilityQueryHelper = agentVisibilityQueryHelper;
}
/**
* 查询当前用户可见且已发布的 Agent。
*
* @return Agent 列表项
*/
@GetMapping("/list")
public Result<List<UcAgentListItemVo>> list() {
requireCurrentAccount();
QueryWrapper queryWrapper = QueryWrapper.create()
.select(
Agent::getId,
Agent::getPublishedSnapshotJson
)
.eq(Agent::getStatus, 1)
.eq(Agent::getPublishStatus, PublishStatus.PUBLISHED.getCode());
agentVisibilityQueryHelper.applyReadableAccess(queryWrapper);
queryWrapper.orderBy(Agent::getModified, false);
List<Agent> agents = agentService.list(queryWrapper);
if (agents == null || agents.isEmpty()) {
return Result.ok(Collections.emptyList());
}
return Result.ok(agents.stream()
.map(agent -> toListItem(agent, agentService.fromSnapshot(agent.getPublishedSnapshotJson())))
.toList());
}
/**
* 获取并校验当前登录账号。
*
* @return 当前登录账号
* @throws BusinessException 登录信息失效时抛出
*/
protected LoginAccount requireCurrentAccount() {
LoginAccount account = SaTokenUtil.getLoginAccount();
if (account == null || account.getTenantId() == null) {
throw new BusinessException("当前登录状态失效,请重新登录后再试");
}
return account;
}
/**
* 将已发布快照转换为最小用户端列表项。
*
* @param liveAgent 当前 Agent 记录
* @param publishedAgent 已发布 Agent 快照
* @return 用户端列表项
*/
private UcAgentListItemVo toListItem(Agent liveAgent, Agent publishedAgent) {
return new UcAgentListItemVo(
liveAgent.getId(),
publishedAgent.getName(),
publishedAgent.getDescription(),
publishedAgent.getAvatar()
);
}
}

View File

@@ -0,0 +1,65 @@
package tech.easyflow.usercenter.model.agent;
import java.math.BigInteger;
/**
* 用户中心已发布 Agent 列表项。
*/
public class UcAgentListItemVo {
private final BigInteger id;
private final String name;
private final String description;
private final String avatar;
/**
* 创建 Agent 列表项。
*
* @param id Agent ID
* @param name Agent 名称
* @param description Agent 描述
* @param avatar Agent 头像
*/
public UcAgentListItemVo(BigInteger id, String name, String description, String avatar) {
this.id = id;
this.name = name;
this.description = description;
this.avatar = avatar;
}
/**
* 获取 Agent ID。
*
* @return Agent ID
*/
public BigInteger getId() {
return id;
}
/**
* 获取 Agent 名称。
*
* @return Agent 名称
*/
public String getName() {
return name;
}
/**
* 获取 Agent 描述。
*
* @return Agent 描述
*/
public String getDescription() {
return description;
}
/**
* 获取 Agent 头像。
*
* @return Agent 头像
*/
public String getAvatar() {
return avatar;
}
}

View File

@@ -0,0 +1,149 @@
package tech.easyflow.usercenter.controller.agent;
import com.mybatisflex.core.query.QueryWrapper;
import org.testng.Assert;
import org.testng.annotations.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.security.AgentVisibilityQueryHelper;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.usercenter.model.agent.UcAgentListItemVo;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
/**
* {@link UcAgentController} 用户端 Agent 可见性测试。
*/
public class UcAgentControllerTest {
/**
* 验证列表把可见性限制下推到查询,并仅返回发布快照的展示字段。
*/
@Test
public void listShouldFilterVisibilityAndReturnPublishedSummary() {
Agent visibleAgent = liveAgent(BigInteger.ONE, "草稿名称一", "发布名称一");
AgentService agentService = proxy(
AgentService.class,
(instance, method, args) -> {
if ("list".equals(method.getName())) {
return List.of(visibleAgent);
}
if ("fromSnapshot".equals(method.getName())) {
Map<?, ?> snapshot = (Map<?, ?>) args[0];
Agent published = new Agent();
published.setName(String.valueOf(snapshot.get("name")));
published.setDescription(String.valueOf(snapshot.get("description")));
published.setAvatar(String.valueOf(snapshot.get("avatar")));
return published;
}
throw new AssertionError("测试路径不应调用 AgentService." + method.getName());
}
);
RecordingVisibilityQueryHelper visibilityQueryHelper = new RecordingVisibilityQueryHelper();
LoginAccount account = new LoginAccount();
account.setTenantId(BigInteger.ONE);
UcAgentController controller = new TestUcAgentController(
agentService,
visibilityQueryHelper,
account
);
List<UcAgentListItemVo> result = controller.list().getData();
Assert.assertEquals(1, result.size());
Assert.assertEquals(visibleAgent.getId(), result.get(0).getId());
Assert.assertEquals("发布名称一", result.get(0).getName());
Assert.assertEquals("发布描述一", result.get(0).getDescription());
Assert.assertEquals("avatar-一", result.get(0).getAvatar());
Assert.assertTrue(visibilityQueryHelper.applied);
}
/**
* 创建带草稿字段和发布快照的 Agent。
*
* @param id Agent ID
* @param draftName 草稿名称
* @param publishedName 发布名称
* @return Agent
*/
private Agent liveAgent(BigInteger id, String draftName, String publishedName) {
Agent agent = new Agent();
agent.setId(id);
agent.setName(draftName);
String suffix = BigInteger.ONE.equals(id) ? "" : "";
agent.setPublishedSnapshotJson(Map.of(
"name", publishedName,
"description", "发布描述" + suffix,
"avatar", "avatar-" + suffix
));
return agent;
}
/**
* 创建接口代理。
*
* @param type 接口类型
* @param handler 调用处理器
* @param <T> 接口类型
* @return 代理实例
*/
private <T> T proxy(Class<T> type, java.lang.reflect.InvocationHandler handler) {
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, handler));
}
/**
* 记录可见性条件是否已下推到查询。
*/
private static final class RecordingVisibilityQueryHelper extends AgentVisibilityQueryHelper {
private boolean applied;
/**
* 创建记录型可见性查询助手。
*/
private RecordingVisibilityQueryHelper() {
super(null, null);
}
/**
* {@inheritDoc}
*/
@Override
public void applyReadableAccess(QueryWrapper queryWrapper) {
applied = true;
}
}
/**
* 使用固定登录账号的用户中心 Agent 控制器。
*/
private static final class TestUcAgentController extends UcAgentController {
private final LoginAccount account;
/**
* 创建测试控制器。
*
* @param agentService Agent 服务
* @param visibilityQueryHelper 可见性查询助手
* @param account 当前账号
*/
private TestUcAgentController(AgentService agentService,
AgentVisibilityQueryHelper visibilityQueryHelper,
LoginAccount account) {
super(agentService, visibilityQueryHelper);
this.account = account;
}
/**
* {@inheritDoc}
*/
@Override
protected LoginAccount requireCurrentAccount() {
return account;
}
}
}