可正常启动

This commit is contained in:
dengqichen 2024-12-03 20:46:24 +08:00
parent 568859ce35
commit 33448f5ac5
28 changed files with 2800 additions and 2 deletions

View File

@ -0,0 +1,548 @@
# 工作流引擎功能开发文档
## 已实现功能
### 1. 核心功能
- [x] 工作流定义管理
- 工作流定义的CRUD操作
- 工作流状态管理(草稿、发布、禁用)
- 工作流版本控制
- [x] 工作流实例管理
- 工作流实例的创建和执行
- 实例状态管理(运行、暂停、完成、失败、取消)
- 实例执行历史记录
- [x] 节点实例管理
- 节点实例的创建和执行
- 节点状态管理(等待、运行、完成、失败、取消)
- 节点执行历史记录
### 2. 节点类型
- [x] Shell节点
- 支持Windows/Unix跨平台
- 工作目录配置
- 环境变量支持
- 超时处理
- 输出流日志记录
### 3. 日志管理
- [x] 日志记录
- 工作流级别日志
- 节点级别日志
- 系统级别日志
- 变量更新日志
- [x] 日志查询
- 工作流日志查询
- 节点日志查询
- 支持分页和条件过滤
## 待实现功能
### 1. 节点类型扩展
#### 1.1 Jenkins节点
```java
// 实现思路:
1. 添加Jenkins客户端依赖
```xml
<dependency>
<groupId>com.offbytwo.jenkins</groupId>
<artifactId>jenkins-client</artifactId>
<version>0.3.8</version>
</dependency>
```
2. 实现Jenkins节点执行器
```java
@Component
public class JenkinsNodeExecutor extends AbstractNodeExecutor {
@Override
protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) {
JenkinsNodeConfig jenkinsConfig = (JenkinsNodeConfig) config;
// 1. 连接Jenkins服务器
// 2. 触发构建任务
// 3. 等待构建完成
// 4. 获取构建结果
// 5. 处理构建产物
}
}
```
3. 添加构建结果处理
- 支持构建状态判断
- 支持测试结果解析
- 支持构建产物下载
```
#### 1.2 Git节点
```java
// 实现思路:
1. 添加JGit依赖
```xml
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.5.0.202303070854-r</version>
</dependency>
```
2. 实现Git节点执行器
```java
@Component
public class GitNodeExecutor extends AbstractNodeExecutor {
@Override
protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) {
GitNodeConfig gitConfig = (GitNodeConfig) config;
// 1. 克隆/拉取代码
// 2. 切换分支/标签
// 3. 执行Git操作
// 4. 提交更改
// 5. 推送远程
}
}
```
3. 添加Git操作支持
- 分支管理
- 标签管理
- 代码合并
- 冲突处理
```
### 2. 日志管理增强
#### 2.1 日志导出功能
```java
// 实现思路:
1. 添加Excel导出依赖
```xml
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
```
2. 实现日志导出服务
```java
@Service
public class WorkflowLogExportService {
public void exportToExcel(Long workflowInstanceId, OutputStream output) {
// 1. 查询日志数据
// 2. 创建Excel工作簿
// 3. 写入日志数据
// 4. 格式化和样式设置
// 5. 输出文件
}
public void exportToCsv(Long workflowInstanceId, Writer writer) {
// 1. 查询日志数据
// 2. 写入CSV头
// 3. 写入日志数据
// 4. 刷新输出
}
}
```
3. 添加导出接口
```java
@RestController
@RequestMapping("/api/v1/workflow")
public class WorkflowLogController {
@GetMapping("/{instanceId}/logs/export")
public void exportLogs(@PathVariable Long instanceId,
@RequestParam String format,
HttpServletResponse response) {
// 1. 设置响应头
// 2. 获取输出流
// 3. 调用导出服务
// 4. 处理异常
}
}
```
#### 2.2 日志清理功能
```java
// 实现思路:
1. 添加定时任务配置
```java
@Configuration
@EnableScheduling
public class WorkflowLogCleanupConfig {
@Scheduled(cron = "${workflow.log.cleanup.cron}")
public void cleanupLogs() {
// 1. 获取清理策略
// 2. 查询过期日志
// 3. 批量删除日志
// 4. 记录清理结果
}
}
```
2. 实现日志清理服务
```java
@Service
public class WorkflowLogCleanupService {
public void cleanupByTime(LocalDateTime before) {
// 1. 分批查询日志
// 2. 物理删除日志
// 3. 记录清理统计
}
public void cleanupByInstance(Long workflowInstanceId) {
// 1. 查询实例日志
// 2. 物理删除日志
// 3. 记录清理结果
}
}
```
### 3. 工作流监控
#### 3.1 执行监控
```java
// 实现思路:
1. 添加监控指标收集
```java
@Component
public class WorkflowMetricsCollector {
private final MeterRegistry registry;
public void recordExecutionTime(String workflowName, long timeMs) {
// 记录执行时长
}
public void recordNodeExecutionTime(String nodeName, long timeMs) {
// 记录节点执行时长
}
public void incrementExecutionCounter(String workflowName, String status) {
// 增加执行计数
}
}
```
2. 实现监控数据存储
```java
@Entity
@Table(name = "wf_workflow_metrics")
public class WorkflowMetrics {
// 执行时长
private Long executionTime;
// 成功率
private Double successRate;
// 失败次数
private Integer failureCount;
// 平均节点执行时长
private Long avgNodeExecutionTime;
}
```
3. 添加监控接口
```java
@RestController
@RequestMapping("/api/v1/workflow/metrics")
public class WorkflowMetricsController {
@GetMapping("/overview")
public WorkflowMetricsOverview getOverview() {
// 返回整体监控数据
}
@GetMapping("/{workflowId}")
public WorkflowMetricsDetail getDetail(@PathVariable Long workflowId) {
// 返回具体工作流监控数据
}
}
```
#### 3.2 告警通知
```java
// 实现思路:
1. 定义告警规则
```java
@Entity
@Table(name = "wf_alert_rule")
public class AlertRule {
// 告警类型
private AlertType type;
// 告警阈值
private String threshold;
// 告警级别
private AlertLevel level;
// 通知方式
private List<NotifyChannel> channels;
}
```
2. 实现告警服务
```java
@Service
public class WorkflowAlertService {
public void checkAndAlert(WorkflowInstance instance) {
// 1. 获取告警规则
// 2. 检查是否触发
// 3. 生成告警信息
// 4. 发送通知
}
public void sendAlert(Alert alert) {
// 1. 获取通知渠道
// 2. 发送通知
// 3. 记录通知结果
}
}
```
### 4. 工作流调度
#### 4.1 定时调度
```java
// 实现思路:
1. 添加调度配置
```java
@Entity
@Table(name = "wf_workflow_schedule")
public class WorkflowSchedule {
// 调度类型
private ScheduleType type;
// Cron表达式
private String cronExpression;
// 生效时间
private LocalDateTime effectiveTime;
// 失效时间
private LocalDateTime expireTime;
}
```
2. 实现调度服务
```java
@Service
public class WorkflowScheduleService {
public void schedule(WorkflowSchedule schedule) {
// 1. 验证调度配置
// 2. 创建调度任务
// 3. 注册到调度器
// 4. 记录调度状态
}
public void executeScheduledWorkflow(Long scheduleId) {
// 1. 获取调度配置
// 2. 创建工作流实例
// 3. 执行工作流
// 4. 记录执行结果
}
}
```
#### 4.2 依赖调度
```java
// 实现思路:
1. 添加依赖配置
```java
@Entity
@Table(name = "wf_workflow_dependency")
public class WorkflowDependency {
// 上游工作流
private Long upstreamWorkflowId;
// 依赖类型
private DependencyType type;
// 触发条件
private String condition;
}
```
2. 实现依赖服务
```java
@Service
public class WorkflowDependencyService {
public void handleWorkflowComplete(Long workflowInstanceId) {
// 1. 查询依赖配置
// 2. 检查触发条件
// 3. 触发下游工作流
// 4. 记录依赖执行
}
}
```
### 5. 工作流权限管理
#### 5.1 权限模型
```java
// 实现思路:
1. 定义权限模型
```java
@Entity
@Table(name = "wf_permission")
public class WorkflowPermission {
// 权限类型
private PermissionType type;
// 权限范围
private PermissionScope scope;
// 权限值
private String value;
}
```
2. 实现权限服务
```java
@Service
public class WorkflowPermissionService {
public boolean hasPermission(Long userId, Long workflowId, PermissionType type) {
// 1. 查询用户权限
// 2. 检查继承权限
// 3. 验证权限范围
// 4. 返回结果
}
public void grantPermission(PermissionGrant grant) {
// 1. 验证授权者权限
// 2. 创建权限记录
// 3. 记录授权日志
}
}
```
### 6. 工作流版本管理
#### 6.1 版本控制
```java
// 实现思路:
1. 添加版本模型
```java
@Entity
@Table(name = "wf_workflow_version")
public class WorkflowVersion {
// 版本号
private String version;
// 变更内容
private String changelog;
// 工作流定义
private String definition;
}
```
2. 实现版本服务
```java
@Service
public class WorkflowVersionService {
public void createVersion(Long workflowId) {
// 1. 生成版本号
// 2. 保存当前定义
// 3. 记录变更日志
}
public void rollback(Long workflowId, String version) {
// 1. 验证版本有效性
// 2. 恢复历史版本
// 3. 记录回滚操作
}
}
```
### 7. 工作流测试
#### 7.1 模拟执行
```java
// 实现思路:
1. 实现测试环境
```java
@Component
public class WorkflowTestEnvironment {
public WorkflowContext createTestContext() {
// 1. 创建测试上下文
// 2. 初始化测试数据
// 3. 配置测试参数
}
}
```
2. 实现测试服务
```java
@Service
public class WorkflowTestService {
public TestResult simulateExecution(Long workflowId) {
// 1. 准备测试环境
// 2. 执行工作流
// 3. 收集测试结果
// 4. 生成测试报告
}
}
```
### 8. 工作流模板
#### 8.1 模板管理
```java
// 实现思路:
1. 添加模板模型
```java
@Entity
@Table(name = "wf_workflow_template")
public class WorkflowTemplate {
// 模板名称
private String name;
// 模板描述
private String description;
// 模板定义
private String definition;
// 参数定义
private List<TemplateParameter> parameters;
}
```
2. 实现模板服务
```java
@Service
public class WorkflowTemplateService {
public Long createFromTemplate(Long templateId, Map<String, Object> parameters) {
// 1. 加载模板定义
// 2. 替换模板参数
// 3. 创建工作流定义
// 4. 返回工作流ID
}
public void saveAsTemplate(Long workflowId) {
// 1. 提取工作流定义
// 2. 提取参数定义
// 3. 保存为模板
}
}
```
## 下一步开发计划
1. 优先实现日志管理增强功能
- 日志导出功能
- 日志清理功能
- 日志查询性能优化
2. 实现工作流监控功能
- 执行监控
- 告警通知
- 监控大屏
3. 实现剩余节点类型
- Jenkins节点
- Git节点
- HTTP节点
- 通知节点
4. 实现工作流调度功能
- 定时调度
- 依赖调度
- 调度监控
## 技术栈
- 后端框架Spring Boot 3.x
- 数据库MySQL 8.x
- ORM框架Spring Data JPA
- 任务调度Quartz
- 监控Micrometer + Prometheus
- 日志Logback
- 工具库:
- Apache Commons
- Guava
- MapStruct
- Jackson

View File

@ -0,0 +1,26 @@
package com.qqchen.deploy.backend.enums;
/**
* 日志级别枚举
*/
public enum LogLevelEnum {
/**
* 调试
*/
DEBUG,
/**
* 信息
*/
INFO,
/**
* 警告
*/
WARN,
/**
* 错误
*/
ERROR
}

View File

@ -0,0 +1,71 @@
package com.qqchen.deploy.backend.enums;
/**
* 日志类型枚举
*/
public enum LogTypeEnum {
/**
* 工作流开始
*/
WORKFLOW_START,
/**
* 工作流完成
*/
WORKFLOW_COMPLETE,
/**
* 工作流暂停
*/
WORKFLOW_PAUSE,
/**
* 工作流恢复
*/
WORKFLOW_RESUME,
/**
* 工作流取消
*/
WORKFLOW_CANCEL,
/**
* 工作流错误
*/
WORKFLOW_ERROR,
/**
* 节点开始
*/
NODE_START,
/**
* 节点完成
*/
NODE_COMPLETE,
/**
* 节点错误
*/
NODE_ERROR,
/**
* 节点重试
*/
NODE_RETRY,
/**
* 节点跳过
*/
NODE_SKIP,
/**
* 变量更新
*/
VARIABLE_UPDATE,
/**
* 系统日志
*/
SYSTEM
}

View File

@ -0,0 +1,21 @@
package com.qqchen.deploy.backend.enums;
/**
* 权限类型枚举
*/
public enum PermissionTypeEnum {
/**
* 查看权限
*/
VIEW,
/**
* 执行权限
*/
EXECUTE,
/**
* 管理权限
*/
MANAGE
}

View File

@ -0,0 +1,141 @@
package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.api.request.WorkflowLogQueryRequest;
import com.qqchen.deploy.backend.workflow.api.request.WorkflowStartRequest;
import com.qqchen.deploy.backend.workflow.api.response.WorkflowLogDTO;
import com.qqchen.deploy.backend.workflow.converter.WorkflowLogConverter;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* 工作流API控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/workflow")
@Tag(name = "工作流API", description = "工作流相关API接口")
public class WorkflowApiController {
@Resource
private IWorkflowInstanceService workflowInstanceService;
@Resource
private INodeInstanceService nodeInstanceService;
@Resource
private IWorkflowLogService workflowLogService;
@Resource
private WorkflowLogConverter workflowLogConverter;
@Operation(summary = "启动工作流")
@PostMapping("/start")
public Response<WorkflowInstanceDTO> startWorkflow(@Valid @RequestBody WorkflowStartRequest request) {
return Response.success(workflowInstanceService.start(
request.getDefinitionId(),
request.getProjectEnvId(),
request.getVariables() != null ? request.getVariables().toString() : null
));
}
@Operation(summary = "取消工作流")
@PostMapping("/{instanceId}/cancel")
public Response<Boolean> cancelWorkflow(
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
) {
return Response.success(workflowInstanceService.cancel(instanceId));
}
@Operation(summary = "获取工作流实例详情")
@GetMapping("/{instanceId}")
public Response<WorkflowInstanceDTO> getWorkflowInstance(
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
) {
return Response.success(workflowInstanceService.findById(instanceId));
}
@Operation(summary = "获取工作流节点列表")
@GetMapping("/{instanceId}/nodes")
public Response<List<NodeInstanceDTO>> getWorkflowNodes(
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
) {
return Response.success(nodeInstanceService.findByWorkflowInstanceId(instanceId));
}
@Operation(summary = "重试工作流节点")
@PostMapping("/node/{nodeId}/retry")
public Response<Boolean> retryNode(
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
) {
// TODO: 实现节点重试逻辑
return Response.success(true);
}
@Operation(summary = "跳过工作流节点")
@PostMapping("/node/{nodeId}/skip")
public Response<Boolean> skipNode(
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
) {
// TODO: 实现节点跳过逻辑
return Response.success(true);
}
@Operation(summary = "查询工作流日志")
@PostMapping("/logs")
public Response<Page<WorkflowLogDTO>> getLogs(@Valid @RequestBody WorkflowLogQueryRequest request) {
Page<WorkflowLog> logs = workflowLogService.findLogs(
request.getWorkflowInstanceId(),
request.getType(),
request.getLevel(),
PageRequest.of(request.getPageNum() - 1, request.getPageSize())
);
return Response.success(logs.map(workflowLogConverter::toDto));
}
@Operation(summary = "查询节点日志")
@GetMapping("/node/{nodeId}/logs")
public Response<List<WorkflowLogDTO>> getNodeLogs(
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId,
@Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type,
@Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level
) {
List<WorkflowLog> logs = workflowLogService.findNodeLogs(nodeId, type, level);
return Response.success(logs.stream()
.map(workflowLogConverter::toDto)
.collect(Collectors.toList()));
}
@Operation(summary = "分页查询节点日志")
@GetMapping("/node/{nodeId}/logs/page")
public Response<Page<WorkflowLogDTO>> getNodeLogsPage(
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId,
@Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type,
@Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize
) {
Page<WorkflowLog> logs = workflowLogService.findNodeLogs(
nodeId, type, level, PageRequest.of(pageNum - 1, pageSize)
);
return Response.success(logs.map(workflowLogConverter::toDto));
}
}

View File

@ -0,0 +1,52 @@
package com.qqchen.deploy.backend.workflow.api.request;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 工作流日志查询请求
*/
@Data
@Schema(description = "工作流日志查询请求")
public class WorkflowLogQueryRequest {
/**
* 工作流实例ID
*/
@NotNull(message = "工作流实例ID不能为空")
@Schema(description = "工作流实例ID", required = true)
private Long workflowInstanceId;
/**
* 节点实例ID
*/
@Schema(description = "节点实例ID")
private Long nodeInstanceId;
/**
* 日志类型
*/
@Schema(description = "日志类型")
private LogTypeEnum type;
/**
* 日志级别
*/
@Schema(description = "日志级别")
private LogLevelEnum level;
/**
* 页码
*/
@Schema(description = "页码", defaultValue = "1")
private Integer pageNum = 1;
/**
* 每页大小
*/
@Schema(description = "每页大小", defaultValue = "20")
private Integer pageSize = 20;
}

View File

@ -0,0 +1,35 @@
package com.qqchen.deploy.backend.workflow.api.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
/**
* 工作流启动请求
*/
@Data
@Schema(description = "工作流启动请求")
public class WorkflowStartRequest {
/**
* 工作流定义ID
*/
@NotNull(message = "工作流定义ID不能为空")
@Schema(description = "工作流定义ID", required = true)
private Long definitionId;
/**
* 项目环境ID
*/
@NotNull(message = "项目环境ID不能为空")
@Schema(description = "项目环境ID", required = true)
private Long projectEnvId;
/**
* 工作流变量
*/
@Schema(description = "工作流变量")
private Map<String, Object> variables;
}

View File

@ -0,0 +1,65 @@
package com.qqchen.deploy.backend.workflow.api.response;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 工作流日志DTO
*/
@Data
@Schema(description = "工作流日志")
public class WorkflowLogDTO extends BaseDTO {
/**
* 日志ID
*/
@Schema(description = "日志ID")
private Long id;
/**
* 工作流实例ID
*/
@Schema(description = "工作流实例ID")
private Long workflowInstanceId;
/**
* 节点实例ID
*/
@Schema(description = "节点实例ID")
private Long nodeInstanceId;
/**
* 日志类型
*/
@Schema(description = "日志类型")
private LogTypeEnum type;
/**
* 日志级别
*/
@Schema(description = "日志级别")
private LogLevelEnum level;
/**
* 日志内容
*/
@Schema(description = "日志内容")
private String content;
/**
* 详细信息
*/
@Schema(description = "详细信息")
private String detail;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.workflow.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.workflow.api.response.WorkflowLogDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import org.mapstruct.Mapper;
/**
* 工作流日志转换器
*/
@Mapper(config = BaseConverter.class)
public interface WorkflowLogConverter extends BaseConverter<WorkflowLog, WorkflowLogDTO> {
}

View File

@ -0,0 +1,81 @@
package com.qqchen.deploy.backend.workflow.engine;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 工作流上下文
*/
@Data
public class WorkflowContext {
/**
* 工作流实例
*/
private WorkflowInstance workflowInstance;
/**
* 当前节点实例
*/
private NodeInstance currentNode;
/**
* 所有节点实例
*/
private List<NodeInstance> allNodes;
/**
* 工作流变量
*/
private Map<String, Object> variables;
/**
* 临时变量节点间传递
*/
private Map<String, Object> tempVariables;
public WorkflowContext() {
this.variables = new ConcurrentHashMap<>();
this.tempVariables = new ConcurrentHashMap<>();
}
/**
* 获取变量值
*/
public Object getVariable(String key) {
return variables.get(key);
}
/**
* 设置变量值
*/
public void setVariable(String key, Object value) {
variables.put(key, value);
}
/**
* 获取临时变量值
*/
public Object getTempVariable(String key) {
return tempVariables.get(key);
}
/**
* 设置临时变量值
*/
public void setTempVariable(String key, Object value) {
tempVariables.put(key, value);
}
/**
* 清除临时变量
*/
public void clearTempVariables() {
tempVariables.clear();
}
}

View File

@ -1 +1,54 @@
package com.qqchen.deploy.backend.workflow.engine;
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
import java.util.Map;
/**
* 工作流引擎接口
*/
public interface WorkflowEngine {
/**
* 启动工作流实例
*
* @param instanceId 工作流实例ID
* @param variables 工作流变量
*/
void startInstance(Long instanceId, Map<String, Object> variables);
/**
* 取消工作流实例
*
* @param instanceId 工作流实例ID
*/
void cancelInstance(Long instanceId);
/**
* 暂停工作流实例
*
* @param instanceId 工作流实例ID
*/
void pauseInstance(Long instanceId);
/**
* 恢复工作流实例
*
* @param instanceId 工作流实例ID
*/
void resumeInstance(Long instanceId);
/**
* 重试工作流节点
*
* @param nodeInstanceId 节点实例ID
*/
void retryNode(Long nodeInstanceId);
/**
* 跳过工作流节点
*
* @param nodeInstanceId 节点实例ID
*/
void skipNode(Long nodeInstanceId);
}

View File

@ -0,0 +1,241 @@
package com.qqchen.deploy.backend.workflow.engine.impl;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
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;
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 工作流引擎默认实现
*/
@Slf4j
@Service
public class DefaultWorkflowEngine implements WorkflowEngine {
@Resource
private IWorkflowInstanceRepository workflowInstanceRepository;
@Resource
private INodeInstanceRepository nodeInstanceRepository;
@Resource
private IWorkflowVariableService workflowVariableService;
@Resource
private IWorkflowLogService workflowLogService;
@Override
@Transactional
public void startInstance(Long instanceId, Map<String, Object> variables) {
// 获取工作流实例
WorkflowInstance instance = getWorkflowInstance(instanceId);
if (instance.getStatus() != WorkflowStatusEnum.CREATED) {
throw new BusinessException(ResponseCode.WORKFLOW_STATUS_INVALID);
}
// 初始化工作流上下文
WorkflowContext context = initContext(instance, variables);
try {
// 更新工作流状态
instance.setStatus(WorkflowStatusEnum.RUNNING);
workflowInstanceRepository.save(instance);
// 保存工作流变量
workflowVariableService.saveVariables(instanceId, variables);
// 记录工作流日志
workflowLogService.logWorkflowStart(instance);
// 执行第一个节点
executeNextNode(context);
} catch (Exception e) {
log.error("Failed to start workflow instance: {}", instanceId, e);
instance.setStatus(WorkflowStatusEnum.FAILED);
workflowInstanceRepository.save(instance);
workflowLogService.logWorkflowError(instance, e.getMessage());
throw new BusinessException(ResponseCode.WORKFLOW_START_FAILED);
}
}
@Override
@Transactional
public void cancelInstance(Long instanceId) {
WorkflowInstance instance = getWorkflowInstance(instanceId);
if (instance.getStatus() != WorkflowStatusEnum.RUNNING && instance.getStatus() != WorkflowStatusEnum.PAUSED) {
throw new BusinessException(ResponseCode.WORKFLOW_STATUS_INVALID);
}
instance.setStatus(WorkflowStatusEnum.CANCELLED);
workflowInstanceRepository.save(instance);
// 取消所有运行中的节点
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
instanceId, NodeStatusEnum.RUNNING);
runningNodes.forEach(node -> {
node.setStatus(NodeStatusEnum.CANCELLED);
nodeInstanceRepository.save(node);
});
workflowLogService.logWorkflowCancel(instance);
}
@Override
@Transactional
public void pauseInstance(Long instanceId) {
WorkflowInstance instance = getWorkflowInstance(instanceId);
if (instance.getStatus() != WorkflowStatusEnum.RUNNING) {
throw new BusinessException(ResponseCode.WORKFLOW_STATUS_INVALID);
}
instance.setStatus(WorkflowStatusEnum.PAUSED);
workflowInstanceRepository.save(instance);
// 暂停所有运行中的节点
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
instanceId, NodeStatusEnum.RUNNING);
runningNodes.forEach(node -> {
node.setStatus(NodeStatusEnum.PAUSED);
nodeInstanceRepository.save(node);
});
workflowLogService.logWorkflowPause(instance);
}
@Override
@Transactional
public void resumeInstance(Long instanceId) {
WorkflowInstance instance = getWorkflowInstance(instanceId);
if (instance.getStatus() != WorkflowStatusEnum.PAUSED) {
throw new BusinessException(ResponseCode.WORKFLOW_STATUS_INVALID);
}
instance.setStatus(WorkflowStatusEnum.RUNNING);
workflowInstanceRepository.save(instance);
// 恢复所有暂停的节点
List<NodeInstance> pausedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
instanceId, NodeStatusEnum.PAUSED);
pausedNodes.forEach(node -> {
node.setStatus(NodeStatusEnum.RUNNING);
nodeInstanceRepository.save(node);
});
workflowLogService.logWorkflowResume(instance);
}
@Override
@Transactional
public void retryNode(Long nodeInstanceId) {
NodeInstance node = getNodeInstance(nodeInstanceId);
if (node.getStatus() != NodeStatusEnum.FAILED) {
throw new BusinessException(ResponseCode.NODE_STATUS_INVALID);
}
WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId());
if (instance.getStatus() != WorkflowStatusEnum.FAILED) {
throw new BusinessException(ResponseCode.WORKFLOW_STATUS_INVALID);
}
// 重置节点状态
node.setStatus(NodeStatusEnum.RUNNING);
nodeInstanceRepository.save(node);
// 重置工作流状态
instance.setStatus(WorkflowStatusEnum.RUNNING);
workflowInstanceRepository.save(instance);
// 记录重试日志
workflowLogService.logNodeRetry(node);
}
@Override
@Transactional
public void skipNode(Long nodeInstanceId) {
NodeInstance node = getNodeInstance(nodeInstanceId);
if (node.getStatus() != NodeStatusEnum.FAILED) {
throw new BusinessException(ResponseCode.NODE_STATUS_INVALID);
}
// 更新节点状态为已跳过
node.setStatus(NodeStatusEnum.SKIPPED);
nodeInstanceRepository.save(node);
// 获取工作流实例
WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId());
// 初始化上下文
WorkflowContext context = initContext(instance, workflowVariableService.getVariables(instance.getId()));
context.setCurrentNode(node);
// 执行下一个节点
executeNextNode(context);
// 记录跳过日志
workflowLogService.logNodeSkip(node);
}
private WorkflowInstance getWorkflowInstance(Long instanceId) {
return workflowInstanceRepository.findById(instanceId)
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NOT_FOUND));
}
private NodeInstance getNodeInstance(Long nodeInstanceId) {
return nodeInstanceRepository.findById(nodeInstanceId)
.orElseThrow(() -> new BusinessException(ResponseCode.NODE_NOT_FOUND));
}
private WorkflowContext initContext(WorkflowInstance instance, Map<String, Object> variables) {
WorkflowContext context = new WorkflowContext();
context.setWorkflowInstance(instance);
context.setVariables(variables);
context.setAllNodes(nodeInstanceRepository.findByWorkflowInstanceId(instance.getId()));
return context;
}
private void executeNextNode(WorkflowContext context) {
NodeInstance currentNode = context.getCurrentNode();
List<NodeInstance> allNodes = context.getAllNodes();
// 如果当前节点为空说明是第一个节点
Optional<NodeInstance> nextNode;
if (currentNode == null) {
nextNode = allNodes.stream()
.filter(node -> node.getPreNodeId() == null)
.findFirst();
} else {
nextNode = allNodes.stream()
.filter(node -> currentNode.getId().equals(node.getPreNodeId()))
.findFirst();
}
if (nextNode.isPresent()) {
NodeInstance node = nextNode.get();
node.setStatus(NodeStatusEnum.RUNNING);
nodeInstanceRepository.save(node);
workflowLogService.logNodeStart(node);
// TODO: 实际节点执行逻辑
} else {
// 没有下一个节点工作流完成
WorkflowInstance instance = context.getWorkflowInstance();
instance.setStatus(WorkflowStatusEnum.COMPLETED);
workflowInstanceRepository.save(instance);
workflowLogService.logWorkflowComplete(instance);
}
}
}

View File

@ -0,0 +1,115 @@
package com.qqchen.deploy.backend.workflow.engine.node;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
/**
* 抽象节点执行器
*/
@Slf4j
public abstract class AbstractNodeExecutor implements NodeExecutor {
@Resource
protected IWorkflowLogService workflowLogService;
@Resource
protected ObjectMapper objectMapper;
@Override
public boolean execute(NodeInstance nodeInstance, WorkflowContext context) {
try {
// 解析节点配置
NodeConfig config = parseConfig(nodeInstance.getConfig());
// 执行前处理
beforeExecute(nodeInstance, context, config);
// 执行节点
boolean result = doExecute(nodeInstance, context, config);
// 执行后处理
afterExecute(nodeInstance, context, config, result);
return result;
} catch (Exception e) {
log.error("Execute node failed: {}", e.getMessage(), e);
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
"Execute node failed: " + e.getMessage(), e.toString());
return false;
}
}
@Override
public void cancel(NodeInstance nodeInstance, WorkflowContext context) {
try {
// 解析节点配置
NodeConfig config = parseConfig(nodeInstance.getConfig());
// 执行取消操作
doCancel(nodeInstance, context, config);
} catch (Exception e) {
log.error("Cancel node failed: {}", e.getMessage(), e);
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
"Cancel node failed: " + e.getMessage(), e.toString());
}
}
/**
* 解析节点配置
*
* @param configJson 配置JSON
* @return 节点配置
*/
protected abstract NodeConfig parseConfig(String configJson) throws Exception;
/**
* 执行前处理
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
* @param config 节点配置
*/
protected void beforeExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) {
workflowLogService.logNodeStart(nodeInstance);
}
/**
* 执行节点
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
* @param config 节点配置
* @return 执行结果
*/
protected abstract boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception;
/**
* 执行后处理
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
* @param config 节点配置
* @param success 是否成功
*/
protected void afterExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config, boolean success) {
if (success) {
workflowLogService.logNodeComplete(nodeInstance);
} else {
workflowLogService.logNodeError(nodeInstance, "Node execution failed");
}
}
/**
* 执行取消操作
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
* @param config 节点配置
*/
protected abstract void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception;
}

View File

@ -0,0 +1,34 @@
package com.qqchen.deploy.backend.workflow.engine.node;
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
/**
* 节点执行器接口
*/
public interface NodeExecutor {
/**
* 执行节点
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
* @return 执行结果
*/
boolean execute(NodeInstance nodeInstance, WorkflowContext context);
/**
* 取消节点执行
*
* @param nodeInstance 节点实例
* @param context 工作流上下文
*/
void cancel(NodeInstance nodeInstance, WorkflowContext context);
/**
* 获取支持的节点类型
*
* @return 节点类型
*/
NodeType getNodeType();
}

View File

@ -0,0 +1,118 @@
package com.qqchen.deploy.backend.workflow.engine.node.executor;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
import com.qqchen.deploy.backend.workflow.engine.node.AbstractNodeExecutor;
import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig;
import com.qqchen.deploy.backend.workflow.engine.node.NodeType;
import com.qqchen.deploy.backend.workflow.engine.node.ScriptNodeConfig;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Shell节点执行器
*/
@Slf4j
@Component
public class ShellNodeExecutor extends AbstractNodeExecutor {
private static final int DEFAULT_TIMEOUT = 3600;
private Process currentProcess;
@Override
public NodeType getNodeType() {
return NodeType.SHELL;
}
@Override
protected NodeConfig parseConfig(String configJson) throws Exception {
return objectMapper.readValue(configJson, ScriptNodeConfig.class);
}
@Override
protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
ScriptNodeConfig shellConfig = (ScriptNodeConfig) config;
// 构建命令
List<String> command = buildCommand(shellConfig);
// 创建进程构建器
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
// 设置工作目录
if (shellConfig.getWorkingDirectory() != null) {
processBuilder.directory(new java.io.File(shellConfig.getWorkingDirectory()));
}
// 设置环境变量
if (shellConfig.getEnvironment() != null) {
processBuilder.environment().putAll(shellConfig.getEnvironment());
}
// 启动进程
currentProcess = processBuilder.start();
// 读取输出
try (BufferedReader reader = new BufferedReader(new InputStreamReader(currentProcess.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.INFO, line, null);
}
}
// 等待进程完成
boolean completed = currentProcess.waitFor(shellConfig.getTimeout() != null ? shellConfig.getTimeout() : DEFAULT_TIMEOUT, TimeUnit.SECONDS);
if (!completed) {
currentProcess.destroyForcibly();
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR, "Shell execution timeout", null);
return false;
}
// 检查退出码
int exitCode = currentProcess.exitValue();
if (exitCode != 0) {
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
"Shell execution failed with exit code: " + exitCode, null);
return false;
}
return true;
}
@Override
protected void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
if (currentProcess != null && currentProcess.isAlive()) {
currentProcess.destroyForcibly();
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.WARN,
"Shell execution cancelled", null);
}
}
private List<String> buildCommand(ScriptNodeConfig config) {
List<String> command = new ArrayList<>();
// 添加Shell解释器
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
command.add("cmd");
command.add("/c");
} else {
command.add("sh");
command.add("-c");
}
// 添加脚本内容
command.add(config.getScript());
return command;
}
}

View File

@ -0,0 +1,58 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 工作流日志
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_workflow_log")
public class WorkflowLog extends Entity<Long> {
/**
* 工作流实例ID
*/
@Column(nullable = false)
private Long workflowInstanceId;
/**
* 节点实例ID
*/
private Long nodeInstanceId;
/**
* 日志类型
*/
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LogTypeEnum type;
/**
* 日志级别
*/
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LogLevelEnum level;
/**
* 日志内容
*/
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
/**
* 详细信息
*/
@Column(columnDefinition = "TEXT")
private String detail;
}

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 工作流权限
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_workflow_permission")
public class WorkflowPermission extends Entity<Long> {
/**
* 工作流定义ID
*/
@Column(nullable = false)
private Long workflowDefinitionId;
/**
* 权限类型
*/
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PermissionTypeEnum type;
/**
* 用户ID
*/
private Long userId;
/**
* 角色ID
*/
private Long roleId;
/**
* 部门ID
*/
private Long departmentId;
}

View File

@ -0,0 +1,41 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 工作流变量
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_workflow_variable")
public class WorkflowVariable extends Entity<Long> {
/**
* 工作流实例ID
*/
@Column(nullable = false)
private Long workflowInstanceId;
/**
* 变量名
*/
@Column(nullable = false)
private String name;
/**
* 变量值
*/
@Column(columnDefinition = "TEXT")
private String value;
/**
* 变量类型
*/
@Column(nullable = false)
private String type;
}

View File

@ -0,0 +1,88 @@
package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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 IWorkflowLogRepository extends IBaseRepository<WorkflowLog, Long> {
/**
* 查询节点日志
*
* @param nodeInstanceId 节点实例ID
* @param type 日志类型
* @param level 日志级别
* @return 日志列表
*/
@Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " +
"AND (:type IS NULL OR l.type = :type) " +
"AND (:level IS NULL OR l.level = :level) " +
"AND l.deleted = false " +
"ORDER BY l.createTime DESC")
List<WorkflowLog> findNodeLogs(
@Param("nodeInstanceId") Long nodeInstanceId,
@Param("type") LogTypeEnum type,
@Param("level") LogLevelEnum level
);
/**
* 分页查询节点日志
*
* @param nodeInstanceId 节点实例ID
* @param type 日志类型
* @param level 日志级别
* @param pageable 分页参数
* @return 日志分页
*/
@Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " +
"AND (:type IS NULL OR l.type = :type) " +
"AND (:level IS NULL OR l.level = :level) " +
"AND l.deleted = false " +
"ORDER BY l.createTime DESC")
Page<WorkflowLog> findNodeLogs(
@Param("nodeInstanceId") Long nodeInstanceId,
@Param("type") LogTypeEnum type,
@Param("level") LogLevelEnum level,
Pageable pageable
);
/**
* 分页查询工作流日志
*
* @param workflowInstanceId 工作流实例ID
* @param type 日志类型
* @param level 日志级别
* @param pageable 分页参数
* @return 日志分页
*/
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :workflowInstanceId " +
"AND (:type IS NULL OR l.type = :type) " +
"AND (:level IS NULL OR l.level = :level) " +
"AND l.deleted = false " +
"ORDER BY l.createTime DESC")
Page<WorkflowLog> findByWorkflowInstanceIdAndTypeAndLevel(
@Param("workflowInstanceId") Long workflowInstanceId,
@Param("type") LogTypeEnum type,
@Param("level") LogLevelEnum level,
Pageable pageable
);
/**
* 删除工作流实例的所有日志
*
* @param workflowInstanceId 工作流实例ID
*/
void deleteByWorkflowInstanceId(Long workflowInstanceId);
}

View File

@ -0,0 +1,123 @@
package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* 工作流权限仓库接口
*/
@Repository
public interface IWorkflowPermissionRepository extends IBaseRepository<WorkflowPermission, Long> {
/**
* 根据工作流定义ID查询权限列表
*
* @param workflowDefinitionId 工作流定义ID
* @return 权限列表
*/
List<WorkflowPermission> findByWorkflowDefinitionId(Long workflowDefinitionId);
/**
* 根据工作流定义ID和权限类型分页查询权限
*
* @param workflowDefinitionId 工作流定义ID
* @param type 权限类型
* @param pageable 分页参数
* @return 权限分页
*/
Page<WorkflowPermission> findByWorkflowDefinitionIdAndType(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
/**
* 查询用户权限
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
* @return 权限
*/
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 查询角色权限
*
* @param workflowDefinitionId 工作流定义ID
* @param roleId 角色ID
* @param type 权限类型
* @return 权限
*/
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
/**
* 查询部门权限
*
* @param workflowDefinitionId 工作流定义ID
* @param departmentId 部门ID
* @param type 权限类型
* @return 权限
*/
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
/**
* 检查用户权限是否存在
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
* @return 是否存在
*/
boolean existsByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 检查角色权限是否存在
*
* @param workflowDefinitionId 工作流定义ID
* @param roleId 角色ID
* @param type 权限类型
* @return 是否存在
*/
boolean existsByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
/**
* 检查部门权限是否存在
*
* @param workflowDefinitionId 工作流定义ID
* @param departmentId 部门ID
* @param type 权限类型
* @return 是否存在
*/
boolean existsByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
/**
* 删除用户权限
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
*/
void deleteByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 删除角色权限
*
* @param workflowDefinitionId 工作流定义ID
* @param roleId 角色ID
* @param type 权限类型
*/
void deleteByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
/**
* 删除部门权限
*
* @param workflowDefinitionId 工作流定义ID
* @param departmentId 部门ID
* @param type 权限类型
*/
void deleteByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
}

View File

@ -0,0 +1,47 @@
package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* 工作流变量仓库接口
*/
@Repository
public interface IWorkflowVariableRepository extends IBaseRepository<WorkflowVariable, Long> {
/**
* 根据工作流实例ID查询变量列表
*
* @param workflowInstanceId 工作流实例ID
* @return 变量列表
*/
List<WorkflowVariable> findByWorkflowInstanceId(Long workflowInstanceId);
/**
* 根据工作流实例ID和变量名查询变量
*
* @param workflowInstanceId 工作流实例ID
* @param name 变量名
* @return 变量
*/
Optional<WorkflowVariable> findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
/**
* 根据工作流实例ID和变量名删除变量
*
* @param workflowInstanceId 工作流实例ID
* @param name 变量名
*/
void deleteByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
/**
* 根据工作流实例ID删除所有变量
*
* @param workflowInstanceId 工作流实例ID
*/
void deleteByWorkflowInstanceId(Long workflowInstanceId);
}

View File

@ -0,0 +1,147 @@
package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* 工作流日志服务接口
*/
public interface IWorkflowLogService {
/**
* 记录工作流开始日志
*
* @param instance 工作流实例
*/
void logWorkflowStart(WorkflowInstance instance);
/**
* 记录工作流完成日志
*
* @param instance 工作流实例
*/
void logWorkflowComplete(WorkflowInstance instance);
/**
* 记录工作流暂停日志
*
* @param instance 工作流实例
*/
void logWorkflowPause(WorkflowInstance instance);
/**
* 记录工作流恢复日志
*
* @param instance 工作流实例
*/
void logWorkflowResume(WorkflowInstance instance);
/**
* 记录工作流取消日志
*
* @param instance 工作流实例
*/
void logWorkflowCancel(WorkflowInstance instance);
/**
* 记录工作流错误日志
*
* @param instance 工作流实例
* @param error 错误信息
*/
void logWorkflowError(WorkflowInstance instance, String error);
/**
* 记录节点开始日志
*
* @param node 节点实例
*/
void logNodeStart(NodeInstance node);
/**
* 记录节点完成日志
*
* @param node 节点实例
*/
void logNodeComplete(NodeInstance node);
/**
* 记录节点错误日志
*
* @param node 节点实例
* @param error 错误信息
*/
void logNodeError(NodeInstance node, String error);
/**
* 记录节点重试日志
*
* @param node 节点实例
*/
void logNodeRetry(NodeInstance node);
/**
* 记录节点跳过日志
*
* @param node 节点实例
*/
void logNodeSkip(NodeInstance node);
/**
* 记录变量更新日志
*
* @param instance 工作流实例
* @param variableName 变量名
* @param value 变量值
*/
void logVariableUpdate(WorkflowInstance instance, String variableName, Object value);
/**
* 记录系统日志
*
* @param instance 工作流实例
* @param level 日志级别
* @param content 日志内容
* @param detail 详细信息
*/
void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail);
/**
* 分页查询工作流日志
*
* @param workflowInstanceId 工作流实例ID
* @param type 日志类型
* @param level 日志级别
* @param pageable 分页参数
* @return 日志分页
*/
Page<WorkflowLog> findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
/**
* 查询节点日志
*
* @param nodeInstanceId 节点实例ID
* @param type 日志类型
* @param level 日志级别
* @return 日志列表
*/
List<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level);
/**
* 分页查询节点日志
*
* @param nodeInstanceId 节点实例ID
* @param type 日志类型
* @param level 日志级别
* @param pageable 分页参数
* @return 日志分页
*/
Page<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
}

View File

@ -0,0 +1,96 @@
package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* 工作流权限服务接口
*/
public interface IWorkflowPermissionService {
/**
* 添加用户权限
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
*/
void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 添加角色权限
*
* @param workflowDefinitionId 工作流定义ID
* @param roleId 角色ID
* @param type 权限类型
*/
void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
/**
* 添加部门权限
*
* @param workflowDefinitionId 工作流定义ID
* @param departmentId 部门ID
* @param type 权限类型
*/
void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
/**
* 移除用户权限
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
*/
void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 移除角色权限
*
* @param workflowDefinitionId 工作流定义ID
* @param roleId 角色ID
* @param type 权限类型
*/
void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
/**
* 移除部门权限
*
* @param workflowDefinitionId 工作流定义ID
* @param departmentId 部门ID
* @param type 权限类型
*/
void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
/**
* 检查用户是否有指定权限
*
* @param workflowDefinitionId 工作流定义ID
* @param userId 用户ID
* @param type 权限类型
* @return 是否有权限
*/
boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
/**
* 获取工作流定义的所有权限
*
* @param workflowDefinitionId 工作流定义ID
* @return 权限列表
*/
List<WorkflowPermission> getPermissions(Long workflowDefinitionId);
/**
* 分页查询工作流权限
*
* @param workflowDefinitionId 工作流定义ID
* @param type 权限类型
* @param pageable 分页参数
* @return 权限分页
*/
Page<WorkflowPermission> findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
}

View File

@ -0,0 +1,58 @@
package com.qqchen.deploy.backend.workflow.service;
import java.util.Map;
/**
* 工作流变量服务接口
*/
public interface IWorkflowVariableService {
/**
* 保存工作流变量
*
* @param workflowInstanceId 工作流实例ID
* @param variables 变量Map
*/
void saveVariables(Long workflowInstanceId, Map<String, Object> variables);
/**
* 获取工作流变量
*
* @param workflowInstanceId 工作流实例ID
* @return 变量Map
*/
Map<String, Object> getVariables(Long workflowInstanceId);
/**
* 获取工作流变量值
*
* @param workflowInstanceId 工作流实例ID
* @param name 变量名
* @return 变量值
*/
Object getVariable(Long workflowInstanceId, String name);
/**
* 设置工作流变量值
*
* @param workflowInstanceId 工作流实例ID
* @param name 变量名
* @param value 变量值
*/
void setVariable(Long workflowInstanceId, String name, Object value);
/**
* 删除工作流变量
*
* @param workflowInstanceId 工作流实例ID
* @param name 变量名
*/
void deleteVariable(Long workflowInstanceId, String name);
/**
* 清空工作流变量
*
* @param workflowInstanceId 工作流实例ID
*/
void clearVariables(Long workflowInstanceId);
}

View File

@ -0,0 +1,181 @@
package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.enums.LogLevelEnum;
import com.qqchen.deploy.backend.enums.LogTypeEnum;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowLogRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 工作流日志服务实现类
*/
@Slf4j
@Service
public class WorkflowLogServiceImpl implements IWorkflowLogService {
@Resource
private IWorkflowLogRepository workflowLogRepository;
@Override
public List<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level) {
return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level);
}
@Override
public Page<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) {
return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level, pageable);
}
@Override
public Page<WorkflowLog> findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) {
return workflowLogRepository.findByWorkflowInstanceIdAndTypeAndLevel(workflowInstanceId, type, level, pageable);
}
@Override
public void logWorkflowStart(WorkflowInstance instance) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_START);
log.setLevel(LogLevelEnum.INFO);
log.setContent("工作流开始执行");
workflowLogRepository.save(log);
}
@Override
public void logWorkflowComplete(WorkflowInstance instance) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_COMPLETE);
log.setLevel(LogLevelEnum.INFO);
log.setContent("工作流执行完成");
workflowLogRepository.save(log);
}
@Override
public void logWorkflowPause(WorkflowInstance instance) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_PAUSE);
log.setLevel(LogLevelEnum.INFO);
log.setContent("工作流已暂停");
workflowLogRepository.save(log);
}
@Override
public void logWorkflowResume(WorkflowInstance instance) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_RESUME);
log.setLevel(LogLevelEnum.INFO);
log.setContent("工作流已恢复");
workflowLogRepository.save(log);
}
@Override
public void logWorkflowCancel(WorkflowInstance instance) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_CANCEL);
log.setLevel(LogLevelEnum.INFO);
log.setContent("工作流已取消");
workflowLogRepository.save(log);
}
@Override
public void logWorkflowError(WorkflowInstance instance, String error) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.WORKFLOW_ERROR);
log.setLevel(LogLevelEnum.ERROR);
log.setContent("工作流执行出错");
log.setDetail(error);
workflowLogRepository.save(log);
}
@Override
public void logNodeStart(NodeInstance node) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
log.setNodeInstanceId(node.getId());
log.setType(LogTypeEnum.NODE_START);
log.setLevel(LogLevelEnum.INFO);
log.setContent(String.format("节点[%s]开始执行", node.getName()));
workflowLogRepository.save(log);
}
@Override
public void logNodeComplete(NodeInstance node) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
log.setNodeInstanceId(node.getId());
log.setType(LogTypeEnum.NODE_COMPLETE);
log.setLevel(LogLevelEnum.INFO);
log.setContent(String.format("节点[%s]执行完成", node.getName()));
workflowLogRepository.save(log);
}
@Override
public void logNodeError(NodeInstance node, String error) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
log.setNodeInstanceId(node.getId());
log.setType(LogTypeEnum.NODE_ERROR);
log.setLevel(LogLevelEnum.ERROR);
log.setContent(String.format("节点[%s]执行出错", node.getName()));
log.setDetail(error);
workflowLogRepository.save(log);
}
@Override
public void logNodeRetry(NodeInstance node) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
log.setNodeInstanceId(node.getId());
log.setType(LogTypeEnum.NODE_RETRY);
log.setLevel(LogLevelEnum.WARN);
log.setContent(String.format("节点[%s]重试执行", node.getName()));
workflowLogRepository.save(log);
}
@Override
public void logNodeSkip(NodeInstance node) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
log.setNodeInstanceId(node.getId());
log.setType(LogTypeEnum.NODE_SKIP);
log.setLevel(LogLevelEnum.WARN);
log.setContent(String.format("节点[%s]已跳过", node.getName()));
workflowLogRepository.save(log);
}
@Override
public void logVariableUpdate(WorkflowInstance instance, String variableName, Object value) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.VARIABLE_UPDATE);
log.setLevel(LogLevelEnum.INFO);
log.setContent(String.format("变量[%s]更新为[%s]", variableName, value));
workflowLogRepository.save(log);
}
@Override
public void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail) {
WorkflowLog log = new WorkflowLog();
log.setWorkflowInstanceId(instance.getId());
log.setType(LogTypeEnum.SYSTEM);
log.setLevel(level);
log.setContent(content);
log.setDetail(detail);
workflowLogRepository.save(log);
}
}

View File

@ -0,0 +1,126 @@
package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
import com.qqchen.deploy.backend.system.entity.User;
import com.qqchen.deploy.backend.system.repository.IUserRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowPermissionRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowPermissionService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
* 工作流权限服务实现
*/
@Slf4j
@Service
public class WorkflowPermissionServiceImpl implements IWorkflowPermissionService {
@Resource
private IWorkflowPermissionRepository workflowPermissionRepository;
@Resource
private IUserRepository userRepository;
@Override
@Transactional
public void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
.findByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type);
if (existingPermission.isEmpty()) {
WorkflowPermission permission = new WorkflowPermission();
permission.setWorkflowDefinitionId(workflowDefinitionId);
permission.setUserId(userId);
permission.setType(type);
workflowPermissionRepository.save(permission);
}
}
@Override
@Transactional
public void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) {
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
.findByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type);
if (existingPermission.isEmpty()) {
WorkflowPermission permission = new WorkflowPermission();
permission.setWorkflowDefinitionId(workflowDefinitionId);
permission.setRoleId(roleId);
permission.setType(type);
workflowPermissionRepository.save(permission);
}
}
@Override
@Transactional
public void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) {
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
.findByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type);
if (existingPermission.isEmpty()) {
WorkflowPermission permission = new WorkflowPermission();
permission.setWorkflowDefinitionId(workflowDefinitionId);
permission.setDepartmentId(departmentId);
permission.setType(type);
workflowPermissionRepository.save(permission);
}
}
@Override
@Transactional
public void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type);
}
@Override
@Transactional
public void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) {
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type);
}
@Override
@Transactional
public void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) {
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type);
}
@Override
public boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
// 检查用户直接权限
if (workflowPermissionRepository.existsByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type)) {
return true;
}
// 获取用户信息
Optional<User> userOpt = userRepository.findById(userId);
if (userOpt.isEmpty()) {
return false;
}
User user = userOpt.get();
// 检查角色权限
if (user.getRoleId() != null && workflowPermissionRepository
.existsByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, user.getRoleId(), type)) {
return true;
}
// 检查部门权限
return user.getDepartmentId() != null && workflowPermissionRepository
.existsByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, user.getDepartmentId(), type);
}
@Override
public List<WorkflowPermission> getPermissions(Long workflowDefinitionId) {
return workflowPermissionRepository.findByWorkflowDefinitionId(workflowDefinitionId);
}
@Override
public Page<WorkflowPermission> findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable) {
return workflowPermissionRepository.findByWorkflowDefinitionIdAndType(workflowDefinitionId, type, pageable);
}
}

View File

@ -0,0 +1,113 @@
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.exception.BusinessException;
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 工作流变量服务实现
*/
@Slf4j
@Service
public class WorkflowVariableServiceImpl implements IWorkflowVariableService {
@Resource
private IWorkflowVariableRepository workflowVariableRepository;
@Resource
private ObjectMapper objectMapper;
@Override
@Transactional
public void saveVariables(Long workflowInstanceId, Map<String, Object> variables) {
variables.forEach((name, value) -> setVariable(workflowInstanceId, name, value));
}
@Override
public Map<String, Object> getVariables(Long workflowInstanceId) {
List<WorkflowVariable> variables = workflowVariableRepository.findByWorkflowInstanceId(workflowInstanceId);
Map<String, Object> result = new HashMap<>(variables.size());
variables.forEach(variable -> {
try {
result.put(variable.getName(), deserializeValue(variable.getValue(), variable.getType()));
} catch (JsonProcessingException e) {
log.error("Failed to deserialize variable value: {}", variable.getName(), e);
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_DESERIALIZE_ERROR);
}
});
return result;
}
@Override
public Object getVariable(Long workflowInstanceId, String name) {
return workflowVariableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
.map(variable -> {
try {
return deserializeValue(variable.getValue(), variable.getType());
} catch (JsonProcessingException e) {
log.error("Failed to deserialize variable value: {}", name, e);
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_DESERIALIZE_ERROR);
}
})
.orElse(null);
}
@Override
@Transactional
public void setVariable(Long workflowInstanceId, String name, Object value) {
try {
WorkflowVariable variable = workflowVariableRepository
.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
.orElseGet(() -> {
WorkflowVariable newVariable = new WorkflowVariable();
newVariable.setWorkflowInstanceId(workflowInstanceId);
newVariable.setName(name);
return newVariable;
});
variable.setType(value.getClass().getName());
variable.setValue(serializeValue(value));
workflowVariableRepository.save(variable);
} catch (JsonProcessingException e) {
log.error("Failed to serialize variable value: {}", name, e);
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_SERIALIZE_ERROR);
}
}
@Override
@Transactional
public void deleteVariable(Long workflowInstanceId, String name) {
workflowVariableRepository.deleteByWorkflowInstanceIdAndName(workflowInstanceId, name);
}
@Override
@Transactional
public void clearVariables(Long workflowInstanceId) {
workflowVariableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
}
private String serializeValue(Object value) throws JsonProcessingException {
return objectMapper.writeValueAsString(value);
}
private Object deserializeValue(String value, String type) throws JsonProcessingException {
try {
Class<?> clazz = Class.forName(type);
return objectMapper.readValue(value, clazz);
} catch (ClassNotFoundException e) {
log.error("Failed to find class for type: {}", type, e);
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_ERROR);
}
}
}

View File

@ -145,7 +145,7 @@ CREATE TABLE sys_role_tag (
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
name VARCHAR(50) NOT NULL COMMENT '标签名称', name VARCHAR(50) NOT NULL COMMENT '标签名称',
color VARCHAR(20) NULL COMMENT '标签六进制颜色码)' color VARCHAR(20) NULL COMMENT '标签<EFBFBD><EFBFBD><EFBFBD>六进制颜色码)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
-- 角色标签关联表 -- 角色标签关联表
@ -446,3 +446,61 @@ CREATE TABLE sys_node_instance (
CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id) CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
-- 工作流变量表
CREATE TABLE wf_workflow_variable (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
name VARCHAR(255) NOT NULL COMMENT '变量名',
value TEXT COMMENT '变量值',
type VARCHAR(255) NOT NULL COMMENT '变量类型',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by BIGINT NOT NULL COMMENT '创建人',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
update_by BIGINT NOT NULL COMMENT '更新人',
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
INDEX idx_workflow_instance_id (workflow_instance_id),
INDEX idx_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流变量';
-- 工作流日志表
CREATE TABLE wf_workflow_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
node_instance_id BIGINT COMMENT '节点实例ID',
type VARCHAR(50) NOT NULL COMMENT '日志类型',
level VARCHAR(20) NOT NULL COMMENT '日志级别',
content TEXT NOT NULL COMMENT '日志内容',
detail TEXT COMMENT '详细信息',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by BIGINT NOT NULL COMMENT '创建人',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
update_by BIGINT NOT NULL COMMENT '更新人',
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
INDEX idx_workflow_instance_id (workflow_instance_id),
INDEX idx_node_instance_id (node_instance_id),
INDEX idx_type (type),
INDEX idx_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流日志';
-- 工作流权限表
CREATE TABLE wf_workflow_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
type VARCHAR(50) NOT NULL COMMENT '权限类型',
user_id BIGINT COMMENT '用户ID',
role_id BIGINT COMMENT '角色ID',
department_id BIGINT COMMENT '部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by BIGINT NOT NULL COMMENT '创建人',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
update_by BIGINT NOT NULL COMMENT '更新人',
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
INDEX idx_workflow_definition_id (workflow_definition_id),
INDEX idx_user_id (user_id),
INDEX idx_role_id (role_id),
INDEX idx_department_id (department_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流权限';