From 8596f5074b0e1bdbb9b855de239f6cf7c82dbbfe Mon Sep 17 00:00:00 2001 From: bruce Date: Mon, 1 Jun 2026 04:18:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20=E8=A1=A5=E9=BD=90=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=89=88=E6=9C=AC=E8=BF=90=E8=A1=8C=E4=B8=8E=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProjectController.java | 38 ++++++ .../WorkflowDefinitionController.java | 6 +- .../controller/WorkflowRunController.java | 33 +++++ .../controller/WorkflowVersionController.java | 33 +++++ .../WorkflowWorkspaceController.java | 24 ++++ .../bruce/workflow/dto/ProjectSaveDTO.java | 14 ++ .../workflow/dto/WorkflowRunCreateDTO.java | 18 +++ .../workflow/dto/WorkflowVersionSaveDTO.java | 13 ++ .../bruce/workflow/entity/StudioProject.java | 4 + .../workflow/entity/WorkflowDefinition.java | 4 + .../bruce/workflow/entity/WorkflowRun.java | 4 + .../workflow/entity/WorkflowRunStep.java | 4 + .../workflow/entity/WorkflowVersion.java | 4 + .../workflow/factory/ProjectFactory.java | 52 ++++++++ .../factory/WorkflowDefinitionFactory.java | 12 +- .../workflow/factory/WorkflowRunFactory.java | 56 ++++++++ .../factory/WorkflowVersionFactory.java | 51 +++++++ .../workflow/service/IProjectService.java | 17 +++ .../service/IWorkflowDefinitionService.java | 2 + .../workflow/service/IWorkflowRunService.java | 15 +++ .../service/IWorkflowVersionService.java | 16 +++ .../service/IWorkflowWorkspaceService.java | 8 ++ .../service/impl/ProjectServiceImpl.java | 82 ++++++++++++ .../impl/WorkflowDefinitionServiceImpl.java | 59 ++++++++- .../service/impl/WorkflowRunServiceImpl.java | 69 ++++++++++ .../impl/WorkflowVersionServiceImpl.java | 124 ++++++++++++++++++ .../impl/WorkflowWorkspaceServiceImpl.java | 88 +++++++++++++ .../java/com/bruce/workflow/vo/ProjectVO.java | 14 ++ .../workflow/vo/WorkflowDefinitionVO.java | 2 + .../com/bruce/workflow/vo/WorkflowRunVO.java | 21 +++ .../bruce/workflow/vo/WorkflowVersionVO.java | 17 +++ .../workflow/vo/WorkflowWorkspaceVO.java | 24 ++++ .../factory/WorkflowFactoryTests.java | 64 +++++++++ .../version/WorkflowVersionServiceTests.java | 91 +++++++++++++ .../WorkflowWorkspaceServiceTests.java | 92 +++++++++++++ frontend/src/api/__tests__/workflow.spec.ts | 44 +++++++ frontend/src/api/workflow.ts | 87 ++++++++++++ 37 files changed, 1300 insertions(+), 6 deletions(-) create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/controller/ProjectController.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowRunController.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowVersionController.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowWorkspaceController.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/dto/ProjectSaveDTO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowRunCreateDTO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowVersionSaveDTO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/factory/ProjectFactory.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowRunFactory.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowVersionFactory.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/IProjectService.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowRunService.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowVersionService.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowWorkspaceService.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/ProjectServiceImpl.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowRunServiceImpl.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowVersionServiceImpl.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowWorkspaceServiceImpl.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/vo/ProjectVO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowRunVO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowVersionVO.java create mode 100644 common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowWorkspaceVO.java create mode 100644 common-agent-workflow/src/test/java/com/bruce/workflow/factory/WorkflowFactoryTests.java create mode 100644 common-agent-workflow/src/test/java/com/bruce/workflow/version/WorkflowVersionServiceTests.java create mode 100644 common-agent-workflow/src/test/java/com/bruce/workflow/workspace/WorkflowWorkspaceServiceTests.java create mode 100644 frontend/src/api/__tests__/workflow.spec.ts create mode 100644 frontend/src/api/workflow.ts diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/ProjectController.java b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/ProjectController.java new file mode 100644 index 0000000..7a1fa6f --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/ProjectController.java @@ -0,0 +1,38 @@ +package com.bruce.workflow.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.workflow.dto.ProjectSaveDTO; +import com.bruce.workflow.service.IProjectService; +import com.bruce.workflow.vo.ProjectVO; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/studio-projects") +@RequiredArgsConstructor +public class ProjectController { + + private final IProjectService projectService; + + @GetMapping("/list") + public RequestResult> list() { + return RequestResult.success(projectService.listProjects()); + } + + @GetMapping("/detail") + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(projectService.getProject(id)); + } + + @PostMapping("/save") + public RequestResult save(@RequestBody ProjectSaveDTO request) { + return RequestResult.success(projectService.saveProject(request)); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowDefinitionController.java b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowDefinitionController.java index 275bf4b..b68b6d8 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowDefinitionController.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowDefinitionController.java @@ -22,8 +22,10 @@ public class WorkflowDefinitionController { private final IWorkflowDefinitionService workflowDefinitionService; @GetMapping("/definitions") - public RequestResult> list() { - return RequestResult.success(workflowDefinitionService.listDefinitions()); + public RequestResult> list(@RequestParam(value = "projectId", required = false) Long projectId) { + return RequestResult.success(projectId == null + ? workflowDefinitionService.listDefinitions() + : workflowDefinitionService.listByProjectId(projectId)); } @GetMapping("/definition/detail") diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowRunController.java b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowRunController.java new file mode 100644 index 0000000..f0bed83 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowRunController.java @@ -0,0 +1,33 @@ +package com.bruce.workflow.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.workflow.dto.WorkflowRunCreateDTO; +import com.bruce.workflow.service.IWorkflowRunService; +import com.bruce.workflow.vo.WorkflowRunVO; +import lombok.RequiredArgsConstructor; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/workflow-runs") +@RequiredArgsConstructor +public class WorkflowRunController { + + private final IWorkflowRunService workflowRunService; + + @PostMapping("/create") + public RequestResult create(@RequestBody WorkflowRunCreateDTO request) { + return RequestResult.success(workflowRunService.createRun(request)); + } + + @GetMapping("/{workflowId}") + public RequestResult> list(@PathVariable("workflowId") Long workflowId) { + return RequestResult.success(workflowRunService.listRecentByWorkflowId(workflowId)); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowVersionController.java b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowVersionController.java new file mode 100644 index 0000000..9de164d --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowVersionController.java @@ -0,0 +1,33 @@ +package com.bruce.workflow.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.service.IWorkflowVersionService; +import com.bruce.workflow.vo.WorkflowVersionVO; +import lombok.RequiredArgsConstructor; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/workflow-versions") +@RequiredArgsConstructor +public class WorkflowVersionController { + + private final IWorkflowVersionService workflowVersionService; + + @GetMapping("/{workflowId}") + public RequestResult> list(@PathVariable("workflowId") Long workflowId) { + return RequestResult.success(workflowVersionService.listByWorkflowId(workflowId)); + } + + @PostMapping("/publish") + public RequestResult publish(@RequestBody WorkflowVersionSaveDTO request) { + return RequestResult.success(workflowVersionService.publishVersion(request)); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowWorkspaceController.java b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowWorkspaceController.java new file mode 100644 index 0000000..25b675c --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/controller/WorkflowWorkspaceController.java @@ -0,0 +1,24 @@ +package com.bruce.workflow.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.workflow.service.IWorkflowWorkspaceService; +import com.bruce.workflow.vo.WorkflowWorkspaceVO; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/workflow-workspace") +@RequiredArgsConstructor +public class WorkflowWorkspaceController { + + private final IWorkflowWorkspaceService workflowWorkspaceService; + + @GetMapping("/detail") + public RequestResult detail(@RequestParam("projectId") Long projectId, + @RequestParam(value = "workflowId", required = false) Long workflowId) { + return RequestResult.success(workflowWorkspaceService.getWorkspace(projectId, workflowId)); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/dto/ProjectSaveDTO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/ProjectSaveDTO.java new file mode 100644 index 0000000..37ff67f --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/ProjectSaveDTO.java @@ -0,0 +1,14 @@ +package com.bruce.workflow.dto; + +import lombok.Data; + +@Data +public class ProjectSaveDTO { + private Long id; + private String projectCode; + private String projectName; + private String environment; + private String publishStatus; + private String currentVersion; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowRunCreateDTO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowRunCreateDTO.java new file mode 100644 index 0000000..fca026b --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowRunCreateDTO.java @@ -0,0 +1,18 @@ +package com.bruce.workflow.dto; + +import lombok.Data; + +@Data +public class WorkflowRunCreateDTO { + private Long workflowId; + private Long workflowVersionId; + private Long agentId; + private String requestId; + private String runSource; + private String status; + private String inputJson; + private String outputJson; + private Integer durationMs; + private java.math.BigDecimal estimatedCost; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowVersionSaveDTO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowVersionSaveDTO.java new file mode 100644 index 0000000..5d2a367 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/dto/WorkflowVersionSaveDTO.java @@ -0,0 +1,13 @@ +package com.bruce.workflow.dto; + +import lombok.Data; + +@Data +public class WorkflowVersionSaveDTO { + private Long workflowId; + private Integer versionNo; + private String snapshotName; + private String graphJson; + private String publishStatus; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/StudioProject.java b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/StudioProject.java index 07f5618..127b434 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/StudioProject.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/StudioProject.java @@ -15,6 +15,8 @@ import lombok.EqualsAndHashCode; @Schema(description = "Studio项目空间") public class StudioProject extends BaseEntity { + private static final long serialVersionUID = 1L; + private String projectCode; private String projectName; @@ -24,4 +26,6 @@ public class StudioProject extends BaseEntity { private String publishStatus; private String currentVersion; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowDefinition.java b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowDefinition.java index d41523f..71e0955 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowDefinition.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowDefinition.java @@ -13,6 +13,8 @@ import lombok.EqualsAndHashCode; @TableName("workflow_definition") public class WorkflowDefinition extends BaseEntity { + private static final long serialVersionUID = 1L; + private Long projectId; private String workflowCode; @@ -24,4 +26,6 @@ public class WorkflowDefinition extends BaseEntity { private Long boundAgentId; private String status; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRun.java b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRun.java index 28deb21..a137b9c 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRun.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRun.java @@ -17,6 +17,8 @@ import java.math.BigDecimal; @TableName("workflow_run") public class WorkflowRun extends BaseEntity { + private static final long serialVersionUID = 1L; + private String requestId; private Long workflowId; @@ -38,4 +40,6 @@ public class WorkflowRun extends BaseEntity { private Integer durationMs; private BigDecimal estimatedCost; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRunStep.java b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRunStep.java index f2e7634..3e287e3 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRunStep.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowRunStep.java @@ -15,6 +15,8 @@ import lombok.EqualsAndHashCode; @TableName("workflow_run_step") public class WorkflowRunStep extends BaseEntity { + private static final long serialVersionUID = 1L; + private Long runId; private String nodeId; @@ -34,4 +36,6 @@ public class WorkflowRunStep extends BaseEntity { private Integer durationMs; private String errorMessage; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowVersion.java b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowVersion.java index 2aed67b..cd9832d 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowVersion.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/entity/WorkflowVersion.java @@ -15,6 +15,8 @@ import lombok.EqualsAndHashCode; @TableName("workflow_version") public class WorkflowVersion extends BaseEntity { + private static final long serialVersionUID = 1L; + private Long workflowId; private Integer versionNo; @@ -27,4 +29,6 @@ public class WorkflowVersion extends BaseEntity { private String publishStatus; private java.time.LocalDateTime publishedTime; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/factory/ProjectFactory.java b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/ProjectFactory.java new file mode 100644 index 0000000..bb1be0c --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/ProjectFactory.java @@ -0,0 +1,52 @@ +package com.bruce.workflow.factory; + +import com.bruce.workflow.dto.ProjectSaveDTO; +import com.bruce.workflow.entity.StudioProject; +import com.bruce.workflow.vo.ProjectVO; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Studio 项目空间工厂,统一处理请求、实体和返回对象之间的转换。 + */ +@Component +public class ProjectFactory { + + public StudioProject toEntity(ProjectSaveDTO request) { + if (request == null) { + return null; + } + StudioProject entity = new StudioProject(); + entity.setId(request.getId()); + entity.setProjectCode(trimToNull(request.getProjectCode())); + entity.setProjectName(trimToNull(request.getProjectName())); + entity.setEnvironment(trimToNull(request.getEnvironment())); + entity.setPublishStatus(trimToNull(request.getPublishStatus())); + entity.setCurrentVersion(trimToNull(request.getCurrentVersion())); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public ProjectVO toVO(StudioProject entity) { + if (entity == null) { + return null; + } + ProjectVO vo = new ProjectVO(); + BeanUtils.copyProperties(entity, vo); + return vo; + } + + public List toVOList(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toVO).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowDefinitionFactory.java b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowDefinitionFactory.java index 64d9871..8df632d 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowDefinitionFactory.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowDefinitionFactory.java @@ -3,6 +3,7 @@ package com.bruce.workflow.factory; import com.bruce.workflow.dto.WorkflowDefinitionSaveDTO; import com.bruce.workflow.entity.WorkflowDefinition; import com.bruce.workflow.vo.WorkflowDefinitionVO; +import org.springframework.util.StringUtils; import org.springframework.stereotype.Component; /** @@ -23,7 +24,8 @@ public class WorkflowDefinitionFactory { entity.setWorkflowName(dto.getWorkflowName()); entity.setDescription(dto.getDescription()); entity.setBoundAgentId(dto.getBoundAgentId()); - entity.setStatus(dto.getStatus()); + entity.setStatus(trimToNull(dto.getStatus())); + entity.setRemark(trimToNull(dto.getRemark())); return entity; } @@ -39,6 +41,14 @@ public class WorkflowDefinitionFactory { vo.setBoundAgentId(entity.getBoundAgentId()); vo.setStatus(entity.getStatus()); vo.setDescription(entity.getDescription()); + vo.setRemark(entity.getRemark()); return vo; } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowRunFactory.java b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowRunFactory.java new file mode 100644 index 0000000..8f8a6c3 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowRunFactory.java @@ -0,0 +1,56 @@ +package com.bruce.workflow.factory; + +import com.bruce.workflow.dto.WorkflowRunCreateDTO; +import com.bruce.workflow.entity.WorkflowRun; +import com.bruce.workflow.vo.WorkflowRunVO; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Workflow 运行工厂,统一处理运行记录转换。 + */ +@Component +public class WorkflowRunFactory { + + public WorkflowRun toEntity(WorkflowRunCreateDTO request) { + if (request == null) { + return null; + } + WorkflowRun entity = new WorkflowRun(); + entity.setWorkflowId(request.getWorkflowId()); + entity.setWorkflowVersionId(request.getWorkflowVersionId()); + entity.setAgentId(request.getAgentId()); + entity.setRequestId(trimToNull(request.getRequestId())); + entity.setRunSource(trimToNull(request.getRunSource())); + entity.setStatus(trimToNull(request.getStatus())); + entity.setInputJson(trimToNull(request.getInputJson())); + entity.setOutputJson(trimToNull(request.getOutputJson())); + entity.setDurationMs(request.getDurationMs()); + entity.setEstimatedCost(request.getEstimatedCost()); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public WorkflowRunVO toVO(WorkflowRun entity) { + if (entity == null) { + return null; + } + WorkflowRunVO vo = new WorkflowRunVO(); + BeanUtils.copyProperties(entity, vo); + return vo; + } + + public List toVOList(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toVO).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowVersionFactory.java b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowVersionFactory.java new file mode 100644 index 0000000..ff6fea4 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/factory/WorkflowVersionFactory.java @@ -0,0 +1,51 @@ +package com.bruce.workflow.factory; + +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.entity.WorkflowVersion; +import com.bruce.workflow.vo.WorkflowVersionVO; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Workflow 版本工厂,统一负责快照 DTO、Entity、VO 转换。 + */ +@Component +public class WorkflowVersionFactory { + + public WorkflowVersion toEntity(WorkflowVersionSaveDTO request) { + if (request == null) { + return null; + } + WorkflowVersion entity = new WorkflowVersion(); + entity.setWorkflowId(request.getWorkflowId()); + entity.setVersionNo(request.getVersionNo()); + entity.setSnapshotName(trimToNull(request.getSnapshotName())); + entity.setGraphJson(trimToNull(request.getGraphJson())); + entity.setPublishStatus(trimToNull(request.getPublishStatus())); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public WorkflowVersionVO toVO(WorkflowVersion entity) { + if (entity == null) { + return null; + } + WorkflowVersionVO vo = new WorkflowVersionVO(); + BeanUtils.copyProperties(entity, vo); + return vo; + } + + public List toVOList(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toVO).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IProjectService.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IProjectService.java new file mode 100644 index 0000000..37b5810 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IProjectService.java @@ -0,0 +1,17 @@ +package com.bruce.workflow.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.workflow.dto.ProjectSaveDTO; +import com.bruce.workflow.entity.StudioProject; +import com.bruce.workflow.vo.ProjectVO; + +import java.util.List; + +public interface IProjectService extends IService { + + List listProjects(); + + ProjectVO getProject(Long id); + + boolean saveProject(ProjectSaveDTO request); +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowDefinitionService.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowDefinitionService.java index 7d5e373..e4441ce 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowDefinitionService.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowDefinitionService.java @@ -11,6 +11,8 @@ public interface IWorkflowDefinitionService extends IService List listDefinitions(); + List listByProjectId(Long projectId); + WorkflowDefinitionVO getDefinition(Long id); boolean saveDefinition(WorkflowDefinitionSaveDTO request); diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowRunService.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowRunService.java new file mode 100644 index 0000000..3c985f4 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowRunService.java @@ -0,0 +1,15 @@ +package com.bruce.workflow.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.workflow.dto.WorkflowRunCreateDTO; +import com.bruce.workflow.entity.WorkflowRun; +import com.bruce.workflow.vo.WorkflowRunVO; + +import java.util.List; + +public interface IWorkflowRunService extends IService { + + boolean createRun(WorkflowRunCreateDTO request); + + List listRecentByWorkflowId(Long workflowId); +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowVersionService.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowVersionService.java new file mode 100644 index 0000000..0d872f1 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowVersionService.java @@ -0,0 +1,16 @@ +package com.bruce.workflow.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.entity.WorkflowDefinition; +import com.bruce.workflow.entity.WorkflowVersion; +import com.bruce.workflow.vo.WorkflowVersionVO; + +import java.util.List; + +public interface IWorkflowVersionService extends IService { + + boolean publishVersion(WorkflowVersionSaveDTO request); + + List listByWorkflowId(Long workflowId); +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowWorkspaceService.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowWorkspaceService.java new file mode 100644 index 0000000..47c95ef --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/IWorkflowWorkspaceService.java @@ -0,0 +1,8 @@ +package com.bruce.workflow.service; + +import com.bruce.workflow.vo.WorkflowWorkspaceVO; + +public interface IWorkflowWorkspaceService { + + WorkflowWorkspaceVO getWorkspace(Long projectId, Long workflowId); +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/ProjectServiceImpl.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..b7925de --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/ProjectServiceImpl.java @@ -0,0 +1,82 @@ +package com.bruce.workflow.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.workflow.dto.ProjectSaveDTO; +import com.bruce.workflow.entity.StudioProject; +import com.bruce.workflow.factory.ProjectFactory; +import com.bruce.workflow.mapper.StudioProjectMapper; +import com.bruce.workflow.service.IProjectService; +import com.bruce.workflow.vo.ProjectVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ProjectServiceImpl extends ServiceImpl implements IProjectService { + + private final ProjectFactory projectFactory; + + @Override + public List listProjects() { + List result = projectFactory.toVOList(lambdaQuery() + .orderByAsc(StudioProject::getProjectCode) + .list()); + log.info("查询Studio项目列表完成,count={}", result.size()); + return result; + } + + @Override + public ProjectVO getProject(Long id) { + if (id == null) { + throw new IllegalArgumentException("项目ID不能为空"); + } + ProjectVO result = projectFactory.toVO(getById(id)); + log.info("查询Studio项目详情完成,projectId={}, found={}", id, result != null); + return result; + } + + @Override + public boolean saveProject(ProjectSaveDTO request) { + validateRequest(request); + String projectCode = request.getProjectCode().trim(); + StudioProject duplicate = lambdaQuery() + .eq(StudioProject::getProjectCode, projectCode) + .ne(request.getId() != null, StudioProject::getId, request.getId()) + .one(); + if (duplicate != null) { + throw new IllegalArgumentException("项目编码已存在: " + projectCode); + } + StudioProject requestEntity = projectFactory.toEntity(request); + StudioProject entity = request.getId() == null ? new StudioProject() : getById(request.getId()); + if (entity == null) { + throw new IllegalArgumentException("项目不存在,ID: " + request.getId()); + } + entity.setProjectCode(requestEntity.getProjectCode()); + entity.setProjectName(requestEntity.getProjectName()); + entity.setEnvironment(StringUtils.hasText(requestEntity.getEnvironment()) ? requestEntity.getEnvironment() : "DEV"); + entity.setPublishStatus(StringUtils.hasText(requestEntity.getPublishStatus()) ? requestEntity.getPublishStatus() : "DRAFT"); + entity.setCurrentVersion(requestEntity.getCurrentVersion()); + entity.setRemark(requestEntity.getRemark()); + boolean result = request.getId() == null ? save(entity) : updateById(entity); + log.info("保存Studio项目完成,projectId={}, projectCode={}, result={}", + entity.getId(), entity.getProjectCode(), result); + return result; + } + + private void validateRequest(ProjectSaveDTO request) { + if (request == null) { + throw new IllegalArgumentException("项目保存请求不能为空"); + } + if (!StringUtils.hasText(request.getProjectCode())) { + throw new IllegalArgumentException("项目编码不能为空"); + } + if (!StringUtils.hasText(request.getProjectName())) { + throw new IllegalArgumentException("项目名称不能为空"); + } + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowDefinitionServiceImpl.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowDefinitionServiceImpl.java index 88c29a2..e645119 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowDefinitionServiceImpl.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowDefinitionServiceImpl.java @@ -10,6 +10,7 @@ import com.bruce.workflow.vo.WorkflowDefinitionVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.util.List; @@ -33,6 +34,22 @@ public class WorkflowDefinitionServiceImpl extends ServiceImpl listByProjectId(Long projectId) { + if (projectId == null) { + throw new IllegalArgumentException("项目ID不能为空"); + } + List result = lambdaQuery() + .eq(WorkflowDefinition::getProjectId, projectId) + .orderByAsc(WorkflowDefinition::getWorkflowCode) + .list() + .stream() + .map(workflowDefinitionFactory::toVO) + .toList(); + log.info("按项目查询Workflow定义完成,projectId={}, count={}", projectId, result.size()); + return result; + } + @Override public WorkflowDefinitionVO getDefinition(Long id) { log.info("查询Workflow定义详情开始,workflowId={}", id); @@ -45,11 +62,47 @@ public class WorkflowDefinitionServiceImpl extends ServiceImpl implements IWorkflowRunService { + + private final WorkflowRunFactory workflowRunFactory; + + @Override + public boolean createRun(WorkflowRunCreateDTO request) { + validateRequest(request); + WorkflowRun entity = workflowRunFactory.toEntity(request); + entity.setStatus(StringUtils.hasText(entity.getStatus()) ? entity.getStatus() : "RUNNING"); + entity.setRunSource(StringUtils.hasText(entity.getRunSource()) ? entity.getRunSource() : "MANUAL_TEST"); + if (!StringUtils.hasText(entity.getInputJson())) { + entity.setInputJson("{}"); + } + if (!StringUtils.hasText(entity.getOutputJson())) { + entity.setOutputJson("{}"); + } + boolean result = save(entity); + log.info("Workflow运行记录创建完成,workflowId={}, versionId={}, requestId={}, result={}", + entity.getWorkflowId(), entity.getWorkflowVersionId(), entity.getRequestId(), result); + return result; + } + + @Override + public List listRecentByWorkflowId(Long workflowId) { + if (workflowId == null) { + throw new IllegalArgumentException("Workflow ID不能为空"); + } + List runs = lambdaQuery() + .eq(WorkflowRun::getWorkflowId, workflowId) + .orderByDesc(WorkflowRun::getId) + .last("limit 10") + .list(); + return workflowRunFactory.toVOList(runs); + } + + private void validateRequest(WorkflowRunCreateDTO request) { + if (request == null) { + throw new IllegalArgumentException("Workflow运行请求不能为空"); + } + if (request.getWorkflowId() == null) { + throw new IllegalArgumentException("Workflow ID不能为空"); + } + if (request.getWorkflowVersionId() == null) { + throw new IllegalArgumentException("Workflow版本ID不能为空"); + } + if (!StringUtils.hasText(request.getRequestId())) { + throw new IllegalArgumentException("requestId不能为空"); + } + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowVersionServiceImpl.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowVersionServiceImpl.java new file mode 100644 index 0000000..6240321 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowVersionServiceImpl.java @@ -0,0 +1,124 @@ +package com.bruce.workflow.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.entity.WorkflowDefinition; +import com.bruce.workflow.entity.WorkflowVersion; +import com.bruce.workflow.factory.WorkflowVersionFactory; +import com.bruce.workflow.mapper.WorkflowVersionMapper; +import com.bruce.workflow.service.IWorkflowDefinitionService; +import com.bruce.workflow.service.IWorkflowVersionService; +import com.bruce.workflow.vo.WorkflowVersionVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WorkflowVersionServiceImpl extends ServiceImpl implements IWorkflowVersionService { + + private final IWorkflowDefinitionService workflowDefinitionService; + private final IAgentDefinitionService agentDefinitionService; + private final WorkflowVersionFactory workflowVersionFactory; + + @Override + public boolean publishVersion(WorkflowVersionSaveDTO request) { + validateRequest(request); + WorkflowDefinition definition = loadDefinition(request.getWorkflowId()); + WorkflowVersion duplicate = findByWorkflowAndVersionNo(request.getWorkflowId(), request.getVersionNo()); + if (duplicate != null) { + throw new IllegalArgumentException("发布版本号已存在: " + request.getVersionNo()); + } + validateDefinitionBinding(definition); + validateGraphJson(request.getGraphJson()); + + WorkflowVersion entity = loadFactory().toEntity(request); + entity.setPublishStatus(StringUtils.hasText(entity.getPublishStatus()) ? entity.getPublishStatus() : "PUBLISHED"); + entity.setPublishedTime(LocalDateTime.now()); + boolean result = save(entity); + log.info("Workflow版本发布完成,workflowId={}, versionNo={}, result={}", + entity.getWorkflowId(), entity.getVersionNo(), result); + return result; + } + + @Override + public List listByWorkflowId(Long workflowId) { + if (workflowId == null) { + throw new IllegalArgumentException("Workflow ID不能为空"); + } + return loadFactory().toVOList(lambdaQuery() + .eq(WorkflowVersion::getWorkflowId, workflowId) + .orderByDesc(WorkflowVersion::getVersionNo) + .list()); + } + + public WorkflowDefinition loadDefinition(Long workflowId) { + WorkflowDefinition definition = workflowDefinitionService.getById(workflowId); + if (definition == null) { + throw new IllegalArgumentException("Workflow定义不存在,ID: " + workflowId); + } + return definition; + } + + public WorkflowVersion findByWorkflowAndVersionNo(Long workflowId, Integer versionNo) { + if (baseMapper == null) { + return null; + } + return lambdaQuery() + .eq(WorkflowVersion::getWorkflowId, workflowId) + .eq(WorkflowVersion::getVersionNo, versionNo) + .one(); + } + + private void validateDefinitionBinding(WorkflowDefinition definition) { + if (definition.getBoundAgentId() == null) { + return; + } + AgentDefinition agent = agentDefinitionService.getById(definition.getBoundAgentId()); + if (agent == null) { + throw new IllegalArgumentException("绑定Agent不存在,ID: " + definition.getBoundAgentId()); + } + if (!"ENABLED".equals(agent.getStatus())) { + throw new IllegalArgumentException("绑定Agent未启用,无法发布"); + } + } + + /** + * 当前版本先做最小图结构校验,确保至少有节点定义,后续节点级配置校验会在运行器接入后继续增强。 + */ + private void validateGraphJson(String graphJson) { + if (!StringUtils.hasText(graphJson)) { + throw new IllegalArgumentException("graphJson不能为空"); + } + String normalized = graphJson.trim(); + if (!normalized.startsWith("{") || !normalized.contains("\"nodes\"")) { + throw new IllegalArgumentException("graphJson结构非法,至少需要包含nodes"); + } + } + + private void validateRequest(WorkflowVersionSaveDTO request) { + if (request == null) { + throw new IllegalArgumentException("Workflow版本保存请求不能为空"); + } + if (request.getWorkflowId() == null) { + throw new IllegalArgumentException("Workflow ID不能为空"); + } + if (request.getVersionNo() == null) { + throw new IllegalArgumentException("版本号不能为空"); + } + if (!StringUtils.hasText(request.getSnapshotName())) { + throw new IllegalArgumentException("快照名称不能为空"); + } + } + + private WorkflowVersionFactory loadFactory() { + return workflowVersionFactory == null ? new WorkflowVersionFactory() : workflowVersionFactory; + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowWorkspaceServiceImpl.java b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowWorkspaceServiceImpl.java new file mode 100644 index 0000000..9685d44 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/service/impl/WorkflowWorkspaceServiceImpl.java @@ -0,0 +1,88 @@ +package com.bruce.workflow.service.impl; + +import com.bruce.workflow.service.IProjectService; +import com.bruce.workflow.service.IWorkflowDefinitionService; +import com.bruce.workflow.service.IWorkflowRunService; +import com.bruce.workflow.service.IWorkflowVersionService; +import com.bruce.workflow.service.IWorkflowWorkspaceService; +import com.bruce.workflow.vo.ProjectVO; +import com.bruce.workflow.vo.WorkflowDefinitionVO; +import com.bruce.workflow.vo.WorkflowRunVO; +import com.bruce.workflow.vo.WorkflowVersionVO; +import com.bruce.workflow.vo.WorkflowWorkspaceVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WorkflowWorkspaceServiceImpl implements IWorkflowWorkspaceService { + + private final IProjectService projectService; + private final IWorkflowDefinitionService workflowDefinitionService; + private final IWorkflowVersionService workflowVersionService; + private final IWorkflowRunService workflowRunService; + + @Override + public WorkflowWorkspaceVO getWorkspace(Long projectId, Long workflowId) { + log.info("Workflow工作台查询开始,projectId={}, workflowId={}", projectId, workflowId); + if (projectId == null) { + throw new IllegalArgumentException("项目ID不能为空"); + } + ProjectVO project = projectService.getProject(projectId); + if (project == null) { + throw new IllegalArgumentException("项目不存在,ID: " + projectId); + } + + List workflows = workflowDefinitionService.listByProjectId(projectId); + WorkflowDefinitionVO currentWorkflow = resolveWorkflow(workflowId, workflows); + List versions = currentWorkflow == null ? List.of() : workflowVersionService.listByWorkflowId(currentWorkflow.getId()); + List runs = currentWorkflow == null ? List.of() : workflowRunService.listRecentByWorkflowId(currentWorkflow.getId()); + + WorkflowWorkspaceVO workspace = new WorkflowWorkspaceVO(); + workspace.setProjectId(project.getId()); + workspace.setProjectCode(project.getProjectCode()); + workspace.setProjectName(project.getProjectName()); + workspace.setEnvironment(project.getEnvironment()); + workspace.setPublishStatus(project.getPublishStatus()); + workspace.setWorkflows(workflows); + workspace.setVersions(versions); + workspace.setRecentRuns(runs); + + if (currentWorkflow != null) { + workspace.setWorkflowId(currentWorkflow.getId()); + workspace.setWorkflowCode(currentWorkflow.getWorkflowCode()); + workspace.setWorkflowName(currentWorkflow.getWorkflowName()); + workspace.setWorkflowStatus(currentWorkflow.getStatus()); + } + WorkflowVersionVO currentPublishedVersion = versions.stream() + .filter(version -> "PUBLISHED".equals(version.getPublishStatus())) + .findFirst() + .orElse(null); + if (currentPublishedVersion != null) { + workspace.setCurrentPublishedVersionNo(currentPublishedVersion.getVersionNo()); + } + WorkflowRunVO latestRun = runs.isEmpty() ? null : runs.getFirst(); + if (latestRun != null) { + workspace.setLatestRequestId(latestRun.getRequestId()); + workspace.setLatestDurationMs(latestRun.getDurationMs()); + } + log.info("Workflow工作台查询结束,projectId={}, workflowId={}, versionCount={}, recentRunCount={}", + projectId, workspace.getWorkflowId(), versions.size(), runs.size()); + return workspace; + } + + private WorkflowDefinitionVO resolveWorkflow(Long workflowId, List workflows) { + if (workflows == null || workflows.isEmpty()) { + return null; + } + if (workflowId == null) { + return workflows.getFirst(); + } + return workflows.stream().filter(item -> workflowId.equals(item.getId())).findFirst().orElse(null); + } +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/ProjectVO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/ProjectVO.java new file mode 100644 index 0000000..51de922 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/ProjectVO.java @@ -0,0 +1,14 @@ +package com.bruce.workflow.vo; + +import lombok.Data; + +@Data +public class ProjectVO { + private Long id; + private String projectCode; + private String projectName; + private String environment; + private String publishStatus; + private String currentVersion; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowDefinitionVO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowDefinitionVO.java index afc5cbf..d1c7993 100644 --- a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowDefinitionVO.java +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowDefinitionVO.java @@ -21,4 +21,6 @@ public class WorkflowDefinitionVO { private String status; private String description; + + private String remark; } diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowRunVO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowRunVO.java new file mode 100644 index 0000000..79f48a9 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowRunVO.java @@ -0,0 +1,21 @@ +package com.bruce.workflow.vo; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class WorkflowRunVO { + private Long id; + private Long workflowId; + private Long workflowVersionId; + private Long agentId; + private String requestId; + private String runSource; + private String status; + private String inputJson; + private String outputJson; + private Integer durationMs; + private BigDecimal estimatedCost; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowVersionVO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowVersionVO.java new file mode 100644 index 0000000..868c0f4 --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowVersionVO.java @@ -0,0 +1,17 @@ +package com.bruce.workflow.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class WorkflowVersionVO { + private Long id; + private Long workflowId; + private Integer versionNo; + private String snapshotName; + private String graphJson; + private String publishStatus; + private LocalDateTime publishedTime; + private String remark; +} diff --git a/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowWorkspaceVO.java b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowWorkspaceVO.java new file mode 100644 index 0000000..dec5b3b --- /dev/null +++ b/common-agent-workflow/src/main/java/com/bruce/workflow/vo/WorkflowWorkspaceVO.java @@ -0,0 +1,24 @@ +package com.bruce.workflow.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class WorkflowWorkspaceVO { + private Long projectId; + private String projectCode; + private String projectName; + private String environment; + private String publishStatus; + private Long workflowId; + private String workflowCode; + private String workflowName; + private String workflowStatus; + private Integer currentPublishedVersionNo; + private String latestRequestId; + private Integer latestDurationMs; + private List workflows; + private List versions; + private List recentRuns; +} diff --git a/common-agent-workflow/src/test/java/com/bruce/workflow/factory/WorkflowFactoryTests.java b/common-agent-workflow/src/test/java/com/bruce/workflow/factory/WorkflowFactoryTests.java new file mode 100644 index 0000000..ff4df94 --- /dev/null +++ b/common-agent-workflow/src/test/java/com/bruce/workflow/factory/WorkflowFactoryTests.java @@ -0,0 +1,64 @@ +package com.bruce.workflow.factory; + +import com.bruce.workflow.dto.ProjectSaveDTO; +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.entity.StudioProject; +import com.bruce.workflow.entity.WorkflowVersion; +import com.bruce.workflow.vo.ProjectVO; +import com.bruce.workflow.vo.WorkflowVersionVO; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class WorkflowFactoryTests { + + private final ProjectFactory projectFactory = new ProjectFactory(); + private final WorkflowVersionFactory workflowVersionFactory = new WorkflowVersionFactory(); + + @Test + void projectFactoryShouldTrimRequestAndBuildVo() { + ProjectSaveDTO request = new ProjectSaveDTO(); + request.setId(11L); + request.setProjectCode(" STUDIO_DEMO "); + request.setProjectName(" 演示项目 "); + request.setEnvironment(" DEV "); + request.setPublishStatus(" DRAFT "); + request.setCurrentVersion(" v1 "); + request.setRemark(" 初始项目 "); + + StudioProject entity = projectFactory.toEntity(request); + assertEquals("STUDIO_DEMO", entity.getProjectCode()); + assertEquals("演示项目", entity.getProjectName()); + assertEquals("DEV", entity.getEnvironment()); + assertEquals("DRAFT", entity.getPublishStatus()); + assertEquals("v1", entity.getCurrentVersion()); + assertEquals("初始项目", entity.getRemark()); + + ProjectVO vo = projectFactory.toVO(entity); + assertEquals(11L, vo.getId()); + assertEquals("STUDIO_DEMO", vo.getProjectCode()); + } + + @Test + void workflowVersionFactoryShouldPreserveGraphJsonAndPublishStatus() { + WorkflowVersionSaveDTO request = new WorkflowVersionSaveDTO(); + request.setWorkflowId(1001L); + request.setVersionNo(7); + request.setSnapshotName(" 知识问答发布版 "); + request.setGraphJson(" {\"nodes\":[{\"id\":\"start\"}]} "); + request.setPublishStatus(" PUBLISHED "); + request.setRemark(" 首次发布 "); + + WorkflowVersion entity = workflowVersionFactory.toEntity(request); + assertNotNull(entity); + assertEquals("知识问答发布版", entity.getSnapshotName()); + assertEquals("{\"nodes\":[{\"id\":\"start\"}]}", entity.getGraphJson()); + assertEquals("PUBLISHED", entity.getPublishStatus()); + + WorkflowVersionVO vo = workflowVersionFactory.toVO(entity); + assertEquals(1001L, vo.getWorkflowId()); + assertEquals(7, vo.getVersionNo()); + assertEquals("PUBLISHED", vo.getPublishStatus()); + } +} diff --git a/common-agent-workflow/src/test/java/com/bruce/workflow/version/WorkflowVersionServiceTests.java b/common-agent-workflow/src/test/java/com/bruce/workflow/version/WorkflowVersionServiceTests.java new file mode 100644 index 0000000..83c5e6a --- /dev/null +++ b/common-agent-workflow/src/test/java/com/bruce/workflow/version/WorkflowVersionServiceTests.java @@ -0,0 +1,91 @@ +package com.bruce.workflow.version; + +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.workflow.dto.WorkflowVersionSaveDTO; +import com.bruce.workflow.entity.WorkflowDefinition; +import com.bruce.workflow.entity.WorkflowVersion; +import com.bruce.workflow.service.IWorkflowDefinitionService; +import com.bruce.workflow.service.impl.WorkflowVersionServiceImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class WorkflowVersionServiceTests { + + @Mock + private IWorkflowDefinitionService workflowDefinitionService; + + @Mock + private IAgentDefinitionService agentDefinitionService; + + @Spy + @InjectMocks + private WorkflowVersionServiceImpl workflowVersionService; + + @Test + void publishShouldRejectDuplicateVersionNo() { + WorkflowDefinition definition = new WorkflowDefinition(); + definition.setId(1001L); + definition.setWorkflowCode("WF_RAG_SUPPORT"); + definition.setBoundAgentId(2001L); + doReturn(definition).when(workflowVersionService).loadDefinition(1001L); + + WorkflowVersion duplicate = new WorkflowVersion(); + duplicate.setId(3001L); + doReturn(duplicate).when(workflowVersionService).findByWorkflowAndVersionNo(1001L, 2); + + WorkflowVersionSaveDTO request = new WorkflowVersionSaveDTO(); + request.setWorkflowId(1001L); + request.setVersionNo(2); + request.setSnapshotName("版本2"); + request.setGraphJson("{\"nodes\":[{\"id\":\"start\",\"type\":\"START\"}]}"); + + assertThrows(IllegalArgumentException.class, () -> workflowVersionService.publishVersion(request)); + } + + @Test + void publishShouldPersistPublishedSnapshot() { + WorkflowDefinition definition = new WorkflowDefinition(); + definition.setId(1001L); + definition.setWorkflowCode("WF_RAG_SUPPORT"); + definition.setBoundAgentId(2001L); + doReturn(definition).when(workflowVersionService).loadDefinition(1001L); + doReturn(null).when(workflowVersionService).findByWorkflowAndVersionNo(1001L, 3); + doAnswer(invocation -> true).when(workflowVersionService).save(any(WorkflowVersion.class)); + + AgentDefinition agent = new AgentDefinition(); + agent.setId(2001L); + agent.setStatus("ENABLED"); + when(agentDefinitionService.getById(2001L)).thenReturn(agent); + + WorkflowVersionSaveDTO request = new WorkflowVersionSaveDTO(); + request.setWorkflowId(1001L); + request.setVersionNo(3); + request.setSnapshotName("知识问答正式版"); + request.setGraphJson("{\"nodes\":[{\"id\":\"start\",\"type\":\"START\"},{\"id\":\"answer\",\"type\":\"ANSWER\"}],\"edges\":[{\"from\":\"start\",\"to\":\"answer\"}]}"); + request.setRemark("正式发布"); + + boolean result = workflowVersionService.publishVersion(request); + assertTrue(result); + + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkflowVersion.class); + verify(workflowVersionService).save(captor.capture()); + assertEquals("PUBLISHED", captor.getValue().getPublishStatus()); + assertEquals("知识问答正式版", captor.getValue().getSnapshotName()); + } +} diff --git a/common-agent-workflow/src/test/java/com/bruce/workflow/workspace/WorkflowWorkspaceServiceTests.java b/common-agent-workflow/src/test/java/com/bruce/workflow/workspace/WorkflowWorkspaceServiceTests.java new file mode 100644 index 0000000..d3786a9 --- /dev/null +++ b/common-agent-workflow/src/test/java/com/bruce/workflow/workspace/WorkflowWorkspaceServiceTests.java @@ -0,0 +1,92 @@ +package com.bruce.workflow.workspace; + +import com.bruce.workflow.entity.StudioProject; +import com.bruce.workflow.service.IProjectService; +import com.bruce.workflow.service.IWorkflowDefinitionService; +import com.bruce.workflow.service.IWorkflowRunService; +import com.bruce.workflow.service.IWorkflowVersionService; +import com.bruce.workflow.service.impl.WorkflowWorkspaceServiceImpl; +import com.bruce.workflow.vo.ProjectVO; +import com.bruce.workflow.vo.WorkflowDefinitionVO; +import com.bruce.workflow.vo.WorkflowRunVO; +import com.bruce.workflow.vo.WorkflowVersionVO; +import com.bruce.workflow.vo.WorkflowWorkspaceVO; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class WorkflowWorkspaceServiceTests { + + @Mock + private IProjectService projectService; + + @Mock + private IWorkflowDefinitionService workflowDefinitionService; + + @Mock + private IWorkflowVersionService workflowVersionService; + + @Mock + private IWorkflowRunService workflowRunService; + + @InjectMocks + private WorkflowWorkspaceServiceImpl workflowWorkspaceService; + + @Test + void getWorkspaceShouldAggregateProjectDefinitionsVersionsAndRuns() { + ProjectVO project = new ProjectVO(); + project.setId(101L); + project.setProjectCode("STUDIO_DEMO"); + project.setProjectName("演示项目"); + project.setEnvironment("DEV"); + project.setPublishStatus("DRAFT"); + + WorkflowDefinitionVO definition = new WorkflowDefinitionVO(); + definition.setId(201L); + definition.setProjectId(101L); + definition.setWorkflowCode("WF_RAG_SUPPORT"); + definition.setWorkflowName("知识问答流程"); + definition.setStatus("ENABLED"); + + WorkflowVersionVO version = new WorkflowVersionVO(); + version.setId(301L); + version.setWorkflowId(201L); + version.setVersionNo(7); + version.setPublishStatus("PUBLISHED"); + version.setSnapshotName("发布版v7"); + + WorkflowRunVO run = new WorkflowRunVO(); + run.setId(401L); + run.setWorkflowId(201L); + run.setWorkflowVersionId(301L); + run.setRequestId("req-wf-001"); + run.setStatus("SUCCESS"); + run.setDurationMs(1280); + run.setEstimatedCost(new BigDecimal("0.018")); + + when(projectService.getProject(101L)).thenReturn(project); + when(workflowDefinitionService.listByProjectId(101L)).thenReturn(List.of(definition)); + when(workflowVersionService.listByWorkflowId(201L)).thenReturn(List.of(version)); + when(workflowRunService.listRecentByWorkflowId(201L)).thenReturn(List.of(run)); + + WorkflowWorkspaceVO workspace = workflowWorkspaceService.getWorkspace(101L, 201L); + + assertNotNull(workspace); + assertEquals("STUDIO_DEMO", workspace.getProjectCode()); + assertEquals("WF_RAG_SUPPORT", workspace.getWorkflowCode()); + assertEquals(1, workspace.getRecentRuns().size()); + assertEquals("req-wf-001", workspace.getLatestRequestId()); + assertEquals(1280, workspace.getLatestDurationMs()); + assertEquals(7, workspace.getCurrentPublishedVersionNo()); + } +} diff --git a/frontend/src/api/__tests__/workflow.spec.ts b/frontend/src/api/__tests__/workflow.spec.ts new file mode 100644 index 0000000..be691cf --- /dev/null +++ b/frontend/src/api/__tests__/workflow.spec.ts @@ -0,0 +1,44 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + getWorkflowDefinition, + getWorkflowWorkspace, + listWorkflowDefinitions, + saveWorkflowDefinition, +} from '../workflow'; +import { get, post } from '../request'; + +vi.mock('../request', () => ({ + get: vi.fn(), + post: vi.fn(), +})); + +describe('workflow api', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('maps workflow endpoints correctly', () => { + listWorkflowDefinitions(); + getWorkflowDefinition('1001'); + saveWorkflowDefinition({ + projectId: '2001', + workflowCode: 'WF_RAG_SUPPORT', + workflowName: '知识问答流程', + status: 'DRAFT', + }); + getWorkflowWorkspace('2001', '1001'); + + expect(get).toHaveBeenCalledWith('/workflows/definitions'); + expect(get).toHaveBeenCalledWith('/workflows/definition/detail', { params: { id: '1001' } }); + expect(post).toHaveBeenCalledWith('/workflows/definition/save', { + projectId: '2001', + workflowCode: 'WF_RAG_SUPPORT', + workflowName: '知识问答流程', + status: 'DRAFT', + }); + expect(get).toHaveBeenCalledWith('/workflow-workspace/detail', { + params: { projectId: '2001', workflowId: '1001' }, + }); + }); +}); diff --git a/frontend/src/api/workflow.ts b/frontend/src/api/workflow.ts new file mode 100644 index 0000000..67a6e62 --- /dev/null +++ b/frontend/src/api/workflow.ts @@ -0,0 +1,87 @@ +import { get, post } from './request'; + +export interface ProjectRecord { + id?: string; + projectCode: string; + projectName: string; + environment?: string; + publishStatus?: string; + currentVersion?: string; + remark?: string; +} + +export interface WorkflowDefinitionRecord { + id?: string; + projectId: string; + workflowCode: string; + workflowName: string; + description?: string; + boundAgentId?: string; + status?: string; + remark?: string; +} + +export interface WorkflowVersionRecord { + id?: string; + workflowId: string; + versionNo: number; + snapshotName: string; + graphJson: string; + publishStatus?: string; + publishedTime?: string; + remark?: string; +} + +export interface WorkflowRunRecord { + id?: string; + workflowId: string; + workflowVersionId: string; + agentId?: string; + requestId: string; + runSource?: string; + status?: string; + inputJson?: string; + outputJson?: string; + durationMs?: number; + estimatedCost?: number; + remark?: string; +} + +export interface WorkflowWorkspace { + projectId: string; + projectCode: string; + projectName: string; + environment?: string; + publishStatus?: string; + workflowId?: string; + workflowCode?: string; + workflowName?: string; + workflowStatus?: string; + currentPublishedVersionNo?: number; + latestRequestId?: string; + latestDurationMs?: number; + workflows: WorkflowDefinitionRecord[]; + versions: WorkflowVersionRecord[]; + recentRuns: WorkflowRunRecord[]; +} + +export function listWorkflowDefinitions() { + return get('/workflows/definitions'); +} + +export function getWorkflowDefinition(id: string) { + return get('/workflows/definition/detail', { params: { id } }); +} + +export function saveWorkflowDefinition(data: WorkflowDefinitionRecord) { + return post('/workflows/definition/save', data); +} + +export function getWorkflowWorkspace(projectId: string, workflowId?: string) { + return get('/workflow-workspace/detail', { + params: { + projectId, + workflowId, + }, + }); +}