diff --git a/backend/docs/workflow-development.md b/backend/docs/workflow-development.md
new file mode 100644
index 00000000..65f0a0b8
--- /dev/null
+++ b/backend/docs/workflow-development.md
@@ -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
+
+ com.offbytwo.jenkins
+ jenkins-client
+ 0.3.8
+
+```
+
+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
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ 6.5.0.202303070854-r
+
+```
+
+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
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
+```
+
+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 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 parameters;
+}
+```
+
+2. 实现模板服务
+```java
+@Service
+public class WorkflowTemplateService {
+ public Long createFromTemplate(Long templateId, Map 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
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/enums/LogLevelEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/enums/LogLevelEnum.java
new file mode 100644
index 00000000..3a70fbf4
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/enums/LogLevelEnum.java
@@ -0,0 +1,26 @@
+package com.qqchen.deploy.backend.enums;
+
+/**
+ * 日志级别枚举
+ */
+public enum LogLevelEnum {
+ /**
+ * 调试
+ */
+ DEBUG,
+
+ /**
+ * 信息
+ */
+ INFO,
+
+ /**
+ * 警告
+ */
+ WARN,
+
+ /**
+ * 错误
+ */
+ ERROR
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/enums/LogTypeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/enums/LogTypeEnum.java
new file mode 100644
index 00000000..c68f7235
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/enums/LogTypeEnum.java
@@ -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
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/enums/PermissionTypeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/enums/PermissionTypeEnum.java
new file mode 100644
index 00000000..c646fbdc
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/enums/PermissionTypeEnum.java
@@ -0,0 +1,21 @@
+package com.qqchen.deploy.backend.enums;
+
+/**
+ * 权限类型枚举
+ */
+public enum PermissionTypeEnum {
+ /**
+ * 查看权限
+ */
+ VIEW,
+
+ /**
+ * 执行权限
+ */
+ EXECUTE,
+
+ /**
+ * 管理权限
+ */
+ MANAGE
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowApiController.java
new file mode 100644
index 00000000..b08dac41
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowApiController.java
@@ -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 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 cancelWorkflow(
+ @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
+ ) {
+ return Response.success(workflowInstanceService.cancel(instanceId));
+ }
+
+ @Operation(summary = "获取工作流实例详情")
+ @GetMapping("/{instanceId}")
+ public Response getWorkflowInstance(
+ @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
+ ) {
+ return Response.success(workflowInstanceService.findById(instanceId));
+ }
+
+ @Operation(summary = "获取工作流节点列表")
+ @GetMapping("/{instanceId}/nodes")
+ public Response> getWorkflowNodes(
+ @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
+ ) {
+ return Response.success(nodeInstanceService.findByWorkflowInstanceId(instanceId));
+ }
+
+ @Operation(summary = "重试工作流节点")
+ @PostMapping("/node/{nodeId}/retry")
+ public Response retryNode(
+ @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
+ ) {
+ // TODO: 实现节点重试逻辑
+ return Response.success(true);
+ }
+
+ @Operation(summary = "跳过工作流节点")
+ @PostMapping("/node/{nodeId}/skip")
+ public Response skipNode(
+ @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
+ ) {
+ // TODO: 实现节点跳过逻辑
+ return Response.success(true);
+ }
+
+ @Operation(summary = "查询工作流日志")
+ @PostMapping("/logs")
+ public Response> getLogs(@Valid @RequestBody WorkflowLogQueryRequest request) {
+ Page 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> getNodeLogs(
+ @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId,
+ @Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type,
+ @Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level
+ ) {
+ List 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> 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 logs = workflowLogService.findNodeLogs(
+ nodeId, type, level, PageRequest.of(pageNum - 1, pageSize)
+ );
+ return Response.success(logs.map(workflowLogConverter::toDto));
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java
new file mode 100644
index 00000000..e829f38e
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java
@@ -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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java
new file mode 100644
index 00000000..62ed566a
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java
@@ -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 variables;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java
new file mode 100644
index 00000000..43d5932f
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java
@@ -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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java
new file mode 100644
index 00000000..f2760192
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java
@@ -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 {
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java
new file mode 100644
index 00000000..4d213bc5
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java
@@ -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 allNodes;
+
+ /**
+ * 工作流变量
+ */
+ private Map variables;
+
+ /**
+ * 临时变量(节点间传递)
+ */
+ private Map 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();
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java
index 0519ecba..0853f737 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java
@@ -1 +1,54 @@
-
\ No newline at end of file
+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 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);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java
new file mode 100644
index 00000000..e6d7388c
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java
@@ -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 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 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 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 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 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 allNodes = context.getAllNodes();
+
+ // 如果当前节点为空,说明是第一个节点
+ Optional 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java
new file mode 100644
index 00000000..2fd9b147
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java
@@ -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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java
new file mode 100644
index 00000000..5d51e4c6
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java
@@ -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();
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java
new file mode 100644
index 00000000..91d6d4ba
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java
@@ -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 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 buildCommand(ScriptNodeConfig config) {
+ List 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;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java
new file mode 100644
index 00000000..608f9bca
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java
@@ -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 {
+
+ /**
+ * 工作流实例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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java
new file mode 100644
index 00000000..873af021
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java
@@ -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 {
+
+ /**
+ * 工作流定义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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java
new file mode 100644
index 00000000..4ed39983
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java
@@ -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 {
+
+ /**
+ * 工作流实例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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java
new file mode 100644
index 00000000..a2095f38
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java
@@ -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 {
+
+ /**
+ * 查询节点日志
+ *
+ * @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 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 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 findByWorkflowInstanceIdAndTypeAndLevel(
+ @Param("workflowInstanceId") Long workflowInstanceId,
+ @Param("type") LogTypeEnum type,
+ @Param("level") LogLevelEnum level,
+ Pageable pageable
+ );
+
+ /**
+ * 删除工作流实例的所有日志
+ *
+ * @param workflowInstanceId 工作流实例ID
+ */
+ void deleteByWorkflowInstanceId(Long workflowInstanceId);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java
new file mode 100644
index 00000000..c72d5d04
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java
@@ -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 {
+
+ /**
+ * 根据工作流定义ID查询权限列表
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @return 权限列表
+ */
+ List findByWorkflowDefinitionId(Long workflowDefinitionId);
+
+ /**
+ * 根据工作流定义ID和权限类型分页查询权限
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @param type 权限类型
+ * @param pageable 分页参数
+ * @return 权限分页
+ */
+ Page findByWorkflowDefinitionIdAndType(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
+
+ /**
+ * 查询用户权限
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @param userId 用户ID
+ * @param type 权限类型
+ * @return 权限
+ */
+ Optional findByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
+
+ /**
+ * 查询角色权限
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @param roleId 角色ID
+ * @param type 权限类型
+ * @return 权限
+ */
+ Optional findByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
+
+ /**
+ * 查询部门权限
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @param departmentId 部门ID
+ * @param type 权限类型
+ * @return 权限
+ */
+ Optional 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);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java
new file mode 100644
index 00000000..12f0bffc
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java
@@ -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 {
+
+ /**
+ * 根据工作流实例ID查询变量列表
+ *
+ * @param workflowInstanceId 工作流实例ID
+ * @return 变量列表
+ */
+ List findByWorkflowInstanceId(Long workflowInstanceId);
+
+ /**
+ * 根据工作流实例ID和变量名查询变量
+ *
+ * @param workflowInstanceId 工作流实例ID
+ * @param name 变量名
+ * @return 变量
+ */
+ Optional 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);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java
new file mode 100644
index 00000000..85643580
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java
@@ -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 findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
+
+ /**
+ * 查询节点日志
+ *
+ * @param nodeInstanceId 节点实例ID
+ * @param type 日志类型
+ * @param level 日志级别
+ * @return 日志列表
+ */
+ List findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level);
+
+ /**
+ * 分页查询节点日志
+ *
+ * @param nodeInstanceId 节点实例ID
+ * @param type 日志类型
+ * @param level 日志级别
+ * @param pageable 分页参数
+ * @return 日志分页
+ */
+ Page findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java
new file mode 100644
index 00000000..b128e08a
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java
@@ -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 getPermissions(Long workflowDefinitionId);
+
+ /**
+ * 分页查询工作流权限
+ *
+ * @param workflowDefinitionId 工作流定义ID
+ * @param type 权限类型
+ * @param pageable 分页参数
+ * @return 权限分页
+ */
+ Page findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java
new file mode 100644
index 00000000..a6b11095
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java
@@ -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 variables);
+
+ /**
+ * 获取工作流变量
+ *
+ * @param workflowInstanceId 工作流实例ID
+ * @return 变量Map
+ */
+ Map 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);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java
new file mode 100644
index 00000000..99d11798
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java
@@ -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 findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level) {
+ return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level);
+ }
+
+ @Override
+ public Page findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) {
+ return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level, pageable);
+ }
+
+ @Override
+ public Page 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);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java
new file mode 100644
index 00000000..097b5cef
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java
@@ -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 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 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 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 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 getPermissions(Long workflowDefinitionId) {
+ return workflowPermissionRepository.findByWorkflowDefinitionId(workflowDefinitionId);
+ }
+
+ @Override
+ public Page findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable) {
+ return workflowPermissionRepository.findByWorkflowDefinitionIdAndType(workflowDefinitionId, type, pageable);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java
new file mode 100644
index 00000000..2f2f934b
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java
@@ -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 variables) {
+ variables.forEach((name, value) -> setVariable(workflowInstanceId, name, value));
+ }
+
+ @Override
+ public Map getVariables(Long workflowInstanceId) {
+ List variables = workflowVariableRepository.findByWorkflowInstanceId(workflowInstanceId);
+ Map 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql
index d71b672e..18ed02db 100644
--- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql
+++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql
@@ -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='角色标签表';
-- 角色标签关联表
@@ -445,4 +445,62 @@ CREATE TABLE sys_node_instance (
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='节点实例表';
+
+-- 工作流变量表
+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='工作流权限';
\ No newline at end of file