通用解析
This commit is contained in:
parent
d048858015
commit
205c87dc5a
380
backend/docs/workflow/workflow_instance_sync.md
Normal file
380
backend/docs/workflow/workflow_instance_sync.md
Normal file
@ -0,0 +1,380 @@
|
||||
# 工作流实例同步机制设计文档
|
||||
|
||||
## 目录
|
||||
1. [整体数据流向](#整体数据流向)
|
||||
2. [Flowable与系统映射关系](#Flowable与系统映射关系)
|
||||
3. [数据同步实现](#数据同步实现)
|
||||
4. [事件监听机制](#事件监听机制)
|
||||
5. [状态管理](#状态管理)
|
||||
6. [最佳实践](#最佳实践)
|
||||
|
||||
## 整体数据流向
|
||||
|
||||
```
|
||||
[前端流程设计器]
|
||||
|
|
||||
| 1. 用户拖拽设计流程
|
||||
| - 选择节点类型(从workflow_node_definition获取可用节点)
|
||||
| - 配置节点属性(根据节点的form_config渲染表单)
|
||||
| - 连接节点、设置条件
|
||||
↓
|
||||
[前端数据模型]
|
||||
| - 图形数据(nodes、edges)
|
||||
| - 表单数据(各节点的配置信息)
|
||||
| - 流程基本信息(名称、描述等)
|
||||
|
|
||||
| 2. 保存流程设计
|
||||
↓
|
||||
[后端 WorkflowDefinitionController]
|
||||
|
|
||||
| 3. 接收流程设计数据
|
||||
↓
|
||||
[后端 WorkflowDefinitionService]
|
||||
|
|
||||
| 4. 处理流程数据
|
||||
| - 保存流程定义基本信息到 workflow_definition
|
||||
| - 转换图形数据为BPMN XML
|
||||
| - 解析节点配置,更新workflow_node_definition
|
||||
| * 对于新的节点类型:创建新记录
|
||||
| * 对于已有节点:更新配置
|
||||
|
|
||||
| 5. 部署到Flowable
|
||||
↓
|
||||
[Flowable引擎]
|
||||
|
|
||||
| 6. 部署流程
|
||||
| - 保存BPMN XML
|
||||
| - 生成流程定义ID
|
||||
|
|
||||
| 7. 启动流程实例
|
||||
↓
|
||||
[后端 WorkflowInstanceService]
|
||||
|
|
||||
| 8. 创建流程实例
|
||||
| - 创建workflow_instance记录
|
||||
| - 关联workflow_definition
|
||||
|
|
||||
| 9. 节点实例化
|
||||
| - 创建workflow_node_instance记录
|
||||
| - 关联workflow_node_definition
|
||||
↓
|
||||
[数据库]
|
||||
|
|
||||
| 实时同步的表:
|
||||
| - workflow_definition(流程定义)
|
||||
| - workflow_node_definition(节点定义)
|
||||
| - workflow_instance(流程实例)
|
||||
| - workflow_node_instance(节点实例)
|
||||
|
|
||||
| Flowable表:
|
||||
| - ACT_RE_*(流程定义相关表)
|
||||
| - ACT_RU_*(运行时数据表)
|
||||
| - ACT_HI_*(历史数据表)
|
||||
↓
|
||||
[前端任务列表/监控页面]
|
||||
|
|
||||
| 10. 展示流程实例
|
||||
| - 查询实例状态
|
||||
| - 显示节点执行情况
|
||||
| - 处理用户任务
|
||||
| - 查看历史记录
|
||||
```
|
||||
|
||||
## Flowable与系统映射关系
|
||||
|
||||
### 表结构映射
|
||||
|
||||
#### Flowable核心表
|
||||
```
|
||||
[流程定义相关]
|
||||
ACT_RE_DEPLOYMENT: 流程部署表
|
||||
ACT_RE_PROCDEF: 流程定义表
|
||||
|
||||
[流程实例相关]
|
||||
ACT_RU_EXECUTION: 运行时流程实例表
|
||||
ACT_RU_TASK: 运行时任务表
|
||||
ACT_RU_VARIABLE: 运行时变量表
|
||||
|
||||
[历史数据]
|
||||
ACT_HI_PROCINST: 历史流程实例表
|
||||
ACT_HI_TASKINST: 历史任务实例表
|
||||
ACT_HI_ACTINST: 历史活动实例表
|
||||
```
|
||||
|
||||
#### 系统表与Flowable映射关系
|
||||
```
|
||||
我们的表 Flowable的表
|
||||
--------------------------------------------------
|
||||
workflow_definition <-> ACT_RE_PROCDEF
|
||||
workflow_instance <-> ACT_RU_EXECUTION/ACT_HI_PROCINST
|
||||
workflow_node_instance <-> ACT_RU_TASK/ACT_HI_TASKINST
|
||||
```
|
||||
|
||||
## 数据同步实现
|
||||
|
||||
### 流程实例服务实现
|
||||
```java
|
||||
@Service
|
||||
@Transactional
|
||||
public class WorkflowInstanceService {
|
||||
|
||||
@Resource
|
||||
private RuntimeService runtimeService; // Flowable的运行时服务
|
||||
|
||||
@Resource
|
||||
private TaskService taskService; // Flowable的任务服务
|
||||
|
||||
public WorkflowInstance startProcess(WorkflowInstanceCreateDTO createDTO) {
|
||||
// 1. 启动Flowable流程实例
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceById(
|
||||
createDTO.getProcessDefinitionId(),
|
||||
createDTO.getBusinessKey(),
|
||||
createDTO.getVariables()
|
||||
);
|
||||
|
||||
// 2. 创建我们自己的流程实例记录
|
||||
WorkflowInstance workflowInstance = new WorkflowInstance();
|
||||
workflowInstance.setProcessInstanceId(processInstance.getId());
|
||||
workflowInstance.setProcessDefinitionId(createDTO.getProcessDefinitionId());
|
||||
workflowInstance.setBusinessKey(createDTO.getBusinessKey());
|
||||
workflowInstance.setStatus(WorkflowInstanceStatusEnums.RUNNING);
|
||||
workflowInstance.setVariables(JsonUtils.toJsonString(createDTO.getVariables()));
|
||||
workflowInstance.setStartTime(LocalDateTime.now());
|
||||
workflowInstanceRepository.save(workflowInstance);
|
||||
|
||||
// 3. 获取当前活动的任务
|
||||
List<Task> tasks = taskService.createTaskQuery()
|
||||
.processInstanceId(processInstance.getId())
|
||||
.list();
|
||||
|
||||
// 4. 为每个活动的任务创建节点实例
|
||||
for (Task task : tasks) {
|
||||
WorkflowNodeInstance nodeInstance = new WorkflowNodeInstance();
|
||||
nodeInstance.setWorkflowInstanceId(workflowInstance.getId());
|
||||
nodeInstance.setNodeId(task.getTaskDefinitionKey());
|
||||
nodeInstance.setNodeName(task.getName());
|
||||
nodeInstance.setNodeType("userTask"); // 或者从定义中获取
|
||||
nodeInstance.setStatus("ACTIVE");
|
||||
nodeInstance.setStartTime(LocalDateTime.now());
|
||||
workflowNodeInstanceRepository.save(nodeInstance);
|
||||
}
|
||||
|
||||
return workflowInstance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据同步服务实现
|
||||
```java
|
||||
@Service
|
||||
public class WorkflowSyncService {
|
||||
|
||||
@Scheduled(fixedRate = 60000) // 每分钟执行一次
|
||||
public void syncWorkflowStatus() {
|
||||
// 1. 查找所有运行中的实例
|
||||
List<WorkflowInstance> runningInstances = workflowInstanceRepository
|
||||
.findByStatus(WorkflowInstanceStatusEnums.RUNNING);
|
||||
|
||||
for (WorkflowInstance instance : runningInstances) {
|
||||
// 2. 检查Flowable中的状态
|
||||
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
|
||||
.processInstanceId(instance.getProcessInstanceId())
|
||||
.singleResult();
|
||||
|
||||
if (processInstance == null) {
|
||||
// Flowable中实例已结束,更新我们的状态
|
||||
HistoricProcessInstance historicInstance = historyService
|
||||
.createHistoricProcessInstanceQuery()
|
||||
.processInstanceId(instance.getProcessInstanceId())
|
||||
.singleResult();
|
||||
|
||||
instance.setStatus(convertFlowableStatus(historicInstance.getEndActivityId()));
|
||||
instance.setEndTime(LocalDateTime.now());
|
||||
workflowInstanceRepository.save(instance);
|
||||
}
|
||||
|
||||
// 3. 同步节点实例状态
|
||||
syncNodeInstances(instance.getId(), instance.getProcessInstanceId());
|
||||
}
|
||||
}
|
||||
|
||||
private void syncNodeInstances(Long workflowInstanceId, String processInstanceId) {
|
||||
// 1. 获取当前活动的任务
|
||||
List<Task> activeTasks = taskService.createTaskQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.list();
|
||||
|
||||
// 2. 获取历史任务
|
||||
List<HistoricTaskInstance> historicTasks = historyService
|
||||
.createHistoricTaskInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.finished()
|
||||
.list();
|
||||
|
||||
// 3. 更新节点实例状态
|
||||
Set<String> processedTaskIds = new HashSet<>();
|
||||
|
||||
// 处理活动任务
|
||||
for (Task task : activeTasks) {
|
||||
WorkflowNodeInstance nodeInstance = getOrCreateNodeInstance(
|
||||
workflowInstanceId, task.getTaskDefinitionKey());
|
||||
updateNodeInstance(nodeInstance, task, null);
|
||||
processedTaskIds.add(task.getTaskDefinitionKey());
|
||||
}
|
||||
|
||||
// 处理历史任务
|
||||
for (HistoricTaskInstance historicTask : historicTasks) {
|
||||
if (!processedTaskIds.contains(historicTask.getTaskDefinitionKey())) {
|
||||
WorkflowNodeInstance nodeInstance = getOrCreateNodeInstance(
|
||||
workflowInstanceId, historicTask.getTaskDefinitionKey());
|
||||
updateNodeInstance(nodeInstance, null, historicTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 事件监听机制
|
||||
|
||||
### Flowable事件监听器
|
||||
```java
|
||||
@Component
|
||||
public class FlowableEventListener implements TaskListener, ExecutionListener {
|
||||
|
||||
@Resource
|
||||
private WorkflowInstanceService workflowInstanceService;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateTask task) {
|
||||
// 任务事件处理
|
||||
String eventName = task.getEventName();
|
||||
switch (eventName) {
|
||||
case "create":
|
||||
workflowInstanceService.onTaskCreate(task);
|
||||
break;
|
||||
case "complete":
|
||||
workflowInstanceService.onTaskComplete(task);
|
||||
break;
|
||||
case "delete":
|
||||
workflowInstanceService.onTaskDelete(task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(DelegateExecution execution) {
|
||||
// 流程执行事件处理
|
||||
String eventName = execution.getEventName();
|
||||
switch (eventName) {
|
||||
case "start":
|
||||
workflowInstanceService.onProcessStart(execution);
|
||||
break;
|
||||
case "end":
|
||||
workflowInstanceService.onProcessEnd(execution);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 状态管理
|
||||
|
||||
### 工作流实例状态枚举
|
||||
```java
|
||||
public enum WorkflowInstanceStatusEnums {
|
||||
NOT_STARTED, // 未开始
|
||||
RUNNING, // 运行中
|
||||
SUSPENDED, // 已暂停
|
||||
COMPLETED, // 已完成
|
||||
TERMINATED, // 已终止
|
||||
FAILED; // 执行失败
|
||||
|
||||
public static WorkflowInstanceStatusEnums fromFlowableStatus(String flowableStatus) {
|
||||
switch (flowableStatus) {
|
||||
case "active":
|
||||
return RUNNING;
|
||||
case "suspended":
|
||||
return SUSPENDED;
|
||||
case "completed":
|
||||
return COMPLETED;
|
||||
case "terminated":
|
||||
return TERMINATED;
|
||||
default:
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 为什么需要自己维护实例表
|
||||
|
||||
1. **业务扩展性**
|
||||
- 可以添加更多业务相关的字段
|
||||
- 可以实现自定义的状态管理
|
||||
- 可以关联更多业务数据
|
||||
|
||||
2. **性能优化**
|
||||
- 避免频繁查询Flowable表
|
||||
- 可以建立更适合业务查询的索引
|
||||
- 可以实现更好的缓存策略
|
||||
|
||||
3. **数据完整性**
|
||||
- 保存完整的业务上下文
|
||||
- 记录更详细的审计信息
|
||||
- 支持自定义的数据分析
|
||||
|
||||
### 数据一致性保证
|
||||
|
||||
1. **事务管理**
|
||||
```java
|
||||
@Transactional
|
||||
public void startProcess() {
|
||||
// 1. 启动Flowable流程
|
||||
// 2. 创建系统流程实例
|
||||
// 3. 创建节点实例
|
||||
}
|
||||
```
|
||||
|
||||
2. **定时同步**
|
||||
- 定期检查运行中的实例状态
|
||||
- 自动修复不一致的数据
|
||||
- 记录同步日志
|
||||
|
||||
3. **事件驱动**
|
||||
- 监听Flowable事件
|
||||
- 实时更新系统状态
|
||||
- 保证数据实时性
|
||||
|
||||
### 性能优化建议
|
||||
|
||||
1. **索引优化**
|
||||
- 为常用查询字段建立索引
|
||||
- 使用复合索引优化多字段查询
|
||||
- 避免过多索引影响写入性能
|
||||
|
||||
2. **缓存策略**
|
||||
- 缓存活动的流程实例
|
||||
- 缓存常用的节点定义
|
||||
- 使用分布式缓存提高性能
|
||||
|
||||
3. **批量处理**
|
||||
- 批量同步数据
|
||||
- 批量更新状态
|
||||
- 使用队列处理异步任务
|
||||
|
||||
## 总结
|
||||
|
||||
1. Flowable负责流程的实际执行和调度
|
||||
2. 系统维护业务层面的状态和数据
|
||||
3. 通过事件监听和定时同步保证数据一致性
|
||||
4. 使用状态映射处理不同系统间的状态转换
|
||||
5. 通过事务确保关键操作的原子性
|
||||
|
||||
这样的设计可以:
|
||||
- 保持与Flowable的数据同步
|
||||
- 支持业务扩展
|
||||
- 提供更好的性能
|
||||
- 确保数据一致性
|
||||
- 便于问题追踪和修复
|
||||
@ -0,0 +1,58 @@
|
||||
package com.qqchen.deploy.backend.workflow.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Schema属性注解
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SchemaProperty {
|
||||
/**
|
||||
* 字段标题
|
||||
*/
|
||||
String title() default "";
|
||||
|
||||
/**
|
||||
* 字段描述
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
boolean required() default false;
|
||||
|
||||
/**
|
||||
* 字段格式
|
||||
*/
|
||||
String format() default "";
|
||||
|
||||
/**
|
||||
* 默认值
|
||||
*/
|
||||
String defaultValue() default "";
|
||||
|
||||
/**
|
||||
* 最小值(用于数值类型)
|
||||
*/
|
||||
int minimum() default Integer.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* 最大值(用于数值类型)
|
||||
*/
|
||||
int maximum() default Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 枚举值(用于字符串类型)
|
||||
*/
|
||||
String[] enumValues() default {};
|
||||
|
||||
/**
|
||||
* 枚举值显示名称
|
||||
*/
|
||||
String[] enumNames() default {};
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 执行人配置
|
||||
*/
|
||||
@Data
|
||||
public class AssigneeConfig {
|
||||
/**
|
||||
* 执行人ID
|
||||
*/
|
||||
private String assignee;
|
||||
|
||||
/**
|
||||
* 候选执行人列表
|
||||
*/
|
||||
private List<String> candidateUsers;
|
||||
|
||||
/**
|
||||
* 候选组列表
|
||||
*/
|
||||
private List<String> candidateGroups;
|
||||
|
||||
/**
|
||||
* 执行人表达式
|
||||
*/
|
||||
private String assigneeExpression;
|
||||
|
||||
/**
|
||||
* 候选人表达式
|
||||
*/
|
||||
private String candidateExpression;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener.ListenerConfig;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基础节点配置
|
||||
*/
|
||||
@Data
|
||||
public class BaseNodeConfig {
|
||||
/**
|
||||
* 监听器配置列表
|
||||
*/
|
||||
private List<ListenerConfig> listeners;
|
||||
|
||||
/**
|
||||
* 是否异步
|
||||
*/
|
||||
private Boolean async;
|
||||
|
||||
/**
|
||||
* 是否独占
|
||||
*/
|
||||
private Boolean exclusive;
|
||||
|
||||
/**
|
||||
* 自定义属性
|
||||
*/
|
||||
private Map<String, Object> customProperties;
|
||||
|
||||
/**
|
||||
* 文档说明
|
||||
*/
|
||||
private String documentation;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 结束节点配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EndEventConfig extends EventNodeConfig {
|
||||
/**
|
||||
* 结束类型(normal, terminate, error, etc.)
|
||||
*/
|
||||
private String terminateType;
|
||||
|
||||
/**
|
||||
* 错误代码(当terminateType为error时使用)
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 错误消息(当terminateType为error时使用)
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 结束时的输出变量
|
||||
*/
|
||||
private Map<String, Object> outputVariables;
|
||||
|
||||
/**
|
||||
* 是否终止所有活动分支
|
||||
*/
|
||||
private Boolean terminateAll;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 事件节点(如START、END)的基础配置
|
||||
*/
|
||||
public class EventConfig {
|
||||
@JsonProperty(required = true)
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 事件节点基础配置
|
||||
*/
|
||||
@Data
|
||||
public class EventNodeConfig {
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "节点名称",
|
||||
required = true
|
||||
)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 节点描述
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "节点描述"
|
||||
)
|
||||
private String description;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
@Data
|
||||
public class FormConfig {
|
||||
/**
|
||||
* 表单Key
|
||||
*/
|
||||
private String formKey;
|
||||
|
||||
/**
|
||||
* 表单属性
|
||||
*/
|
||||
private Map<String, Object> properties;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
private Boolean required;
|
||||
|
||||
/**
|
||||
* 表单验证规则
|
||||
*/
|
||||
private Map<String, Object> validations;
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Git Clone任务配置
|
||||
* 继承TaskConfig以获取通用的任务配置(优先级、超时、重试等)
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GitCloneConfig extends TaskConfig {
|
||||
/**
|
||||
* 仓库URL
|
||||
*/
|
||||
private String repositoryUrl;
|
||||
|
||||
/**
|
||||
* 分支或标签
|
||||
*/
|
||||
private String ref;
|
||||
|
||||
/**
|
||||
* 目标目录
|
||||
*/
|
||||
private String targetDirectory;
|
||||
|
||||
/**
|
||||
* 是否递归克隆子模块
|
||||
*/
|
||||
private Boolean recursive;
|
||||
|
||||
/**
|
||||
* 克隆深度(0表示完整克隆)
|
||||
*/
|
||||
private Integer depth;
|
||||
|
||||
/**
|
||||
* 身份验证类型(NONE, SSH_KEY, USERNAME_PASSWORD)
|
||||
*/
|
||||
private String authenticationType;
|
||||
|
||||
/**
|
||||
* SSH私钥(当authenticationType为SSH_KEY时使用)
|
||||
*/
|
||||
private String sshPrivateKey;
|
||||
|
||||
/**
|
||||
* 用户名(当authenticationType为USERNAME_PASSWORD时使用)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码或令牌(当authenticationType为USERNAME_PASSWORD时使用)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Git配置
|
||||
*/
|
||||
private Map<String, String> gitConfig;
|
||||
|
||||
public GitCloneConfig() {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 节点类型配置
|
||||
*/
|
||||
@Data
|
||||
public class NodeTypeConfig {
|
||||
/**
|
||||
* 节点类型代码
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "节点类型代码",
|
||||
description = "节点类型的唯一标识",
|
||||
required = true
|
||||
)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 节点类型名称
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "节点类型名称",
|
||||
description = "节点类型的显示名称",
|
||||
required = true
|
||||
)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 节点类型描述
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "节点类型描述",
|
||||
description = "节点类型的详细描述"
|
||||
)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 节点配置Schema
|
||||
*/
|
||||
private JsonNode configSchema;
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 脚本执行器配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ScriptExecutorConfig extends TaskConfig {
|
||||
/**
|
||||
* 脚本内容
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "脚本内容",
|
||||
description = "需要执行的脚本内容,例如:\n#!/bin/bash\necho \"开始执行脚本\"\nls -la\necho \"脚本执行完成\"",
|
||||
format = "textarea",
|
||||
required = true
|
||||
)
|
||||
private String script;
|
||||
|
||||
/**
|
||||
* 脚本语言
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "脚本语言",
|
||||
description = "脚本语言类型",
|
||||
required = true,
|
||||
enumValues = {"shell", "python", "javascript", "groovy"},
|
||||
enumNames = {"Shell脚本 (已支持)", "Python脚本 (开发中)", "JavaScript脚本 (开发中)", "Groovy脚本 (开发中)"},
|
||||
defaultValue = "shell"
|
||||
)
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 解释器路径
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "解释器路径",
|
||||
description = "脚本解释器的路径,例如:/bin/bash, /usr/bin/python3",
|
||||
required = true
|
||||
)
|
||||
private String interpreter;
|
||||
|
||||
/**
|
||||
* 工作目录
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "工作目录",
|
||||
description = "脚本执行的工作目录",
|
||||
defaultValue = "/tmp"
|
||||
)
|
||||
private String workingDirectory;
|
||||
|
||||
/**
|
||||
* 环境变量
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "环境变量",
|
||||
description = "脚本执行时的环境变量"
|
||||
)
|
||||
private Map<String, String> environment;
|
||||
|
||||
/**
|
||||
* 成功退出码
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "成功退出码",
|
||||
description = "脚本执行成功时的退出码",
|
||||
defaultValue = "0"
|
||||
)
|
||||
private Integer successExitCode;
|
||||
|
||||
/**
|
||||
* 支持的脚本语言列表
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "支持的脚本语言",
|
||||
enumValues = {"shell", "python", "javascript", "groovy"}
|
||||
)
|
||||
private List<String> supportedLanguages;
|
||||
|
||||
public ScriptExecutorConfig() {
|
||||
setTaskType("scriptTask");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 开始节点配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class StartEventConfig extends EventNodeConfig {
|
||||
/**
|
||||
* 触发器类型
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "触发器类型",
|
||||
description = "工作流的触发方式"
|
||||
)
|
||||
private String triggerType;
|
||||
|
||||
/**
|
||||
* 表单Key(当需要启动表单时使用)
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "表单Key",
|
||||
description = "启动表单的标识符"
|
||||
)
|
||||
private String formKey;
|
||||
|
||||
/**
|
||||
* 初始化变量
|
||||
*/
|
||||
private String initiator;
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener.TaskListenerConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务节点基础配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskConfig extends BaseNodeConfig {
|
||||
/**
|
||||
* 任务类型(如:userTask, serviceTask, scriptTask等)
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务类型",
|
||||
description = "工作流节点的任务类型",
|
||||
required = true
|
||||
)
|
||||
private String taskType;
|
||||
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务名称",
|
||||
description = "工作流节点的显示名称",
|
||||
required = true
|
||||
)
|
||||
private String taskName;
|
||||
|
||||
/**
|
||||
* 任务描述
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务描述",
|
||||
description = "工作流节点的详细描述"
|
||||
)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "是否启用",
|
||||
description = "是否启用该任务节点",
|
||||
defaultValue = "true"
|
||||
)
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 任务优先级(0-100)
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务优先级",
|
||||
description = "工作流节点的任务优先级",
|
||||
minimum = 0,
|
||||
maximum = 100
|
||||
)
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 超时时间(秒)
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "超时时间",
|
||||
description = "任务执行的最大时间(秒)",
|
||||
minimum = 1,
|
||||
maximum = 3600,
|
||||
defaultValue = "300"
|
||||
)
|
||||
private Integer timeoutDuration;
|
||||
|
||||
/**
|
||||
* 超时处理策略
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "超时处理策略",
|
||||
description = "任务超时后的处理策略",
|
||||
enumValues = {"FAIL", "CONTINUE", "RETRY"},
|
||||
enumNames = {"失败", "继续", "重试"},
|
||||
defaultValue = "FAIL"
|
||||
)
|
||||
private String timeoutStrategy;
|
||||
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "重试次数",
|
||||
description = "任务失败后的重试次数",
|
||||
minimum = 0,
|
||||
maximum = 10,
|
||||
defaultValue = "0"
|
||||
)
|
||||
private Integer retryTimes;
|
||||
|
||||
/**
|
||||
* 重试间隔(秒)
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "重试间隔",
|
||||
description = "两次重试之间的等待时间(秒)",
|
||||
minimum = 1,
|
||||
maximum = 3600,
|
||||
defaultValue = "60"
|
||||
)
|
||||
private Integer retryInterval;
|
||||
|
||||
/**
|
||||
* 重试策略
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "重试策略",
|
||||
description = "任务重试的策略",
|
||||
enumValues = {"FIXED", "EXPONENTIAL"},
|
||||
enumNames = {"固定间隔", "指数退避"},
|
||||
defaultValue = "FIXED"
|
||||
)
|
||||
private String retryStrategy;
|
||||
|
||||
/**
|
||||
* 执行人配置
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "执行人配置",
|
||||
description = "工作流节点的执行人配置"
|
||||
)
|
||||
private AssigneeConfig assignee;
|
||||
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "表单配置",
|
||||
description = "工作流节点的表单配置"
|
||||
)
|
||||
private FormConfig form;
|
||||
|
||||
/**
|
||||
* 任务输入变量
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务输入变量",
|
||||
description = "工作流节点的任务输入变量"
|
||||
)
|
||||
private Map<String, Object> inputVariables;
|
||||
|
||||
/**
|
||||
* 任务输出变量
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务输出变量",
|
||||
description = "工作流节点的任务输出变量"
|
||||
)
|
||||
private Map<String, Object> outputVariables;
|
||||
|
||||
/**
|
||||
* 任务特定的监听器配置
|
||||
*/
|
||||
@SchemaProperty(
|
||||
title = "任务监听器配置",
|
||||
description = "工作流节点的任务特定监听器配置"
|
||||
)
|
||||
private List<TaskListenerConfig> taskListeners;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务监听器
|
||||
*/
|
||||
@Data
|
||||
public class TaskListener {
|
||||
/**
|
||||
* 监听器类型(如:class, expression, delegateExpression)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 监听器实现(类名、表达式或代理表达式)
|
||||
*/
|
||||
private String implementation;
|
||||
|
||||
/**
|
||||
* 事件类型(create, assignment, complete, delete)
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 监听器字段
|
||||
*/
|
||||
private Map<String, Object> fields;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基础监听器配置
|
||||
*/
|
||||
@Data
|
||||
public class BaseListener {
|
||||
/**
|
||||
* 监听器类型(class, expression, delegateExpression)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 监听器实现(类名、表达式或代理表达式)
|
||||
*/
|
||||
private String implementation;
|
||||
|
||||
/**
|
||||
* 监听器字段
|
||||
*/
|
||||
private Map<String, Object> fields;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 事件监听器配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EventListener extends BaseListener {
|
||||
/**
|
||||
* 事件类型
|
||||
* start: 事件开始
|
||||
* end: 事件结束
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 是否中断事件
|
||||
*/
|
||||
private Boolean cancelActivity;
|
||||
|
||||
/**
|
||||
* 事件抛出的变量
|
||||
*/
|
||||
private String eventVariable;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 事件监听器配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EventListenerConfig extends ListenerConfig {
|
||||
/**
|
||||
* 事件类型
|
||||
* start: 事件开始
|
||||
* end: 事件结束
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 是否中断事件
|
||||
*/
|
||||
private Boolean cancelActivity;
|
||||
|
||||
/**
|
||||
* 事件抛出的变量
|
||||
*/
|
||||
private String eventVariable;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基础监听器配置
|
||||
*/
|
||||
@Data
|
||||
public class ListenerConfig {
|
||||
/**
|
||||
* 监听器类型(class, expression, delegateExpression)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 监听器实现(类名、表达式或代理表达式)
|
||||
*/
|
||||
private String implementation;
|
||||
|
||||
/**
|
||||
* 监听器字段
|
||||
*/
|
||||
private Map<String, Object> fields;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 任务监听器配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskListener extends BaseListener {
|
||||
/**
|
||||
* 事件类型
|
||||
* create: 任务创建
|
||||
* assignment: 任务分配
|
||||
* complete: 任务完成
|
||||
* delete: 任务删除
|
||||
* update: 任务更新
|
||||
* timeout: 任务超时
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 任务相关的用户或组
|
||||
*/
|
||||
private String assignee;
|
||||
|
||||
/**
|
||||
* 任务优先级
|
||||
*/
|
||||
private Integer priority;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 任务监听器配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskListenerConfig extends ListenerConfig {
|
||||
/**
|
||||
* 事件类型
|
||||
* create: 任务创建
|
||||
* assignment: 任务分配
|
||||
* complete: 任务完成
|
||||
* delete: 任务删除
|
||||
* update: 任务更新
|
||||
* timeout: 任务超时
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 任务相关的用户或组
|
||||
*/
|
||||
private String assignee;
|
||||
|
||||
/**
|
||||
* 任务优先级
|
||||
*/
|
||||
private Integer priority;
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 脚本执行配置
|
||||
*/
|
||||
@Data
|
||||
public class ScriptExecutionConfig {
|
||||
/**
|
||||
* 脚本内容
|
||||
*/
|
||||
private String script;
|
||||
|
||||
/**
|
||||
* 工作目录
|
||||
*/
|
||||
private String workingDirectory;
|
||||
|
||||
/**
|
||||
* 超时时间(秒)
|
||||
*/
|
||||
private Integer timeout;
|
||||
|
||||
/**
|
||||
* 环境变量
|
||||
*/
|
||||
private Map<String, String> environment;
|
||||
|
||||
/**
|
||||
* 成功退出码
|
||||
*/
|
||||
private Integer successExitCode;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 脚本语言配置
|
||||
*/
|
||||
@Data
|
||||
public class ScriptLanguageConfig {
|
||||
/**
|
||||
* 脚本语言类型
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 解释器路径
|
||||
*/
|
||||
private String interpreter;
|
||||
|
||||
/**
|
||||
* 支持的脚本语言列表
|
||||
*/
|
||||
private List<String> supportedLanguages;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 脚本重试配置
|
||||
*/
|
||||
@Data
|
||||
public class ScriptRetryConfig {
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
private Integer retryTimes;
|
||||
|
||||
/**
|
||||
* 重试间隔(秒)
|
||||
*/
|
||||
private Integer retryInterval;
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
/**
|
||||
* 节点类型枚举
|
||||
*/
|
||||
public enum NodeTypeEnum {
|
||||
START_EVENT("START", "开始节点", "流程的开始节点"),
|
||||
END_EVENT("END", "结束节点", "流程的结束节点"),
|
||||
TASK("TASK", "任务节点", "通用任务节点"),
|
||||
SCRIPT_TASK("SCRIPT", "脚本执行器", "支持执行多种脚本语言(Shell、Python、JavaScript等)");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
NodeTypeEnum(String code, String name, String description) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public static NodeTypeEnum fromCode(String code) {
|
||||
for (NodeTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown node type code: " + code);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,292 @@
|
||||
package com.qqchen.deploy.backend.workflow.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
|
||||
import com.qqchen.deploy.backend.workflow.dto.nodeConfig.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Schema生成器工具类
|
||||
*/
|
||||
public class SchemaGenerator {
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private SchemaGenerator() {
|
||||
// 私有构造函数防止实例化
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成所有节点类型的配置Schema
|
||||
*/
|
||||
public static JsonNode generateAllNodeTypeSchemas() {
|
||||
ObjectNode schemas = mapper.createObjectNode();
|
||||
|
||||
// START节点
|
||||
schemas.set("START", generateNodeTypeSchema("START", "开始", "开始节点", StartEventConfig.class));
|
||||
|
||||
// SCRIPT节点
|
||||
schemas.set("SCRIPT", generateNodeTypeSchema("SCRIPT", "脚本执行", "执行脚本", ScriptExecutorConfig.class));
|
||||
|
||||
// GIT_CLONE节点
|
||||
schemas.set("GIT_CLONE", generateNodeTypeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库到指定目录", GitCloneConfig.class));
|
||||
|
||||
// END节点
|
||||
schemas.set("END", generateNodeTypeSchema("END", "结束", "结束节点", EndEventConfig.class));
|
||||
|
||||
return schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成节点类型的配置Schema
|
||||
*/
|
||||
private static JsonNode generateNodeTypeSchema(String code, String name, String description, Class<?> configClass) {
|
||||
ObjectNode schema = mapper.createObjectNode();
|
||||
schema.put("code", code);
|
||||
schema.put("name", name);
|
||||
schema.put("description", description);
|
||||
schema.set("configSchema", generateNodeConfigSchema(configClass));
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成节点配置的JSON Schema
|
||||
*/
|
||||
public static JsonNode generateNodeConfigSchema(Class<?> configClass) {
|
||||
ObjectNode schema = mapper.createObjectNode();
|
||||
schema.put("type", "object");
|
||||
ObjectNode properties = schema.putObject("properties");
|
||||
List<String> required = new ArrayList<>();
|
||||
|
||||
// 只有TaskConfig的子类才添加任务相关字段
|
||||
if (TaskConfig.class.isAssignableFrom(configClass)) {
|
||||
processTaskConfigFields(properties, required);
|
||||
}
|
||||
|
||||
// 处理特定配置类的字段
|
||||
for (Field field : configClass.getDeclaredFields()) {
|
||||
SchemaProperty annotation = field.getAnnotation(SchemaProperty.class);
|
||||
if (annotation != null) {
|
||||
ObjectNode property = properties.putObject(field.getName());
|
||||
generateFieldSchema(property, field, annotation);
|
||||
if (annotation.required()) {
|
||||
required.add(field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理父类的字段(除了TaskConfig)
|
||||
Class<?> superClass = configClass.getSuperclass();
|
||||
while (superClass != null && superClass != TaskConfig.class && superClass != Object.class) {
|
||||
for (Field field : superClass.getDeclaredFields()) {
|
||||
SchemaProperty annotation = field.getAnnotation(SchemaProperty.class);
|
||||
if (annotation != null) {
|
||||
ObjectNode property = properties.putObject(field.getName());
|
||||
generateFieldSchema(property, field, annotation);
|
||||
if (annotation.required()) {
|
||||
required.add(field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
if (!required.isEmpty()) {
|
||||
ArrayNode requiredArray = schema.putArray("required");
|
||||
required.forEach(requiredArray::add);
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理TaskConfig中的基础字段
|
||||
*/
|
||||
private static void processTaskConfigFields(ObjectNode properties, List<String> required) {
|
||||
// 添加name字段
|
||||
ObjectNode nameProperty = properties.putObject("name");
|
||||
nameProperty.put("type", "string");
|
||||
nameProperty.put("title", "节点名称");
|
||||
required.add("name");
|
||||
|
||||
// 添加description字段
|
||||
ObjectNode descProperty = properties.putObject("description");
|
||||
descProperty.put("type", "string");
|
||||
descProperty.put("title", "节点描述");
|
||||
|
||||
// 添加timeout字段
|
||||
ObjectNode timeoutProperty = properties.putObject("timeout");
|
||||
timeoutProperty.put("type", "object");
|
||||
timeoutProperty.put("title", "超时配置");
|
||||
|
||||
ObjectNode timeoutProperties = timeoutProperty.putObject("properties");
|
||||
|
||||
ObjectNode durationProperty = timeoutProperties.putObject("duration");
|
||||
durationProperty.put("type", "integer");
|
||||
durationProperty.put("title", "超时时间");
|
||||
durationProperty.put("description", "任务执行的最大时间(秒)");
|
||||
durationProperty.put("minimum", 1);
|
||||
durationProperty.put("maximum", 3600);
|
||||
durationProperty.put("default", 300);
|
||||
|
||||
ObjectNode strategyProperty = timeoutProperties.putObject("strategy");
|
||||
strategyProperty.put("type", "string");
|
||||
strategyProperty.put("title", "超时处理策略");
|
||||
strategyProperty.put("description", "任务超时后的处理策略");
|
||||
ArrayNode strategyEnum = strategyProperty.putArray("enum");
|
||||
strategyEnum.add("FAIL").add("CONTINUE").add("RETRY");
|
||||
ArrayNode strategyEnumNames = strategyProperty.putArray("enumNames");
|
||||
strategyEnumNames.add("失败").add("继续").add("重试");
|
||||
strategyProperty.put("default", "FAIL");
|
||||
|
||||
// 添加retry字段
|
||||
ObjectNode retryProperty = properties.putObject("retry");
|
||||
retryProperty.put("type", "object");
|
||||
retryProperty.put("title", "重试配置");
|
||||
|
||||
ObjectNode retryProperties = retryProperty.putObject("properties");
|
||||
|
||||
ObjectNode timesProperty = retryProperties.putObject("times");
|
||||
timesProperty.put("type", "integer");
|
||||
timesProperty.put("title", "重试次数");
|
||||
timesProperty.put("description", "任务失败后的重试次数");
|
||||
timesProperty.put("minimum", 0);
|
||||
timesProperty.put("maximum", 10);
|
||||
timesProperty.put("default", 0);
|
||||
|
||||
ObjectNode intervalProperty = retryProperties.putObject("interval");
|
||||
intervalProperty.put("type", "integer");
|
||||
intervalProperty.put("title", "重试间隔");
|
||||
intervalProperty.put("description", "两次重试之间的等待时间(秒)");
|
||||
intervalProperty.put("minimum", 1);
|
||||
intervalProperty.put("maximum", 3600);
|
||||
intervalProperty.put("default", 60);
|
||||
|
||||
ObjectNode retryStrategyProperty = retryProperties.putObject("strategy");
|
||||
retryStrategyProperty.put("type", "string");
|
||||
retryStrategyProperty.put("title", "重试策略");
|
||||
retryStrategyProperty.put("description", "任务重试的策略");
|
||||
ArrayNode retryStrategyEnum = retryStrategyProperty.putArray("enum");
|
||||
retryStrategyEnum.add("FIXED").add("EXPONENTIAL");
|
||||
ArrayNode retryStrategyEnumNames = retryStrategyProperty.putArray("enumNames");
|
||||
retryStrategyEnumNames.add("固定间隔").add("指数退避");
|
||||
retryStrategyProperty.put("default", "FIXED");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字段的Schema
|
||||
*/
|
||||
private static void generateFieldSchema(ObjectNode property, Field field, SchemaProperty annotation) {
|
||||
// 设置基本属性
|
||||
if (!annotation.title().isEmpty()) {
|
||||
property.put("title", annotation.title());
|
||||
}
|
||||
if (!annotation.description().isEmpty()) {
|
||||
property.put("description", annotation.description());
|
||||
}
|
||||
if (!annotation.format().isEmpty()) {
|
||||
property.put("format", annotation.format());
|
||||
}
|
||||
if (!annotation.defaultValue().isEmpty()) {
|
||||
setDefaultValue(property, field, annotation.defaultValue());
|
||||
}
|
||||
|
||||
// 设置字段类型
|
||||
setFieldType(property, field);
|
||||
|
||||
// 设置数值范围
|
||||
if (isNumericType(field.getType()) &&
|
||||
(annotation.minimum() != Integer.MIN_VALUE || annotation.maximum() != Integer.MAX_VALUE)) {
|
||||
if (annotation.minimum() != Integer.MIN_VALUE) {
|
||||
property.put("minimum", annotation.minimum());
|
||||
}
|
||||
if (annotation.maximum() != Integer.MAX_VALUE) {
|
||||
property.put("maximum", annotation.maximum());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置枚举值
|
||||
if (annotation.enumValues().length > 0) {
|
||||
ArrayNode enumNode = property.putArray("enum");
|
||||
Arrays.stream(annotation.enumValues()).forEach(enumNode::add);
|
||||
|
||||
if (annotation.enumNames().length > 0) {
|
||||
ArrayNode enumNamesNode = property.putArray("enumNames");
|
||||
Arrays.stream(annotation.enumNames()).forEach(enumNamesNode::add);
|
||||
|
||||
// 生成oneOf结构
|
||||
ArrayNode oneOf = property.putArray("oneOf");
|
||||
for (int i = 0; i < annotation.enumValues().length; i++) {
|
||||
ObjectNode oneOfItem = oneOf.addObject();
|
||||
oneOfItem.put("const", annotation.enumValues()[i]);
|
||||
oneOfItem.put("title", annotation.enumNames()[i]);
|
||||
|
||||
// 对于非shell的选项,设置为只读
|
||||
if (!"shell".equals(annotation.enumValues()[i])) {
|
||||
oneOfItem.put("readOnly", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段类型
|
||||
*/
|
||||
private static void setFieldType(ObjectNode property, Field field) {
|
||||
Class<?> type = field.getType();
|
||||
if (String.class.isAssignableFrom(type)) {
|
||||
property.put("type", "string");
|
||||
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
|
||||
property.put("type", "integer");
|
||||
} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
|
||||
property.put("type", "boolean");
|
||||
} else if (List.class.isAssignableFrom(type)) {
|
||||
property.put("type", "array");
|
||||
// 如果需要,可以在这里添加items的schema
|
||||
} else if (Map.class.isAssignableFrom(type)) {
|
||||
property.put("type", "object");
|
||||
ObjectNode additionalProperties = property.putObject("additionalProperties");
|
||||
additionalProperties.put("type", "string");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认值
|
||||
*/
|
||||
private static void setDefaultValue(ObjectNode property, Field field, String defaultValue) {
|
||||
Class<?> type = field.getType();
|
||||
if (String.class.isAssignableFrom(type)) {
|
||||
property.put("default", defaultValue);
|
||||
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
|
||||
try {
|
||||
property.put("default", Integer.parseInt(defaultValue));
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略无效的默认值
|
||||
}
|
||||
} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
|
||||
property.put("default", Boolean.parseBoolean(defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数值类型
|
||||
*/
|
||||
private static boolean isNumericType(Class<?> type) {
|
||||
return Integer.class.isAssignableFrom(type) ||
|
||||
int.class.isAssignableFrom(type) ||
|
||||
Long.class.isAssignableFrom(type) ||
|
||||
long.class.isAssignableFrom(type) ||
|
||||
Double.class.isAssignableFrom(type) ||
|
||||
double.class.isAssignableFrom(type) ||
|
||||
Float.class.isAssignableFrom(type) ||
|
||||
float.class.isAssignableFrom(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.workflow.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Schema生成器的主类,用于测试和生成Schema
|
||||
*/
|
||||
public class SchemaGeneratorMain {
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
}
|
||||
|
||||
private SchemaGeneratorMain() {
|
||||
// 私有构造函数防止实例化
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 生成所有节点类型的Schema
|
||||
System.out.println("=== Node Type Schemas ===");
|
||||
JsonNode schemas = SchemaGenerator.generateAllNodeTypeSchemas();
|
||||
System.out.println(mapper.writeValueAsString(schemas));
|
||||
} catch (Exception e) {
|
||||
System.err.println("生成Schema时发生错误:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.qqchen.deploy.backend.workflow.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SchemaGeneratorTest {
|
||||
private final SchemaGenerator generator = new SchemaGenerator();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void testGenerateNodeTypeSchema() throws Exception {
|
||||
JsonNode schema = generator.generateNodeTypeSchema();
|
||||
|
||||
// 将生成的schema打印出来以便查看
|
||||
String prettySchema = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
|
||||
System.out.println(prettySchema);
|
||||
|
||||
// 基本验证
|
||||
assertTrue(schema.isArray());
|
||||
assertEquals(1, schema.size());
|
||||
|
||||
JsonNode firstNode = schema.get(0);
|
||||
assertEquals("SCRIPT", firstNode.get("code").asText());
|
||||
assertEquals("脚本执行器", firstNode.get("name").asText());
|
||||
|
||||
JsonNode configSchema = firstNode.get("configSchema");
|
||||
assertTrue(configSchema.has("properties"));
|
||||
assertTrue(configSchema.has("required"));
|
||||
|
||||
// 验证必填字段
|
||||
JsonNode required = configSchema.get("required");
|
||||
assertTrue(required.isArray());
|
||||
assertTrue(required.toString().contains("script"));
|
||||
assertTrue(required.toString().contains("language"));
|
||||
assertTrue(required.toString().contains("interpreter"));
|
||||
|
||||
// 验证属性
|
||||
JsonNode properties = configSchema.get("properties");
|
||||
assertTrue(properties.has("script"));
|
||||
assertTrue(properties.has("language"));
|
||||
assertTrue(properties.has("interpreter"));
|
||||
assertTrue(properties.has("workingDirectory"));
|
||||
assertTrue(properties.has("timeout"));
|
||||
assertTrue(properties.has("retryTimes"));
|
||||
assertTrue(properties.has("retryInterval"));
|
||||
assertTrue(properties.has("environment"));
|
||||
assertTrue(properties.has("successExitCode"));
|
||||
assertTrue(properties.has("supportedLanguages"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user