diff --git a/backend/.cursorrules b/backend/.cursorrules index 807441b8..a1266d62 100644 --- a/backend/.cursorrules +++ b/backend/.cursorrules @@ -1,6 +1,7 @@ - 作为Java编程、Spring Boot、Spring Framework、Maven、JUnit和相关Java技术专家,需要实现企业应用级别的高性能管理框架 ### 严格遵循要求 +- 不要随意删除代码,请先详细浏览下现在所有的代码,再进行询问修改,得到确认再修改。 - 首先一步一步思考,详细描述伪代码构建计划,确认后再写代码 - 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则 - 专注于简单易读的代码实现,确保代码完整性 @@ -12,7 +13,8 @@ ### 包结构规范 - 框架包路径(com.qqchen.deploy.backend.framework):包含annotation、api、audit、controller等多个子包 -- 业务包路径(com.qqchen.deploy.backend):包含api、controller、converter、entity等多个子包 +- 系统业务路径(com.qqchen.deploy.backend.system):包含api、controller、converter、entity等多个子包 +- 工作流业务路径(com.qqchen.deploy.backend.workflow):包含api、controller、converter、entity等多个子包 ### 数据对象规范 - DTO设计:简单CRUD使用统一DTO,复杂场景使用专门Request/Response,继承BaseDTO获取基础字段 @@ -23,6 +25,7 @@ - 简单CRUD继承BaseServiceImpl,复杂业务需定义专门接口和实现,包含事务控制和异常处理 - 使用@Transactional注解控制事务,合理设置事务传播机制和隔离级别 - 实现乐观锁(@Version)或悲观锁(findByIdWithLock)进行并发控制 +- 使用@Resource 注入其他BEAN,不要使用构造方法 - 示例 @Slf4j @Service @@ -41,13 +44,22 @@ public class ExternalSystemServiceImpl extends BaseServiceImpl - 统一使用GlobalExceptionHandler处理异常 +- 使用@Resource 注入其他BEAN,不要使用构造方法 - 示例: @Slf4j @RestController @RequestMapping("/api/v1/external-system") @Tag(name = "外部系统管理", description = "外部系统管理相关接口") public class ExternalSystemApiController extends BaseController { - // 特定业务方法实现 + + //特殊方法实现,其他的均可使用父类BaseController + @Operation(summary = "测试连接") + @GetMapping("/{id}/test-connection") + public Response testConnection( + @Parameter(description = "系统ID", required = true) @PathVariable Long id + ) { + return Response.success(externalSystemService.testConnection(id)); + } } ### Repository层规范 @@ -77,6 +89,37 @@ public interface IExternalSystemRepository extends IBaseRepository { + @Column(nullable = false) + private String name; +} +``` + ### 枚举规范 - 新增加枚举应添加到com.qqchen.deploy.backend.enums,后缀以Enum结尾 - 增加枚举时,不要修改现有没有,只追加即可 @@ -87,7 +130,7 @@ public interface IExternalSystemRepository extends IBaseRepository params); - void pauseWorkflow(Long instanceId); - void resumeWorkflow(Long instanceId); - void terminateWorkflow(Long instanceId, String reason); +public interface DistributedWorkflowEngine extends WorkflowEngine { + // 分布式锁支持 + void acquireWorkflowLock(String lockKey, Duration timeout); + void releaseWorkflowLock(String lockKey); - // 批量操作支持 - List batchStartWorkflow(List requests); + // 集群协调 + void registerWorker(WorkerInfo workerInfo); + void updateWorkerStatus(String workerId, WorkerStatus status); - // 定时执行支持 - WorkflowInstance scheduleWorkflow(String code, Map params, LocalDateTime executeTime); + // 任务分发 + void assignTask(String workerId, WorkflowTask task); + void reassignTask(String fromWorkerId, String toWorkerId, WorkflowTask task); - // 条件触发支持 - void registerTrigger(WorkflowTrigger trigger); - - // 子流程支持 - WorkflowInstance startSubWorkflow(Long parentInstanceId, String code, Map params); -} - -/** - * 工作流触发器 - */ -public interface WorkflowTrigger { - String getCode(); - boolean shouldTrigger(TriggerContext context); - Map prepareParams(TriggerContext context); + // 故障转移 + void handleWorkerFailure(String workerId); + void recoverTasks(String failedWorkerId); } ``` -### 1.2 工作流执行优化 +### 6.2 工作流版本控制 ```java /** - * 优化的工作流执行器 + * 工作流版本管理 + */ +public interface WorkflowVersionManager { + // 版本控制 + String createVersion(WorkflowDefinition definition); + WorkflowDefinition getVersion(String versionId); + List getVersionHistory(String code); + + // 版本迁移 + void migrateInstances(String fromVersion, String toVersion); + void rollbackVersion(String versionId); + + // 版本比较 + WorkflowDiff compareVersions(String version1, String version2); +} +``` + +### 6.3 动态节点支持 +```java +/** + * 动态节点管理器 + */ +public interface DynamicNodeManager { + // 动态节点注册 + void registerNodeType(String type, NodeExecutor executor); + void unregisterNodeType(String type); + + // 动态配置 + void updateNodeConfig(String type, NodeConfig config); + NodeConfig getNodeConfig(String type); + + // 节点发现 + List discoverNodes(); + boolean isNodeTypeAvailable(String type); +} +``` + +### 6.4 工作流监控增强 +```java +/** + * 增强的工作流监控 + */ +@Component +@Slf4j +public class EnhancedWorkflowMonitor extends PerformanceMonitor { + // 业务监控指标 + private final Counter businessErrorCounter; + private final Counter approvalTimeoutCounter; + private final Timer approvalDurationTimer; + + // 资源监控 + private final Gauge databaseConnectionGauge; + private final Gauge cacheUsageGauge; + private final Gauge messageQueueDepthGauge; + + // SLA监控 + private final Timer workflowSLATimer; + private final Counter slaViolationCounter; + + public void recordBusinessError(String errorType, String errorCode) { + businessErrorCounter.increment(); + // 记录详细错误信息 + } + + public void checkSLA(WorkflowInstance instance) { + Duration duration = Duration.between(instance.getStartTime(), LocalDateTime.now()); + workflowSLATimer.record(duration); + + if (duration.compareTo(instance.getSLADuration()) > 0) { + slaViolationCounter.increment(); + // 触发SLA违规告警 + } + } +} +``` + +### 6.5 工作流数据分析 +```java +/** + * 工作流分析服务 */ @Service @Slf4j -public class OptimizedWorkflowExecutor { - private final ExecutorService executorService; - private final WorkflowEventBus eventBus; - private final RetryTemplate retryTemplate; - - @Async - public void executeWorkflow(WorkflowInstance instance) { - MDC.put("workflowInstanceId", instance.getId().toString()); - try { - // 1. 前置处理 - preProcess(instance); - - // 2. 并行执行支持 - if (instance.isParallelExecutionEnabled()) { - executeParallel(instance); - } else { - executeSequential(instance); - } - - // 3. 后置处理 - postProcess(instance); - - } catch (Exception e) { - handleError(instance, e); - } finally { - MDC.remove("workflowInstanceId"); - } - } - - private void executeParallel(WorkflowInstance instance) { - List parallelNodes = instance.getParallelNodes(); - List> futures = new ArrayList<>(); - - // 提交并行任务 - for (NodeDefinition node : parallelNodes) { - futures.add(executorService.submit(() -> executeNode(instance, node))); - } - - // 等待所有任务完成 - for (Future future : futures) { - try { - future.get(instance.getTimeout(), TimeUnit.SECONDS); - } catch (Exception e) { - handleParallelExecutionError(instance, e); - } - } - } - - private NodeExecuteResult executeNode(WorkflowInstance instance, NodeDefinition node) { - return retryTemplate.execute(context -> { - NodeExecutor executor = getExecutor(node); - return executor.execute(createContext(instance, node)); - }); - } -} -``` - -## 二、节点执行优化 - -### 2.1 增强的节点上下文 -```java -/** - * 增强的节点上下文 - */ -public interface NodeContext { - // 基础功能 - String getNodeId(); - T getNodeConfig(Class clazz); - Map getVariables(); - NodeLogger getLogger(); - - // 数据传递 - void setVariable(String key, Object value); - void setTransientVariable(String key, Object value); - - // 条件判断 - boolean evaluateCondition(String expression); - - // 重试策略 - RetryStrategy getRetryStrategy(); - - // 超时控制 - Duration getTimeout(); - - // 回滚操作 - void registerRollback(Runnable rollback); -} - -/** - * 重试策略 - */ -@Data -@Builder -public class RetryStrategy { - private int maxAttempts; - private Duration initialDelay; - private Duration maxDelay; - private double multiplier; - private List> retryableExceptions; -} -``` - -### 2.2 节点执行器增强 -```java -/** - * 增强的节点执行器 - */ -public interface NodeExecutor { - String getType(); - NodeExecuteResult execute(NodeContext context); - void cancel(NodeContext context); - - // 新增方法 - boolean canExecute(NodeContext context); - void validate(NodeContext context); - void prepare(NodeContext context); - void cleanup(NodeContext context); -} - -/** - * 节点执行结果 - */ -@Data -@Builder -public class NodeExecuteResult { - private boolean success; - private Map output; - private String errorMessage; - private Throwable error; - private long executionTime; - private Map metrics; -} -``` - -## 三、事件系统优化 - -### 3.1 事件总线实现 -```java -/** - * 事件总线 - */ -@Component -@Slf4j -public class WorkflowEventBus { - private final Map> listeners = new ConcurrentHashMap<>(); - private final ExecutorService executorService; - - public void publish(WorkflowEvent event) { - String eventType = event.getType(); - List eventListeners = listeners.get(eventType); - - if (eventListeners != null) { - // 异步处理事件 - for (WorkflowEventListener listener : eventListeners) { - executorService.submit(() -> { - try { - listener.onEvent(event); - } catch (Exception e) { - log.error("Handle event failed", e); - } - }); - } - } - } - - public void subscribe(String eventType, WorkflowEventListener listener) { - listeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) - .add(listener); - } - - public void unsubscribe(String eventType, WorkflowEventListener listener) { - List eventListeners = listeners.get(eventType); - if (eventListeners != null) { - eventListeners.remove(listener); - } - } -} -``` - -### 3.2 事件处理器 -```java -/** - * 日志事件处理器 - */ -@Component -@Slf4j -public class LoggingEventHandler implements WorkflowEventListener { - - @Override - public void onEvent(WorkflowEvent event) { - switch (event.getType()) { - case "WORKFLOW_START": - logWorkflowStart(event); - break; - case "WORKFLOW_END": - logWorkflowEnd(event); - break; - case "NODE_START": - logNodeStart(event); - break; - case "NODE_END": - logNodeEnd(event); - break; - default: - log.debug("Unhandled event type: {}", event.getType()); - } - } - - private void logWorkflowStart(WorkflowEvent event) { - WorkflowInstance instance = (WorkflowInstance) event.getPayload().get("instance"); - log.info("Workflow started: {}, type: {}", instance.getId(), instance.getType()); - } -} - -/** - * 指标收集处理器 - */ -@Component -@Slf4j -public class MetricsEventHandler implements WorkflowEventListener { - private final MeterRegistry registry; - - @Override - public void onEvent(WorkflowEvent event) { - switch (event.getType()) { - case "WORKFLOW_END": - recordWorkflowMetrics(event); - break; - case "NODE_END": - recordNodeMetrics(event); - break; - } - } - - private void recordWorkflowMetrics(WorkflowEvent event) { - WorkflowInstance instance = (WorkflowInstance) event.getPayload().get("instance"); - Timer.builder("workflow.execution") - .tag("type", instance.getType()) - .tag("status", instance.getStatus().name()) - .register(registry) - .record(Duration.between(instance.getStartTime(), instance.getEndTime())); - } -} -``` - -## 四、插件系统优化 - -### 4.1 插件生命周期 -```java -/** - * 插件生命周期管理 - */ -public interface PluginLifecycle { - void onLoad(PluginContext context); - void onEnable(); - void onDisable(); - void onUnload(); -} - -/** - * 插件热更新支持 - */ -@Component -@Slf4j -public class PluginHotLoader { - private final PluginLoader pluginLoader; - private final FileWatcher fileWatcher; - - public void watchPluginDirectory(Path directory) { - fileWatcher.watch(directory, event -> { - if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { - String jarPath = event.context().toString(); - reloadPlugin(jarPath); - } - }); - } - - private void reloadPlugin(String jarPath) { - try { - // 1. 卸载旧插件 - pluginLoader.unloadPlugin(jarPath); - - // 2. 加载新插件 - pluginLoader.loadPlugin(jarPath); - - log.info("Plugin reloaded: {}", jarPath); - } catch (Exception e) { - log.error("Reload plugin failed: {}", jarPath, e); - } - } -} -``` - -### 4.2 插件配置 -```java -/** - * 插件配置管理 - */ -@Component -@Slf4j -public class PluginConfigManager { - private final Map configs = new ConcurrentHashMap<>(); - - @PostConstruct - public void init() { - // 加载插件配置 - loadConfigs(); - - // 监听配置变更 - watchConfigChanges(); - } - - public void updateConfig(String pluginId, PluginConfig config) { - configs.put(pluginId, config); - // 通知插件配置更新 - notifyConfigUpdate(pluginId, config); - } - - private void notifyConfigUpdate(String pluginId, PluginConfig config) { - Plugin plugin = pluginLoader.getPlugin(pluginId); - if (plugin instanceof ConfigurablePlugin) { - ((ConfigurablePlugin) plugin).onConfigUpdate(config); - } - } -} -``` - -## 五、监控增强 - -### 5.1 性能监控 -```java -/** - * 性能监控 - */ -@Component -@Slf4j -public class PerformanceMonitor { - private final MeterRegistry registry; - - // 节点执行时间分布 - private final Timer nodeExecutionTimer; - - // 工作流执行时间分布 - private final Timer workflowExecutionTimer; - - // 资源使用情况 - private final Gauge threadPoolActiveThreads; - private final Gauge threadPoolQueueSize; - - // 业务指标 - private final Counter workflowSuccessCounter; - private final Counter workflowFailureCounter; - private final Counter nodeFailureCounter; - - public void recordNodeExecution(String nodeType, long duration) { - nodeExecutionTimer.record(duration, TimeUnit.MILLISECONDS, - Tags.of("nodeType", nodeType)); - } - - public void recordWorkflowExecution(String workflowType, long duration) { - workflowExecutionTimer.record(duration, TimeUnit.MILLISECONDS, - Tags.of("workflowType", workflowType)); - } -} -``` - -### 5.2 健康检查 -```java -/** - * 健康检查 - */ -@Component -public class WorkflowHealthIndicator implements HealthIndicator { +public class WorkflowAnalyticsService { private final WorkflowInstanceRepository instanceRepository; - private final NodeExecutorRegistry executorRegistry; + private final NodeInstanceRepository nodeRepository; + private final AnalyticsEngine analyticsEngine; - @Override - public Health health() { - try { - // 检查运行中的实例数 - long runningInstances = instanceRepository.countByStatus(WorkflowStatus.RUNNING); - - // 检查执行器状态 - Map executorStatus = checkExecutors(); - - return Health.up() - .withDetail("runningInstances", runningInstances) - .withDetail("executorStatus", executorStatus) - .build(); - - } catch (Exception e) { - return Health.down() - .withException(e) - .build(); - } + // 性能分析 + public PerformanceReport analyzePerformance(String workflowType, DateRange range) { + // 分析工作流执行性能 + return analyticsEngine.analyzePerformance(workflowType, range); } - private Map checkExecutors() { - Map status = new HashMap<>(); - for (NodeExecutor executor : executorRegistry.getExecutors()) { - status.put(executor.getType(), checkExecutor(executor)); - } - return status; + // 瓶颈分析 + public List findBottlenecks(String workflowType) { + // 识别执行瓶颈 + return analyticsEngine.findBottlenecks(workflowType); + } + + // 趋势分析 + public TrendReport analyzeTrend(String metric, DateRange range) { + // 分析指标趋势 + return analyticsEngine.analyzeTrend(metric, range); } } ``` -### 5.3 告警配置 +### 6.6 工作流调度优化 ```java /** - * 告警配置 + * 优化的工作流调度器 */ -@Configuration -public class AlertConfig { +@Component +@Slf4j +public class OptimizedWorkflowScheduler { + private final WorkloadBalancer workloadBalancer; + private final PriorityQueue taskQueue; - @Bean - public AlertManager alertManager(MeterRegistry registry) { - return AlertManager.builder() - .addRule(AlertRule.builder() - .metric("workflow.failure.count") - .threshold(5) - .duration(Duration.ofMinutes(5)) - .action(this::sendAlert) - .build()) - .addRule(AlertRule.builder() - .metric("node.failure.count") - .threshold(10) - .duration(Duration.ofMinutes(5)) - .action(this::sendAlert) - .build()) - .build(); + // 优先级调度 + public void schedulePriorityTask(WorkflowTask task) { + task.setPriority(calculatePriority(task)); + taskQueue.offer(task); } - private void sendAlert(Alert alert) { - // 发送告警通知 + // 负载均衡 + public void balanceWorkload() { + List workers = workloadBalancer.getActiveWorkers(); + for (WorkerNode worker : workers) { + if (worker.isOverloaded()) { + redistributeTasks(worker); + } + } + } + + // 资源预留 + public void reserveResources(WorkflowTask task) { + ResourceRequirements requirements = task.getResourceRequirements(); + resourceManager.reserve(requirements); } } -``` - -[保留现有的其他内容...] \ No newline at end of file +``` \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java new file mode 100644 index 00000000..6c299631 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java @@ -0,0 +1,74 @@ +package com.qqchen.deploy.backend.workflow.api.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 节点实例DTO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeInstanceDTO extends BaseDTO { + + /** + * 工作流实例ID + */ + @NotNull(message = "工作流实例ID不能为空") + private Long workflowInstanceId; + + /** + * 节点ID + */ + @NotBlank(message = "节点ID不能为空") + @Size(max = 50, message = "节点ID长度不能超过50") + private String nodeId; + + /** + * 节点类型 + */ + @NotBlank(message = "节点类型不能为空") + @Size(max = 50, message = "节点类型长度不能超过50") + private String nodeType; + + /** + * 节点名称 + */ + @NotBlank(message = "节点名称不能为空") + @Size(max = 100, message = "节点名称长度不能超过100") + private String name; + + /** + * 状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED + */ + private String status; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 结束时间 + */ + private LocalDateTime endTime; + + /** + * 输入参数(JSON) + */ + private String input; + + /** + * 输出结果(JSON) + */ + private String output; + + /** + * 错误信息 + */ + private String error; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java new file mode 100644 index 00000000..2d779e9e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.api.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工作流定义DTO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowDefinitionDTO extends BaseDTO { + + /** + * 工作流编码 + */ + @NotBlank(message = "工作流编码不能为空") + @Size(max = 50, message = "工作流编码长度不能超过50") + private String code; + + /** + * 工作流名称 + */ + @NotBlank(message = "工作流名称不能为空") + @Size(max = 100, message = "工作流名称长度不能超过100") + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 工作流定义(JSON) + */ + @NotBlank(message = "工作流定义不能为空") + private String content; + + /** + * 类型:DEPLOY/CONFIG_SYNC + */ + @NotBlank(message = "工作流类型不能为空") + @Size(max = 20, message = "工作流类型长度不能超过20") + private String type; + + /** + * 状态:DRAFT/PUBLISHED/DISABLED + */ + private String status; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java new file mode 100644 index 00000000..2b6340e9 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java @@ -0,0 +1,57 @@ +package com.qqchen.deploy.backend.workflow.api.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 工作流实例DTO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowInstanceDTO extends BaseDTO { + + /** + * 工作流定义ID + */ + @NotNull(message = "工作流定义ID不能为空") + private Long definitionId; + + /** + * 项目环境ID + */ + @NotNull(message = "项目环境ID不能为空") + private Long projectEnvId; + + /** + * 状态:RUNNING/COMPLETED/FAILED/CANCELED + */ + private String status; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 结束时间 + */ + private LocalDateTime endTime; + + /** + * 工作流变量(JSON) + */ + private String variables; + + /** + * 错误信息 + */ + private String error; + + /** + * 工作流定义名称(冗余字段) + */ + private String workflowName; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java new file mode 100644 index 00000000..fdde4a8c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java @@ -0,0 +1,58 @@ +package com.qqchen.deploy.backend.workflow.api.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 节点实例查询对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeInstanceQuery extends BaseQuery { + + /** + * 工作流实例ID + */ + @QueryField(field = "workflowInstance.id") + private Long workflowInstanceId; + + /** + * 节点ID + */ + @QueryField(field = "nodeId") + private String nodeId; + + /** + * 节点类型 + */ + @QueryField(field = "nodeType") + private String nodeType; + + /** + * 节点名称 + */ + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + /** + * 节点状态 + */ + @QueryField(field = "status") + private String status; + +// /** +// * 开始时间范围(起始) +// */ +// @QueryField(field = "startTime", type = QueryType.BETWEEN) +// private LocalDateTime startTimeBegin; +// +// /** +// * 开始时间范围(结束) +// */ +// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL) +// private LocalDateTime startTimeEnd; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java new file mode 100644 index 00000000..25d1f841 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java @@ -0,0 +1,39 @@ +package com.qqchen.deploy.backend.workflow.api.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工作流定义查询对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowDefinitionQuery extends BaseQuery { + + /** + * 工作流编码 + */ + @QueryField(field = "code", type = QueryType.LIKE) + private String code; + + /** + * 工作流名称 + */ + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + /** + * 工作流类型 + */ + @QueryField(field = "type") + private String type; + + /** + * 工作流状态 + */ + @QueryField(field = "status") + private String status; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java new file mode 100644 index 00000000..01b3289e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.api.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 工作流实例查询对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowInstanceQuery extends BaseQuery { + + /** + * 工作流定义ID + */ + @QueryField(field = "definition.id") + private Long definitionId; + + /** + * 项目环境ID + */ + @QueryField(field = "projectEnvId") + private Long projectEnvId; + + /** + * 工作流状态 + */ + @QueryField(field = "status") + private String status; + +// /** +// * 开始时间范围(起始) +// */ +// @QueryField(field = "startTime", type = QueryType.GREATER_THAN_OR_EQUAL) +// private LocalDateTime startTimeBegin; +// +// /** +// * 开始时间范围(结束) +// */ +// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL) +// private LocalDateTime startTimeEnd; + + /** + * 工作流定义名称 + */ + @QueryField(field = "definition.name", type = QueryType.LIKE) + private String workflowName; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java new file mode 100644 index 00000000..01661007 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java @@ -0,0 +1,72 @@ +package com.qqchen.deploy.backend.workflow.controller; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; +import com.qqchen.deploy.backend.workflow.api.query.NodeInstanceQuery; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.service.INodeInstanceService; +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.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 节点实例控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/workflow/node") +@Tag(name = "工作流节点管理", description = "工作流节点管理相关接口") +public class NodeInstanceApiController extends BaseController { + + @Resource + private INodeInstanceService nodeInstanceService; + + + @Operation(summary = "根据工作流实例ID查询节点实例列表") + @GetMapping("/workflow/{workflowInstanceId}") + public Response> findByWorkflowInstanceId( + @Parameter(description = "工作流实例ID", required = true) + @PathVariable Long workflowInstanceId) { + return Response.success(nodeInstanceService.findByWorkflowInstanceId(workflowInstanceId)); + } + + @Operation(summary = "根据工作流实例ID和状态查询节点实例列表") + @GetMapping("/workflow/{workflowInstanceId}/status/{status}") + public Response> findByWorkflowInstanceIdAndStatus( + @Parameter(description = "工作流实例ID", required = true) + @PathVariable Long workflowInstanceId, + @Parameter(description = "状态", required = true) + @PathVariable String status) { + return Response.success(nodeInstanceService.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status)); + } + + @Operation(summary = "更新节点状态") + @PostMapping("/{id}/status") + public Response updateStatus( + @Parameter(description = "节点实例ID", required = true) + @PathVariable Long id, + @Parameter(description = "状态", required = true) + @RequestParam String status, + @Parameter(description = "输出结果") + @RequestParam(required = false) String output, + @Parameter(description = "错误信息") + @RequestParam(required = false) String error) { + return Response.success(nodeInstanceService.updateStatus(id, status, output, error)); + } + + @Override + protected void exportData(HttpServletResponse response, List data) { + // TODO: 实现导出功能 + } + + protected INodeInstanceService getService() { + return nodeInstanceService; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java new file mode 100644 index 00000000..53d60c1d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java @@ -0,0 +1,60 @@ +package com.qqchen.deploy.backend.workflow.controller; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.api.query.WorkflowDefinitionQuery; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; +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.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 工作流定义控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/workflow/definition") +@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口") +public class WorkflowDefinitionApiController extends BaseController { + + @Resource + private IWorkflowDefinitionService workflowDefinitionService; + + @Operation(summary = "发布工作流") + @PostMapping("/{id}/publish") + public Response publish( + @Parameter(description = "工作流定义ID", required = true) + @PathVariable Long id) { + return Response.success(workflowDefinitionService.publish(id)); + } + + @Operation(summary = "禁用工作流") + @PostMapping("/{id}/disable") + public Response disable( + @Parameter(description = "工作流定义ID", required = true) + @PathVariable Long id) { + return Response.success(workflowDefinitionService.disable(id)); + } + + @Operation(summary = "根据编码查询工作流定义") + @GetMapping("/code/{code}") + public Response findByCode( + @Parameter(description = "工作流编码", required = true) + @PathVariable String code) { + return Response.success(workflowDefinitionService.findByCode(code)); + } + + @Override + protected void exportData(HttpServletResponse response, List data) { + // TODO: 实现导出功能 + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java new file mode 100644 index 00000000..1de44b97 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java @@ -0,0 +1,79 @@ +package com.qqchen.deploy.backend.workflow.controller; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.api.query.WorkflowInstanceQuery; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 工作流实例控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/workflow/instance") +@Tag(name = "工作流实例管理", description = "工作流实例管理相关接口") +public class WorkflowInstanceApiController extends BaseController { + + private final IWorkflowInstanceService workflowInstanceService; + + public WorkflowInstanceApiController(IWorkflowInstanceService workflowInstanceService) { + this.workflowInstanceService = workflowInstanceService; + } + + @Operation(summary = "启动工作流实例") + @PostMapping("/start") + public Response start( + @Parameter(description = "工作流定义ID", required = true) + @RequestParam Long definitionId, + @Parameter(description = "项目环境ID", required = true) + @RequestParam Long projectEnvId, + @Parameter(description = "工作流变量") + @RequestParam(required = false) String variables) { + return Response.success(workflowInstanceService.start(definitionId, projectEnvId, variables)); + } + + @Operation(summary = "取消工作流实例") + @PostMapping("/{id}/cancel") + public Response cancel( + @Parameter(description = "工作流实例ID", required = true) + @PathVariable Long id) { + return Response.success(workflowInstanceService.cancel(id)); + } + + @Operation(summary = "根据项目环境ID查询工作流实例列表") + @GetMapping("/project-env/{projectEnvId}") + public Response> findByProjectEnvId( + @Parameter(description = "项目环境ID", required = true) + @PathVariable Long projectEnvId) { + return Response.success(workflowInstanceService.findByProjectEnvId(projectEnvId)); + } + + @Operation(summary = "根据项目环境ID和状态查询工作流实例列表") + @GetMapping("/project-env/{projectEnvId}/status/{status}") + public Response> findByProjectEnvIdAndStatus( + @Parameter(description = "项目环境ID", required = true) + @PathVariable Long projectEnvId, + @Parameter(description = "状态", required = true) + @PathVariable String status) { + return Response.success(workflowInstanceService.findByProjectEnvIdAndStatus(projectEnvId, status)); + } + + @Override + protected void exportData(HttpServletResponse response, List data) { + // TODO: 实现导出功能 + } + + protected IWorkflowInstanceService getService() { + return workflowInstanceService; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java new file mode 100644 index 00000000..5c6829a8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java @@ -0,0 +1,20 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * 节点实例转换器 + */ +@Mapper(config = BaseConverter.class) +public interface NodeInstanceConverter extends BaseConverter { + + @Mapping(target = "workflowInstanceId", source = "workflowInstance.id") + NodeInstanceDTO toDto(NodeInstance entity); + + @Mapping(target = "workflowInstance.id", source = "workflowInstanceId") + NodeInstance toEntity(NodeInstanceDTO dto); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java new file mode 100644 index 00000000..611447a1 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.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.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import org.mapstruct.Mapper; + +/** + * 工作流定义转换器 + */ +@Mapper(config = BaseConverter.class) +public interface WorkflowDefinitionConverter extends BaseConverter { +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java new file mode 100644 index 00000000..ef8664cf --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java @@ -0,0 +1,21 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * 工作流实例转换器 + */ +@Mapper(config = BaseConverter.class) +public interface WorkflowInstanceConverter extends BaseConverter { + + @Mapping(target = "definitionId", source = "definition.id") + @Mapping(target = "workflowName", source = "definition.name") + WorkflowInstanceDTO toDto(WorkflowInstance entity); + + @Mapping(target = "definition.id", source = "definitionId") + WorkflowInstance toEntity(WorkflowInstanceDTO dto); +} \ 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 new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ApprovalNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ApprovalNodeConfig.java new file mode 100644 index 00000000..0afc317e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ApprovalNodeConfig.java @@ -0,0 +1,39 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 审批节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ApprovalNodeConfig extends NodeConfig { + + /** + * 审批人类型:USER-指定用户,ROLE-指定角色,LEADER-直属领导 + */ + private String approverType; + + /** + * 审批人ID列表(用户ID或角色ID) + */ + private List approverIds; + + /** + * 审批策略:ALL-全部通过,ANY-任意通过 + */ + private String strategy; + + /** + * 审批超时自动处理:PASS-通过,REJECT-拒绝 + */ + private String timeoutAction; + + /** + * 审批表单定义 + */ + private String formDefinition; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ConditionNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ConditionNodeConfig.java new file mode 100644 index 00000000..55e2394f --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ConditionNodeConfig.java @@ -0,0 +1,46 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.List; + +/** + * 条件节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ConditionNodeConfig extends NodeConfig { + + /** + * 条件表达式(使用SpEL表达式) + */ + private String expression; + + /** + * 条件分支列表 + */ + private List branches; + + /** + * 默认分支节点ID + */ + private String defaultBranchNodeId; + + @Data + public static class Branch { + /** + * 分支名称 + */ + private String name; + + /** + * 条件表达式 + */ + private String condition; + + /** + * 下一个节点ID + */ + private String nextNodeId; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/GitNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/GitNodeConfig.java new file mode 100644 index 00000000..a67ec11c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/GitNodeConfig.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Git节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GitNodeConfig extends NodeConfig { + + /** + * Git仓库URL + */ + private String repositoryUrl; + + /** + * 分支名称 + */ + private String branch; + + /** + * 标签名称 + */ + private String tag; + + /** + * Commit ID + */ + private String commitId; + + /** + * 操作类型:CLONE, PULL, PUSH, TAG, MERGE + */ + private String operation; + + /** + * 目标分支(合并时使用) + */ + private String targetBranch; + + /** + * 工作目录 + */ + private String workingDirectory; + + /** + * 凭证ID + */ + private Long credentialId; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/HttpNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/HttpNodeConfig.java new file mode 100644 index 00000000..b9dd461d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/HttpNodeConfig.java @@ -0,0 +1,58 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Map; + +/** + * HTTP节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HttpNodeConfig extends NodeConfig { + + /** + * 请求URL + */ + private String url; + + /** + * 请求方法:GET, POST, PUT, DELETE + */ + private String method; + + /** + * 请求头 + */ + private Map headers; + + /** + * 请求参数 + */ + private Map parameters; + + /** + * 请求体 + */ + private String body; + + /** + * 请求体格式:JSON, XML, FORM + */ + private String bodyFormat; + + /** + * 成功状态码 + */ + private Integer successCode; + + /** + * 成功响应匹配规则(JSON Path表达式) + */ + private String successResponse; + + /** + * 凭证ID + */ + private Long credentialId; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/JenkinsNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/JenkinsNodeConfig.java new file mode 100644 index 00000000..d3624dd6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/JenkinsNodeConfig.java @@ -0,0 +1,43 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Map; + +/** + * Jenkins节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class JenkinsNodeConfig extends NodeConfig { + + /** + * Jenkins服务器ID + */ + private Long jenkinsServerId; + + /** + * Jenkins任务名称 + */ + private String jobName; + + /** + * 构建参数 + */ + private Map parameters; + + /** + * 等待构建完成 + */ + private Boolean waitForComplete; + + /** + * 构建超时时间(分钟) + */ + private Integer buildTimeout; + + /** + * 构建成功条件:BUILD_RESULT-构建结果,TEST_RESULT-测试结果,QUALITY_GATE-质量门禁 + */ + private String successCondition; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NacosNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NacosNodeConfig.java new file mode 100644 index 00000000..98c0025c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NacosNodeConfig.java @@ -0,0 +1,58 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Map; + +/** + * Nacos节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NacosNodeConfig extends NodeConfig { + + /** + * Nacos服务器ID + */ + private Long nacosServerId; + + /** + * 命名空间 + */ + private String namespace; + + /** + * 分组 + */ + private String group; + + /** + * 数据ID + */ + private String dataId; + + /** + * 操作类型:PUBLISH-发布配置,ROLLBACK-回滚配置 + */ + private String operation; + + /** + * 配置内容 + */ + private String content; + + /** + * 配置格式:TEXT, JSON, XML, YAML, PROPERTIES + */ + private String format; + + /** + * 回滚版本ID(回滚时使用) + */ + private Long historyId; + + /** + * 标签列表 + */ + private Map tags; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeConfig.java new file mode 100644 index 00000000..f36b3d3a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeConfig.java @@ -0,0 +1,64 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; + +/** + * 节点配置基类 + */ +@Data +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = ApprovalNodeConfig.class, name = "APPROVAL"), + @JsonSubTypes.Type(value = ScriptNodeConfig.class, name = "SCRIPT"), + @JsonSubTypes.Type(value = JenkinsNodeConfig.class, name = "JENKINS"), + @JsonSubTypes.Type(value = GitNodeConfig.class, name = "GIT"), + @JsonSubTypes.Type(value = ConditionNodeConfig.class, name = "CONDITION"), + @JsonSubTypes.Type(value = ParallelNodeConfig.class, name = "PARALLEL"), + @JsonSubTypes.Type(value = NacosNodeConfig.class, name = "NACOS"), + @JsonSubTypes.Type(value = HttpNodeConfig.class, name = "HTTP"), + @JsonSubTypes.Type(value = NotifyNodeConfig.class, name = "NOTIFY") +}) +public abstract class NodeConfig { + + /** + * 节点ID + */ + private String nodeId; + + /** + * 节点名称 + */ + private String name; + + /** + * 节点类型 + */ + private NodeType type; + + /** + * 超时时间(分钟) + */ + private Integer timeout; + + /** + * 重试次数 + */ + private Integer retryCount; + + /** + * 重试间隔(秒) + */ + private Integer retryInterval; + + /** + * 失败是否继续 + */ + private Boolean continueOnFailed; + + /** + * 描述 + */ + private String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeType.java new file mode 100644 index 00000000..f11c66e3 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeType.java @@ -0,0 +1,27 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 节点类型枚举 + */ +@Getter +@AllArgsConstructor +public enum NodeType { + + START("START", "开始节点"), + END("END", "结束节点"), + APPROVAL("APPROVAL", "审批节点"), + SCRIPT("SCRIPT", "脚本节点"), + JENKINS("JENKINS", "Jenkins构建节点"), + GIT("GIT", "Git操作节点"), + CONDITION("CONDITION", "条件节点"), + PARALLEL("PARALLEL", "并行节点"), + NACOS("NACOS", "Nacos配置节点"), + HTTP("HTTP", "HTTP请求节点"), + NOTIFY("NOTIFY", "通知节点"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NotifyNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NotifyNodeConfig.java new file mode 100644 index 00000000..628aad74 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NotifyNodeConfig.java @@ -0,0 +1,54 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.List; +import java.util.Map; + +/** + * 通知节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotifyNodeConfig extends NodeConfig { + + /** + * 通知类型:EMAIL-邮件,SMS-短信,DINGTALK-钉钉,WEIXIN-企业微信 + */ + private String notifyType; + + /** + * 通知人类型:USER-指定用户,ROLE-指定角色,LEADER-直属领导 + */ + private String receiverType; + + /** + * 通知人ID列表(用户ID或角色ID) + */ + private List receiverIds; + + /** + * 通知标题 + */ + private String title; + + /** + * 通知内容 + */ + private String content; + + /** + * 通知模板ID + */ + private Long templateId; + + /** + * 模板参数 + */ + private Map templateParams; + + /** + * 附件列表 + */ + private List attachments; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ParallelNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ParallelNodeConfig.java new file mode 100644 index 00000000..a24838ad --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ParallelNodeConfig.java @@ -0,0 +1,51 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.List; + +/** + * 并行节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ParallelNodeConfig extends NodeConfig { + + /** + * 并行分支列表 + */ + private List branches; + + /** + * 汇聚策略:ALL-全部完成,ANY-任一完成,N-N个完成 + */ + private String joinStrategy; + + /** + * 需要完成的分支数量(N个完成时使用) + */ + private Integer requiredCount; + + /** + * 下一个节点ID + */ + private String nextNodeId; + + @Data + public static class Branch { + /** + * 分支名称 + */ + private String name; + + /** + * 起始节点ID + */ + private String startNodeId; + + /** + * 结束节点ID + */ + private String endNodeId; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ScriptNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ScriptNodeConfig.java new file mode 100644 index 00000000..31e33bf7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/ScriptNodeConfig.java @@ -0,0 +1,48 @@ +package com.qqchen.deploy.backend.workflow.engine.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Map; + +/** + * 脚本节点配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ScriptNodeConfig extends NodeConfig { + + /** + * 脚本类型:SHELL, PYTHON, GROOVY + */ + private String scriptType; + + /** + * 脚本内容 + */ + private String content; + + /** + * 脚本参数 + */ + private Map parameters; + + /** + * 执行主机ID + */ + private Long hostId; + + /** + * 工作目录 + */ + private String workingDirectory; + + /** + * 环境变量 + */ + private Map environment; + + /** + * 成功退出码 + */ + private Integer successExitCode; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java new file mode 100644 index 00000000..dfcbe1b0 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java @@ -0,0 +1,80 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + + +/** + * 节点实例实体 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_node_instance") +@LogicDelete +public class NodeInstance extends com.qqchen.deploy.backend.framework.domain.Entity { + + /** + * 工作流实例 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "workflow_instance_id", nullable = false) + private WorkflowInstance workflowInstance; + + /** + * 节点ID + */ + @Column(name = "node_id", nullable = false, length = 50) + private String nodeId; + + /** + * 节点类型 + */ + @Column(name = "node_type", nullable = false, length = 50) + private String nodeType; + + /** + * 节点名称 + */ + @Column(name = "name", nullable = false, length = 100) + private String name; + + /** + * 状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED + */ + @Column(name = "status", nullable = false, length = 20) + private String status; + + /** + * 开始时间 + */ + @Column(name = "start_time") + private LocalDateTime startTime; + + /** + * 结束时间 + */ + @Column(name = "end_time") + private LocalDateTime endTime; + + /** + * 输入参数(JSON) + */ + @Column(name = "input", columnDefinition = "TEXT") + private String input; + + /** + * 输出结果(JSON) + */ + @Column(name = "output", columnDefinition = "TEXT") + private String output; + + /** + * 错误信息 + */ + @Column(name = "error", columnDefinition = "TEXT") + private String error; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java new file mode 100644 index 00000000..ecdccba2 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java @@ -0,0 +1,56 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +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 = "sys_workflow_definition") +@LogicDelete +public class WorkflowDefinition extends Entity { + + /** + * 工作流编码 + */ + @Column(name = "code", nullable = false, length = 50) + private String code; + + /** + * 工作流名称 + */ + @Column(name = "name", nullable = false, length = 100) + private String name; + + /** + * 描述 + */ + @Column(name = "description", columnDefinition = "TEXT") + private String description; + + /** + * 工作流定义(JSON) + */ + @Column(name = "content", nullable = false, columnDefinition = "TEXT") + private String content; + + /** + * 类型:DEPLOY/CONFIG_SYNC + */ + @Column(name = "type", nullable = false, length = 20) + private String type; + + /** + * 状态:DRAFT/PUBLISHED/DISABLED + */ + @Column(name = "status", nullable = false, length = 20) + private String status = "DRAFT"; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java new file mode 100644 index 00000000..690a1078 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java @@ -0,0 +1,67 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 工作流实例实体 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_workflow_instance") +@LogicDelete + +public class WorkflowInstance extends Entity { + + /** + * 工作流定义ID + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "definition_id", nullable = false) + private WorkflowDefinition definition; + + /** + * 项目环境ID + */ + @Column(name = "project_env_id", nullable = false) + private Long projectEnvId; + + /** + * 状态:RUNNING/COMPLETED/FAILED/CANCELED + */ + @Column(name = "status", nullable = false, length = 20) + private String status; + + /** + * 开始时间 + */ + @Column(name = "start_time", nullable = false) + private LocalDateTime startTime; + + /** + * 结束时间 + */ + @Column(name = "end_time") + private LocalDateTime endTime; + + /** + * 工作流变量(JSON) + */ + @Column(name = "variables", columnDefinition = "TEXT") + private String variables; + + /** + * 错误信息 + */ + @Column(name = "error", columnDefinition = "TEXT") + private String error; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java new file mode 100644 index 00000000..72331868 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java @@ -0,0 +1,21 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 节点状态枚举 + */ +@Getter +@AllArgsConstructor +public enum NodeStatusEnum { + + PENDING("PENDING", "等待执行"), + RUNNING("RUNNING", "执行中"), + COMPLETED("COMPLETED", "已完成"), + FAILED("FAILED", "执行失败"), + CANCELED("CANCELED", "已取消"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java new file mode 100644 index 00000000..db4f2bd8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java @@ -0,0 +1,19 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 工作流状态枚举 + */ +@Getter +@AllArgsConstructor +public enum WorkflowStatusEnum { + + DRAFT("DRAFT", "草稿"), + PUBLISHED("PUBLISHED", "已发布"), + DISABLED("DISABLED", "已禁用"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowTypeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowTypeEnum.java new file mode 100644 index 00000000..c2a97ba5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowTypeEnum.java @@ -0,0 +1,18 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 工作流类型枚举 + */ +@Getter +@AllArgsConstructor +public enum WorkflowTypeEnum { + + DEPLOY("DEPLOY", "部署工作流"), + CONFIG_SYNC("CONFIG_SYNC", "配置同步工作流"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java new file mode 100644 index 00000000..45693e40 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java @@ -0,0 +1,34 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +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 INodeInstanceRepository extends IBaseRepository { + + /** + * 根据工作流实例ID查询节点实例列表 + * + * @param workflowInstanceId 工作流实例ID + * @return 节点实例列表 + */ + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.deleted = false ORDER BY n.createTime ASC") + List findByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId); + + /** + * 根据工作流实例ID和状态查询节点实例列表 + * + * @param workflowInstanceId 工作流实例ID + * @param status 状态 + * @return 节点实例列表 + */ + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.status = :status AND n.deleted = false ORDER BY n.createTime ASC") + List findByWorkflowInstanceIdAndStatus(@Param("workflowInstanceId") Long workflowInstanceId, @Param("status") String status); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java new file mode 100644 index 00000000..6a788bea --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java @@ -0,0 +1,28 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import org.springframework.stereotype.Repository; + +/** + * 工作流定义仓库接口 + */ +@Repository +public interface IWorkflowDefinitionRepository extends IBaseRepository { + + /** + * 根据编码查询未删除的工作流定义 + * + * @param code 工作流编码 + * @return 工作流定义 + */ + WorkflowDefinition findByCodeAndDeletedFalse(String code); + + /** + * 检查编码是否存在(排除已删除的) + * + * @param code 工作流编码 + * @return 是否存在 + */ + boolean existsByCodeAndDeletedFalse(String code); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java new file mode 100644 index 00000000..95912e0a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java @@ -0,0 +1,34 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +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 IWorkflowInstanceRepository extends IBaseRepository { + + /** + * 根据项目环境ID查询工作流实例列表 + * + * @param projectEnvId 项目环境ID + * @return 工作流实例列表 + */ + @Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.deleted = false ORDER BY w.createTime DESC") + List findByProjectEnvId(@Param("projectEnvId") Long projectEnvId); + + /** + * 根据项目环境ID和状态查询工作流实例列表 + * + * @param projectEnvId 项目环境ID + * @param status 状态 + * @return 工作流实例列表 + */ + @Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.status = :status AND w.deleted = false ORDER BY w.createTime DESC") + List findByProjectEnvIdAndStatus(@Param("projectEnvId") Long projectEnvId, @Param("status") String status); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java new file mode 100644 index 00000000..2d182377 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; + +/** + * 工作流定义服务接口 + */ +public interface IWorkflowDefinitionService extends IBaseService { + + /** + * 发布工作流 + * + * @param id 工作流定义ID + * @return 是否成功 + */ + boolean publish(Long id); + + /** + * 禁用工作流 + * + * @param id 工作流定义ID + * @return 是否成功 + */ + boolean disable(Long id); + + /** + * 根据编码查询工作流定义 + * + * @param code 工作流编码 + * @return 工作流定义 + */ + WorkflowDefinitionDTO findByCode(String code); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java new file mode 100644 index 00000000..73f3d2fc --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java @@ -0,0 +1,47 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import java.util.List; + +/** + * 工作流实例服务接口 + */ +public interface IWorkflowInstanceService extends IBaseService { + + /** + * 启动工作流实例 + * + * @param definitionId 工作流定义ID + * @param projectEnvId 项目环境ID + * @param variables 工作流变量 + * @return 工作流实例 + */ + WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables); + + /** + * 取消工作流实例 + * + * @param id 工作流实例ID + * @return 是否成功 + */ + boolean cancel(Long id); + + /** + * 根据项目环境ID查询工作流实例列表 + * + * @param projectEnvId 项目环境ID + * @return 工作流实例列表 + */ + List findByProjectEnvId(Long projectEnvId); + + /** + * 根据项目环境ID和状态查询工作流实例列表 + * + * @param projectEnvId 项目环境ID + * @param status 状态 + * @return 工作流实例列表 + */ + List findByProjectEnvIdAndStatus(Long projectEnvId, String status); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java new file mode 100644 index 00000000..8d46b555 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java @@ -0,0 +1,71 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; +import com.qqchen.deploy.backend.workflow.converter.NodeInstanceConverter; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; +import com.qqchen.deploy.backend.workflow.service.INodeInstanceService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 节点实例服务实现类 + */ +@Slf4j +@Service +public class NodeInstanceServiceImpl extends BaseServiceImpl + implements INodeInstanceService { + + @Resource + private INodeInstanceRepository nodeInstanceRepository; + + @Resource + private NodeInstanceConverter nodeInstanceConverter; + + @Override + public List findByWorkflowInstanceId(Long workflowInstanceId) { + List instances = nodeInstanceRepository.findByWorkflowInstanceId(workflowInstanceId); + return instances.stream() + .map(nodeInstanceConverter::toDto) + .collect(Collectors.toList()); + } + + @Override + public List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, String status) { + List instances = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status); + return instances.stream() + .map(nodeInstanceConverter::toDto) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public boolean updateStatus(Long id, String status, String output, String error) { + NodeInstance instance = findEntityById(id); + instance.setStatus(status); + instance.setOutput(output); + instance.setError(error); + + // 如果是开始执行,设置开始时间 + if (NodeStatusEnum.RUNNING.getCode().equals(status)) { + instance.setStartTime(LocalDateTime.now()); + } + // 如果是结束状态,设置结束时间 + else if (NodeStatusEnum.COMPLETED.getCode().equals(status) + || NodeStatusEnum.FAILED.getCode().equals(status) + || NodeStatusEnum.CANCELED.getCode().equals(status)) { + instance.setEndTime(LocalDateTime.now()); + } + + nodeInstanceRepository.save(instance); + return true; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java new file mode 100644 index 00000000..7f365051 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java @@ -0,0 +1,63 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; +import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 工作流定义服务实现类 + */ +@Slf4j +@Service +public class WorkflowDefinitionServiceImpl extends BaseServiceImpl + implements IWorkflowDefinitionService { + + @Resource + private IWorkflowDefinitionRepository workflowDefinitionRepository; + + @Resource + private WorkflowDefinitionConverter workflowDefinitionConverter; + + @Override + @Transactional + public boolean publish(Long id) { + WorkflowDefinition definition = findEntityById(id); + definition.setStatus(WorkflowStatusEnum.PUBLISHED.getCode()); + workflowDefinitionRepository.save(definition); + return true; + } + + @Override + @Transactional + public boolean disable(Long id) { + WorkflowDefinition definition = findEntityById(id); + definition.setStatus(WorkflowStatusEnum.DISABLED.getCode()); + workflowDefinitionRepository.save(definition); + return true; + } + + @Override + public WorkflowDefinitionDTO findByCode(String code) { + WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(code); + return workflowDefinitionConverter.toDto(definition); + } + + @Transactional + public WorkflowDefinitionDTO save(WorkflowDefinitionDTO dto) { + // 检查编码是否已存在 + if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) { + throw new IllegalArgumentException("工作流编码已存在"); + } + // 设置初始状态为草稿 + dto.setStatus(WorkflowStatusEnum.DRAFT.getCode()); + return super.create(dto); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java new file mode 100644 index 00000000..662e3931 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java @@ -0,0 +1,90 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; +import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 工作流实例服务实现类 + */ +@Slf4j +@Service +public class WorkflowInstanceServiceImpl extends BaseServiceImpl + implements IWorkflowInstanceService { + + @Resource + private IWorkflowInstanceRepository workflowInstanceRepository; + + @Resource + private IWorkflowDefinitionRepository workflowDefinitionRepository; + + @Resource + private WorkflowInstanceConverter workflowInstanceConverter; + + @Override + @Transactional + public WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables) { + // 查询工作流定义 + WorkflowDefinition definition = workflowDefinitionRepository.findById(definitionId) + .orElseThrow(() -> new IllegalArgumentException("工作流定义不存在")); + + // 创建工作流实例 + WorkflowInstance instance = new WorkflowInstance(); + instance.setDefinition(definition); + instance.setProjectEnvId(projectEnvId); + instance.setVariables(variables); + instance.setStatus(NodeStatusEnum.RUNNING.getCode()); + instance.setStartTime(LocalDateTime.now()); + + // 保存工作流实例 + WorkflowInstanceDTO dto = workflowInstanceConverter.toDto(instance); + dto = super.create(dto); + + // TODO: 启动工作流执行引擎 + + return dto; + } + + @Override + @Transactional + public boolean cancel(Long id) { + WorkflowInstance instance = findEntityById(id); + instance.setStatus(NodeStatusEnum.CANCELED.getCode()); + instance.setEndTime(LocalDateTime.now()); + workflowInstanceRepository.save(instance); + + // TODO: 通知工作流执行引擎取消执行 + + return true; + } + + @Override + public List findByProjectEnvId(Long projectEnvId) { + List instances = workflowInstanceRepository.findByProjectEnvId(projectEnvId); + return instances.stream() + .map(workflowInstanceConverter::toDto) + .collect(Collectors.toList()); + } + + @Override + public List findByProjectEnvIdAndStatus(Long projectEnvId, String status) { + List instances = workflowInstanceRepository.findByProjectEnvIdAndStatus(projectEnvId, status); + return instances.stream() + .map(workflowInstanceConverter::toDto) + .collect(Collectors.toList()); + } +} \ 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 6fb0c843..d71b672e 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='角色标签表'; -- 角色标签关联表 @@ -376,4 +376,73 @@ CREATE TABLE deploy_repo_branch ( CONSTRAINT fk_branch_external_system FOREIGN KEY (external_system_id) REFERENCES sys_external_system (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表'; + +-- 工作流定义表 +CREATE TABLE sys_workflow_definition ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + tenant_id BIGINT NOT NULL COMMENT '租户ID', + + code VARCHAR(50) NOT NULL COMMENT '工作流编码', + name VARCHAR(100) NOT NULL COMMENT '工作流名称', + description TEXT COMMENT '描述', + content TEXT NOT NULL COMMENT '工作流定义(JSON)', + type VARCHAR(20) NOT NULL COMMENT '类型:DEPLOY/CONFIG_SYNC', + status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/DISABLED', + + CONSTRAINT uk_workflow_code UNIQUE (tenant_id, code), + CONSTRAINT uk_workflow_name UNIQUE (tenant_id, name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表'; + +-- 工作流实例表 +CREATE TABLE sys_workflow_instance ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + tenant_id BIGINT NOT NULL COMMENT '租户ID', + + definition_id BIGINT NOT NULL COMMENT '工作流定义ID', + project_env_id BIGINT NOT NULL COMMENT '项目环境ID', + status VARCHAR(20) NOT NULL COMMENT '状态:RUNNING/COMPLETED/FAILED/CANCELED', + start_time DATETIME NOT NULL COMMENT '开始时间', + end_time DATETIME COMMENT '结束时间', + variables TEXT COMMENT '工作流变量(JSON)', + error TEXT COMMENT '错误信息', + + CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES sys_workflow_definition(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表'; + +-- 节点实例表 +CREATE TABLE sys_node_instance ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + tenant_id BIGINT NOT NULL COMMENT '租户ID', + + workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', + node_id VARCHAR(50) NOT NULL COMMENT '节点ID', + node_type VARCHAR(50) NOT NULL COMMENT '节点类型', + name VARCHAR(100) NOT NULL COMMENT '节点名称', + status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED', + start_time DATETIME COMMENT '开始时间', + end_time DATETIME COMMENT '结束时间', + input TEXT COMMENT '输入参数(JSON)', + output TEXT COMMENT '输出结果(JSON)', + error TEXT COMMENT '错误信息', + + 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='节点实例表'; \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql index e33eedb0..e1fa1bb7 100644 --- a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql +++ b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql @@ -56,7 +56,7 @@ INSERT INTO sys_role (id, create_time, code, name, type, description, sort) VALUES (1, NOW(), 'SUPER_ADMIN', '超级管理员', 1, '系统超级管理员,拥有所有权限', 1), (2, NOW(), 'SYSTEM_ADMIN', '系统管理员', 1, '系统管理员,拥有大部分系统管理权限', 2), -(3, NOW(), 'COMMON_USER', '普通用户', 2, '普通用��,仅拥有基本操作权限', 3); +(3, NOW(), 'COMMON_USER', '普通用户', 2, '普通用,仅拥有基本操作权限', 3); -- 初始化角色标签 INSERT INTO sys_role_tag (id, create_time, name, color) @@ -150,4 +150,121 @@ INSERT INTO sys_external_system ( '链宇GIT', 'GIT', 'http://119.3.203.210:8088/', NULL, 1, 1, 'TOKEN', NULL, NULL, 'cNSud7D1GmYQKEMco7s5', NULL, NULL, NULL, '{}' +); + +-- 初始化工作流定义数据 +INSERT INTO sys_workflow_definition ( + create_by, create_time, deleted, update_by, update_time, version, tenant_id, + code, name, description, content, type, status +) VALUES ( + 'admin', NOW(), 0, 'admin', NOW(), 0, 1, + 'DEPLOY_JAVA_APP', 'Java应用部署流程', 'Java应用标准部署流程', + '{ + "nodes": [ + { + "nodeId": "start", + "type": "START", + "name": "开始", + "nextNodeId": "build" + }, + { + "nodeId": "build", + "type": "JENKINS", + "name": "构建应用", + "jenkinsServerId": 1, + "jobName": "$${projectName}-build", + "parameters": { + "BRANCH": "$${branch}", + "ENV": "$${env}" + }, + "nextNodeId": "approval" + }, + { + "nodeId": "approval", + "type": "APPROVAL", + "name": "部署审批", + "approverType": "ROLE", + "approverIds": [1], + "strategy": "ANY", + "nextNodeId": "deploy" + }, + { + "nodeId": "deploy", + "type": "SCRIPT", + "name": "部署应用", + "scriptType": "SHELL", + "content": "kubectl apply -f $${deployYaml}", + "nextNodeId": "end" + }, + { + "nodeId": "end", + "type": "END", + "name": "结束" + } + ] + }', + 'DEPLOY', + 'PUBLISHED' +); + +INSERT INTO sys_workflow_definition ( + create_by, create_time, deleted, update_by, update_time, version, tenant_id, + code, name, description, content, type, status +) VALUES ( + 'admin', NOW(), 0, 'admin', NOW(), 0, 1, + 'SYNC_NACOS_CONFIG', 'Nacos配置同步流程', 'Nacos配置跨环境同步流程', + '{ + "nodes": [ + { + "nodeId": "start", + "type": "START", + "name": "开始", + "nextNodeId": "export" + }, + { + "nodeId": "export", + "type": "NACOS", + "name": "导出配置", + "nacosServerId": 1, + "operation": "EXPORT", + "namespace": "$${sourceNamespace}", + "nextNodeId": "approval" + }, + { + "nodeId": "approval", + "type": "APPROVAL", + "name": "同步审批", + "approverType": "ROLE", + "approverIds": [1], + "strategy": "ANY", + "nextNodeId": "import" + }, + { + "nodeId": "import", + "type": "NACOS", + "name": "导入配置", + "nacosServerId": 2, + "operation": "IMPORT", + "namespace": "$${targetNamespace}", + "nextNodeId": "notify" + }, + { + "nodeId": "notify", + "type": "NOTIFY", + "name": "通知结果", + "notifyType": "DINGTALK", + "receiverType": "ROLE", + "receiverIds": [1], + "title": "配置同步完成通知", + "nextNodeId": "end" + }, + { + "nodeId": "end", + "type": "END", + "name": "结束" + } + ] + }', + 'CONFIG_SYNC', + 'PUBLISHED' ); \ No newline at end of file diff --git a/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx b/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx new file mode 100644 index 00000000..663a9889 --- /dev/null +++ b/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Menu } from 'antd'; +import type { MenuProps } from 'antd'; +import { DeleteOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons'; + +export const NodeContextMenu = () => { + return ( + + }> + Delete + + }> + Edit + + }> + Copy + + + ); +}; \ No newline at end of file