增加工作流代码可正常启动
This commit is contained in:
parent
ec59608cc3
commit
dde2ad27ae
362
backend/docs/frontend-guide.md
Normal file
362
backend/docs/frontend-guide.md
Normal file
@ -0,0 +1,362 @@
|
||||
# 工作流平台前端开发指南
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
### 1.1 技术栈
|
||||
- 框架:Ant Design Pro
|
||||
- 语言:TypeScript
|
||||
- HTTP请求:Umi Request
|
||||
- 流程设计器:LogicFlow
|
||||
- 表单设计器:FormRender
|
||||
- 图表库:ECharts
|
||||
|
||||
### 1.2 开发环境
|
||||
- Node.js >= 16
|
||||
- yarn >= 1.22
|
||||
- TypeScript >= 4.x
|
||||
|
||||
## 二、接口定义
|
||||
|
||||
### 2.1 工作流定义管理
|
||||
```typescript
|
||||
// 工作流定义相关接口
|
||||
interface WorkflowDefinitionAPI {
|
||||
// 基础CRUD接口
|
||||
list: '/api/v1/workflow-definitions' // GET 查询列表
|
||||
create: '/api/v1/workflow-definitions' // POST 创建
|
||||
update: '/api/v1/workflow-definitions/{id}' // PUT 更新
|
||||
delete: '/api/v1/workflow-definitions/{id}' // DELETE 删除
|
||||
get: '/api/v1/workflow-definitions/{id}' // GET 获取详情
|
||||
|
||||
// 特殊操作接口
|
||||
publish: '/api/v1/workflow-definitions/{id}/publish' // POST 发布
|
||||
disable: '/api/v1/workflow-definitions/{id}/disable' // POST 禁用
|
||||
enable: '/api/v1/workflow-definitions/{id}/enable' // POST 启用
|
||||
}
|
||||
|
||||
// 工作流定义数据结构
|
||||
interface WorkflowDefinitionDTO {
|
||||
id: number
|
||||
code: string // 工作流编码
|
||||
name: string // 工作流名称
|
||||
description: string // 描述
|
||||
status: 'DRAFT' | 'PUBLISHED' | 'DISABLED' // 状态
|
||||
version: number // 版本号
|
||||
nodeConfig: string // 节点配置(JSON)
|
||||
transitionConfig: string // 流转配置(JSON)
|
||||
formDefinition: string // 表单定义
|
||||
graphDefinition: string // 图形信息
|
||||
enabled: boolean // 是否启用
|
||||
remark: string // 备注
|
||||
nodes: NodeDefinitionDTO[] // 节点定义列表
|
||||
}
|
||||
|
||||
// 节点定义数据结构
|
||||
interface NodeDefinitionDTO {
|
||||
id: number
|
||||
nodeId: string // 节点ID
|
||||
name: string // 节点名称
|
||||
type: 'START' | 'END' | 'TASK' | 'GATEWAY' | 'SUB_PROCESS' | 'SHELL' // 节点类型
|
||||
config: string // 节点配置(JSON)
|
||||
description: string // 节点描述
|
||||
workflowDefinitionId: number // 工作流定义ID
|
||||
orderNum: number // 排序号
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 工作流实例管理
|
||||
```typescript
|
||||
// 工作流实例相关接口
|
||||
interface WorkflowInstanceAPI {
|
||||
// 基础CRUD接口
|
||||
list: '/api/v1/workflow-instance' // GET 查询列表
|
||||
get: '/api/v1/workflow-instance/{id}' // GET 获取详情
|
||||
|
||||
// 实例操作接口
|
||||
create: '/api/v1/workflow-instance' // POST 创建实例
|
||||
start: '/api/v1/workflow-instance/{id}/start' // POST 启动
|
||||
cancel: '/api/v1/workflow-instance/{id}/cancel' // POST 取消
|
||||
pause: '/api/v1/workflow-instance/{id}/pause' // POST 暂停
|
||||
resume: '/api/v1/workflow-instance/{id}/resume' // POST 恢复
|
||||
}
|
||||
|
||||
// 工作流实例数据结构
|
||||
interface WorkflowInstanceDTO {
|
||||
id: number
|
||||
definitionId: number // 工作流定义ID
|
||||
businessKey: string // 业务标识
|
||||
status: 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'PAUSED' // 状态
|
||||
startTime: string // 开始时间
|
||||
endTime: string // 结束时间
|
||||
variables: string // 工作流变量(JSON)
|
||||
error: string // 错误信息
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 节点实例管理
|
||||
```typescript
|
||||
// 节点实例相关接口
|
||||
interface NodeInstanceAPI {
|
||||
// 查询接口
|
||||
list: '/api/v1/node-instance' // GET 查询列表
|
||||
get: '/api/v1/node-instance/{id}' // GET 获取详情
|
||||
findByWorkflow: '/api/v1/node-instance/workflow/{workflowInstanceId}' // GET 查询工作流下的节点
|
||||
findByStatus: '/api/v1/node-instance/workflow/{workflowInstanceId}/status/{status}' // GET 查询指定状态的节点
|
||||
|
||||
// 节点操作
|
||||
updateStatus: '/api/v1/node-instance/{id}/status' // PUT 更新状态
|
||||
}
|
||||
|
||||
// 节点实例数据结构
|
||||
interface NodeInstanceDTO {
|
||||
id: number
|
||||
workflowInstanceId: number // 工作流实例ID
|
||||
nodeId: string // 节点ID
|
||||
nodeType: 'START' | 'END' | 'TASK' | 'GATEWAY' | 'SUB_PROCESS' | 'SHELL' // 节点类型
|
||||
name: string // 节点名称
|
||||
status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'PAUSED' | 'SKIPPED' // 状态
|
||||
startTime: string // 开始时间
|
||||
endTime: string // 结束时间
|
||||
config: string // 节点配置(JSON)
|
||||
input: string // 输入参数(JSON)
|
||||
output: string // 输出结果(JSON)
|
||||
error: string // 错误信息
|
||||
preNodeId: string // 前置节点ID
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 工作流日志管理
|
||||
```typescript
|
||||
// 日志相关接口
|
||||
interface WorkflowLogAPI {
|
||||
// 基础CRUD接口
|
||||
list: '/api/v1/workflow-logs' // GET 查询列表
|
||||
create: '/api/v1/workflow-logs' // POST 创建
|
||||
|
||||
// 特殊查询接口
|
||||
getWorkflowLogs: '/api/v1/workflow-logs/workflow/{workflowInstanceId}' // GET 查询工作流日志
|
||||
getNodeLogs: '/api/v1/workflow-logs/node/{workflowInstanceId}/{nodeId}' // GET 查询节点日志
|
||||
record: '/api/v1/workflow-logs/record' // POST 记录日志
|
||||
}
|
||||
|
||||
// 日志数据结构
|
||||
interface WorkflowLogDTO {
|
||||
id: number
|
||||
workflowInstanceId: number // 工作流实例ID
|
||||
nodeId: string // 节点ID
|
||||
message: string // 日志内容
|
||||
level: 'INFO' | 'WARN' | 'ERROR' // 日志级别
|
||||
detail: string // 详细信息
|
||||
createTime: string // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
## 三、页面功能
|
||||
|
||||
### 3.1 工作流定义管理(/workflow/definition)
|
||||
|
||||
#### 3.1.1 列表页(/workflow/definition/list)
|
||||
- 功能点:
|
||||
- 工作流定义列表展示
|
||||
- 支持按名称、编码、状态搜索
|
||||
- 支持创建、编辑、删除操作
|
||||
- 支持发布、禁用操作
|
||||
- 支持查看历史版本
|
||||
- 列表字段:
|
||||
- 工作流编码
|
||||
- 工作流名称
|
||||
- 版本号
|
||||
- 状态
|
||||
- 创建时间
|
||||
- 更新时间
|
||||
- 操作按钮
|
||||
|
||||
#### 3.1.2 编辑页(/workflow/definition/edit)
|
||||
- 功能点:
|
||||
1. 基本信息编辑
|
||||
- 工作流编码
|
||||
- 工作流名称
|
||||
- 描述
|
||||
- 备注
|
||||
2. 流程设计器
|
||||
- 节点拖拽
|
||||
- 连线绘制
|
||||
- 节点配置
|
||||
- 流程校验
|
||||
3. 表单设计器
|
||||
- 表单字段配置
|
||||
- 字段验证规则
|
||||
- 表单预览
|
||||
4. 节点配置面板
|
||||
- 节点基本信息
|
||||
- 节点类型配置
|
||||
- 执行条件配置
|
||||
- 表单关联配置
|
||||
|
||||
### 3.2 工作流实例管理(/workflow/instance)
|
||||
|
||||
#### 3.2.1 列表页(/workflow/instance/list)
|
||||
- 功能点:
|
||||
- 工作流实例列表展示
|
||||
- 支持按工作流名称、状态、时间搜索
|
||||
- 支持启动、暂停、恢复、取消操作
|
||||
- 列表字段:
|
||||
- 实例ID
|
||||
- 工作流名称
|
||||
- 业务标识
|
||||
- 状态
|
||||
- 开始时间
|
||||
- 结束时间
|
||||
- 操作按钮
|
||||
|
||||
#### 3.2.2 详情页(/workflow/instance/detail)
|
||||
- 功能点:
|
||||
1. 基本信息展示
|
||||
- 实例ID
|
||||
- 工作流名称
|
||||
- 状态
|
||||
- 时间信息
|
||||
2. 流程图展示
|
||||
- 显示当前节点
|
||||
- 节点状态标识
|
||||
- 流程进度展示
|
||||
3. 变量信息
|
||||
- 变量列表
|
||||
- 变量历史值
|
||||
4. 日志信息
|
||||
- 操作日志
|
||||
- 执行日志
|
||||
- 错误日志
|
||||
|
||||
### 3.3 监控大盘(/workflow/monitor)
|
||||
- 功能点:
|
||||
1. 统计信息
|
||||
- 总工作流数
|
||||
- 运行中实例数
|
||||
- 完成实例数
|
||||
- 失败实例数
|
||||
2. 状态分布
|
||||
- 饼图展示各状态占比
|
||||
- 支持时间范围筛选
|
||||
3. 执行时长统计
|
||||
- 平均执行时长
|
||||
- 最长执行时长
|
||||
- 执行时长分布
|
||||
4. 异常情况统计
|
||||
- 异常类型分布
|
||||
- 异常趋势图
|
||||
- 异常节点TOP5
|
||||
|
||||
## 四、开发规范
|
||||
|
||||
### 4.1 项目结构
|
||||
```
|
||||
src/
|
||||
├── components/ # 公共组件
|
||||
│ ├── FlowDesigner/ # 流程设计器组件
|
||||
│ ├── FormDesigner/ # 表单设计器组件
|
||||
│ └── NodeConfig/ # 节点配置组件
|
||||
├── pages/ # 页面组件
|
||||
│ └── workflow/ # 工作流相关页面
|
||||
├── services/ # API服务
|
||||
│ └── workflow/ # 工作流相关API
|
||||
├── models/ # 数据模型
|
||||
├── utils/ # 工具函数
|
||||
├── constants/ # 常量定义
|
||||
├── types/ # TypeScript类型
|
||||
└── locales/ # 国际化资源
|
||||
```
|
||||
|
||||
### 4.2 命名规范
|
||||
- 文件命名:使用 PascalCase
|
||||
- 组件文件:`FlowDesigner.tsx`
|
||||
- 类型文件:`WorkflowTypes.ts`
|
||||
- 工具文件:`FlowUtils.ts`
|
||||
- 变量命名:使用 camelCase
|
||||
- 普通变量:`flowInstance`
|
||||
- 布尔值:`isRunning`、`hasError`
|
||||
- 常量命名:使用 UPPER_CASE
|
||||
- `MAX_NODE_COUNT`
|
||||
- `DEFAULT_CONFIG`
|
||||
|
||||
### 4.3 组件开发规范
|
||||
1. 使用函数组件和 Hooks
|
||||
2. 使用 TypeScript 类型声明
|
||||
3. 添加必要的注释
|
||||
4. 实现错误处理
|
||||
5. 添加加载状态
|
||||
6. 做好性能优化
|
||||
|
||||
### 4.4 代码提交规范
|
||||
- feat: 新功能
|
||||
- fix: 修复bug
|
||||
- docs: 文档更新
|
||||
- style: 代码格式
|
||||
- refactor: 重构
|
||||
- test: 测试
|
||||
- chore: 构建过程或辅助工具的变动
|
||||
|
||||
## 五、开发流程
|
||||
|
||||
### 5.1 环境搭建
|
||||
1. 创建项目
|
||||
```bash
|
||||
yarn create umi
|
||||
```
|
||||
|
||||
2. 安装依赖
|
||||
```bash
|
||||
yarn add @ant-design/pro-components @logicflow/core @logicflow/extension form-render echarts
|
||||
```
|
||||
|
||||
### 5.2 开发步骤
|
||||
1. 搭建基础框架和路由(2天)
|
||||
2. 实现工作流定义CRUD(3天)
|
||||
3. 开发流程设计器(5天)
|
||||
4. 开发表单设计器(3天)
|
||||
5. 实现工作流实例管理(3天)
|
||||
6. 实现节点实例管理(2天)
|
||||
7. 实现日志管理(2天)
|
||||
8. 开发监控大盘(3天)
|
||||
9. 测试和优化(2天)
|
||||
|
||||
### 5.3 测试要求
|
||||
1. 单元测试覆盖率 > 80%
|
||||
2. 必须包含组件测试
|
||||
3. 必须包含集成测试
|
||||
4. 必须进行性能测试
|
||||
|
||||
### 5.4 部署要求
|
||||
1. 使用 Docker 部署
|
||||
2. 配置 Nginx 代理
|
||||
3. 启用 GZIP 压缩
|
||||
4. 配置缓存策略
|
||||
|
||||
## 六、注意事项
|
||||
|
||||
### 6.1 性能优化
|
||||
1. 使用路由懒加载
|
||||
2. 组件按需加载
|
||||
3. 大数据列表虚拟化
|
||||
4. 合理使用缓存
|
||||
5. 避免不必要的渲染
|
||||
|
||||
### 6.2 安全性
|
||||
1. 添加权限控制
|
||||
2. 防止XSS攻击
|
||||
3. 添加数据验证
|
||||
4. 敏感信息加密
|
||||
|
||||
### 6.3 用户体验
|
||||
1. 添加适当的加载状态
|
||||
2. 提供操作反馈
|
||||
3. 添加错误处理
|
||||
4. 支持快捷键操作
|
||||
5. 添加操作确认
|
||||
6. 支持数据导出
|
||||
|
||||
### 6.4 兼容性
|
||||
1. 支持主流浏览器最新版本
|
||||
2. 支持响应式布局
|
||||
3. 支持暗黑模式
|
||||
4. 支持国际化
|
||||
@ -0,0 +1,84 @@
|
||||
package com.qqchen.deploy.backend.workflow.api.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 节点实例DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NodeInstanceDTO extends BaseDTO {
|
||||
|
||||
/**
|
||||
* 工作流实例ID
|
||||
*/
|
||||
@NotNull(message = "工作流实例ID不能为空")
|
||||
private Long workflowInstanceId;
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
@NotBlank(message = "节点ID不能为空")
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@NotNull(message = "节点类型不能为空")
|
||||
private NodeTypeEnum nodeType;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@NotBlank(message = "节点名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
@NotNull(message = "节点状态不能为空")
|
||||
private NodeStatusEnum status;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 节点配置(JSON)
|
||||
*/
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 输入参数(JSON)
|
||||
*/
|
||||
private String input;
|
||||
|
||||
/**
|
||||
* 输出结果(JSON)
|
||||
*/
|
||||
private String output;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 前置节点ID
|
||||
*/
|
||||
private String preNodeId;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.qqchen.deploy.backend.workflow.api.query;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 节点实例查询条件
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NodeInstanceQuery extends BaseQuery {
|
||||
|
||||
/**
|
||||
* 工作流实例ID
|
||||
*/
|
||||
@QueryField(field = "workflowInstanceId")
|
||||
private Long workflowInstanceId;
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
@QueryField(field = "nodeId", type = QueryType.LIKE)
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@QueryField(field = "nodeType")
|
||||
private NodeTypeEnum nodeType;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@QueryField(field = "name", type = QueryType.LIKE)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
@QueryField(field = "status")
|
||||
private NodeStatusEnum status;
|
||||
}
|
||||
@ -44,7 +44,7 @@ public class WorkflowLogApiController extends BaseController<WorkflowLog, Workfl
|
||||
}
|
||||
|
||||
@Operation(summary = "记录日志")
|
||||
@PostMapping
|
||||
@PostMapping("/record")
|
||||
public Response<Void> log(
|
||||
@Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId,
|
||||
@Parameter(description = "节点ID") @RequestParam(required = false) String nodeId,
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.NodeDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* 节点定义转换器
|
||||
*/
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface NodeDefinitionConverter extends BaseConverter<NodeDefinition, NodeDefinitionDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowDefinitionId", source = "workflowDefinition", qualifiedByName = "getWorkflowDefinitionId")
|
||||
NodeDefinitionDTO toDto(NodeDefinition entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowDefinition", source = "workflowDefinitionId", qualifiedByName = "createWorkflowDefinition")
|
||||
NodeDefinition toEntity(NodeDefinitionDTO dto);
|
||||
|
||||
@Named("getWorkflowDefinitionId")
|
||||
default Long getWorkflowDefinitionId(WorkflowDefinition workflowDefinition) {
|
||||
return workflowDefinition != null ? workflowDefinition.getId() : null;
|
||||
}
|
||||
|
||||
@Named("createWorkflowDefinition")
|
||||
default WorkflowDefinition createWorkflowDefinition(Long id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
WorkflowDefinition workflowDefinition = new WorkflowDefinition();
|
||||
workflowDefinition.setId(id);
|
||||
return workflowDefinition;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
@ -13,10 +13,33 @@ import org.mapstruct.Mapping;
|
||||
public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowInstanceId", source = "workflowInstance.id")
|
||||
@Mapping(target = "workflowInstanceId", source = "workflowInstanceId")
|
||||
@Mapping(target = "nodeId", source = "nodeId")
|
||||
@Mapping(target = "nodeType", source = "nodeType")
|
||||
@Mapping(target = "name", source = "name")
|
||||
@Mapping(target = "status", source = "status")
|
||||
@Mapping(target = "startTime", source = "startTime")
|
||||
@Mapping(target = "endTime", source = "endTime")
|
||||
@Mapping(target = "config", source = "config")
|
||||
@Mapping(target = "input", source = "input")
|
||||
@Mapping(target = "output", source = "output")
|
||||
@Mapping(target = "error", source = "error")
|
||||
@Mapping(target = "preNodeId", source = "preNodeId")
|
||||
NodeInstanceDTO toDto(NodeInstance entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowInstance.id", source = "workflowInstanceId")
|
||||
@Mapping(target = "workflowInstanceId", source = "workflowInstanceId")
|
||||
@Mapping(target = "nodeId", source = "nodeId")
|
||||
@Mapping(target = "nodeType", source = "nodeType")
|
||||
@Mapping(target = "name", source = "name")
|
||||
@Mapping(target = "status", source = "status")
|
||||
@Mapping(target = "startTime", source = "startTime")
|
||||
@Mapping(target = "endTime", source = "endTime")
|
||||
@Mapping(target = "config", source = "config")
|
||||
@Mapping(target = "input", source = "input")
|
||||
@Mapping(target = "output", source = "output")
|
||||
@Mapping(target = "error", source = "error")
|
||||
@Mapping(target = "preNodeId", source = "preNodeId")
|
||||
@Mapping(target = "workflowInstance", ignore = true)
|
||||
NodeInstance toEntity(NodeInstanceDTO dto);
|
||||
}
|
||||
@ -4,10 +4,19 @@ import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* 工作流定义转换器
|
||||
*/
|
||||
@Mapper(config = BaseConverter.class)
|
||||
@Mapper(config = BaseConverter.class, uses = {NodeDefinitionConverter.class})
|
||||
public interface WorkflowDefinitionConverter extends BaseConverter<WorkflowDefinition, WorkflowDefinitionDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "nodes", source = "nodes")
|
||||
WorkflowDefinitionDTO toDto(WorkflowDefinition entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "nodes", source = "nodes")
|
||||
WorkflowDefinition toEntity(WorkflowDefinitionDTO dto);
|
||||
}
|
||||
@ -13,10 +13,10 @@ import org.mapstruct.Mapping;
|
||||
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "definitionId", source = "definition.id")
|
||||
@Mapping(target = "definitionId", source = "workflowDefinition.id")
|
||||
WorkflowInstanceDTO toDto(WorkflowInstance entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "definition.id", source = "definitionId")
|
||||
@Mapping(target = "workflowDefinition.id", source = "definitionId")
|
||||
WorkflowInstance toEntity(WorkflowInstanceDTO dto);
|
||||
}
|
||||
@ -7,16 +7,13 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 节点定义DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NodeDefinitionDTO extends BaseDTO {
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
*/
|
||||
@NotNull(message = "工作流定义ID不能为空")
|
||||
private Long workflowDefinitionId;
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
@ -41,7 +38,18 @@ public class NodeDefinitionDTO extends BaseDTO {
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 序号
|
||||
* 节点描述
|
||||
*/
|
||||
private Integer sequence;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
*/
|
||||
@NotNull(message = "工作流定义ID不能为空")
|
||||
private Long workflowDefinitionId;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
private Integer orderNum;
|
||||
}
|
||||
@ -7,16 +7,15 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流定义DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WorkflowDefinitionDTO extends BaseDTO {
|
||||
|
||||
/**
|
||||
* 工作流名称
|
||||
*/
|
||||
@NotBlank(message = "工作流名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 工作流编码
|
||||
*/
|
||||
@ -24,23 +23,61 @@ public class WorkflowDefinitionDTO extends BaseDTO {
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
* 工作流名称
|
||||
*/
|
||||
@NotBlank(message = "工作流名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 工作流描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
* 工作流状态
|
||||
*/
|
||||
@NotNull(message = "状态不能为空")
|
||||
@NotNull(message = "工作流状态不能为空")
|
||||
private WorkflowStatusEnum status;
|
||||
|
||||
/**
|
||||
* 节点配置(JSON)
|
||||
* 版本号
|
||||
*/
|
||||
@NotNull(message = "版本号不能为空")
|
||||
private Integer version;
|
||||
|
||||
/**
|
||||
* 节点定义列表
|
||||
*/
|
||||
private List<NodeDefinitionDTO> nodes;
|
||||
|
||||
/**
|
||||
* 节点配置
|
||||
*/
|
||||
private String nodeConfig;
|
||||
|
||||
/**
|
||||
* 流转配置(JSON)
|
||||
* 流转配置
|
||||
*/
|
||||
private String transitionConfig;
|
||||
|
||||
/**
|
||||
* 表单定义
|
||||
*/
|
||||
private String formDefinition;
|
||||
|
||||
/**
|
||||
* 图形信息
|
||||
*/
|
||||
private String graphDefinition;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
@NotNull(message = "是否启用不能为空")
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@ -142,7 +142,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
}
|
||||
|
||||
// 终止所有运行中的节点
|
||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
||||
instanceId, NodeStatusEnum.RUNNING);
|
||||
|
||||
WorkflowContext context = workflowContextFactory.create(instance);
|
||||
@ -173,7 +173,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
}
|
||||
|
||||
// 暂停所有运行中的节点
|
||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
||||
instanceId, NodeStatusEnum.RUNNING);
|
||||
|
||||
for (NodeInstance node : runningNodes) {
|
||||
@ -196,7 +196,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
}
|
||||
|
||||
// 恢复所有暂停的节点
|
||||
List<NodeInstance> pausedNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||
List<NodeInstance> pausedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
||||
instanceId, NodeStatusEnum.PAUSED);
|
||||
|
||||
for (NodeInstance node : pausedNodes) {
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.qqchen.deploy.backend.workflow.engine.config;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor;
|
||||
import com.qqchen.deploy.backend.workflow.engine.executor.task.TaskExecutor;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Configuration
|
||||
public class WorkflowEngineConfig {
|
||||
|
||||
@Bean
|
||||
public Map<String, TaskExecutor> taskExecutorMap(List<TaskExecutor> executors) {
|
||||
return executors.stream()
|
||||
.collect(Collectors.toMap(
|
||||
executor -> executor.getClass().getSimpleName().replace("TaskExecutor", "").toUpperCase(),
|
||||
Function.identity()
|
||||
));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Map<NodeTypeEnum, NodeExecutor> nodeExecutorMap(List<NodeExecutor> executors) {
|
||||
return executors.stream()
|
||||
.collect(Collectors.toMap(
|
||||
NodeExecutor::getNodeType,
|
||||
Function.identity()
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,6 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class DefaultWorkflowContext implements WorkflowContext {
|
||||
|
||||
@Getter
|
||||
|
||||
@ -75,9 +75,6 @@ public class TaskNodeExecutor implements NodeExecutor {
|
||||
|
||||
private void executeTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
switch (config.getType()) {
|
||||
case SHELL:
|
||||
executeShellTask(config, nodeInstance, context);
|
||||
break;
|
||||
case HTTP:
|
||||
executeHttpTask(config, nodeInstance, context);
|
||||
break;
|
||||
@ -92,9 +89,6 @@ public class TaskNodeExecutor implements NodeExecutor {
|
||||
private void terminateTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// 根据任务类型执行终止操作
|
||||
switch (config.getType()) {
|
||||
case SHELL:
|
||||
terminateShellTask(config, nodeInstance, context);
|
||||
break;
|
||||
case HTTP:
|
||||
terminateHttpTask(config, nodeInstance, context);
|
||||
break;
|
||||
@ -104,11 +98,6 @@ public class TaskNodeExecutor implements NodeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// 具体任务执行方法...
|
||||
private void executeShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// TODO: 实现Shell脚本执行
|
||||
}
|
||||
|
||||
private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// TODO: 实现HTTP请求执行
|
||||
}
|
||||
@ -117,10 +106,6 @@ public class TaskNodeExecutor implements NodeExecutor {
|
||||
// TODO: 实现Java方法调用
|
||||
}
|
||||
|
||||
private void terminateShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// TODO: 实现Shell脚本终止
|
||||
}
|
||||
|
||||
private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// TODO: 实现HTTP请求终止
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import lombok.Getter;
|
||||
@AllArgsConstructor
|
||||
public enum TaskType {
|
||||
|
||||
SHELL("SHELL", "Shell脚本"),
|
||||
HTTP("HTTP", "HTTP请求"),
|
||||
JAVA("JAVA", "Java方法");
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
|
||||
|
||||
@Override
|
||||
public NodeTypeEnum getNodeType() {
|
||||
return NodeTypeEnum.TASK;
|
||||
return NodeTypeEnum.SHELL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -7,6 +7,9 @@ import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 节点定义实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@ -15,15 +18,16 @@ import lombok.EqualsAndHashCode;
|
||||
public class NodeDefinition extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
* 所属工作流定义
|
||||
*/
|
||||
@Column(name = "workflow_definition_id", nullable = false)
|
||||
private Long workflowDefinitionId;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "workflow_definition_id", nullable = false)
|
||||
private WorkflowDefinition workflowDefinition;
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Column(name = "node_id", nullable = false)
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
@ -46,7 +50,8 @@ public class NodeDefinition extends Entity<Long> {
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 序号
|
||||
* 排序号
|
||||
*/
|
||||
private Integer sequence;
|
||||
@Column(name = "order_num")
|
||||
private Integer orderNum;
|
||||
}
|
||||
@ -41,8 +41,8 @@ public class TransitionConfig extends Entity<Long> {
|
||||
* - "${amount > 1000}"
|
||||
* - "${result.code == 200 && result.data != null}"
|
||||
*/
|
||||
@Column(columnDefinition = "text")
|
||||
private String condition;
|
||||
@Column(name = "transition_condition", columnDefinition = "text")
|
||||
private String transitionCondition;
|
||||
|
||||
/**
|
||||
* 优先级,数字越小优先级越高
|
||||
@ -86,7 +86,7 @@ public class TransitionConfig extends Entity<Long> {
|
||||
* @return true if conditional, false otherwise
|
||||
*/
|
||||
public boolean isConditional() {
|
||||
return condition != null && !condition.trim().isEmpty();
|
||||
return transitionCondition != null && !transitionCondition.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,17 +7,25 @@ import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流定义实体
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "wf_definition")
|
||||
@Table(name = "wf_workflow_definition")
|
||||
@LogicDelete
|
||||
public class WorkflowDefinition extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流编码,唯一标识
|
||||
*/
|
||||
@Column(nullable = false, unique = true)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 工作流名称
|
||||
*/
|
||||
@ -25,32 +33,46 @@ public class WorkflowDefinition extends Entity<Long> {
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 工作流编码
|
||||
*/
|
||||
@Column(nullable = false, unique = true)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
* 工作流描述
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
* 工作流状态
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private WorkflowStatusEnum status = WorkflowStatusEnum.DRAFT;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 节点配置(JSON)
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT")
|
||||
@Column(name = "node_config", columnDefinition = "TEXT")
|
||||
private String nodeConfig;
|
||||
|
||||
/**
|
||||
* 流转配置(JSON)
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT")
|
||||
@Column(name = "transition_config", columnDefinition = "TEXT")
|
||||
private String transitionConfig;
|
||||
|
||||
/**
|
||||
* 节点定义列表
|
||||
*/
|
||||
@OneToMany(mappedBy = "workflowDefinition", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<NodeDefinition> nodes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 获取流转配置
|
||||
*/
|
||||
public String getTransitionConfig() {
|
||||
return transitionConfig;
|
||||
}
|
||||
}
|
||||
@ -3,13 +3,7 @@ package com.qqchen.deploy.backend.workflow.entity;
|
||||
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.time.LocalDateTime;
|
||||
@ -20,35 +14,40 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "wf_instance")
|
||||
@Table(name = "wf_workflow_instance")
|
||||
@LogicDelete
|
||||
|
||||
public class WorkflowInstance extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
* 工作流定义
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "definition_id", nullable = false)
|
||||
private WorkflowDefinition definition;
|
||||
@JoinColumn(name = "workflow_definition_id", nullable = false)
|
||||
private WorkflowDefinition workflowDefinition;
|
||||
|
||||
/**
|
||||
* 项目环境ID
|
||||
*/
|
||||
@Column(name = "project_env_id", nullable = false)
|
||||
@Column(name = "project_env_id")
|
||||
private Long projectEnvId;
|
||||
|
||||
/**
|
||||
* 业务标识
|
||||
*/
|
||||
@Column(name = "business_key")
|
||||
private String businessKey;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false)
|
||||
private WorkflowStatusEnum status;
|
||||
@Column(nullable = false)
|
||||
private WorkflowStatusEnum status = WorkflowStatusEnum.RUNNING;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Column(name = "start_time", nullable = false)
|
||||
@Column(name = "start_time")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
@ -57,15 +56,16 @@ public class WorkflowInstance extends Entity<Long> {
|
||||
@Column(name = "end_time")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 工作流变量(JSON)
|
||||
*/
|
||||
@Column(name = "variables", columnDefinition = "TEXT")
|
||||
private String variables;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@Column(name = "error", columnDefinition = "TEXT")
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 设置工作流定义
|
||||
*/
|
||||
public void setDefinition(WorkflowDefinition definition) {
|
||||
this.workflowDefinition = definition;
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,7 @@ package com.qqchen.deploy.backend.workflow.entity;
|
||||
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -14,7 +13,7 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "wf_variable")
|
||||
@Table(name = "wf_workflow_variable")
|
||||
@LogicDelete
|
||||
public class WorkflowVariable extends Entity<Long> {
|
||||
|
||||
@ -33,7 +32,7 @@ public class WorkflowVariable extends Entity<Long> {
|
||||
/**
|
||||
* 变量值(JSON)
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
@ -46,5 +45,11 @@ public class WorkflowVariable extends Entity<Long> {
|
||||
* 变量作用域
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private String scope = VariableScopeEnum.INSTANCE.name();
|
||||
private String scope = VariableScopeEnum.GLOBAL.name();
|
||||
|
||||
/**
|
||||
* 节点ID(scope为NODE时必填)
|
||||
*/
|
||||
@Column(name = "node_id")
|
||||
private String nodeId;
|
||||
}
|
||||
@ -14,7 +14,8 @@ public enum NodeTypeEnum {
|
||||
END("END", "结束节点"),
|
||||
TASK("TASK", "任务节点"),
|
||||
GATEWAY("GATEWAY", "网关节点"),
|
||||
SUB_PROCESS("SUB_PROCESS", "子流程节点");
|
||||
SUB_PROCESS("SUB_PROCESS", "子流程节点"),
|
||||
SHELL("SHELL", "Shell脚本节点");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
@ -2,23 +2,69 @@ package com.qqchen.deploy.backend.workflow.repository;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 节点定义仓库接口
|
||||
*/
|
||||
@Repository
|
||||
public interface INodeDefinitionRepository extends IBaseRepository<NodeDefinition, Long> {
|
||||
|
||||
/**
|
||||
* 查询工作流定义的所有节点
|
||||
* 根据工作流定义ID查询节点列表
|
||||
*
|
||||
* @param workflowDefinitionId 工作流定义ID
|
||||
* @return 节点列表
|
||||
*/
|
||||
@Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinitionId = :definitionId AND n.deleted = false ORDER BY n.sequence")
|
||||
List<NodeDefinition> findByWorkflowDefinitionIdOrderBySequence(@Param("definitionId") Long definitionId);
|
||||
@Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinition.id = :workflowDefinitionId AND n.deleted = false ORDER BY n.orderNum")
|
||||
List<NodeDefinition> findByWorkflowDefinitionId(@Param("workflowDefinitionId") Long workflowDefinitionId);
|
||||
|
||||
/**
|
||||
* 根据节点ID查询节点定义
|
||||
* 根据工作流定义ID和节点类型查询节点列表
|
||||
*
|
||||
* @param workflowDefinitionId 工作流定义ID
|
||||
* @param type 节点类型
|
||||
* @return 节点列表
|
||||
*/
|
||||
NodeDefinition findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(Long workflowDefinitionId, String nodeId);
|
||||
@Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinition.id = :workflowDefinitionId AND n.type = :type AND n.deleted = false ORDER BY n.orderNum")
|
||||
List<NodeDefinition> findByWorkflowDefinitionIdAndType(
|
||||
@Param("workflowDefinitionId") Long workflowDefinitionId,
|
||||
@Param("type") NodeTypeEnum type);
|
||||
|
||||
/**
|
||||
* 根据工作流定义ID和节点ID查询节点
|
||||
*
|
||||
* @param workflowDefinitionId 工作流定义ID
|
||||
* @param nodeId 节点ID
|
||||
* @return 节点定义
|
||||
*/
|
||||
@Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinition.id = :workflowDefinitionId AND n.nodeId = :nodeId AND n.deleted = false")
|
||||
NodeDefinition findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(
|
||||
@Param("workflowDefinitionId") Long workflowDefinitionId,
|
||||
@Param("nodeId") String nodeId);
|
||||
|
||||
/**
|
||||
* 检查节点ID是否已存在
|
||||
*
|
||||
* @param workflowDefinitionId 工作流定义ID
|
||||
* @param nodeId 节点ID
|
||||
* @return 是否存在
|
||||
*/
|
||||
@Query("SELECT COUNT(n) > 0 FROM NodeDefinition n WHERE n.workflowDefinition.id = :workflowDefinitionId AND n.nodeId = :nodeId AND n.deleted = false")
|
||||
boolean existsByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(
|
||||
@Param("workflowDefinitionId") Long workflowDefinitionId,
|
||||
@Param("nodeId") String nodeId);
|
||||
|
||||
/**
|
||||
* 删除工作流定义的所有节点
|
||||
*
|
||||
* @param workflowDefinitionId 工作流定义ID
|
||||
*/
|
||||
@Query("UPDATE NodeDefinition n SET n.deleted = true WHERE n.workflowDefinition.id = :workflowDefinitionId")
|
||||
void deleteByWorkflowDefinitionId(@Param("workflowDefinitionId") Long workflowDefinitionId);
|
||||
}
|
||||
@ -23,9 +23,9 @@ public interface INodeInstanceRepository extends IBaseRepository<NodeInstance, L
|
||||
*/
|
||||
List<NodeInstance> findByWorkflowInstanceIdAndStatusAndDeletedFalse(Long workflowInstanceId, NodeStatusEnum status);
|
||||
|
||||
List<NodeInstance> findByInstanceIdAndStatus(Long instanceId, NodeStatusEnum status);
|
||||
List<NodeInstance> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status);
|
||||
|
||||
List<NodeInstance> findByInstanceId(Long instanceId);
|
||||
List<NodeInstance> findByWorkflowInstanceId(Long workflowInstanceId);
|
||||
|
||||
void deleteByInstanceId(Long instanceId);
|
||||
void deleteByWorkflowInstanceId(Long workflowInstanceId);
|
||||
}
|
||||
@ -2,8 +2,14 @@ package com.qqchen.deploy.backend.workflow.repository;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 工作流定义仓库接口
|
||||
*/
|
||||
@ -11,17 +17,62 @@ import org.springframework.stereotype.Repository;
|
||||
public interface IWorkflowDefinitionRepository extends IBaseRepository<WorkflowDefinition, Long> {
|
||||
|
||||
/**
|
||||
* 根据编码查询工作流定义
|
||||
* 根据编码查询最新版本的工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinition findByCodeAndDeletedFalse(String code);
|
||||
@Query("SELECT w FROM WorkflowDefinition w WHERE w.code = :code AND w.deleted = false ORDER BY w.version DESC")
|
||||
Optional<WorkflowDefinition> findLatestByCode(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 检查编码是否存在
|
||||
* 根据编码和版本号查询工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @param version 版本号
|
||||
* @return 工作流定义
|
||||
*/
|
||||
Optional<WorkflowDefinition> findByCodeAndVersionAndDeletedFalse(String code, Integer version);
|
||||
|
||||
/**
|
||||
* 根据编码查询所有版本的工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义列表
|
||||
*/
|
||||
@Query("SELECT w FROM WorkflowDefinition w WHERE w.code = :code AND w.deleted = false ORDER BY w.version DESC")
|
||||
List<WorkflowDefinition> findAllVersionsByCode(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 根据状态查询工作流定义列表
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 工作流定义列表
|
||||
*/
|
||||
List<WorkflowDefinition> findByStatusAndDeletedFalse(WorkflowStatusEnum status);
|
||||
|
||||
/**
|
||||
* 检查编码是否已存在
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByCodeAndDeletedFalse(String code);
|
||||
|
||||
/**
|
||||
* 检查名称是否存在
|
||||
* 获取工作流定义的最新版本号
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 最新版本号
|
||||
*/
|
||||
boolean existsByNameAndDeletedFalse(String name);
|
||||
@Query("SELECT MAX(w.version) FROM WorkflowDefinition w WHERE w.code = :code AND w.deleted = false")
|
||||
Integer findLatestVersionByCode(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 根据编码查询工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinition findByCodeAndDeletedFalse(String code);
|
||||
}
|
||||
@ -36,4 +36,22 @@ public interface IWorkflowVariableRepository extends IBaseRepository<WorkflowVar
|
||||
* 删除工作流实例的所有变量
|
||||
*/
|
||||
void deleteByWorkflowInstanceId(Long instanceId);
|
||||
|
||||
/**
|
||||
* 查询工作流实例的指定作用域和节点的变量
|
||||
*/
|
||||
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.nodeId = :nodeId AND v.deleted = false")
|
||||
List<WorkflowVariable> findByWorkflowInstanceIdAndScopeAndNodeId(
|
||||
@Param("instanceId") Long instanceId,
|
||||
@Param("scope") String scope,
|
||||
@Param("nodeId") String nodeId);
|
||||
|
||||
/**
|
||||
* 查询工作流实例的指定名称和作用域的变量
|
||||
*/
|
||||
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.name = :name AND v.scope = :scope AND v.deleted = false")
|
||||
Optional<WorkflowVariable> findByWorkflowInstanceIdAndNameAndScope(
|
||||
@Param("instanceId") Long instanceId,
|
||||
@Param("name") String name,
|
||||
@Param("scope") String scope);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.qqchen.deploy.backend.workflow.service;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 节点实例服务接口
|
||||
*/
|
||||
public interface INodeInstanceService extends IBaseService<NodeInstance, NodeInstanceDTO, Long> {
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID查询节点列表
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
* @return 节点列表
|
||||
*/
|
||||
List<NodeInstanceDTO> findByWorkflowInstanceId(Long workflowInstanceId);
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID和状态查询节点列表
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
* @param status 状态
|
||||
* @return 节点列表
|
||||
*/
|
||||
List<NodeInstanceDTO> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status);
|
||||
|
||||
/**
|
||||
* 更新节点状态
|
||||
*
|
||||
* @param id 节点ID
|
||||
* @param status 状态
|
||||
* @param output 输出结果
|
||||
* @param error 错误信息
|
||||
*/
|
||||
void updateStatus(Long id, NodeStatusEnum status, String output, String error);
|
||||
}
|
||||
@ -3,7 +3,8 @@ package com.qqchen.deploy.backend.workflow.service;
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流定义服务接口
|
||||
@ -11,32 +12,84 @@ import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
|
||||
|
||||
/**
|
||||
* 发布工作流定义
|
||||
* 创建工作流定义
|
||||
*
|
||||
* @param dto 工作流定义DTO
|
||||
* @return 工作流定义
|
||||
*/
|
||||
void publish(Long id);
|
||||
WorkflowDefinitionDTO create(WorkflowDefinitionDTO dto);
|
||||
|
||||
/**
|
||||
* 更新工作流定义
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @param dto 工作流定义DTO
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO update(Long id, WorkflowDefinitionDTO dto);
|
||||
|
||||
/**
|
||||
* 发布工作流定义
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO publish(Long id);
|
||||
|
||||
/**
|
||||
* 禁用工作流定义
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 工作流定义
|
||||
*/
|
||||
void disable(Long id);
|
||||
WorkflowDefinitionDTO disable(Long id);
|
||||
|
||||
/**
|
||||
* 创建新版本
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO createNewVersion(Long id);
|
||||
|
||||
/**
|
||||
* 根据编码查询最新版本
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO findLatestByCode(String code);
|
||||
|
||||
/**
|
||||
* 根据编码和版本号查询
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @param version 版本号
|
||||
* @return 工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO findByCodeAndVersion(String code, Integer version);
|
||||
|
||||
/**
|
||||
* 查询所有版本
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义列表
|
||||
*/
|
||||
List<WorkflowDefinitionDTO> findAllVersions(String code);
|
||||
|
||||
/**
|
||||
* 验证工作流定义
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 验证结果
|
||||
*/
|
||||
boolean validate(Long id);
|
||||
|
||||
/**
|
||||
* 启用工作流定义
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 工作流定义
|
||||
*/
|
||||
void enable(Long id);
|
||||
|
||||
/**
|
||||
* 检查工作流名称是否唯一
|
||||
*/
|
||||
void checkNameUnique(String name);
|
||||
|
||||
/**
|
||||
* 检查工作流编码是否唯一
|
||||
*/
|
||||
void checkCodeUnique(String code);
|
||||
|
||||
/**
|
||||
* 验证工作流状态
|
||||
*/
|
||||
void validateStatus(WorkflowStatusEnum status);
|
||||
WorkflowDefinitionDTO enable(Long id);
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.converter.NodeInstanceConverter;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 节点实例服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class NodeInstanceServiceImpl extends BaseServiceImpl<NodeInstance, NodeInstanceDTO, Long> implements INodeInstanceService {
|
||||
|
||||
@Resource
|
||||
private INodeInstanceRepository nodeInstanceRepository;
|
||||
|
||||
@Resource
|
||||
private NodeInstanceConverter nodeInstanceConverter;
|
||||
|
||||
@Override
|
||||
public List<NodeInstanceDTO> findByWorkflowInstanceId(Long workflowInstanceId) {
|
||||
return nodeInstanceRepository.findByWorkflowInstanceId(workflowInstanceId)
|
||||
.stream()
|
||||
.map(nodeInstanceConverter::toDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NodeInstanceDTO> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status) {
|
||||
return nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status)
|
||||
.stream()
|
||||
.map(nodeInstanceConverter::toDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateStatus(Long id, NodeStatusEnum status, String output, String error) {
|
||||
NodeInstance node = nodeInstanceRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NODE_NOT_FOUND));
|
||||
node.setStatus(status);
|
||||
node.setOutput(output);
|
||||
node.setError(error);
|
||||
|
||||
if (NodeStatusEnum.RUNNING.equals(status)) {
|
||||
node.setStartTime(LocalDateTime.now());
|
||||
} else if (NodeStatusEnum.COMPLETED.equals(status) ||
|
||||
NodeStatusEnum.FAILED.equals(status) ||
|
||||
NodeStatusEnum.CANCELLED.equals(status) ||
|
||||
NodeStatusEnum.SKIPPED.equals(status)) {
|
||||
node.setEndTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
nodeInstanceRepository.save(node);
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,11 @@ package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.repository.INodeDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -13,69 +15,189 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流定义服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WorkflowDefinitionServiceImpl
|
||||
extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
||||
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
||||
implements IWorkflowDefinitionService {
|
||||
|
||||
@Resource
|
||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void publish(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.DRAFT) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
repository.save(definition);
|
||||
}
|
||||
@Resource
|
||||
private INodeDefinitionRepository nodeDefinitionRepository;
|
||||
|
||||
@Resource
|
||||
private WorkflowDefinitionConverter workflowDefinitionConverter;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void disable(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_PUBLISHED);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.DISABLED);
|
||||
repository.save(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void enable(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.DISABLED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DISABLED);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
repository.save(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void checkNameUnique(String name) {
|
||||
if (workflowDefinitionRepository.existsByNameAndDeletedFalse(name)) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NAME_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void checkCodeUnique(String code) {
|
||||
if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(code)) {
|
||||
public WorkflowDefinitionDTO create(WorkflowDefinitionDTO dto) {
|
||||
// 检查编码是否已存在
|
||||
if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_CODE_EXISTS);
|
||||
}
|
||||
|
||||
// 设置初始状态
|
||||
dto.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
dto.setEnabled(true);
|
||||
|
||||
// 保存工作流定义
|
||||
WorkflowDefinition workflowDefinition = workflowDefinitionConverter.toEntity(dto);
|
||||
workflowDefinition = workflowDefinitionRepository.save(workflowDefinition);
|
||||
|
||||
return workflowDefinitionConverter.toDto(workflowDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void validateStatus(WorkflowStatusEnum status) {
|
||||
if (status == null) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_INVALID_STATUS);
|
||||
}
|
||||
public WorkflowDefinitionDTO update(Long id, WorkflowDefinitionDTO dto) {
|
||||
// 获取当前工作流定义
|
||||
WorkflowDefinition current = workflowDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
|
||||
// 只有草稿状态可以修改
|
||||
if (current.getStatus() != WorkflowStatusEnum.DRAFT) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT);
|
||||
}
|
||||
|
||||
// 检查编码是否已存在(排除自身)
|
||||
if (!Objects.equals(current.getCode(), dto.getCode()) &&
|
||||
workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_CODE_EXISTS);
|
||||
}
|
||||
|
||||
// 更新基本信息
|
||||
current.setName(dto.getName());
|
||||
current.setDescription(dto.getDescription());
|
||||
current.setNodeConfig(dto.getNodeConfig());
|
||||
|
||||
// 保存更新
|
||||
current = workflowDefinitionRepository.save(current);
|
||||
|
||||
return workflowDefinitionConverter.toDto(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public WorkflowDefinitionDTO publish(Long id) {
|
||||
// 获取当前工作流定义
|
||||
WorkflowDefinition current = workflowDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
|
||||
// 验证当前状态
|
||||
if (current.getStatus() != WorkflowStatusEnum.DRAFT) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT);
|
||||
}
|
||||
|
||||
// 验证工作流定义
|
||||
if (!validate(id)) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_CONFIG_INVALID);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
current.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
current = workflowDefinitionRepository.save(current);
|
||||
|
||||
return workflowDefinitionConverter.toDto(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public WorkflowDefinitionDTO disable(Long id) {
|
||||
// 获取当前工作流定义
|
||||
WorkflowDefinition current = workflowDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
|
||||
// 验证当前状态
|
||||
if (current.getStatus() != WorkflowStatusEnum.PUBLISHED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_PUBLISHED);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
current.setStatus(WorkflowStatusEnum.DISABLED);
|
||||
current = workflowDefinitionRepository.save(current);
|
||||
|
||||
return workflowDefinitionConverter.toDto(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public WorkflowDefinitionDTO enable(Long id) {
|
||||
// 获取当前工作流定义
|
||||
WorkflowDefinition current = workflowDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
|
||||
// 验证当前状态
|
||||
if (current.getStatus() != WorkflowStatusEnum.DISABLED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DISABLED);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
current.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
current = workflowDefinitionRepository.save(current);
|
||||
|
||||
return workflowDefinitionConverter.toDto(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkflowDefinitionDTO findLatestByCode(String code) {
|
||||
return workflowDefinitionRepository.findLatestByCode(code)
|
||||
.map(workflowDefinitionConverter::toDto)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WorkflowDefinitionDTO> findAllVersions(String code) {
|
||||
return workflowDefinitionRepository.findAllVersionsByCode(code)
|
||||
.stream()
|
||||
.map(workflowDefinitionConverter::toDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Long id) {
|
||||
// TODO: 实现工作流定义验证逻辑
|
||||
// 1. 验证节点配置的完整性
|
||||
// 2. 验证节点之间的连接关系
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public WorkflowDefinitionDTO findByCodeAndVersion(String code, Integer version) {
|
||||
return workflowDefinitionConverter.toDto(workflowDefinitionRepository.findByCodeAndVersionAndDeletedFalse(code, version)
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public WorkflowDefinitionDTO createNewVersion(Long id) {
|
||||
// 获取原工作流定义
|
||||
WorkflowDefinition oldDefinition = workflowDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
|
||||
|
||||
// 获取最新版本号
|
||||
Integer latestVersion = workflowDefinitionRepository.findLatestVersionByCode(oldDefinition.getCode());
|
||||
Integer newVersion = latestVersion != null ? latestVersion + 1 : 1;
|
||||
|
||||
// 创建新版本
|
||||
WorkflowDefinition newDefinition = new WorkflowDefinition();
|
||||
newDefinition.setCode(oldDefinition.getCode());
|
||||
newDefinition.setName(oldDefinition.getName());
|
||||
newDefinition.setDescription(oldDefinition.getDescription());
|
||||
newDefinition.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
newDefinition.setEnabled(true);
|
||||
newDefinition.setNodeConfig(oldDefinition.getNodeConfig());
|
||||
newDefinition.setTransitionConfig(oldDefinition.getTransitionConfig());
|
||||
|
||||
// 保存新版本
|
||||
return workflowDefinitionConverter.toDto(workflowDefinitionRepository.save(newDefinition));
|
||||
}
|
||||
}
|
||||
@ -3,15 +3,14 @@ package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO;
|
||||
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
|
||||
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -19,88 +18,68 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流变量服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WorkflowVariableServiceImpl extends BaseServiceImpl<WorkflowVariable, WorkflowVariableDTO, Long> implements IWorkflowVariableService {
|
||||
|
||||
@Resource
|
||||
private IWorkflowVariableRepository variableRepository;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void setVariable(Long workflowInstanceId, String name, Object value) {
|
||||
try {
|
||||
WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
|
||||
.orElse(new WorkflowVariable());
|
||||
WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndNameAndScope(
|
||||
workflowInstanceId, name, VariableScopeEnum.GLOBAL.name())
|
||||
.orElseGet(() -> {
|
||||
WorkflowVariable newVar = new WorkflowVariable();
|
||||
newVar.setWorkflowInstanceId(workflowInstanceId);
|
||||
newVar.setName(name);
|
||||
newVar.setScope(VariableScopeEnum.GLOBAL.name());
|
||||
return newVar;
|
||||
});
|
||||
|
||||
variable.setWorkflowInstanceId(workflowInstanceId);
|
||||
variable.setName(name);
|
||||
variable.setValue(objectMapper.writeValueAsString(value));
|
||||
variable.setType(value.getClass().getName());
|
||||
|
||||
repository.save(variable);
|
||||
} catch (Exception e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||
variableRepository.save(variable);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Map<String, Object> getVariables(Long workflowInstanceId) {
|
||||
try {
|
||||
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
for (WorkflowVariable variable : variables) {
|
||||
Object value = deserializeValue(variable);
|
||||
result.put(variable.getName(), value);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e);
|
||||
}
|
||||
return convertToMap(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) {
|
||||
try {
|
||||
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceIdAndScope(
|
||||
workflowInstanceId, scope.name());
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
for (WorkflowVariable variable : variables) {
|
||||
Object value = deserializeValue(variable);
|
||||
result.put(variable.getName(), value);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e);
|
||||
}
|
||||
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceIdAndScope(workflowInstanceId, scope.name());
|
||||
return convertToMap(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteVariables(Long workflowInstanceId) {
|
||||
variableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
|
||||
log.debug("删除工作流变量成功: instanceId={}", workflowInstanceId);
|
||||
}
|
||||
|
||||
private Object deserializeValue(WorkflowVariable variable) {
|
||||
private Map<String, Object> convertToMap(List<WorkflowVariable> variables) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
for (WorkflowVariable variable : variables) {
|
||||
try {
|
||||
Class<?> type = Class.forName(variable.getType());
|
||||
return objectMapper.readValue(variable.getValue(), type);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||
Object value = objectMapper.readValue(variable.getValue(), type);
|
||||
result.put(variable.getName(), value);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -145,7 +145,7 @@ CREATE TABLE sys_role_tag (
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
name VARCHAR(50) NOT NULL COMMENT '标签名称',
|
||||
color VARCHAR(20) NULL COMMENT '标签进制颜色码)'
|
||||
color VARCHAR(20) NULL COMMENT '标签颜色'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
|
||||
|
||||
-- 角色标签关联表
|
||||
@ -274,7 +274,7 @@ CREATE TABLE deploy_repo_group (
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除:0-未删除,1-已删除',
|
||||
|
||||
-- 业务字段
|
||||
name VARCHAR(100) NOT NULL COMMENT '仓库组名称',
|
||||
name VARCHAR(100) NOT NULL COMMENT '仓库组名',
|
||||
description VARCHAR(500) NULL COMMENT '仓库组描述',
|
||||
group_id BIGINT NOT NULL COMMENT '外部系统中的组ID',
|
||||
parent_id BIGINT NULL COMMENT '父级仓库组ID',
|
||||
@ -377,103 +377,135 @@ CREATE TABLE deploy_repo_branch (
|
||||
REFERENCES sys_external_system (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表';
|
||||
|
||||
-- --------------------------------------------------------------------------------------
|
||||
-- 工作流相关表
|
||||
-- --------------------------------------------------------------------------------------
|
||||
|
||||
-- 工作流定义表
|
||||
CREATE TABLE wf_definition (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
CREATE TABLE wf_workflow_definition (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
code VARCHAR(100) NOT NULL COMMENT '工作流编码',
|
||||
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
||||
description TEXT COMMENT '描述',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
node_config TEXT COMMENT '节点配置(JSON)',
|
||||
transition_config TEXT COMMENT '流转配置(JSON)',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
UNIQUE KEY uk_code (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流定义表';
|
||||
description TEXT NULL COMMENT '工作流描述',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT' COMMENT '状态(DRAFT:草稿,PUBLISHED:已发布,DISABLED:已禁用)',
|
||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||
node_config TEXT NULL COMMENT '节点配置(JSON)',
|
||||
|
||||
CONSTRAINT UK_workflow_definition_code UNIQUE (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表';
|
||||
|
||||
-- 流转配置表
|
||||
CREATE TABLE wf_transition_config (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
source_node_id VARCHAR(255) NOT NULL COMMENT '源节点ID',
|
||||
target_node_id VARCHAR(255) NOT NULL COMMENT '目标节点ID',
|
||||
transition_condition TEXT NULL COMMENT '流转条件',
|
||||
priority INT NOT NULL DEFAULT 0 COMMENT '优先级',
|
||||
|
||||
CONSTRAINT FK_transition_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流转配置表';
|
||||
|
||||
-- 工作流实例表
|
||||
CREATE TABLE wf_workflow_instance (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
project_env_id BIGINT NULL COMMENT '项目环境ID',
|
||||
business_key VARCHAR(100) NULL COMMENT '业务标识',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'RUNNING' COMMENT '状态(RUNNING:运行中,COMPLETED:已完成,TERMINATED:已终止,FAILED:失败)',
|
||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
||||
error TEXT NULL COMMENT '错误信息',
|
||||
|
||||
CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
|
||||
|
||||
-- 节点定义表
|
||||
CREATE TABLE wf_node_definition (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||
type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
config TEXT COMMENT '节点配置(JSON)',
|
||||
sequence INT NOT NULL DEFAULT 0 COMMENT '序号',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
UNIQUE KEY uk_workflow_node (workflow_definition_id, node_id),
|
||||
CONSTRAINT fk_node_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_definition(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点定义表';
|
||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||
order_num INT NOT NULL DEFAULT 0 COMMENT '排序号',
|
||||
|
||||
-- 工作流实例表(修改现有表)
|
||||
DROP TABLE IF EXISTS sys_workflow_instance;
|
||||
CREATE TABLE wf_instance (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
business_key VARCHAR(100) NOT NULL COMMENT '业务标识',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
variables TEXT COMMENT '工作流变量(JSON)',
|
||||
start_time DATETIME NOT NULL COMMENT '开始时间',
|
||||
end_time DATETIME COMMENT '结束时间',
|
||||
error TEXT COMMENT '错误信息',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES wf_definition(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流实例表';
|
||||
CONSTRAINT FK_node_definition_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流节点定义表';
|
||||
|
||||
-- 节点实例表(修改现有表)
|
||||
DROP TABLE IF EXISTS sys_node_instance;
|
||||
-- 节点实例表
|
||||
CREATE TABLE wf_node_instance (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||
node_type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
node_id VARCHAR(100) NOT NULL COMMENT '节点ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状<EFBFBD><EFBFBD><EFBFBD>',
|
||||
config TEXT COMMENT '节点配置(JSON)',
|
||||
input TEXT COMMENT '输入参数(JSON)',
|
||||
output TEXT COMMENT '输出结果(JSON)',
|
||||
error TEXT COMMENT '错误信息',
|
||||
start_time DATETIME COMMENT '开始时间',
|
||||
end_time DATETIME COMMENT '结束时间',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
CONSTRAINT fk_node_instance_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_instance(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点实例表';
|
||||
type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'PENDING' COMMENT '状态(PENDING:待执行,RUNNING:执行中,COMPLETED:已完成,FAILED:失败,TERMINATED:已终止)',
|
||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||
input TEXT NULL COMMENT '输入参数(JSON)',
|
||||
output TEXT NULL COMMENT '输出结果(JSON)',
|
||||
error TEXT NULL COMMENT '错误信息',
|
||||
pre_node_id VARCHAR(100) NULL COMMENT '前置节点ID',
|
||||
|
||||
CONSTRAINT FK_node_instance_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
|
||||
|
||||
-- 工作流变量表
|
||||
CREATE TABLE wf_variable (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
workflow_instance_id BIGINT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
type VARCHAR(255),
|
||||
scope VARCHAR(50),
|
||||
create_time DATETIME NOT NULL,
|
||||
create_by VARCHAR(50),
|
||||
update_time DATETIME,
|
||||
update_by VARCHAR(50),
|
||||
version INT DEFAULT 0,
|
||||
deleted BOOLEAN DEFAULT FALSE,
|
||||
CONSTRAINT uk_wf_variable UNIQUE (workflow_instance_id, name, deleted)
|
||||
);
|
||||
CREATE TABLE wf_workflow_variable (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '变量名',
|
||||
value TEXT NULL COMMENT '变量值',
|
||||
type VARCHAR(50) NOT NULL COMMENT '变量类型',
|
||||
scope VARCHAR(50) NOT NULL DEFAULT 'GLOBAL' COMMENT '作用域(GLOBAL:全局,NODE:节点)',
|
||||
node_id VARCHAR(100) NULL COMMENT '节点ID(scope为NODE时必填)',
|
||||
|
||||
CONSTRAINT FK_variable_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流变量表';
|
||||
|
||||
-- 工作流日志表
|
||||
CREATE TABLE wf_log (
|
||||
@ -492,7 +524,7 @@ CREATE TABLE wf_log (
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_wf_variable_instance ON wf_variable (workflow_instance_id);
|
||||
CREATE INDEX idx_wf_workflow_variable_instance ON wf_workflow_variable (workflow_instance_id);
|
||||
CREATE INDEX idx_wf_log_instance ON wf_log (workflow_instance_id);
|
||||
CREATE INDEX idx_wf_log_node ON wf_log (workflow_instance_id, node_id);
|
||||
|
||||
|
||||
@ -184,3 +184,41 @@ workflow.log.not.found=工作流日志不存在
|
||||
workflow.transition.invalid=工作流流转配置无效
|
||||
workflow.node.type.not.supported=不支持的节点类型
|
||||
workflow.condition.invalid=工作流条件配置无效
|
||||
|
||||
# 工作流相关错误消息
|
||||
workflow.not.found=工作流定义不存在
|
||||
workflow.code.exists=工作流编码已存在
|
||||
workflow.name.exists=工作流名称已存在
|
||||
workflow.disabled=工作流已禁用
|
||||
workflow.not.published=工作流未发布
|
||||
workflow.already.published=工作流已发布
|
||||
workflow.already.disabled=工作流已禁用
|
||||
workflow.not.draft=工作流不是草稿状态
|
||||
workflow.not.disabled=工作流不是禁用状态
|
||||
workflow.invalid.status=工作流状态无效
|
||||
workflow.config.invalid=工作流配置无效
|
||||
workflow.transition.invalid=工作流流转规则无效
|
||||
workflow.condition.invalid=工作流条件配置无效
|
||||
|
||||
workflow.instance.not.found=工作流实例不存在
|
||||
workflow.instance.already.completed=工作流实例已完成
|
||||
workflow.instance.already.canceled=工作流实例已取消
|
||||
workflow.instance.not.running=工作流实例未运行
|
||||
|
||||
workflow.node.not.found=工作流节点不存在
|
||||
workflow.node.type.not.supported=不支持的节点类型
|
||||
workflow.node.config.invalid=节点配置无效
|
||||
workflow.node.execution.failed=节点执行失败
|
||||
workflow.node.timeout=节点执行超时
|
||||
workflow.node.config.error=节点配置错误
|
||||
|
||||
workflow.execution.error=工作流执行错误
|
||||
workflow.variable.not.found=工作流变量不存在
|
||||
workflow.variable.type.invalid=工作流变量类型无效
|
||||
workflow.permission.denied=无权限操作工作流
|
||||
workflow.approval.required=需要审批
|
||||
workflow.approval.rejected=审批被拒绝
|
||||
workflow.dependency.not.satisfied=工作流依赖条件未满足
|
||||
workflow.circular.dependency=工作流存在循环依赖
|
||||
workflow.schedule.invalid=工作流调度配置无效
|
||||
workflow.concurrent.limit.exceeded=工作流并发限制超出
|
||||
@ -4,3 +4,41 @@ department.code.exists=Department code already exists
|
||||
department.name.exists=Department name already exists
|
||||
department.parent.not.found=Parent department not found
|
||||
department.has.children=Cannot delete department with children
|
||||
|
||||
# Workflow related error messages
|
||||
workflow.not.found=Workflow definition not found
|
||||
workflow.code.exists=Workflow code already exists
|
||||
workflow.name.exists=Workflow name already exists
|
||||
workflow.disabled=Workflow is disabled
|
||||
workflow.not.published=Workflow is not published
|
||||
workflow.already.published=Workflow is already published
|
||||
workflow.already.disabled=Workflow is already disabled
|
||||
workflow.not.draft=Workflow is not in draft status
|
||||
workflow.not.disabled=Workflow is not in disabled status
|
||||
workflow.invalid.status=Invalid workflow status
|
||||
workflow.config.invalid=Invalid workflow configuration
|
||||
workflow.transition.invalid=Invalid workflow transition rules
|
||||
workflow.condition.invalid=Invalid workflow condition configuration
|
||||
|
||||
workflow.instance.not.found=Workflow instance not found
|
||||
workflow.instance.already.completed=Workflow instance already completed
|
||||
workflow.instance.already.canceled=Workflow instance already canceled
|
||||
workflow.instance.not.running=Workflow instance is not running
|
||||
|
||||
workflow.node.not.found=Workflow node not found
|
||||
workflow.node.type.not.supported=Unsupported node type
|
||||
workflow.node.config.invalid=Invalid node configuration
|
||||
workflow.node.execution.failed=Node execution failed
|
||||
workflow.node.timeout=Node execution timeout
|
||||
workflow.node.config.error=Node configuration error
|
||||
|
||||
workflow.execution.error=Workflow execution error
|
||||
workflow.variable.not.found=Workflow variable not found
|
||||
workflow.variable.type.invalid=Invalid workflow variable type
|
||||
workflow.permission.denied=Permission denied to operate workflow
|
||||
workflow.approval.required=Approval required
|
||||
workflow.approval.rejected=Approval rejected
|
||||
workflow.dependency.not.satisfied=Workflow dependency not satisfied
|
||||
workflow.circular.dependency=Circular dependency detected in workflow
|
||||
workflow.schedule.invalid=Invalid workflow schedule configuration
|
||||
workflow.concurrent.limit.exceeded=Workflow concurrent limit exceeded
|
||||
@ -93,3 +93,41 @@ workflow.variable.invalid=工作流变量"{0}"的值无效
|
||||
workflow.permission.denied=无权操作此工作流
|
||||
workflow.operation.not.allowed=当前状态不允许此操作
|
||||
workflow.concurrent.operation=工作流正在执行其他操作,请稍后重试
|
||||
|
||||
# 工作流相关错误消息
|
||||
workflow.not.found=工作流定义不存在
|
||||
workflow.code.exists=工作流编码已存在
|
||||
workflow.name.exists=工作流名称已存在
|
||||
workflow.disabled=工作流已禁用
|
||||
workflow.not.published=工作流未发布
|
||||
workflow.already.published=工作流已发布
|
||||
workflow.already.disabled=工作流已禁用
|
||||
workflow.not.draft=工作流不是草稿状态
|
||||
workflow.not.disabled=工作流不是禁用状态
|
||||
workflow.invalid.status=工作流状态无效
|
||||
workflow.config.invalid=工作流配置无效
|
||||
workflow.transition.invalid=工作流流转规则无效
|
||||
workflow.condition.invalid=工作流条件配置无效
|
||||
|
||||
workflow.instance.not.found=工作流实例不存在
|
||||
workflow.instance.already.completed=工作流实例已完成
|
||||
workflow.instance.already.canceled=工作流实例已取消
|
||||
workflow.instance.not.running=工作流实例未运行
|
||||
|
||||
workflow.node.not.found=工作流节点不存在
|
||||
workflow.node.type.not.supported=不支持的节点类型
|
||||
workflow.node.config.invalid=节点配置无效
|
||||
workflow.node.execution.failed=节点执行失败
|
||||
workflow.node.timeout=节点执行超时
|
||||
workflow.node.config.error=节点配置错误
|
||||
|
||||
workflow.execution.error=工作流执行错误
|
||||
workflow.variable.not.found=工作流变量不存在
|
||||
workflow.variable.type.invalid=工作流变量类型无效
|
||||
workflow.permission.denied=无权限操作工作流
|
||||
workflow.approval.required=需要审批
|
||||
workflow.approval.rejected=审批被拒绝
|
||||
workflow.dependency.not.satisfied=工作流依赖条件未满足
|
||||
workflow.circular.dependency=工作流存在循环依赖
|
||||
workflow.schedule.invalid=工作流调度配置无效
|
||||
workflow.concurrent.limit.exceeded=工作流并发限制超出
|
||||
@ -0,0 +1,138 @@
|
||||
package com.qqchen.deploy.backend.workflow.repository;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ActiveProfiles("test")
|
||||
class NodeDefinitionRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private INodeDefinitionRepository nodeDefinitionRepository;
|
||||
|
||||
@Autowired
|
||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||
|
||||
private WorkflowDefinition workflowDefinition;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 创建工作流定义
|
||||
workflowDefinition = new WorkflowDefinition();
|
||||
workflowDefinition.setCode("TEST-WF");
|
||||
workflowDefinition.setName("Test Workflow");
|
||||
workflowDefinition.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
workflowDefinition.setVersion(1);
|
||||
workflowDefinition.setEnabled(true);
|
||||
workflowDefinition = workflowDefinitionRepository.save(workflowDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByWorkflowDefinitionId_ShouldReturnNodesOrderedByOrderNum() {
|
||||
// Given
|
||||
NodeDefinition node1 = createNodeDefinition("NODE-001", "Node 1", NodeTypeEnum.START, 1);
|
||||
NodeDefinition node2 = createNodeDefinition("NODE-002", "Node 2", NodeTypeEnum.TASK, 2);
|
||||
NodeDefinition node3 = createNodeDefinition("NODE-003", "Node 3", NodeTypeEnum.END, 3);
|
||||
|
||||
nodeDefinitionRepository.save(node1);
|
||||
nodeDefinitionRepository.save(node2);
|
||||
nodeDefinitionRepository.save(node3);
|
||||
|
||||
// When
|
||||
List<NodeDefinition> results = nodeDefinitionRepository.findByWorkflowDefinitionId(workflowDefinition.getId());
|
||||
|
||||
// Then
|
||||
assertThat(results).hasSize(3);
|
||||
assertThat(results.get(0).getOrderNum()).isEqualTo(1);
|
||||
assertThat(results.get(1).getOrderNum()).isEqualTo(2);
|
||||
assertThat(results.get(2).getOrderNum()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByWorkflowDefinitionIdAndType_ShouldReturnNodesWithSpecificType() {
|
||||
// Given
|
||||
NodeDefinition startNode = createNodeDefinition("START-001", "Start Node", NodeTypeEnum.START, 1);
|
||||
NodeDefinition taskNode1 = createNodeDefinition("TASK-001", "Task Node 1", NodeTypeEnum.TASK, 2);
|
||||
NodeDefinition taskNode2 = createNodeDefinition("TASK-002", "Task Node 2", NodeTypeEnum.TASK, 3);
|
||||
NodeDefinition endNode = createNodeDefinition("END-001", "End Node", NodeTypeEnum.END, 4);
|
||||
|
||||
nodeDefinitionRepository.save(startNode);
|
||||
nodeDefinitionRepository.save(taskNode1);
|
||||
nodeDefinitionRepository.save(taskNode2);
|
||||
nodeDefinitionRepository.save(endNode);
|
||||
|
||||
// When
|
||||
List<NodeDefinition> results = nodeDefinitionRepository.findByWorkflowDefinitionIdAndType(
|
||||
workflowDefinition.getId(), NodeTypeEnum.TASK);
|
||||
|
||||
// Then
|
||||
assertThat(results).hasSize(2);
|
||||
assertThat(results).allMatch(node -> node.getType() == NodeTypeEnum.TASK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByWorkflowDefinitionIdAndCode_ShouldReturnSpecificNode() {
|
||||
// Given
|
||||
NodeDefinition node = createNodeDefinition("NODE-001", "Test Node", NodeTypeEnum.TASK, 1);
|
||||
nodeDefinitionRepository.save(node);
|
||||
|
||||
// When
|
||||
NodeDefinition result = nodeDefinitionRepository.findByWorkflowDefinitionIdAndCodeAndDeletedFalse(
|
||||
workflowDefinition.getId(), "NODE-001");
|
||||
|
||||
// Then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getCode()).isEqualTo("NODE-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByWorkflowDefinitionIdAndCode_ShouldReturnTrue_WhenNodeExists() {
|
||||
// Given
|
||||
NodeDefinition node = createNodeDefinition("NODE-001", "Test Node", NodeTypeEnum.TASK, 1);
|
||||
nodeDefinitionRepository.save(node);
|
||||
|
||||
// When
|
||||
boolean exists = nodeDefinitionRepository.existsByWorkflowDefinitionIdAndCodeAndDeletedFalse(
|
||||
workflowDefinition.getId(), "NODE-001");
|
||||
|
||||
// Then
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteByWorkflowDefinitionId_ShouldMarkNodesAsDeleted() {
|
||||
// Given
|
||||
NodeDefinition node1 = createNodeDefinition("NODE-001", "Node 1", NodeTypeEnum.START, 1);
|
||||
NodeDefinition node2 = createNodeDefinition("NODE-002", "Node 2", NodeTypeEnum.TASK, 2);
|
||||
nodeDefinitionRepository.save(node1);
|
||||
nodeDefinitionRepository.save(node2);
|
||||
|
||||
// When
|
||||
nodeDefinitionRepository.deleteByWorkflowDefinitionId(workflowDefinition.getId());
|
||||
|
||||
// Then
|
||||
List<NodeDefinition> remainingNodes = nodeDefinitionRepository.findByWorkflowDefinitionId(workflowDefinition.getId());
|
||||
assertThat(remainingNodes).isEmpty();
|
||||
}
|
||||
|
||||
private NodeDefinition createNodeDefinition(String code, String name, NodeTypeEnum type, Integer orderNum) {
|
||||
NodeDefinition node = new NodeDefinition();
|
||||
node.setCode(code);
|
||||
node.setName(name);
|
||||
node.setType(type);
|
||||
node.setOrderNum(orderNum);
|
||||
node.setWorkflowDefinition(workflowDefinition);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
package com.qqchen.deploy.backend.workflow.repository;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ActiveProfiles("test")
|
||||
class WorkflowDefinitionRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||
|
||||
@Test
|
||||
void findLatestByCode_ShouldReturnLatestVersion() {
|
||||
// Given
|
||||
WorkflowDefinition def1 = createWorkflowDefinition("TEST-001", "Test Workflow 1", 1);
|
||||
WorkflowDefinition def2 = createWorkflowDefinition("TEST-001", "Test Workflow 1", 2);
|
||||
workflowDefinitionRepository.save(def1);
|
||||
workflowDefinitionRepository.save(def2);
|
||||
|
||||
// When
|
||||
Optional<WorkflowDefinition> result = workflowDefinitionRepository.findLatestByCode("TEST-001");
|
||||
|
||||
// Then
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getVersion()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByCodeAndVersion_ShouldReturnSpecificVersion() {
|
||||
// Given
|
||||
WorkflowDefinition def1 = createWorkflowDefinition("TEST-002", "Test Workflow 2", 1);
|
||||
WorkflowDefinition def2 = createWorkflowDefinition("TEST-002", "Test Workflow 2", 2);
|
||||
workflowDefinitionRepository.save(def1);
|
||||
workflowDefinitionRepository.save(def2);
|
||||
|
||||
// When
|
||||
Optional<WorkflowDefinition> result = workflowDefinitionRepository.findByCodeAndVersionAndDeletedFalse("TEST-002", 1);
|
||||
|
||||
// Then
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getVersion()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllVersionsByCode_ShouldReturnAllVersionsOrderedByVersionDesc() {
|
||||
// Given
|
||||
WorkflowDefinition def1 = createWorkflowDefinition("TEST-003", "Test Workflow 3", 1);
|
||||
WorkflowDefinition def2 = createWorkflowDefinition("TEST-003", "Test Workflow 3", 2);
|
||||
WorkflowDefinition def3 = createWorkflowDefinition("TEST-003", "Test Workflow 3", 3);
|
||||
workflowDefinitionRepository.save(def1);
|
||||
workflowDefinitionRepository.save(def2);
|
||||
workflowDefinitionRepository.save(def3);
|
||||
|
||||
// When
|
||||
List<WorkflowDefinition> results = workflowDefinitionRepository.findAllVersionsByCode("TEST-003");
|
||||
|
||||
// Then
|
||||
assertThat(results).hasSize(3);
|
||||
assertThat(results.get(0).getVersion()).isEqualTo(3);
|
||||
assertThat(results.get(1).getVersion()).isEqualTo(2);
|
||||
assertThat(results.get(2).getVersion()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByStatus_ShouldReturnWorkflowsWithSpecificStatus() {
|
||||
// Given
|
||||
WorkflowDefinition def1 = createWorkflowDefinition("TEST-004", "Test Workflow 4", 1);
|
||||
def1.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
WorkflowDefinition def2 = createWorkflowDefinition("TEST-005", "Test Workflow 5", 1);
|
||||
def2.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
workflowDefinitionRepository.save(def1);
|
||||
workflowDefinitionRepository.save(def2);
|
||||
|
||||
// When
|
||||
List<WorkflowDefinition> results = workflowDefinitionRepository.findByStatusAndDeletedFalse(WorkflowStatusEnum.PUBLISHED);
|
||||
|
||||
// Then
|
||||
assertThat(results).hasSize(1);
|
||||
assertThat(results.get(0).getStatus()).isEqualTo(WorkflowStatusEnum.PUBLISHED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByCode_ShouldReturnTrueWhenCodeExists() {
|
||||
// Given
|
||||
WorkflowDefinition def = createWorkflowDefinition("TEST-006", "Test Workflow 6", 1);
|
||||
workflowDefinitionRepository.save(def);
|
||||
|
||||
// When
|
||||
boolean exists = workflowDefinitionRepository.existsByCodeAndDeletedFalse("TEST-006");
|
||||
|
||||
// Then
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findLatestVersionByCode_ShouldReturnHighestVersion() {
|
||||
// Given
|
||||
WorkflowDefinition def1 = createWorkflowDefinition("TEST-007", "Test Workflow 7", 1);
|
||||
WorkflowDefinition def2 = createWorkflowDefinition("TEST-007", "Test Workflow 7", 2);
|
||||
WorkflowDefinition def3 = createWorkflowDefinition("TEST-007", "Test Workflow 7", 3);
|
||||
workflowDefinitionRepository.save(def1);
|
||||
workflowDefinitionRepository.save(def2);
|
||||
workflowDefinitionRepository.save(def3);
|
||||
|
||||
// When
|
||||
Integer latestVersion = workflowDefinitionRepository.findLatestVersionByCode("TEST-007");
|
||||
|
||||
// Then
|
||||
assertThat(latestVersion).isEqualTo(3);
|
||||
}
|
||||
|
||||
private WorkflowDefinition createWorkflowDefinition(String code, String name, Integer version) {
|
||||
WorkflowDefinition def = new WorkflowDefinition();
|
||||
def.setCode(code);
|
||||
def.setName(name);
|
||||
def.setVersion(version);
|
||||
def.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
def.setEnabled(true);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
@ -1,51 +1,168 @@
|
||||
package com.qqchen.deploy.backend.workflow.service;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.repository.INodeDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.impl.WorkflowDefinitionServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = {
|
||||
WorkflowDefinitionServiceImpl.class
|
||||
},
|
||||
properties = {
|
||||
"spring.main.allow-bean-definition-overriding=true"
|
||||
}
|
||||
)
|
||||
@ComponentScan(
|
||||
basePackages = "com.qqchen.deploy.backend",
|
||||
excludeFilters = {
|
||||
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Controller")
|
||||
}
|
||||
)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WorkflowDefinitionServiceTest {
|
||||
|
||||
@Resource
|
||||
private IWorkflowDefinitionService workflowDefinitionService;
|
||||
|
||||
@MockBean
|
||||
@Mock
|
||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||
|
||||
@Test
|
||||
void testCreateWorkflowDefinition() {
|
||||
// 准备测试数据
|
||||
when(workflowDefinitionRepository.save(any(WorkflowDefinition.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
WorkflowDefinition definition = invocation.getArgument(0);
|
||||
definition.setId(1L);
|
||||
return definition;
|
||||
});
|
||||
@Mock
|
||||
private INodeDefinitionRepository nodeDefinitionRepository;
|
||||
|
||||
// 执行测试
|
||||
// ... 测试代码 ...
|
||||
@Mock
|
||||
private WorkflowDefinitionConverter workflowDefinitionConverter;
|
||||
|
||||
@InjectMocks
|
||||
private WorkflowDefinitionServiceImpl workflowDefinitionService;
|
||||
|
||||
private WorkflowDefinition mockWorkflowDefinition;
|
||||
private WorkflowDefinitionDTO mockWorkflowDefinitionDTO;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockWorkflowDefinition = new WorkflowDefinition();
|
||||
mockWorkflowDefinition.setId(1L);
|
||||
mockWorkflowDefinition.setCode("TEST-001");
|
||||
mockWorkflowDefinition.setName("Test Workflow");
|
||||
mockWorkflowDefinition.setVersion(1);
|
||||
mockWorkflowDefinition.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
mockWorkflowDefinition.setEnabled(true);
|
||||
|
||||
mockWorkflowDefinitionDTO = new WorkflowDefinitionDTO();
|
||||
mockWorkflowDefinitionDTO.setId(1L);
|
||||
mockWorkflowDefinitionDTO.setCode("TEST-001");
|
||||
mockWorkflowDefinitionDTO.setName("Test Workflow");
|
||||
mockWorkflowDefinitionDTO.setVersion(1);
|
||||
mockWorkflowDefinitionDTO.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
mockWorkflowDefinitionDTO.setEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_ShouldCreateNewWorkflowDefinition() {
|
||||
// Given
|
||||
when(workflowDefinitionRepository.existsByCodeAndDeletedFalse(anyString())).thenReturn(false);
|
||||
when(workflowDefinitionRepository.save(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinition);
|
||||
when(workflowDefinitionConverter.toEntity(any(WorkflowDefinitionDTO.class))).thenReturn(mockWorkflowDefinition);
|
||||
when(workflowDefinitionConverter.toDto(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinitionDTO);
|
||||
|
||||
// When
|
||||
WorkflowDefinitionDTO result = workflowDefinitionService.create(mockWorkflowDefinitionDTO);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStatus()).isEqualTo(WorkflowStatusEnum.DRAFT);
|
||||
assertThat(result.getVersion()).isEqualTo(1);
|
||||
verify(workflowDefinitionRepository).save(any(WorkflowDefinition.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_ShouldThrowException_WhenCodeExists() {
|
||||
// Given
|
||||
when(workflowDefinitionRepository.existsByCodeAndDeletedFalse(anyString())).thenReturn(true);
|
||||
|
||||
// When/Then
|
||||
assertThatThrownBy(() -> workflowDefinitionService.create(mockWorkflowDefinitionDTO))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessage("工作流编码已存在");
|
||||
}
|
||||
|
||||
@Test
|
||||
void publish_ShouldPublishWorkflowDefinition() {
|
||||
// Given
|
||||
mockWorkflowDefinition.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.of(mockWorkflowDefinition));
|
||||
when(workflowDefinitionRepository.save(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinition);
|
||||
when(workflowDefinitionConverter.toDto(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinitionDTO);
|
||||
|
||||
// When
|
||||
WorkflowDefinitionDTO result = workflowDefinitionService.publish(1L);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStatus()).isEqualTo(WorkflowStatusEnum.PUBLISHED);
|
||||
verify(workflowDefinitionRepository).save(any(WorkflowDefinition.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void publish_ShouldThrowException_WhenNotDraft() {
|
||||
// Given
|
||||
mockWorkflowDefinition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.of(mockWorkflowDefinition));
|
||||
|
||||
// When/Then
|
||||
assertThatThrownBy(() -> workflowDefinitionService.publish(1L))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessage("只有草稿状态的工作流定义可以发布");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNewVersion_ShouldCreateNewVersion() {
|
||||
// Given
|
||||
when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.of(mockWorkflowDefinition));
|
||||
when(workflowDefinitionRepository.findLatestVersionByCode(anyString())).thenReturn(1);
|
||||
when(workflowDefinitionRepository.save(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinition);
|
||||
when(workflowDefinitionConverter.toDto(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinitionDTO);
|
||||
|
||||
// When
|
||||
WorkflowDefinitionDTO result = workflowDefinitionService.createNewVersion(1L);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStatus()).isEqualTo(WorkflowStatusEnum.DRAFT);
|
||||
assertThat(result.getVersion()).isEqualTo(2);
|
||||
verify(workflowDefinitionRepository).save(any(WorkflowDefinition.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllVersions_ShouldReturnAllVersions() {
|
||||
// Given
|
||||
WorkflowDefinition version1 = createWorkflowDefinition(1);
|
||||
WorkflowDefinition version2 = createWorkflowDefinition(2);
|
||||
List<WorkflowDefinition> versions = Arrays.asList(version2, version1);
|
||||
|
||||
when(workflowDefinitionRepository.findAllVersionsByCode(anyString())).thenReturn(versions);
|
||||
when(workflowDefinitionConverter.toDto(any(WorkflowDefinition.class))).thenReturn(mockWorkflowDefinitionDTO);
|
||||
|
||||
// When
|
||||
List<WorkflowDefinitionDTO> results = workflowDefinitionService.findAllVersions("TEST-001");
|
||||
|
||||
// Then
|
||||
assertThat(results).hasSize(2);
|
||||
verify(workflowDefinitionRepository).findAllVersionsByCode("TEST-001");
|
||||
}
|
||||
|
||||
private WorkflowDefinition createWorkflowDefinition(int version) {
|
||||
WorkflowDefinition def = new WorkflowDefinition();
|
||||
def.setId((long) version);
|
||||
def.setCode("TEST-001");
|
||||
def.setName("Test Workflow");
|
||||
def.setVersion(version);
|
||||
def.setStatus(WorkflowStatusEnum.DRAFT);
|
||||
def.setEnabled(true);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user