fix: 收口管理端页面权限与工作流运行授权
- 页面选项接口改用所属页面权限并返回最小数据视图 - 统一校验工作流引用、租户、状态与定时任务执行主体 - 补充聊天记录权限迁移和权限隔离回归测试
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package tech.easyflow.admin.controller.agent;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -230,6 +231,7 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return SSE Emitter
|
||||
*/
|
||||
@PostMapping("chat")
|
||||
@SaCheckPermission("/api/v1/agent/session/query")
|
||||
public SseEmitter chat(@JsonBody AgentChatRequest request) {
|
||||
return agentRunService.chat(request);
|
||||
}
|
||||
@@ -241,6 +243,7 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return SSE Emitter
|
||||
*/
|
||||
@PostMapping("/chat/draft")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public SseEmitter chatDraft(@JsonBody AgentDraftChatRequest request) {
|
||||
return agentRunService.chatDraft(request);
|
||||
}
|
||||
@@ -255,6 +258,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 上传结果
|
||||
*/
|
||||
@PostMapping(value = "/media/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentMediaUploadView> uploadMedia(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("mode") String mode,
|
||||
@RequestParam("agentId") String agentId,
|
||||
@@ -269,6 +275,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/media/delete")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<Void> deleteMedia(@JsonBody(value = "uploadId", required = true) String uploadId) {
|
||||
agentMediaService.deleteUpload(uploadId, SaTokenUtil.getLoginAccount());
|
||||
return Result.ok();
|
||||
@@ -281,6 +290,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 图片响应
|
||||
*/
|
||||
@GetMapping("/media/content")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
@LogReporterDisabled
|
||||
public ResponseEntity<byte[]> mediaContent(@RequestParam("reference") String reference) {
|
||||
AgentMediaResource resource = agentMediaService.load(reference, SaTokenUtil.getLoginAccount());
|
||||
@@ -303,6 +315,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 上传与读取状态
|
||||
*/
|
||||
@PostMapping(value = "/media/document/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentDocumentUploadView> uploadDocument(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("mode") String mode,
|
||||
@RequestParam("agentId") String agentId,
|
||||
@@ -320,6 +335,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 最新状态
|
||||
*/
|
||||
@GetMapping("/media/document/status")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentDocumentUploadView> documentStatus(@RequestParam("uploadId") String uploadId) {
|
||||
return Result.ok(agentDocumentService.status(uploadId, SaTokenUtil.getLoginAccount()));
|
||||
}
|
||||
@@ -331,6 +349,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 重试后的状态
|
||||
*/
|
||||
@PostMapping("/media/document/retry")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentDocumentUploadView> retryDocument(
|
||||
@JsonBody(value = "uploadId", required = true) String uploadId) {
|
||||
return Result.ok(agentDocumentService.retry(uploadId, SaTokenUtil.getLoginAccount()));
|
||||
@@ -343,6 +364,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/media/document/delete")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<Void> deleteDocument(
|
||||
@JsonBody(value = "uploadId", required = true) String uploadId) {
|
||||
agentDocumentService.deleteUpload(uploadId, SaTokenUtil.getLoginAccount());
|
||||
@@ -356,6 +380,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 文档流
|
||||
*/
|
||||
@GetMapping("/media/document/content")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
@LogReporterDisabled
|
||||
public ResponseEntity<StreamingResponseBody> documentContent(
|
||||
@RequestParam("reference") String reference) {
|
||||
@@ -384,6 +411,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 会话信息
|
||||
*/
|
||||
@PostMapping("/composer/session")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentComposerSession> allocateComposerSession(
|
||||
@JsonBody(value = "mode", required = true) String mode) {
|
||||
return Result.ok(agentComposerDraftService.allocateSession(mode));
|
||||
@@ -396,6 +426,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 保存后的草稿
|
||||
*/
|
||||
@PostMapping("/composer/draft/persist")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentComposerDraft> saveComposerDraft(@JsonBody AgentComposerDraft draft) {
|
||||
return Result.ok(agentComposerDraftService.save(draft, SaTokenUtil.getLoginAccount()));
|
||||
}
|
||||
@@ -409,6 +442,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 输入草稿
|
||||
*/
|
||||
@GetMapping("/composer/draft")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<AgentComposerDraft> getComposerDraft(@RequestParam("mode") String mode,
|
||||
@RequestParam("agentId") String agentId,
|
||||
@RequestParam(value = "sessionId", required = false) String sessionId) {
|
||||
@@ -428,6 +464,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/composer/draft/delete")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<Void> deleteComposerDraft(@JsonBody(value = "mode", required = true) String mode,
|
||||
@JsonBody(value = "agentId", required = true) String agentId,
|
||||
@JsonBody(value = "sessionId", required = true) String sessionId,
|
||||
@@ -446,6 +485,7 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/chat/draft/clear")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public Result<Void> clearDraftSession(@JsonBody(value = "sessionId", required = true) String sessionId) {
|
||||
agentRunService.clearDraftSession(sessionId);
|
||||
return Result.ok();
|
||||
@@ -459,6 +499,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/run/approve")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<Void> approve(@JsonBody("requestId") String requestId,
|
||||
@JsonBody(value = "resumeToken", required = true) String resumeToken) {
|
||||
agentRunService.approve(requestId, resumeToken);
|
||||
@@ -474,6 +517,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/run/reject")
|
||||
@SaCheckPermission(value = {
|
||||
"/api/v1/agent/session/query", "/api/v1/agent/save"
|
||||
}, mode = SaMode.OR)
|
||||
public Result<Void> reject(@JsonBody("requestId") String requestId,
|
||||
@JsonBody(value = "resumeToken", required = true) String resumeToken,
|
||||
@JsonBody("reason") String reason) {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package tech.easyflow.admin.controller.agent;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceConversationView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionDetailView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionPage;
|
||||
import tech.easyflow.admin.service.agent.AgentSessionService;
|
||||
import tech.easyflow.agent.service.AgentOptionQueryService;
|
||||
import tech.easyflow.agent.vo.AgentOptionView;
|
||||
import tech.easyflow.agent.vo.AgentResourceOptionsView;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
@@ -22,17 +26,42 @@ import java.util.List;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/agent/session")
|
||||
@SaCheckPermission("/api/v1/agent/session/query")
|
||||
public class AgentSessionController {
|
||||
|
||||
private final AgentSessionService agentSessionService;
|
||||
private final AgentOptionQueryService agentOptionQueryService;
|
||||
|
||||
/**
|
||||
* 创建 Agent 管理端会话控制器。
|
||||
*
|
||||
* @param agentSessionService Agent 会话服务
|
||||
* @param agentOptionQueryService Agent 安全选项服务
|
||||
*/
|
||||
public AgentSessionController(AgentSessionService agentSessionService) {
|
||||
public AgentSessionController(AgentSessionService agentSessionService,
|
||||
AgentOptionQueryService agentOptionQueryService) {
|
||||
this.agentSessionService = agentSessionService;
|
||||
this.agentOptionQueryService = agentOptionQueryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询正式聊天可使用的已发布 Agent。
|
||||
*
|
||||
* @return Agent 安全选项
|
||||
*/
|
||||
@GetMapping("/options")
|
||||
public Result<List<AgentOptionView>> options() {
|
||||
return Result.ok(agentOptionQueryService.listAgentOptions(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询正式聊天可附加的知识库。
|
||||
*
|
||||
* @return 知识库安全选项
|
||||
*/
|
||||
@GetMapping("/knowledgeOptions")
|
||||
public Result<List<AgentResourceOptionsView.ResourceOption>> knowledgeOptions() {
|
||||
return Result.ok(agentOptionQueryService.listKnowledgeOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package tech.easyflow.admin.controller.ai;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.agent.service.AgentOptionQueryService;
|
||||
import tech.easyflow.agent.vo.AgentOptionView;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
|
||||
@@ -23,21 +26,36 @@ import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/chatHistory")
|
||||
@SaCheckPermission("/api/v1/chatHistory/query")
|
||||
public class ChatHistoryController {
|
||||
|
||||
private final ChatHistoryManageService chatHistoryManageService;
|
||||
private final CategoryPermissionService categoryPermissionService;
|
||||
private final AgentOptionQueryService agentOptionQueryService;
|
||||
|
||||
/**
|
||||
* 创建聊天历史控制器。
|
||||
*
|
||||
* @param chatHistoryManageService 聊天历史管理服务
|
||||
* @param categoryPermissionService 账号权限服务
|
||||
* @param agentOptionQueryService Agent 安全选项服务
|
||||
*/
|
||||
public ChatHistoryController(ChatHistoryManageService chatHistoryManageService,
|
||||
CategoryPermissionService categoryPermissionService) {
|
||||
CategoryPermissionService categoryPermissionService,
|
||||
AgentOptionQueryService agentOptionQueryService) {
|
||||
this.chatHistoryManageService = chatHistoryManageService;
|
||||
this.categoryPermissionService = categoryPermissionService;
|
||||
this.agentOptionQueryService = agentOptionQueryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询聊天记录筛选可使用的 Agent。
|
||||
*
|
||||
* @return Agent 安全选项
|
||||
*/
|
||||
@GetMapping("/agentOptions")
|
||||
public Result<List<AgentOptionView>> agentOptions() {
|
||||
return Result.ok(agentOptionQueryService.listAgentOptions(false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,10 @@ import tech.easyflow.ai.entity.table.ModelTableDef;
|
||||
import tech.easyflow.ai.mapper.ModelMapper;
|
||||
import tech.easyflow.ai.service.AgentResourceReferenceService;
|
||||
import tech.easyflow.ai.service.ModelService;
|
||||
import tech.easyflow.admin.model.ai.ModelGatewayConfigView;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.system.entity.SysOption;
|
||||
import tech.easyflow.system.service.SysOptionService;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilityResolution;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
@@ -40,6 +44,11 @@ import java.util.stream.Collectors;
|
||||
@RequestMapping("/api/v1/model")
|
||||
public class ModelController extends BaseCurdController<ModelService, Model> {
|
||||
|
||||
private static final String CHAT_PUBLISH_BASE_URL = "chat_publish_base_url";
|
||||
|
||||
@Autowired
|
||||
private SysOptionService sysOptionService;
|
||||
|
||||
public ModelController(ModelService service) {
|
||||
super(service);
|
||||
}
|
||||
@@ -47,6 +56,21 @@ public class ModelController extends BaseCurdController<ModelService, Model> {
|
||||
@Autowired
|
||||
ModelService modelService;
|
||||
|
||||
/**
|
||||
* 查询模型统一网关页面所需的安全配置。
|
||||
*
|
||||
* @return 仅包含模型发布基础地址的配置
|
||||
*/
|
||||
@GetMapping("/gatewayConfig")
|
||||
@SaCheckPermission("/api/v1/model/query")
|
||||
public Result<ModelGatewayConfigView> gatewayConfig() {
|
||||
SysOption option = sysOptionService.getByOptionKey(
|
||||
CHAT_PUBLISH_BASE_URL,
|
||||
SaTokenUtil.getLoginAccount().getTenantId()
|
||||
);
|
||||
return Result.ok(new ModelGatewayConfigView(option == null ? null : option.getValue()));
|
||||
}
|
||||
|
||||
@Resource
|
||||
ModelMapper modelMapper;
|
||||
@Resource
|
||||
|
||||
@@ -1,88 +1,45 @@
|
||||
package tech.easyflow.admin.controller.ai;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.easyagents.flow.core.chain.ChainDefinition;
|
||||
import com.easyagents.flow.core.chain.Node;
|
||||
import com.easyagents.flow.core.node.ConfirmNode;
|
||||
import com.easyagents.flow.core.node.EndNode;
|
||||
import com.easyagents.flow.core.node.StartNode;
|
||||
import com.easyagents.flow.core.parser.ChainParser;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import tech.easyflow.admin.service.ai.WorkflowDesignerOptionService;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 工作流节点兼容接口。
|
||||
*/
|
||||
@RequestMapping("/api/v1/workflowNode")
|
||||
@RestController
|
||||
public class WorkFlowNodeController {
|
||||
|
||||
@Resource
|
||||
private WorkflowService workflowService;
|
||||
@Resource
|
||||
private ChainParser chainParser;
|
||||
@Resource
|
||||
private WorkflowDatacenterContentService workflowDatacenterContentService;
|
||||
private final WorkflowDesignerOptionService workflowDesignerOptionService;
|
||||
|
||||
@GetMapping("/getChainParams")
|
||||
public Result<?> getChainParams(String currentId, String workflowId) {
|
||||
if (workflowId.equals(currentId)) {
|
||||
throw new BusinessException("工作流不能作为自身子节点");
|
||||
}
|
||||
JSONObject nodeData = new JSONObject();
|
||||
Workflow workflow = workflowService.getById(workflowId);
|
||||
if (workflow == null) {
|
||||
throw new BusinessException("工作流不存在: " + workflowId);
|
||||
}
|
||||
nodeData.put("workflowId", workflow.getId());
|
||||
nodeData.put("workflowName", workflow.getTitle());
|
||||
|
||||
ChainDefinition definition = chainParser.parse(workflowDatacenterContentService.prepareContent(workflow.getContent()));
|
||||
List<Node> nodes = definition.getNodes();
|
||||
JSONArray inputs = new JSONArray();
|
||||
JSONArray outputs = new JSONArray();
|
||||
for (Node node : nodes) {
|
||||
if (node instanceof StartNode) {
|
||||
inputs = JSON.parseArray(JSON.toJSONString(node.getParameters()));
|
||||
handleArray(inputs);
|
||||
}
|
||||
if (node instanceof EndNode) {
|
||||
outputs = JSON.parseArray(JSON.toJSONString(((EndNode) node).getOutputDefs()));
|
||||
handleArray(outputs);
|
||||
}
|
||||
if (node instanceof ConfirmNode) {
|
||||
throw new BusinessException("工作流存在【确认节点】,暂不支持作为子节点");
|
||||
}
|
||||
}
|
||||
nodeData.put("parameters", inputs);
|
||||
nodeData.put("outputDefs", outputs);
|
||||
return Result.ok(nodeData);
|
||||
/**
|
||||
* 创建工作流节点兼容控制器。
|
||||
*
|
||||
* @param workflowDesignerOptionService 工作流设计器选项服务
|
||||
*/
|
||||
public WorkFlowNodeController(WorkflowDesignerOptionService workflowDesignerOptionService) {
|
||||
this.workflowDesignerOptionService = workflowDesignerOptionService;
|
||||
}
|
||||
|
||||
private void handleArray(JSONArray array) {
|
||||
if (array != null) {
|
||||
for (Object o : array) {
|
||||
JSONObject obj = (JSONObject) o;
|
||||
obj.put("id", IdUtil.simpleUUID());
|
||||
obj.put("nameDisabled", true);
|
||||
obj.put("dataTypeDisabled", true);
|
||||
obj.put("deleteDisabled", true);
|
||||
obj.put("addChildDisabled", true);
|
||||
obj.put("refType", "ref");
|
||||
JSONArray children = obj.getJSONArray("children");
|
||||
if (children != null) {
|
||||
handleArray(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 查询子流程输入输出定义。
|
||||
*
|
||||
* @param currentId 当前工作流 ID
|
||||
* @param workflowId 子流程 ID
|
||||
* @return 子流程节点配置
|
||||
* @deprecated 请使用 {@code /api/v1/workflow/designer/childWorkflow}
|
||||
*/
|
||||
@Deprecated
|
||||
@GetMapping("/getChainParams")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<JSONObject> getChainParams(BigInteger currentId, BigInteger workflowId) {
|
||||
return Result.ok(workflowDesignerOptionService.getChildWorkflowNodeData(currentId, workflowId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
@@ -14,6 +15,8 @@ import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||
import tech.easyflow.admin.model.ai.WorkflowDesignerOptionsView;
|
||||
import tech.easyflow.admin.service.ai.WorkflowDesignerOptionService;
|
||||
import tech.easyflow.agent.entity.AgentToolBinding;
|
||||
import tech.easyflow.agent.enums.AgentToolType;
|
||||
import tech.easyflow.agent.service.AgentToolBindingService;
|
||||
@@ -43,6 +46,7 @@ import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.datacenter.execution.model.DatasetRef;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.enums.ResourceLookup;
|
||||
@@ -101,12 +105,116 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||
@Resource
|
||||
private WorkflowShareResourceAccessGrantProvider workflowShareGrantProvider;
|
||||
@Resource
|
||||
private WorkflowDesignerOptionService workflowDesignerOptionService;
|
||||
|
||||
public WorkflowController(WorkflowService service, ModelService modelService) {
|
||||
super(service);
|
||||
this.modelService = modelService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流设计器初始化所需的安全选项。
|
||||
*
|
||||
* @return 模型、知识库和代码引擎选项
|
||||
*/
|
||||
@GetMapping("/designer/options")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<WorkflowDesignerOptionsView> designerOptions() {
|
||||
return Result.ok(workflowDesignerOptionService.listOptions(
|
||||
codeEngineCapabilityService.listSupportedCodeEngines()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询工作流设计器可用插件。
|
||||
*
|
||||
* @param pageNumber 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 插件安全选项分页
|
||||
*/
|
||||
@GetMapping("/designer/plugins")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<Page<WorkflowDesignerOptionsView.PluginOption>> designerPlugins(
|
||||
Long pageNumber,
|
||||
Long pageSize) {
|
||||
return Result.ok(workflowDesignerOptionService.pagePlugins(pageNumber, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流插件节点配置。
|
||||
*
|
||||
* @param id 插件工具 ID
|
||||
* @return 插件节点配置
|
||||
*/
|
||||
@GetMapping("/designer/pluginTinyFlow")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<?> designerPluginTinyFlow(BigInteger id) {
|
||||
return Result.ok(workflowDesignerOptionService.getPluginTinyFlowData(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流设计器的子流程节点配置。
|
||||
*
|
||||
* @param currentId 当前工作流 ID
|
||||
* @param workflowId 子流程 ID
|
||||
* @return 子流程输入输出定义
|
||||
*/
|
||||
@GetMapping("/designer/childWorkflow")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<JSONObject> designerChildWorkflow(BigInteger currentId, BigInteger workflowId) {
|
||||
return Result.ok(workflowDesignerOptionService.getChildWorkflowNodeData(currentId, workflowId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流数据节点可见的数据源。
|
||||
*
|
||||
* @return 数据源安全选项
|
||||
*/
|
||||
@GetMapping("/designer/dataSources")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<List<WorkflowDesignerOptionsView.DataSourceOption>> designerDataSources() {
|
||||
return Result.ok(workflowDesignerOptionService.listDataSources());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流数据节点的数据目录。
|
||||
*
|
||||
* @param sourceId 数据源 ID
|
||||
* @return 目录安全选项
|
||||
*/
|
||||
@GetMapping("/designer/catalogs")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<List<WorkflowDesignerOptionsView.CatalogOption>> designerCatalogs(BigInteger sourceId) {
|
||||
return Result.ok(workflowDesignerOptionService.listCatalogs(sourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流数据节点的已接入数据集。
|
||||
*
|
||||
* @param sourceId 数据源 ID
|
||||
* @param catalogId 目录 ID
|
||||
* @return 数据集安全选项
|
||||
*/
|
||||
@GetMapping("/designer/managedTables")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<List<WorkflowDesignerOptionsView.DatasetOption>> designerManagedTables(
|
||||
BigInteger sourceId,
|
||||
BigInteger catalogId) {
|
||||
return Result.ok(workflowDesignerOptionService.listManagedDatasets(sourceId, catalogId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流数据节点的数据集结构。
|
||||
*
|
||||
* @param datasetRef 数据集引用
|
||||
* @return 数据集结构安全视图
|
||||
*/
|
||||
@GetMapping("/designer/schema")
|
||||
@SaCheckPermission("/api/v1/workflow/query")
|
||||
public Result<WorkflowDesignerOptionsView.DatasetSchemaOption> designerSchema(DatasetRef datasetRef) {
|
||||
return Result.ok(workflowDesignerOptionService.getDatasetSchema(datasetRef));
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点单独运行
|
||||
*/
|
||||
@@ -128,6 +236,9 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
if (workflow == null) {
|
||||
return Result.fail(1, "工作流不存在");
|
||||
}
|
||||
workflowCheckService.checkOrThrow(
|
||||
workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
|
||||
workflowDesignerOptionService.assertContentReferences(workflow.getContent());
|
||||
if (variables == null) {
|
||||
variables = new HashMap<>();
|
||||
}
|
||||
@@ -161,6 +272,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
throw new RuntimeException("工作流不存在");
|
||||
}
|
||||
workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
|
||||
workflowDesignerOptionService.assertContentReferences(workflow.getContent());
|
||||
variables = workflowRunningParameterResolver.normalizeRuntimeVariables(workflow.getContent(), variables);
|
||||
if (StpUtil.isLogin()) {
|
||||
variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount());
|
||||
@@ -252,6 +364,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
return Result.fail(1, "can not find the workflow by id: " + id);
|
||||
}
|
||||
workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
|
||||
workflowDesignerOptionService.assertContentReferences(workflow.getContent());
|
||||
Map<String, Object> res = workflowRunningParameterResolver.buildRunningParametersView(workflow);
|
||||
if (res == null) {
|
||||
return Result.fail(2, "节点配置错误,请检查! ");
|
||||
@@ -431,6 +544,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
workflow.setAlias(IdUtil.fastSimpleUUID());
|
||||
workflow.setRevision(0);
|
||||
commonFiled(workflow, account.getId(), account.getTenantId(), account.getDeptId());
|
||||
workflowDesignerOptionService.assertContentReferences(workflow.getContent());
|
||||
service.save(workflow);
|
||||
return Result.ok();
|
||||
}
|
||||
@@ -497,6 +611,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
||||
}
|
||||
if (StringUtils.hasLength(entity.getContent())) {
|
||||
workflowCheckService.checkOrThrow(entity.getContent(), WorkflowCheckStage.SAVE, entity.getId());
|
||||
workflowDesignerOptionService.assertContentReferences(entity.getContent());
|
||||
}
|
||||
|
||||
String alias = entity.getAlias();
|
||||
|
||||
@@ -6,6 +6,7 @@ import tech.easyflow.common.dict.Dict;
|
||||
import tech.easyflow.common.dict.DictItem;
|
||||
import tech.easyflow.common.dict.DictLoader;
|
||||
import tech.easyflow.common.dict.DictManager;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -16,18 +17,30 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 提供已注册静态字典的查询接口。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/dict/")
|
||||
public class DictController {
|
||||
|
||||
@Resource
|
||||
DictManager dictManager;
|
||||
private DictManager dictManager;
|
||||
|
||||
/**
|
||||
* 查询指定静态字典的选项。
|
||||
*
|
||||
* @param code 字典编码
|
||||
* @param keyword 搜索关键字
|
||||
* @param request HTTP 请求
|
||||
* @return 字典选项
|
||||
* @throws BusinessException 字典未注册时抛出
|
||||
*/
|
||||
@GetMapping("/items/{code}")
|
||||
public Result<List<DictItem>> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) {
|
||||
DictLoader loader = dictManager.getLoader(code);
|
||||
if (loader == null) {
|
||||
return Result.ok(Collections.emptyList());
|
||||
throw new BusinessException("字典不存在或不支持公共查询");
|
||||
}
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
Dict dict = loader.load(keyword, parameterMap);
|
||||
|
||||
@@ -10,6 +10,8 @@ import tech.easyflow.admin.model.dashboard.DashboardOverviewVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankQuery;
|
||||
import tech.easyflow.admin.service.dashboard.DashboardService;
|
||||
import tech.easyflow.agent.service.AgentOptionQueryService;
|
||||
import tech.easyflow.agent.vo.AgentOptionView;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
|
||||
@@ -28,9 +30,29 @@ import java.util.List;
|
||||
public class DashboardController {
|
||||
|
||||
private final DashboardService dashboardService;
|
||||
private final AgentOptionQueryService agentOptionQueryService;
|
||||
|
||||
public DashboardController(DashboardService dashboardService) {
|
||||
/**
|
||||
* 创建管理端工作台控制器。
|
||||
*
|
||||
* @param dashboardService 工作台统计服务
|
||||
* @param agentOptionQueryService Agent 安全选项服务
|
||||
*/
|
||||
public DashboardController(DashboardService dashboardService,
|
||||
AgentOptionQueryService agentOptionQueryService) {
|
||||
this.dashboardService = dashboardService;
|
||||
this.agentOptionQueryService = agentOptionQueryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作台筛选可使用的 Agent。
|
||||
*
|
||||
* @return Agent 安全选项
|
||||
*/
|
||||
@GetMapping("/agentOptions")
|
||||
@SaCheckPermission("/api/v1/dashboard/query")
|
||||
public Result<List<AgentOptionView>> agentOptions() {
|
||||
return Result.ok(agentOptionQueryService.listAgentOptions(false));
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
|
||||
@@ -3,6 +3,7 @@ package tech.easyflow.admin.controller.job;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.easyagents.flow.core.chain.Parameter;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -11,6 +12,9 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import tech.easyflow.ai.service.WorkflowUsageAuthorizationService;
|
||||
import tech.easyflow.admin.model.SysJobWorkflowOptionView;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.constant.enums.EnumJobType;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
@@ -33,6 +37,7 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 系统任务表 控制层。
|
||||
@@ -47,6 +52,9 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
/** 工作流服务。 */
|
||||
private final WorkflowService workflowService;
|
||||
|
||||
/** 工作流使用权限校验服务。 */
|
||||
private final WorkflowUsageAuthorizationService workflowUsageAuthorizationService;
|
||||
|
||||
/** 资源访问控制服务。 */
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
|
||||
@@ -58,15 +66,18 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
*
|
||||
* @param service 定时任务服务
|
||||
* @param workflowService 工作流服务
|
||||
* @param workflowUsageAuthorizationService 工作流使用权限校验服务
|
||||
* @param resourceAccessService 资源访问控制服务
|
||||
* @param workflowRunningParameterResolver 工作流运行参数解析器
|
||||
*/
|
||||
public SysJobController(SysJobService service,
|
||||
WorkflowService workflowService,
|
||||
WorkflowUsageAuthorizationService workflowUsageAuthorizationService,
|
||||
ResourceAccessService resourceAccessService,
|
||||
WorkflowRunningParameterResolver workflowRunningParameterResolver) {
|
||||
super(service);
|
||||
this.workflowService = workflowService;
|
||||
this.workflowUsageAuthorizationService = workflowUsageAuthorizationService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
this.workflowRunningParameterResolver = workflowRunningParameterResolver;
|
||||
}
|
||||
@@ -75,6 +86,9 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
@SaCheckPermission("/api/v1/sysJob/save")
|
||||
@LogRecord("启动定时任务")
|
||||
public Result<Void> start(BigInteger id) {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
SysJob job = requireExistingJob(id);
|
||||
validateWorkflowReference(job, account);
|
||||
service.startJob(id);
|
||||
return Result.ok();
|
||||
}
|
||||
@@ -88,6 +102,7 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
}
|
||||
|
||||
@GetMapping("/getNextTimes")
|
||||
@SaCheckPermission("/api/v1/sysJob/save")
|
||||
public Result<List<String>> getNextTimes(String cronExpression) throws Exception{
|
||||
CronExpression ex = new CronExpression(cronExpression);
|
||||
List<String> times = new ArrayList<>();
|
||||
@@ -100,16 +115,72 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
return Result.ok(times);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前账号可用于定时任务的工作流。
|
||||
*
|
||||
* @return 工作流安全选项
|
||||
*/
|
||||
@GetMapping("/workflowOptions")
|
||||
@SaCheckPermission("/api/v1/sysJob/save")
|
||||
public Result<List<SysJobWorkflowOptionView>> workflowOptions() {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
List<SysJobWorkflowOptionView> options = workflowService.list(QueryWrapper.create()
|
||||
.eq(Workflow::getTenantId, account.getTenantId())
|
||||
.eq(Workflow::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||
.orderBy(Workflow::getModified, false))
|
||||
.stream()
|
||||
.filter(workflow -> Objects.equals(workflow.getTenantId(), account.getTenantId()))
|
||||
.filter(workflow -> resourceAccessService.canAccess(
|
||||
account,
|
||||
CategoryResourceType.WORKFLOW,
|
||||
workflow,
|
||||
ResourceAction.USE))
|
||||
.map(workflow -> new SysJobWorkflowOptionView(
|
||||
workflow.getId(),
|
||||
workflow.getTitle(),
|
||||
workflow.getDescription()))
|
||||
.toList();
|
||||
return Result.ok(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询定时任务所选工作流的运行参数。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @return 工作流运行参数
|
||||
* @throws BusinessException 工作流不存在或无运行权限时抛出
|
||||
*/
|
||||
@GetMapping("/workflowRunningParameters")
|
||||
@SaCheckPermission("/api/v1/sysJob/save")
|
||||
public Result<Map<String, Object>> workflowRunningParameters(BigInteger id) {
|
||||
Workflow workflow = workflowUsageAuthorizationService.requireUsableWorkflow(
|
||||
id,
|
||||
SaTokenUtil.getLoginAccount(),
|
||||
"工作流不存在、已禁用或无权运行");
|
||||
Map<String, Object> result = workflowRunningParameterResolver.buildRunningParametersView(workflow);
|
||||
if (result == null) {
|
||||
throw new BusinessException("工作流参数配置无效,请检查工作流后重试");
|
||||
}
|
||||
return Result.ok(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result onSaveOrUpdateBefore(SysJob entity, boolean isSave) {
|
||||
if (entity == null) {
|
||||
throw new BusinessException("定时任务不能为空");
|
||||
}
|
||||
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
|
||||
SysJob effectiveEntity = entity;
|
||||
if (isSave) {
|
||||
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
|
||||
} else {
|
||||
SysJob existing = requireExistingJob(entity.getId());
|
||||
preserveServerControlledFields(entity, existing);
|
||||
effectiveEntity = mergeForValidation(entity, existing);
|
||||
entity.setModified(new Date());
|
||||
entity.setModifiedBy(loginUser.getId());
|
||||
}
|
||||
validateWorkflowReference(entity);
|
||||
validateWorkflowReference(effectiveEntity, loginUser);
|
||||
return super.onSaveOrUpdateBefore(entity, isSave);
|
||||
}
|
||||
|
||||
@@ -117,27 +188,71 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
||||
* 校验工作流类型任务引用的工作流可被当前用户运行。
|
||||
*
|
||||
* @param entity 待保存的定时任务
|
||||
* @param account 当前账号
|
||||
* @throws BusinessException 工作流不存在、参数非法或无运行权限时抛出
|
||||
*/
|
||||
private void validateWorkflowReference(SysJob entity) {
|
||||
private void validateWorkflowReference(SysJob entity, LoginAccount account) {
|
||||
if (entity == null
|
||||
|| !Integer.valueOf(EnumJobType.TINY_FLOW.getCode()).equals(entity.getJobType())) {
|
||||
return;
|
||||
}
|
||||
BigInteger workflowId = SysJobWorkflowReferenceSupport.requireWorkflowId(entity);
|
||||
Workflow workflow = workflowService.getById(workflowId);
|
||||
if (workflow == null) {
|
||||
throw new BusinessException("工作流不存在,请重新选择");
|
||||
}
|
||||
resourceAccessService.assertAccess(
|
||||
CategoryResourceType.WORKFLOW,
|
||||
workflow,
|
||||
ResourceAction.USE,
|
||||
"无权限运行所选工作流"
|
||||
);
|
||||
Workflow workflow = workflowUsageAuthorizationService.requireUsableWorkflow(
|
||||
workflowId,
|
||||
account,
|
||||
"工作流不存在、已禁用或无权运行");
|
||||
validateRequiredWorkflowParams(entity, workflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户内存在的定时任务。
|
||||
*
|
||||
* @param id 定时任务 ID
|
||||
* @return 已存在的定时任务
|
||||
* @throws BusinessException ID 缺失或任务不存在时抛出
|
||||
*/
|
||||
private SysJob requireExistingJob(BigInteger id) {
|
||||
if (id == null) {
|
||||
throw new BusinessException("定时任务ID不能为空");
|
||||
}
|
||||
SysJob existing = service.getById(id);
|
||||
if (existing == null) {
|
||||
throw new BusinessException("定时任务不存在");
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保留更新请求不能修改的服务端控制字段。
|
||||
*
|
||||
* @param entity 更新请求
|
||||
* @param existing 数据库中的定时任务
|
||||
*/
|
||||
private void preserveServerControlledFields(SysJob entity, SysJob existing) {
|
||||
entity.setTenantId(existing.getTenantId());
|
||||
entity.setDeptId(existing.getDeptId());
|
||||
entity.setCreated(existing.getCreated());
|
||||
entity.setCreatedBy(existing.getCreatedBy());
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并部分更新请求与原记录,生成用于权限和参数校验的有效任务状态。
|
||||
*
|
||||
* @param entity 更新请求
|
||||
* @param existing 数据库中的定时任务
|
||||
* @return 合并后的校验对象
|
||||
*/
|
||||
private SysJob mergeForValidation(SysJob entity, SysJob existing) {
|
||||
SysJob effective = new SysJob();
|
||||
effective.setJobType(entity.getJobType() == null
|
||||
? existing.getJobType()
|
||||
: entity.getJobType());
|
||||
effective.setJobParams(entity.getJobParams() == null
|
||||
? existing.getJobParams()
|
||||
: entity.getJobParams());
|
||||
return effective;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验定时任务已填写工作流的全部必填运行参数。
|
||||
*
|
||||
|
||||
@@ -10,6 +10,8 @@ import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.admin.model.SystemFormOptionsView;
|
||||
import tech.easyflow.admin.service.system.SystemFormOptionService;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowPageVo;
|
||||
@@ -36,6 +38,20 @@ public class ApprovalFlowController {
|
||||
|
||||
@Resource
|
||||
private ApprovalAssigneeService approvalAssigneeService;
|
||||
@Resource
|
||||
private SystemFormOptionService systemFormOptionService;
|
||||
|
||||
/**
|
||||
* 查询审批流程配置所需的资源范围选项。
|
||||
*
|
||||
* @return 非 Bot 分类和部门树
|
||||
*/
|
||||
@GetMapping("/resourceScopeOptions")
|
||||
@SaCheckPermission("/api/v1/approvalFlow/save")
|
||||
public Result<SystemFormOptionsView.ApprovalResourceScopeOptions> resourceScopeOptions() {
|
||||
assertSuperAdmin();
|
||||
return Result.ok(systemFormOptionService.approvalResourceScopeOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询审批流程。
|
||||
@@ -102,6 +118,9 @@ public class ApprovalFlowController {
|
||||
@SaCheckPermission("/api/v1/approvalFlow/save")
|
||||
public Result<BigInteger> save(@JsonBody ApprovalFlowDetailVo request) {
|
||||
assertSuperAdmin();
|
||||
systemFormOptionService.validateApprovalScopes(
|
||||
request == null ? null : request.getResourceType(),
|
||||
request == null ? null : request.getScopes());
|
||||
BigInteger operatorId = SaTokenUtil.getLoginAccount().getId();
|
||||
return Result.ok(approvalFlowService.saveFlow(request, operatorId));
|
||||
}
|
||||
@@ -116,6 +135,9 @@ public class ApprovalFlowController {
|
||||
@SaCheckPermission("/api/v1/approvalFlow/save")
|
||||
public Result<Void> update(@JsonBody ApprovalFlowDetailVo request) {
|
||||
assertSuperAdmin();
|
||||
systemFormOptionService.validateApprovalScopes(
|
||||
request == null ? null : request.getResourceType(),
|
||||
request == null ? null : request.getScopes());
|
||||
BigInteger operatorId = SaTokenUtil.getLoginAccount().getId();
|
||||
approvalFlowService.updateFlow(request, operatorId);
|
||||
return Result.ok();
|
||||
|
||||
@@ -31,6 +31,8 @@ import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.log.annotation.LogRecord;
|
||||
import tech.easyflow.admin.controller.system.vo.SysAccountProfileVo;
|
||||
import tech.easyflow.admin.model.SystemFormOptionsView;
|
||||
import tech.easyflow.admin.service.system.SystemFormOptionService;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.entity.vo.SysAccountBatchActionResultVo;
|
||||
@@ -43,8 +45,8 @@ import javax.annotation.Resource;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -71,6 +73,7 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
|
||||
|
||||
private final AuthCredentialKeyService credentialKeyService;
|
||||
private final SysRoleService sysRoleService;
|
||||
private final SystemFormOptionService systemFormOptionService;
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@@ -80,13 +83,16 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
|
||||
* @param service 用户服务
|
||||
* @param credentialKeyService 凭证密钥服务
|
||||
* @param sysRoleService 角色服务
|
||||
* @param systemFormOptionService 用户表单安全选项服务
|
||||
*/
|
||||
public SysAccountController(SysAccountService service,
|
||||
AuthCredentialKeyService credentialKeyService,
|
||||
SysRoleService sysRoleService) {
|
||||
SysRoleService sysRoleService,
|
||||
SystemFormOptionService systemFormOptionService) {
|
||||
super(service);
|
||||
this.credentialKeyService = credentialKeyService;
|
||||
this.sysRoleService = sysRoleService;
|
||||
this.systemFormOptionService = systemFormOptionService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,6 +194,7 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
|
||||
|
||||
@Override
|
||||
protected Result onSaveOrUpdateBefore(SysAccount entity, boolean isSave) {
|
||||
systemFormOptionService.validateAccountReferences(entity);
|
||||
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
|
||||
if (isSave) {
|
||||
// 查询用户名是否存在
|
||||
@@ -233,6 +240,17 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询账号表单所需的部门、角色和岗位选项。
|
||||
*
|
||||
* @return 账号表单安全选项
|
||||
*/
|
||||
@GetMapping("/formOptions")
|
||||
@SaCheckPermission("/api/v1/sysAccount/save")
|
||||
public Result<SystemFormOptionsView.AccountFormOptions> formOptions() {
|
||||
return Result.ok(systemFormOptionService.accountFormOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充账号创建的公共字段。
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@ import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.admin.service.system.SystemFormOptionService;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeDetailVo;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.SysRoleCategoryScopeService;
|
||||
@@ -25,6 +26,8 @@ public class SysRoleCategoryScopeController {
|
||||
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
@Resource
|
||||
private SystemFormOptionService systemFormOptionService;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@SaCheckPermission("/api/v1/sysRole/query")
|
||||
@@ -41,6 +44,7 @@ public class SysRoleCategoryScopeController {
|
||||
if (request == null || request.getRoleId() == null) {
|
||||
throw new BusinessException("角色ID不能为空");
|
||||
}
|
||||
systemFormOptionService.validateCategoryScopes(request.getScopes());
|
||||
BigInteger operatorId = SaTokenUtil.getLoginAccount().getId();
|
||||
sysRoleCategoryScopeService.saveRoleScopes(request.getRoleId(), request.getScopes(), operatorId);
|
||||
return Result.ok();
|
||||
|
||||
@@ -9,6 +9,8 @@ import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.admin.model.SystemFormOptionsView;
|
||||
import tech.easyflow.admin.service.system.SystemFormOptionService;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.entity.SysRoleDept;
|
||||
import tech.easyflow.system.entity.SysRoleMenu;
|
||||
@@ -38,11 +40,24 @@ public class SysRoleController extends BaseCurdController<SysRoleService, SysRol
|
||||
private SysRoleMenuService sysRoleMenuService;
|
||||
@Resource
|
||||
private SysRoleDeptService sysRoleDeptService;
|
||||
@Resource
|
||||
private SystemFormOptionService systemFormOptionService;
|
||||
|
||||
public SysRoleController(SysRoleService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询角色表单所需的菜单和非 Bot 分类选项。
|
||||
*
|
||||
* @return 角色表单安全选项
|
||||
*/
|
||||
@GetMapping("formOptions")
|
||||
@SaCheckPermission("/api/v1/sysRole/query")
|
||||
public Result<SystemFormOptionsView.RoleFormOptions> formOptions() {
|
||||
return Result.ok(systemFormOptionService.roleFormOptions());
|
||||
}
|
||||
|
||||
@PostMapping("saveRoleMenu/{roleId}")
|
||||
@SaCheckPermission("/api/v1/sysRole/save")
|
||||
@Deprecated
|
||||
@@ -85,6 +100,7 @@ public class SysRoleController extends BaseCurdController<SysRoleService, SysRol
|
||||
if (entity.getId() == null) {
|
||||
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
|
||||
}
|
||||
systemFormOptionService.validateRoleReferences(entity);
|
||||
service.saveRole(entity);
|
||||
return Result.ok(entity.getId());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package tech.easyflow.admin.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 定时任务可运行的工作流安全选项。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @param title 工作流标题
|
||||
* @param description 工作流描述
|
||||
*/
|
||||
public record SysJobWorkflowOptionView(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String title,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package tech.easyflow.admin.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 管理端系统表单所需的安全选项视图。
|
||||
*/
|
||||
public final class SystemFormOptionsView {
|
||||
|
||||
private SystemFormOptionsView() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批流程资源范围选项。
|
||||
*
|
||||
* @param categories 按资源类型分组的分类选项
|
||||
* @param departments 部门树
|
||||
*/
|
||||
public record ApprovalResourceScopeOptions(
|
||||
Map<String, List<CategoryOption>> categories,
|
||||
List<DepartmentOption> departments
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色表单选项。
|
||||
*
|
||||
* @param menus 菜单树
|
||||
* @param categories 按资源类型分组的非 Bot 分类选项
|
||||
*/
|
||||
public record RoleFormOptions(
|
||||
List<MenuOption> menus,
|
||||
Map<String, List<CategoryOption>> categories
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号表单选项。
|
||||
*
|
||||
* @param departments 部门树
|
||||
* @param roles 可用角色
|
||||
* @param positions 可用岗位
|
||||
*/
|
||||
public record AccountFormOptions(
|
||||
List<DepartmentOption> departments,
|
||||
List<RoleOption> roles,
|
||||
List<PositionOption> positions
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类安全选项。
|
||||
*
|
||||
* @param id 分类 ID
|
||||
* @param categoryName 分类名称
|
||||
*/
|
||||
public record CategoryOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String categoryName
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门树安全选项。
|
||||
*
|
||||
* @param id 部门 ID
|
||||
* @param parentId 上级部门 ID
|
||||
* @param deptName 部门名称
|
||||
* @param children 下级部门
|
||||
*/
|
||||
public record DepartmentOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger parentId,
|
||||
String deptName,
|
||||
List<DepartmentOption> children
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单树安全选项。
|
||||
*
|
||||
* @param id 菜单 ID
|
||||
* @param parentId 上级菜单 ID
|
||||
* @param menuTitle 菜单标题
|
||||
* @param children 下级菜单
|
||||
*/
|
||||
public record MenuOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger parentId,
|
||||
String menuTitle,
|
||||
List<MenuOption> children
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色安全选项。
|
||||
*
|
||||
* @param id 角色 ID
|
||||
* @param roleName 角色名称
|
||||
*/
|
||||
public record RoleOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String roleName
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位安全选项。
|
||||
*
|
||||
* @param id 岗位 ID
|
||||
* @param positionName 岗位名称
|
||||
*/
|
||||
public record PositionOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String positionName
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package tech.easyflow.admin.model.ai;
|
||||
|
||||
/**
|
||||
* 模型统一网关页面所需的安全配置。
|
||||
*
|
||||
* @param publishBaseUrl 模型发布基础地址
|
||||
*/
|
||||
public record ModelGatewayConfigView(String publishBaseUrl) {
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package tech.easyflow.admin.model.ai;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流设计器所需的安全选项视图。
|
||||
*
|
||||
* @param models 模型选项
|
||||
* @param knowledges 知识库选项
|
||||
* @param codeEngines 代码执行引擎选项
|
||||
*/
|
||||
public record WorkflowDesignerOptionsView(
|
||||
List<ModelOption> models,
|
||||
List<KnowledgeOption> knowledges,
|
||||
List<Map<String, Object>> codeEngines
|
||||
) {
|
||||
|
||||
/**
|
||||
* 模型安全选项。
|
||||
*
|
||||
* @param id 模型 ID
|
||||
* @param title 模型标题
|
||||
* @param description 模型描述
|
||||
* @param modelProvider 供应商安全摘要
|
||||
*/
|
||||
public record ModelOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String title,
|
||||
String description,
|
||||
ProviderOption modelProvider
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型供应商安全摘要。
|
||||
*
|
||||
* @param providerName 供应商名称
|
||||
* @param providerType 供应商类型
|
||||
* @param icon 供应商图标
|
||||
*/
|
||||
public record ProviderOption(String providerName, String providerType, String icon) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 知识库安全选项。
|
||||
*
|
||||
* @param id 知识库 ID
|
||||
* @param title 知识库标题
|
||||
* @param description 知识库描述
|
||||
*/
|
||||
public record KnowledgeOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String title,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件安全选项。
|
||||
*
|
||||
* @param id 插件 ID
|
||||
* @param name 插件名称
|
||||
* @param description 插件描述
|
||||
* @param icon 插件图标
|
||||
* @param tools 可用工具
|
||||
*/
|
||||
public record PluginOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String name,
|
||||
String description,
|
||||
String icon,
|
||||
List<PluginToolOption> tools
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件工具安全选项。
|
||||
*
|
||||
* @param id 工具 ID
|
||||
* @param name 工具名称
|
||||
* @param description 工具描述
|
||||
*/
|
||||
public record PluginToolOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String name,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源安全选项。
|
||||
*
|
||||
* @param id 数据源 ID
|
||||
* @param sourceName 数据源名称
|
||||
* @param sourceType 数据源类型
|
||||
*/
|
||||
public record DataSourceOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
String sourceName,
|
||||
String sourceType
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据目录安全选项。
|
||||
*
|
||||
* @param id 目录 ID
|
||||
* @param sourceId 数据源 ID
|
||||
* @param catalogName 目录名称
|
||||
* @param catalogDesc 目录描述
|
||||
*/
|
||||
public record CatalogOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger sourceId,
|
||||
String catalogName,
|
||||
String catalogDesc
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 已接入数据集安全选项。
|
||||
*
|
||||
* @param id 数据集 ID
|
||||
* @param sourceId 数据源 ID
|
||||
* @param catalogId 目录 ID
|
||||
* @param tableName 数据表名称
|
||||
* @param tableDesc 数据表描述
|
||||
*/
|
||||
public record DatasetOption(
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger id,
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger sourceId,
|
||||
@JsonSerialize(using = ToStringSerializer.class) BigInteger catalogId,
|
||||
String tableName,
|
||||
String tableDesc
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据集字段安全视图。
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param fieldDesc 字段描述
|
||||
* @param jdbcType JDBC 类型
|
||||
* @param fieldType 业务字段类型
|
||||
*/
|
||||
public record DatasetFieldOption(
|
||||
String fieldName,
|
||||
String fieldDesc,
|
||||
String jdbcType,
|
||||
Integer fieldType
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据集结构安全视图。
|
||||
*
|
||||
* @param tableName 数据表名称
|
||||
* @param tableDesc 数据表描述
|
||||
* @param fields 字段列表
|
||||
*/
|
||||
public record DatasetSchemaOption(
|
||||
String tableName,
|
||||
String tableDesc,
|
||||
List<DatasetFieldOption> fields
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
package tech.easyflow.admin.service.ai;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.easyagents.flow.core.chain.ChainDefinition;
|
||||
import com.easyagents.flow.core.chain.Node;
|
||||
import com.easyagents.flow.core.node.ConfirmNode;
|
||||
import com.easyagents.flow.core.node.EndNode;
|
||||
import com.easyagents.flow.core.node.StartNode;
|
||||
import com.easyagents.flow.core.parser.ChainParser;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.admin.model.ai.WorkflowDesignerOptionsView;
|
||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.entity.ModelProvider;
|
||||
import tech.easyflow.ai.entity.Plugin;
|
||||
import tech.easyflow.ai.entity.PluginItem;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.enums.PluginType;
|
||||
import tech.easyflow.ai.plugin.workflow.snapshot.WorkflowPluginSnapshotResolver;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.ai.service.ModelService;
|
||||
import tech.easyflow.ai.service.PluginItemService;
|
||||
import tech.easyflow.ai.service.PluginService;
|
||||
import tech.easyflow.ai.service.PluginVisibilityService;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import tech.easyflow.ai.service.WorkflowUsageAuthorizationService;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.execution.model.DatacenterSchemaResponse;
|
||||
import tech.easyflow.datacenter.execution.model.DatasetRef;
|
||||
import tech.easyflow.datacenter.execution.service.DatacenterDatasetQueryService;
|
||||
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
|
||||
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
|
||||
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
|
||||
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
|
||||
import tech.easyflow.datacenter.meta.service.DatacenterSourceService;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 查询工作流设计器所需的场景化安全选项。
|
||||
*/
|
||||
@Service
|
||||
public class WorkflowDesignerOptionService {
|
||||
|
||||
private final ModelService modelService;
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final PluginService pluginService;
|
||||
private final PluginItemService pluginItemService;
|
||||
private final PluginVisibilityService pluginVisibilityService;
|
||||
private final WorkflowService workflowService;
|
||||
private final WorkflowUsageAuthorizationService workflowUsageAuthorizationService;
|
||||
private final WorkflowPluginSnapshotResolver workflowPluginSnapshotResolver;
|
||||
private final ChainParser chainParser;
|
||||
private final WorkflowDatacenterContentService workflowDatacenterContentService;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
private final DatacenterSourceService datacenterSourceService;
|
||||
private final DatacenterDatasetRegistryService datacenterDatasetRegistryService;
|
||||
private final DatacenterDatasetQueryService datacenterDatasetQueryService;
|
||||
|
||||
/**
|
||||
* 创建工作流设计器选项服务。
|
||||
*
|
||||
* @param modelService 模型服务
|
||||
* @param documentCollectionService 知识库服务
|
||||
* @param pluginService 插件服务
|
||||
* @param pluginItemService 插件工具服务
|
||||
* @param pluginVisibilityService 插件可见性服务
|
||||
* @param workflowService 工作流服务
|
||||
* @param workflowUsageAuthorizationService 工作流使用权限校验服务
|
||||
* @param workflowPluginSnapshotResolver 工作流插件快照解析器
|
||||
* @param chainParser 工作流解析器
|
||||
* @param workflowDatacenterContentService 工作流数据中心内容服务
|
||||
* @param resourceAccessService 资源访问服务
|
||||
* @param datacenterSourceService 数据源服务
|
||||
* @param datacenterDatasetRegistryService 数据集注册服务
|
||||
* @param datacenterDatasetQueryService 数据集查询服务
|
||||
*/
|
||||
public WorkflowDesignerOptionService(
|
||||
ModelService modelService,
|
||||
DocumentCollectionService documentCollectionService,
|
||||
PluginService pluginService,
|
||||
PluginItemService pluginItemService,
|
||||
PluginVisibilityService pluginVisibilityService,
|
||||
WorkflowService workflowService,
|
||||
WorkflowUsageAuthorizationService workflowUsageAuthorizationService,
|
||||
WorkflowPluginSnapshotResolver workflowPluginSnapshotResolver,
|
||||
ChainParser chainParser,
|
||||
WorkflowDatacenterContentService workflowDatacenterContentService,
|
||||
ResourceAccessService resourceAccessService,
|
||||
DatacenterSourceService datacenterSourceService,
|
||||
DatacenterDatasetRegistryService datacenterDatasetRegistryService,
|
||||
DatacenterDatasetQueryService datacenterDatasetQueryService) {
|
||||
this.modelService = modelService;
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.pluginService = pluginService;
|
||||
this.pluginItemService = pluginItemService;
|
||||
this.pluginVisibilityService = pluginVisibilityService;
|
||||
this.workflowService = workflowService;
|
||||
this.workflowUsageAuthorizationService = workflowUsageAuthorizationService;
|
||||
this.workflowPluginSnapshotResolver = workflowPluginSnapshotResolver;
|
||||
this.chainParser = chainParser;
|
||||
this.workflowDatacenterContentService = workflowDatacenterContentService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
this.datacenterSourceService = datacenterSourceService;
|
||||
this.datacenterDatasetRegistryService = datacenterDatasetRegistryService;
|
||||
this.datacenterDatasetQueryService = datacenterDatasetQueryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设计器初始化所需的模型和知识库。
|
||||
*
|
||||
* @param codeEngines 代码执行引擎选项
|
||||
* @return 设计器初始化选项
|
||||
*/
|
||||
public WorkflowDesignerOptionsView listOptions(List<Map<String, Object>> codeEngines) {
|
||||
LoginAccount account = requireAccount();
|
||||
return new WorkflowDesignerOptionsView(
|
||||
listModelOptions(account),
|
||||
listKnowledgeOptions(account),
|
||||
codeEngines == null ? List.of() : codeEngines
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验工作流内容引用的场景资源是否仍允许当前账号使用。
|
||||
*
|
||||
* @param content 工作流内容
|
||||
* @throws BusinessException 内容引用越权、跨租户或资源失效时抛出
|
||||
*/
|
||||
public void assertContentReferences(String content) {
|
||||
if (content == null || content.isBlank()) {
|
||||
return;
|
||||
}
|
||||
JSONObject root;
|
||||
try {
|
||||
root = JSON.parseObject(content);
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException("工作流内容不是合法JSON");
|
||||
}
|
||||
JSONArray nodes = root.getJSONArray("nodes");
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
LoginAccount account = requireAccount();
|
||||
Set<BigInteger> modelIds = new HashSet<>();
|
||||
Set<BigInteger> knowledgeIds = new HashSet<>();
|
||||
Set<BigInteger> checkedPluginItemIds = new HashSet<>();
|
||||
Set<BigInteger> checkedWorkflowIds = new HashSet<>();
|
||||
Set<BigInteger> checkedSourceIds = new HashSet<>();
|
||||
Set<String> checkedTableIds = new HashSet<>();
|
||||
|
||||
for (int index = 0; index < nodes.size(); index++) {
|
||||
JSONObject node = nodes.getJSONObject(index);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
JSONObject data = node.getJSONObject("data");
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
String nodeType = data.getString("type");
|
||||
if (nodeType == null || nodeType.isBlank()) {
|
||||
nodeType = node.getString("type");
|
||||
}
|
||||
if ("llmNode".equals(nodeType)) {
|
||||
addReferenceId(modelIds, readReferenceId(data, "llmId", "模型"));
|
||||
} else if ("knowledgeNode".equals(nodeType)) {
|
||||
addReferenceId(knowledgeIds, readReferenceId(data, "knowledgeId", "知识库"));
|
||||
} else if ("plugin-node".equals(nodeType)) {
|
||||
BigInteger pluginItemId = readReferenceId(data, "pluginId", "插件工具");
|
||||
if (pluginItemId != null && checkedPluginItemIds.add(pluginItemId)) {
|
||||
getPluginTinyFlowData(pluginItemId);
|
||||
}
|
||||
} else if ("workflow-node".equals(nodeType)) {
|
||||
assertWorkflowReference(readReferenceId(data, "workflowId", "子流程"),
|
||||
account, checkedWorkflowIds);
|
||||
}
|
||||
assertDatasetReference(data, account, checkedSourceIds, checkedTableIds);
|
||||
}
|
||||
assertModelReferences(modelIds, account);
|
||||
assertKnowledgeReferences(knowledgeIds, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询当前账号可用于工作流的插件。
|
||||
*
|
||||
* @param pageNumber 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 插件安全选项分页
|
||||
*/
|
||||
public Page<WorkflowDesignerOptionsView.PluginOption> pagePlugins(Long pageNumber, Long pageSize) {
|
||||
LoginAccount account = requireAccount();
|
||||
QueryWrapper wrapper = QueryWrapper.create()
|
||||
.eq(Plugin::getTenantId, account.getTenantId().longValue())
|
||||
.orderBy(Plugin::getCreated, false);
|
||||
List<Plugin> plugins = pluginService.getMapper().selectListWithRelationsByQuery(wrapper);
|
||||
List<Plugin> availablePlugins = pluginService.preparePluginsForCurrentUser(plugins, false, true);
|
||||
List<WorkflowDesignerOptionsView.PluginOption> options = availablePlugins.stream()
|
||||
.map(this::toPluginOption)
|
||||
.toList();
|
||||
long actualPageNumber = pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
|
||||
long actualPageSize = pageSize == null || pageSize < 1 ? 10L : Math.min(pageSize, 100L);
|
||||
int fromIndex = Math.toIntExact(Math.min(options.size(), (actualPageNumber - 1) * actualPageSize));
|
||||
int toIndex = Math.toIntExact(Math.min(options.size(), fromIndex + actualPageSize));
|
||||
return new Page<>(
|
||||
options.subList(fromIndex, toIndex),
|
||||
actualPageNumber,
|
||||
actualPageSize,
|
||||
options.size()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一个插件工具的工作流节点安全配置。
|
||||
*
|
||||
* @param pluginItemId 插件工具 ID
|
||||
* @return 节点配置
|
||||
* @throws BusinessException 插件工具不存在或不可用时抛出
|
||||
*/
|
||||
public JSONObject getPluginTinyFlowData(BigInteger pluginItemId) {
|
||||
if (pluginItemId == null) {
|
||||
throw new BusinessException("插件工具不能为空");
|
||||
}
|
||||
PluginItem record = pluginItemService.getById(pluginItemId);
|
||||
if (record == null || !Integer.valueOf(1).equals(record.getStatus())) {
|
||||
throw new BusinessException("插件工具不存在或已禁用");
|
||||
}
|
||||
Plugin plugin = pluginService.getById(record.getPluginId());
|
||||
if (plugin == null) {
|
||||
throw new BusinessException("插件不存在");
|
||||
}
|
||||
LoginAccount account = requireAccount();
|
||||
if (!Objects.equals(plugin.getTenantId(), account.getTenantId().longValue())) {
|
||||
throw new BusinessException("无权限访问插件");
|
||||
}
|
||||
pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限访问插件");
|
||||
Plugin preparedPlugin = pluginService.preparePluginForCurrentUser(plugin);
|
||||
if (Boolean.FALSE.equals(preparedPlugin.getAvailable())) {
|
||||
throw new BusinessException(preparedPlugin.getReasonMessage());
|
||||
}
|
||||
|
||||
JSONObject nodeData = new JSONObject();
|
||||
nodeData.put("pluginId", record.getId().toString());
|
||||
nodeData.put("pluginName", record.getName());
|
||||
nodeData.put("pluginType", preparedPlugin.getType());
|
||||
nodeData.put("workflowId", preparedPlugin.getWorkflowId());
|
||||
nodeData.put("workflowTitle", preparedPlugin.getWorkflowTitle());
|
||||
nodeData.put("available", preparedPlugin.getAvailable());
|
||||
nodeData.put("reasonCode", preparedPlugin.getReasonCode());
|
||||
nodeData.put("reasonMessage", preparedPlugin.getReasonMessage());
|
||||
nodeData.put("parameters", parseSchemaArray(record.getInputData()));
|
||||
nodeData.put("outputDefs", parseSchemaArray(record.getOutputData()));
|
||||
nodeData.put("schemaHash", resolveSchemaHash(record, preparedPlugin));
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询子流程节点所需的输入输出定义。
|
||||
*
|
||||
* @param currentWorkflowId 当前工作流 ID
|
||||
* @param childWorkflowId 子流程 ID
|
||||
* @return 子流程节点配置
|
||||
* @throws BusinessException 子流程不存在、不可用或无权使用时抛出
|
||||
*/
|
||||
public JSONObject getChildWorkflowNodeData(
|
||||
BigInteger currentWorkflowId,
|
||||
BigInteger childWorkflowId) {
|
||||
if (childWorkflowId == null) {
|
||||
throw new BusinessException("子流程不能为空");
|
||||
}
|
||||
if (Objects.equals(childWorkflowId, currentWorkflowId)) {
|
||||
throw new BusinessException("工作流不能作为自身子节点");
|
||||
}
|
||||
LoginAccount account = requireAccount();
|
||||
Workflow workflow = workflowUsageAuthorizationService.requireUsableWorkflow(
|
||||
childWorkflowId,
|
||||
account,
|
||||
"子流程不存在、已禁用或无权使用");
|
||||
assertContentReferences(workflow.getContent());
|
||||
|
||||
ChainDefinition definition = chainParser.parse(
|
||||
workflowDatacenterContentService.prepareContent(workflow.getContent()));
|
||||
JSONArray inputs = new JSONArray();
|
||||
JSONArray outputs = new JSONArray();
|
||||
for (Node node : definition.getNodes()) {
|
||||
if (node instanceof StartNode) {
|
||||
inputs = JSON.parseArray(JSON.toJSONString(node.getParameters()));
|
||||
decorateChildWorkflowSchemaArray(inputs);
|
||||
}
|
||||
if (node instanceof EndNode endNode) {
|
||||
outputs = JSON.parseArray(JSON.toJSONString(endNode.getOutputDefs()));
|
||||
decorateChildWorkflowSchemaArray(outputs);
|
||||
}
|
||||
if (node instanceof ConfirmNode) {
|
||||
throw new BusinessException("工作流存在【确认节点】,暂不支持作为子节点");
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject nodeData = new JSONObject();
|
||||
nodeData.put("workflowId", workflow.getId());
|
||||
nodeData.put("workflowName", workflow.getTitle());
|
||||
nodeData.put("parameters", inputs);
|
||||
nodeData.put("outputDefs", outputs);
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前租户可用于工作流的数据源安全选项。
|
||||
*
|
||||
* @return 数据源安全选项
|
||||
*/
|
||||
public List<WorkflowDesignerOptionsView.DataSourceOption> listDataSources() {
|
||||
LoginAccount account = requireAccount();
|
||||
datacenterDatasetRegistryService.ensureBuiltinSource(DatacenterSourceType.PROJECT_MYSQL, account);
|
||||
return datacenterSourceService.list(QueryWrapper.create()
|
||||
.eq(DatacenterSource::getTenantId, account.getTenantId())
|
||||
.orderBy(DatacenterSource::getModified, false))
|
||||
.stream()
|
||||
.map(source -> new WorkflowDesignerOptionsView.DataSourceOption(
|
||||
source.getId(), source.getSourceName(), source.getSourceType()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一个数据源的目录安全选项。
|
||||
*
|
||||
* @param sourceId 数据源 ID
|
||||
* @return 目录安全选项
|
||||
*/
|
||||
public List<WorkflowDesignerOptionsView.CatalogOption> listCatalogs(BigInteger sourceId) {
|
||||
LoginAccount account = requireAccount();
|
||||
requireTenantSource(sourceId, account);
|
||||
return datacenterSourceService.listCatalogs(sourceId, account).stream()
|
||||
.map(this::toCatalogOption)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一个数据源目录下已接入的数据集。
|
||||
*
|
||||
* @param sourceId 数据源 ID
|
||||
* @param catalogId 目录 ID
|
||||
* @return 数据集安全选项
|
||||
*/
|
||||
public List<WorkflowDesignerOptionsView.DatasetOption> listManagedDatasets(
|
||||
BigInteger sourceId,
|
||||
BigInteger catalogId) {
|
||||
LoginAccount account = requireAccount();
|
||||
requireTenantSource(sourceId, account);
|
||||
return datacenterDatasetRegistryService.listManagedTables(sourceId, catalogId).stream()
|
||||
.filter(table -> Objects.equals(table.getTenantId(), account.getTenantId()))
|
||||
.filter(table -> Objects.equals(table.getSourceId(), sourceId))
|
||||
.filter(table -> catalogId == null || Objects.equals(table.getCatalogId(), catalogId))
|
||||
.map(this::toDatasetOption)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询工作流数据节点所需的数据集结构。
|
||||
*
|
||||
* @param datasetRef 数据集引用
|
||||
* @return 数据集结构安全视图
|
||||
*/
|
||||
public WorkflowDesignerOptionsView.DatasetSchemaOption getDatasetSchema(DatasetRef datasetRef) {
|
||||
if (datasetRef == null || datasetRef.getSourceId() == null || datasetRef.getTableId() == null) {
|
||||
throw new BusinessException("数据集引用不完整");
|
||||
}
|
||||
LoginAccount account = requireAccount();
|
||||
requireTenantSource(datasetRef.getSourceId(), account);
|
||||
DatacenterTable table = datacenterDatasetRegistryService.getTableWithFields(datasetRef.getTableId());
|
||||
if (table == null
|
||||
|| !Objects.equals(table.getTenantId(), account.getTenantId())
|
||||
|| !Objects.equals(table.getSourceId(), datasetRef.getSourceId())) {
|
||||
throw new BusinessException("数据集不存在或无权访问");
|
||||
}
|
||||
DatacenterSchemaResponse schema = datacenterDatasetQueryService.getSchema(datasetRef);
|
||||
List<DatacenterTableField> fields = schema == null || schema.getFields() == null
|
||||
? Collections.emptyList()
|
||||
: schema.getFields();
|
||||
return new WorkflowDesignerOptionsView.DatasetSchemaOption(
|
||||
table.getTableName(),
|
||||
table.getTableDesc(),
|
||||
fields.stream()
|
||||
.map(field -> new WorkflowDesignerOptionsView.DatasetFieldOption(
|
||||
field.getFieldName(),
|
||||
field.getFieldDesc(),
|
||||
field.getJdbcType(),
|
||||
field.getFieldType()
|
||||
))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
private List<WorkflowDesignerOptionsView.ModelOption> listModelOptions(LoginAccount account) {
|
||||
Model query = new Model();
|
||||
query.setTenantId(account.getTenantId());
|
||||
query.setModelType(Model.MODEL_TYPES[0]);
|
||||
return modelService.listSelectableModels(query, false, "id", "desc").stream()
|
||||
.filter(model -> Objects.equals(model.getTenantId(), account.getTenantId()))
|
||||
.map(model -> {
|
||||
ModelProvider provider = model.getModelProvider();
|
||||
WorkflowDesignerOptionsView.ProviderOption providerOption = provider == null
|
||||
? null
|
||||
: new WorkflowDesignerOptionsView.ProviderOption(
|
||||
provider.getProviderName(),
|
||||
provider.getProviderType(),
|
||||
provider.getIcon()
|
||||
);
|
||||
return new WorkflowDesignerOptionsView.ModelOption(
|
||||
model.getId(),
|
||||
model.getTitle(),
|
||||
model.getDescription(),
|
||||
providerOption
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<WorkflowDesignerOptionsView.KnowledgeOption> listKnowledgeOptions(LoginAccount account) {
|
||||
return documentCollectionService.list(QueryWrapper.create()
|
||||
.eq(DocumentCollection::getTenantId, account.getTenantId())
|
||||
.orderBy(DocumentCollection::getModified, false))
|
||||
.stream()
|
||||
.filter(item -> resourceAccessService.canAccess(
|
||||
account, CategoryResourceType.KNOWLEDGE, item, ResourceAction.USE))
|
||||
.map(item -> new WorkflowDesignerOptionsView.KnowledgeOption(
|
||||
item.getId(), item.getTitle(), item.getDescription()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void addReferenceId(Set<BigInteger> resourceIds, BigInteger resourceId) {
|
||||
if (resourceId != null) {
|
||||
resourceIds.add(resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertModelReferences(Set<BigInteger> modelIds, LoginAccount account) {
|
||||
if (modelIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Model> models = modelService.listByIds(modelIds);
|
||||
boolean valid = models.size() == modelIds.size()
|
||||
&& models.stream().allMatch(model ->
|
||||
modelIds.contains(model.getId())
|
||||
&& Objects.equals(model.getTenantId(), account.getTenantId())
|
||||
&& Model.MODEL_TYPES[0].equals(model.getModelType()));
|
||||
if (!valid) {
|
||||
throw new BusinessException("模型不存在、已失效或无权使用");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertKnowledgeReferences(Set<BigInteger> knowledgeIds, LoginAccount account) {
|
||||
if (knowledgeIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<DocumentCollection> knowledges = documentCollectionService.listByIds(knowledgeIds);
|
||||
boolean valid = knowledges.size() == knowledgeIds.size()
|
||||
&& knowledges.stream().allMatch(knowledge ->
|
||||
knowledgeIds.contains(knowledge.getId())
|
||||
&& Objects.equals(knowledge.getTenantId(), account.getTenantId())
|
||||
&& resourceAccessService.canAccess(
|
||||
account, CategoryResourceType.KNOWLEDGE, knowledge, ResourceAction.USE));
|
||||
if (!valid) {
|
||||
throw new BusinessException("知识库不存在、已失效或无权使用");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWorkflowReference(
|
||||
BigInteger workflowId,
|
||||
LoginAccount account,
|
||||
Set<BigInteger> checkedWorkflowIds) {
|
||||
if (workflowId == null || !checkedWorkflowIds.add(workflowId)) {
|
||||
return;
|
||||
}
|
||||
workflowUsageAuthorizationService.requireUsableWorkflow(
|
||||
workflowId,
|
||||
account,
|
||||
"子流程不存在、已禁用或无权使用");
|
||||
}
|
||||
|
||||
private void assertDatasetReference(
|
||||
JSONObject data,
|
||||
LoginAccount account,
|
||||
Set<BigInteger> checkedSourceIds,
|
||||
Set<String> checkedTableIds) {
|
||||
JSONObject datasetRef = data.getJSONObject("datasetRef");
|
||||
if (datasetRef == null) {
|
||||
return;
|
||||
}
|
||||
BigInteger sourceId = readReferenceId(datasetRef, "sourceId", "数据源");
|
||||
if (sourceId != null && checkedSourceIds.add(sourceId)) {
|
||||
requireTenantSource(sourceId, account);
|
||||
}
|
||||
BigInteger tableId = readReferenceId(datasetRef, "tableId", "数据集");
|
||||
if (tableId == null) {
|
||||
return;
|
||||
}
|
||||
if (sourceId == null) {
|
||||
throw new BusinessException("数据集缺少数据源引用");
|
||||
}
|
||||
String checkedKey = sourceId + ":" + tableId;
|
||||
if (!checkedTableIds.add(checkedKey)) {
|
||||
return;
|
||||
}
|
||||
DatacenterTable table = datacenterDatasetRegistryService.getTableWithFields(tableId);
|
||||
if (table == null
|
||||
|| !Objects.equals(table.getTenantId(), account.getTenantId())
|
||||
|| !Objects.equals(table.getSourceId(), sourceId)) {
|
||||
throw new BusinessException("数据集不存在或无权使用");
|
||||
}
|
||||
}
|
||||
|
||||
private BigInteger readReferenceId(JSONObject data, String key, String resourceName) {
|
||||
Object value = data.get(key);
|
||||
if (value == null || String.valueOf(value).isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigInteger(String.valueOf(value));
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new BusinessException(resourceName + "引用格式无效");
|
||||
}
|
||||
}
|
||||
|
||||
private WorkflowDesignerOptionsView.PluginOption toPluginOption(Plugin plugin) {
|
||||
List<WorkflowDesignerOptionsView.PluginToolOption> tools =
|
||||
plugin.getTools() == null ? List.of() : plugin.getTools().stream()
|
||||
.filter(tool -> Integer.valueOf(1).equals(tool.getStatus()))
|
||||
.map(tool -> new WorkflowDesignerOptionsView.PluginToolOption(
|
||||
tool.getId(), tool.getName(), tool.getDescription()))
|
||||
.toList();
|
||||
return new WorkflowDesignerOptionsView.PluginOption(
|
||||
plugin.getId(),
|
||||
plugin.getName(),
|
||||
plugin.getDescription(),
|
||||
plugin.getIcon(),
|
||||
tools
|
||||
);
|
||||
}
|
||||
|
||||
private JSONArray parseSchemaArray(String content) {
|
||||
if (content == null || content.isBlank()) {
|
||||
return new JSONArray();
|
||||
}
|
||||
JSONArray array = JSON.parseArray(content);
|
||||
decorateSchemaArray(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
private void decorateSchemaArray(JSONArray array) {
|
||||
for (Object item : array) {
|
||||
if (!(item instanceof JSONObject value)) {
|
||||
continue;
|
||||
}
|
||||
value.put("id", cn.hutool.core.util.IdUtil.simpleUUID());
|
||||
value.put("nameDisabled", true);
|
||||
value.put("dataTypeDisabled", true);
|
||||
value.put("deleteDisabled", true);
|
||||
value.put("addChildDisabled", true);
|
||||
JSONArray children = value.getJSONArray("children");
|
||||
if (children != null) {
|
||||
decorateSchemaArray(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为子流程参数补充设计器只读元数据。
|
||||
*
|
||||
* @param array 子流程参数定义
|
||||
*/
|
||||
private void decorateChildWorkflowSchemaArray(JSONArray array) {
|
||||
for (Object item : array) {
|
||||
if (!(item instanceof JSONObject value)) {
|
||||
continue;
|
||||
}
|
||||
value.put("id", cn.hutool.core.util.IdUtil.simpleUUID());
|
||||
value.put("nameDisabled", true);
|
||||
value.put("dataTypeDisabled", true);
|
||||
value.put("deleteDisabled", true);
|
||||
value.put("addChildDisabled", true);
|
||||
value.put("refType", "ref");
|
||||
JSONArray children = value.getJSONArray("children");
|
||||
if (children != null) {
|
||||
decorateChildWorkflowSchemaArray(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveSchemaHash(PluginItem record, Plugin plugin) {
|
||||
if (record.getSchemaHash() != null && !record.getSchemaHash().isBlank()) {
|
||||
return record.getSchemaHash();
|
||||
}
|
||||
if (!PluginType.isWorkflow(plugin.getType()) || plugin.getWorkflowId() == null) {
|
||||
return null;
|
||||
}
|
||||
Workflow workflow = workflowService.getPublishedById(plugin.getWorkflowId());
|
||||
return workflow == null ? null : workflowPluginSnapshotResolver.resolveSchemaHash(workflow);
|
||||
}
|
||||
|
||||
private WorkflowDesignerOptionsView.CatalogOption toCatalogOption(DatacenterCatalogMeta catalog) {
|
||||
return new WorkflowDesignerOptionsView.CatalogOption(
|
||||
catalog.getId(),
|
||||
catalog.getSourceId(),
|
||||
catalog.getCatalogName(),
|
||||
catalog.getCatalogDesc()
|
||||
);
|
||||
}
|
||||
|
||||
private WorkflowDesignerOptionsView.DatasetOption toDatasetOption(DatacenterTable table) {
|
||||
return new WorkflowDesignerOptionsView.DatasetOption(
|
||||
table.getId(),
|
||||
table.getSourceId(),
|
||||
table.getCatalogId(),
|
||||
table.getTableName(),
|
||||
table.getTableDesc()
|
||||
);
|
||||
}
|
||||
|
||||
private DatacenterSource requireTenantSource(BigInteger sourceId, LoginAccount account) {
|
||||
if (sourceId == null) {
|
||||
throw new BusinessException("数据源不能为空");
|
||||
}
|
||||
DatacenterSource source = datacenterSourceService.getById(sourceId);
|
||||
if (source == null || !Objects.equals(source.getTenantId(), account.getTenantId())) {
|
||||
throw new BusinessException("数据源不存在或无权访问");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private LoginAccount requireAccount() {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
if (account == null || account.getId() == null || account.getTenantId() == null) {
|
||||
throw new BusinessException("当前登录状态失效,请重新登录后再试");
|
||||
}
|
||||
return account;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
package tech.easyflow.admin.service.system;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.admin.model.SystemFormOptionsView;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowScopeVo;
|
||||
import tech.easyflow.approval.enums.ApprovalScopeType;
|
||||
import tech.easyflow.agent.entity.AgentCategory;
|
||||
import tech.easyflow.agent.service.AgentCategoryService;
|
||||
import tech.easyflow.ai.entity.DocumentCollectionCategory;
|
||||
import tech.easyflow.ai.entity.PluginCategory;
|
||||
import tech.easyflow.ai.entity.ResourceCategory;
|
||||
import tech.easyflow.ai.entity.WorkflowCategory;
|
||||
import tech.easyflow.ai.service.DocumentCollectionCategoryService;
|
||||
import tech.easyflow.ai.service.PluginCategoryService;
|
||||
import tech.easyflow.ai.service.ResourceCategoryService;
|
||||
import tech.easyflow.ai.service.WorkflowCategoryService;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysDept;
|
||||
import tech.easyflow.system.entity.SysMenu;
|
||||
import tech.easyflow.system.entity.SysPosition;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeItemVo;
|
||||
import tech.easyflow.system.service.SysDeptService;
|
||||
import tech.easyflow.system.service.SysMenuService;
|
||||
import tech.easyflow.system.service.SysPositionService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 查询并校验系统管理表单所需的安全选项。
|
||||
*/
|
||||
@Service
|
||||
public class SystemFormOptionService {
|
||||
|
||||
private final AgentCategoryService agentCategoryService;
|
||||
private final WorkflowCategoryService workflowCategoryService;
|
||||
private final DocumentCollectionCategoryService documentCollectionCategoryService;
|
||||
private final PluginCategoryService pluginCategoryService;
|
||||
private final ResourceCategoryService resourceCategoryService;
|
||||
private final SysDeptService sysDeptService;
|
||||
private final SysMenuService sysMenuService;
|
||||
private final SysRoleService sysRoleService;
|
||||
private final SysPositionService sysPositionService;
|
||||
|
||||
/**
|
||||
* 创建系统表单选项服务。
|
||||
*
|
||||
* @param agentCategoryService Agent 分类服务
|
||||
* @param workflowCategoryService 工作流分类服务
|
||||
* @param documentCollectionCategoryService 知识库分类服务
|
||||
* @param pluginCategoryService 插件分类服务
|
||||
* @param resourceCategoryService 素材分类服务
|
||||
* @param sysDeptService 部门服务
|
||||
* @param sysMenuService 菜单服务
|
||||
* @param sysRoleService 角色服务
|
||||
* @param sysPositionService 岗位服务
|
||||
*/
|
||||
public SystemFormOptionService(
|
||||
AgentCategoryService agentCategoryService,
|
||||
WorkflowCategoryService workflowCategoryService,
|
||||
DocumentCollectionCategoryService documentCollectionCategoryService,
|
||||
PluginCategoryService pluginCategoryService,
|
||||
ResourceCategoryService resourceCategoryService,
|
||||
SysDeptService sysDeptService,
|
||||
SysMenuService sysMenuService,
|
||||
SysRoleService sysRoleService,
|
||||
SysPositionService sysPositionService) {
|
||||
this.agentCategoryService = agentCategoryService;
|
||||
this.workflowCategoryService = workflowCategoryService;
|
||||
this.documentCollectionCategoryService = documentCollectionCategoryService;
|
||||
this.pluginCategoryService = pluginCategoryService;
|
||||
this.resourceCategoryService = resourceCategoryService;
|
||||
this.sysDeptService = sysDeptService;
|
||||
this.sysMenuService = sysMenuService;
|
||||
this.sysRoleService = sysRoleService;
|
||||
this.sysPositionService = sysPositionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询审批流程资源范围选项。
|
||||
*
|
||||
* @return 审批流程资源范围选项
|
||||
*/
|
||||
public SystemFormOptionsView.ApprovalResourceScopeOptions approvalResourceScopeOptions() {
|
||||
Map<String, List<SystemFormOptionsView.CategoryOption>> categories = new LinkedHashMap<>();
|
||||
categories.put("AGENT", listAgentCategories());
|
||||
categories.put("WORKFLOW", listWorkflowCategories());
|
||||
categories.put("KNOWLEDGE", listKnowledgeCategories());
|
||||
return new SystemFormOptionsView.ApprovalResourceScopeOptions(
|
||||
categories,
|
||||
listDepartments()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询角色管理表单选项。
|
||||
*
|
||||
* @return 角色管理表单选项
|
||||
*/
|
||||
public SystemFormOptionsView.RoleFormOptions roleFormOptions() {
|
||||
Map<String, List<SystemFormOptionsView.CategoryOption>> categories = new LinkedHashMap<>();
|
||||
categories.put("AGENT", listAgentCategories());
|
||||
categories.put("PLUGIN", listPluginCategories());
|
||||
categories.put("WORKFLOW", listWorkflowCategories());
|
||||
categories.put("KNOWLEDGE", listKnowledgeCategories());
|
||||
categories.put("RESOURCE", listResourceCategories());
|
||||
return new SystemFormOptionsView.RoleFormOptions(
|
||||
listMenus(),
|
||||
categories
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询账号管理表单选项。
|
||||
*
|
||||
* @return 账号管理表单选项
|
||||
*/
|
||||
public SystemFormOptionsView.AccountFormOptions accountFormOptions() {
|
||||
BigInteger tenantId = requireAccount().getTenantId();
|
||||
List<SystemFormOptionsView.RoleOption> roles = sysRoleService.list(QueryWrapper.create()
|
||||
.eq(SysRole::getTenantId, tenantId)
|
||||
.eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||
.orderBy(SysRole::getId, true))
|
||||
.stream()
|
||||
.map(role -> new SystemFormOptionsView.RoleOption(role.getId(), role.getRoleName()))
|
||||
.toList();
|
||||
List<SystemFormOptionsView.PositionOption> positions = sysPositionService.list(QueryWrapper.create()
|
||||
.eq(SysPosition::getTenantId, tenantId)
|
||||
.eq(SysPosition::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||
.orderBy(SysPosition::getSortNo, true))
|
||||
.stream()
|
||||
.map(position -> new SystemFormOptionsView.PositionOption(
|
||||
position.getId(), position.getPositionName()))
|
||||
.toList();
|
||||
return new SystemFormOptionsView.AccountFormOptions(
|
||||
listDepartments(),
|
||||
roles,
|
||||
positions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验账号表单引用的部门、角色和岗位。
|
||||
*
|
||||
* @param account 账号表单
|
||||
* @throws BusinessException 引用不存在或已禁用时抛出
|
||||
*/
|
||||
public void validateAccountReferences(SysAccount account) {
|
||||
if (account == null) {
|
||||
throw new BusinessException("账号信息不能为空");
|
||||
}
|
||||
if (account.getDeptId() == null) {
|
||||
throw new BusinessException("部门不能为空");
|
||||
}
|
||||
SysDept dept = sysDeptService.getById(account.getDeptId());
|
||||
BigInteger tenantId = requireAccount().getTenantId();
|
||||
if (dept == null
|
||||
|| !Objects.equals(dept.getTenantId(), tenantId)
|
||||
|| !EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())) {
|
||||
throw new BusinessException("部门不存在或已禁用");
|
||||
}
|
||||
assertAvailableIds(
|
||||
account.getRoleIds(),
|
||||
sysRoleService::listByIds,
|
||||
SysRole::getId,
|
||||
SysRole::getStatus,
|
||||
SysRole::getTenantId,
|
||||
tenantId,
|
||||
"角色"
|
||||
);
|
||||
if (account.getPositionIds() != null && !account.getPositionIds().isEmpty()) {
|
||||
assertAvailableIds(
|
||||
account.getPositionIds(),
|
||||
sysPositionService::listByIds,
|
||||
SysPosition::getId,
|
||||
SysPosition::getStatus,
|
||||
SysPosition::getTenantId,
|
||||
tenantId,
|
||||
"岗位"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验角色引用的菜单和部门。
|
||||
*
|
||||
* @param role 角色表单
|
||||
* @throws BusinessException 引用不存在时抛出
|
||||
*/
|
||||
public void validateRoleReferences(SysRole role) {
|
||||
if (role == null) {
|
||||
throw new BusinessException("角色信息不能为空");
|
||||
}
|
||||
assertExistingIds(role.getMenuIds(), sysMenuService::listByIds, SysMenu::getId, "菜单");
|
||||
if (role.getDeptIds() != null && !role.getDeptIds().isEmpty()) {
|
||||
assertTenantIds(
|
||||
role.getDeptIds(),
|
||||
sysDeptService::listByIds,
|
||||
SysDept::getId,
|
||||
SysDept::getTenantId,
|
||||
requireAccount().getTenantId(),
|
||||
"部门");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验角色分类授权中提交的非 Bot 分类 ID。
|
||||
*
|
||||
* @param scopes 分类权限范围
|
||||
* @throws BusinessException 分类类型或分类 ID 非法时抛出
|
||||
*/
|
||||
public void validateCategoryScopes(List<SysRoleCategoryScopeItemVo> scopes) {
|
||||
if (scopes == null) {
|
||||
return;
|
||||
}
|
||||
for (SysRoleCategoryScopeItemVo scope : scopes) {
|
||||
if (scope == null || scope.getCategoryIds() == null || scope.getCategoryIds().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
switch (String.valueOf(scope.getResourceType()).toUpperCase()) {
|
||||
case "AGENT" -> assertTenantIds(
|
||||
scope.getCategoryIds(),
|
||||
agentCategoryService::listByIds,
|
||||
AgentCategory::getId,
|
||||
AgentCategory::getTenantId,
|
||||
requireAccount().getTenantId(),
|
||||
"Agent 分类");
|
||||
case "PLUGIN" -> assertExistingIds(
|
||||
scope.getCategoryIds(), pluginCategoryService::listByIds, PluginCategory::getId, "插件分类");
|
||||
case "WORKFLOW" -> assertExistingIds(
|
||||
scope.getCategoryIds(), workflowCategoryService::listByIds, WorkflowCategory::getId, "工作流分类");
|
||||
case "KNOWLEDGE" -> assertExistingIds(
|
||||
scope.getCategoryIds(),
|
||||
documentCollectionCategoryService::listByIds,
|
||||
DocumentCollectionCategory::getId,
|
||||
"知识库分类");
|
||||
case "RESOURCE" -> assertExistingIds(
|
||||
scope.getCategoryIds(), resourceCategoryService::listByIds, ResourceCategory::getId, "素材分类");
|
||||
case "BOT" -> throw new BusinessException("Bot 分类授权已停止维护");
|
||||
default -> throw new BusinessException("不支持的分类资源类型");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验审批流程提交的分类和部门范围。
|
||||
*
|
||||
* @param resourceType 审批资源类型
|
||||
* @param scopes 审批范围
|
||||
* @throws BusinessException 范围引用不存在或资源类型不匹配时抛出
|
||||
*/
|
||||
public void validateApprovalScopes(String resourceType, List<ApprovalFlowScopeVo> scopes) {
|
||||
if (scopes == null) {
|
||||
return;
|
||||
}
|
||||
for (ApprovalFlowScopeVo scope : scopes) {
|
||||
if (scope == null || scope.getScopeValue() == null) {
|
||||
continue;
|
||||
}
|
||||
String scopeType = String.valueOf(scope.getScopeType()).toUpperCase();
|
||||
if (ApprovalScopeType.DEPT.getCode().equals(scopeType)) {
|
||||
assertTenantIds(
|
||||
List.of(scope.getScopeValue()),
|
||||
sysDeptService::listByIds,
|
||||
SysDept::getId,
|
||||
SysDept::getTenantId,
|
||||
requireAccount().getTenantId(),
|
||||
"部门");
|
||||
continue;
|
||||
}
|
||||
if (!ApprovalScopeType.CATEGORY.getCode().equals(scopeType)) {
|
||||
throw new BusinessException("不支持的审批范围类型");
|
||||
}
|
||||
switch (String.valueOf(resourceType).toUpperCase()) {
|
||||
case "AGENT" -> assertTenantIds(
|
||||
List.of(scope.getScopeValue()),
|
||||
agentCategoryService::listByIds,
|
||||
AgentCategory::getId,
|
||||
AgentCategory::getTenantId,
|
||||
requireAccount().getTenantId(),
|
||||
"Agent 分类");
|
||||
case "WORKFLOW" -> assertExistingIds(
|
||||
List.of(scope.getScopeValue()),
|
||||
workflowCategoryService::listByIds,
|
||||
WorkflowCategory::getId,
|
||||
"工作流分类");
|
||||
case "KNOWLEDGE" -> assertExistingIds(
|
||||
List.of(scope.getScopeValue()),
|
||||
documentCollectionCategoryService::listByIds,
|
||||
DocumentCollectionCategory::getId,
|
||||
"知识库分类");
|
||||
default -> throw new BusinessException("当前资源类型不支持分类审批范围");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.DepartmentOption> listDepartments() {
|
||||
BigInteger tenantId = requireAccount().getTenantId();
|
||||
List<SysDept> departments = sysDeptService.list(QueryWrapper.create()
|
||||
.eq(SysDept::getTenantId, tenantId)
|
||||
.eq(SysDept::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||
.orderBy(SysDept::getSortNo, true));
|
||||
return buildDepartmentTree(departments);
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.MenuOption> listMenus() {
|
||||
List<SysMenu> menus = sysMenuService.list(QueryWrapper.create()
|
||||
.orderBy(SysMenu::getSortNo, true));
|
||||
return buildMenuTree(menus);
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.CategoryOption> listAgentCategories() {
|
||||
BigInteger tenantId = requireAccount().getTenantId();
|
||||
return agentCategoryService.list(QueryWrapper.create()
|
||||
.eq(AgentCategory::getTenantId, tenantId)
|
||||
.eq(AgentCategory::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||
.orderBy(AgentCategory::getSortNo, true))
|
||||
.stream()
|
||||
.map(category -> new SystemFormOptionsView.CategoryOption(
|
||||
category.getId(), category.getCategoryName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.CategoryOption> listWorkflowCategories() {
|
||||
return workflowCategoryService.list(QueryWrapper.create()
|
||||
.orderBy(WorkflowCategory::getSortNo, true))
|
||||
.stream()
|
||||
.map(category -> new SystemFormOptionsView.CategoryOption(
|
||||
category.getId(), category.getCategoryName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.CategoryOption> listKnowledgeCategories() {
|
||||
return documentCollectionCategoryService.list(QueryWrapper.create()
|
||||
.orderBy(DocumentCollectionCategory::getSortNo, true))
|
||||
.stream()
|
||||
.map(category -> new SystemFormOptionsView.CategoryOption(
|
||||
category.getId(), category.getCategoryName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.CategoryOption> listPluginCategories() {
|
||||
return pluginCategoryService.list(QueryWrapper.create()
|
||||
.orderBy(PluginCategory::getId, true))
|
||||
.stream()
|
||||
.map(category -> new SystemFormOptionsView.CategoryOption(
|
||||
category.getId(), category.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.CategoryOption> listResourceCategories() {
|
||||
return resourceCategoryService.list(QueryWrapper.create()
|
||||
.orderBy(ResourceCategory::getSortNo, true))
|
||||
.stream()
|
||||
.map(category -> new SystemFormOptionsView.CategoryOption(
|
||||
category.getId(), category.getCategoryName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.DepartmentOption> buildDepartmentTree(List<SysDept> departments) {
|
||||
Set<BigInteger> ids = new HashSet<>();
|
||||
departments.forEach(item -> ids.add(item.getId()));
|
||||
Map<BigInteger, List<SysDept>> children = new LinkedHashMap<>();
|
||||
List<SysDept> roots = new ArrayList<>();
|
||||
for (SysDept department : departments) {
|
||||
BigInteger parentId = department.getParentId();
|
||||
if (parentId == null || BigInteger.ZERO.equals(parentId) || !ids.contains(parentId)) {
|
||||
roots.add(department);
|
||||
} else {
|
||||
children.computeIfAbsent(parentId, ignored -> new ArrayList<>()).add(department);
|
||||
}
|
||||
}
|
||||
return roots.stream().map(item -> toDepartmentOption(item, children)).toList();
|
||||
}
|
||||
|
||||
private SystemFormOptionsView.DepartmentOption toDepartmentOption(
|
||||
SysDept department,
|
||||
Map<BigInteger, List<SysDept>> children) {
|
||||
return new SystemFormOptionsView.DepartmentOption(
|
||||
department.getId(),
|
||||
department.getParentId(),
|
||||
department.getDeptName(),
|
||||
children.getOrDefault(department.getId(), List.of()).stream()
|
||||
.map(item -> toDepartmentOption(item, children))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
private List<SystemFormOptionsView.MenuOption> buildMenuTree(List<SysMenu> menus) {
|
||||
Set<BigInteger> ids = new HashSet<>();
|
||||
menus.forEach(item -> ids.add(item.getId()));
|
||||
Map<BigInteger, List<SysMenu>> children = new LinkedHashMap<>();
|
||||
List<SysMenu> roots = new ArrayList<>();
|
||||
for (SysMenu menu : menus) {
|
||||
BigInteger parentId = menu.getParentId();
|
||||
if (parentId == null || BigInteger.ZERO.equals(parentId) || !ids.contains(parentId)) {
|
||||
roots.add(menu);
|
||||
} else {
|
||||
children.computeIfAbsent(parentId, ignored -> new ArrayList<>()).add(menu);
|
||||
}
|
||||
}
|
||||
return roots.stream().map(item -> toMenuOption(item, children)).toList();
|
||||
}
|
||||
|
||||
private SystemFormOptionsView.MenuOption toMenuOption(
|
||||
SysMenu menu,
|
||||
Map<BigInteger, List<SysMenu>> children) {
|
||||
return new SystemFormOptionsView.MenuOption(
|
||||
menu.getId(),
|
||||
menu.getParentId(),
|
||||
menu.getMenuTitle(),
|
||||
children.getOrDefault(menu.getId(), List.of()).stream()
|
||||
.map(item -> toMenuOption(item, children))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void assertAvailableIds(
|
||||
Collection<BigInteger> rawIds,
|
||||
Function<Collection<BigInteger>, List<T>> loader,
|
||||
Function<T, BigInteger> idGetter,
|
||||
Function<T, Integer> statusGetter,
|
||||
Function<T, BigInteger> tenantGetter,
|
||||
BigInteger tenantId,
|
||||
String label) {
|
||||
Set<BigInteger> ids = normalizeIds(rawIds);
|
||||
if (ids.isEmpty()) {
|
||||
throw new BusinessException(label + "不能为空");
|
||||
}
|
||||
List<T> records = loader.apply(ids);
|
||||
boolean valid = records.size() == ids.size()
|
||||
&& records.stream().allMatch(item ->
|
||||
ids.contains(idGetter.apply(item))
|
||||
&& EnumDataStatus.AVAILABLE.getCode().equals(statusGetter.apply(item))
|
||||
&& Objects.equals(tenantGetter.apply(item), tenantId));
|
||||
if (!valid) {
|
||||
throw new BusinessException(label + "不存在或已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void assertExistingIds(
|
||||
Collection<BigInteger> rawIds,
|
||||
Function<Collection<BigInteger>, List<T>> loader,
|
||||
Function<T, BigInteger> idGetter,
|
||||
String label) {
|
||||
Set<BigInteger> ids = normalizeIds(rawIds);
|
||||
if (ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<T> records = loader.apply(ids);
|
||||
boolean valid = records.size() == ids.size()
|
||||
&& records.stream().allMatch(item -> ids.contains(idGetter.apply(item)));
|
||||
if (!valid) {
|
||||
throw new BusinessException(label + "不存在或无权访问");
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void assertTenantIds(
|
||||
Collection<BigInteger> rawIds,
|
||||
Function<Collection<BigInteger>, List<T>> loader,
|
||||
Function<T, BigInteger> idGetter,
|
||||
Function<T, BigInteger> tenantGetter,
|
||||
BigInteger tenantId,
|
||||
String label) {
|
||||
Set<BigInteger> ids = normalizeIds(rawIds);
|
||||
if (ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<T> records = loader.apply(ids);
|
||||
boolean valid = records.size() == ids.size()
|
||||
&& records.stream().allMatch(item ->
|
||||
ids.contains(idGetter.apply(item))
|
||||
&& Objects.equals(tenantGetter.apply(item), tenantId));
|
||||
if (!valid) {
|
||||
throw new BusinessException(label + "不存在或无权访问");
|
||||
}
|
||||
}
|
||||
|
||||
private LoginAccount requireAccount() {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
if (account == null || account.getTenantId() == null) {
|
||||
throw new BusinessException("当前登录状态失效,请重新登录后再试");
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
private Set<BigInteger> normalizeIds(Collection<BigInteger> rawIds) {
|
||||
Set<BigInteger> ids = new LinkedHashSet<>();
|
||||
if (rawIds != null) {
|
||||
rawIds.stream().filter(Objects::nonNull).forEach(ids::add);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user