diff --git a/backend/docs/workflow-data-format.md b/backend/docs/workflow-data-format.md new file mode 100644 index 00000000..d4ca8541 --- /dev/null +++ b/backend/docs/workflow-data-format.md @@ -0,0 +1,102 @@ +# 工作流数据格式说明 + +## 1. 节点类型数据示例 + +```json +{ + "id": 2003, + "createTime": "2024-12-05 12:40:03", + "createBy": "system", + "updateTime": "2024-12-05 12:40:03", + "updateBy": "system", + "version": 1, + "deleted": false, + "extraData": null, + "code": "SHELL", + "name": "Shell脚本节点", + "description": "执行Shell脚本的任务节点", + "category": "TASK", + "icon": "code", + "color": "#1890ff", + "executors": [ + { + "code": "SHELL", + "name": "Shell脚本执行器", + "description": "执行Shell脚本,支持配置超时时间和工作目录", + "configSchema": "{\"type\":\"object\",\"required\":[\"script\"],\"properties\":{\"script\":{\"type\":\"string\",\"title\":\"脚本内容\",\"format\":\"shell\",\"description\":\"需要执行的Shell脚本内容\"},\"timeout\":{\"type\":\"number\",\"title\":\"超时时间\",\"description\":\"脚本执行的最大时间(秒)\",\"minimum\":1,\"maximum\":3600,\"default\":300},\"workingDir\":{\"type\":\"string\",\"title\":\"工作目录\",\"description\":\"脚本执行的工作目录\",\"default\":\"/tmp\"}}}", + "defaultConfig": null + } + ], + "configSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"title\": \"节点名称\",\n \"minLength\": 1,\n \"maxLength\": 50\n },\n \"description\": {\n \"type\": \"string\",\n \"title\": \"节点描述\",\n \"maxLength\": 200\n },\n \"executor\": {\n \"type\": \"string\",\n \"title\": \"执行器\",\n \"enum\": [\"SHELL\"],\n \"enumNames\": [\"Shell脚本执行器\"]\n },\n \"retryTimes\": {\n \"type\": \"number\",\n \"title\": \"重试次数\",\n \"minimum\": 0,\n \"maximum\": 3,\n \"default\": 0\n },\n \"retryInterval\": {\n \"type\": \"number\",\n \"title\": \"重试间隔(秒)\",\n \"minimum\": 1,\n \"maximum\": 300,\n \"default\": 60\n }\n },\n \"required\": [\"name\", \"executor\"]\n}", + "defaultConfig": "{\"name\": \"Shell脚本\", \"executor\": \"SHELL\", \"retryTimes\": 0, \"retryInterval\": 60}", + "enabled": true +} +``` + +## 2. 流程设计数据示例 + +```json +{ + "nodes": [ + { + "id": "node_1", + "type": "START", + "position": { "x": 100, "y": 100 }, + "data": { + "name": "开始", + "description": "流程开始节点", + "config": { + "name": "开始", + "description": "这是一个开始节点" + } + } + } + ], + "edges": [ + { + "id": "edge_1", + "source": "node_1", + "target": "node_2", + "type": "default", + "data": { + "condition": null + } + } + ] +} +``` + +## 3. 关键字段说明 + +### configSchema(节点配置模式) +**用途**: +1. 前端动态生成配置表单 +2. 后端验证配置数据的合法性 +3. 提供配置项的约束和验证规则 + +### defaultConfig(默认配置) +**用途**: +1. 新建节点时的默认值 +2. 重置配置时的参考值 +3. 必须符合configSchema定义的格式 + +### executors(执行器定义) +**用途**: +1. 定义节点支持的执行器类型 +2. 每个执行器的配置要求 +3. 用于任务节点的具体执行逻辑 + +## 4. 字段关系说明 + +1. configSchema定义节点的整体配置结构 +2. defaultConfig提供符合configSchema的默认值 +3. executors中的configSchema定义具体执行器的配置结构 +4. 实际节点配置时,executorConfig需要符合选定执行器的configSchema + +## 5. 设计优点 + +1. 配置灵活可扩展 +2. 前端可以动态生成表单 +3. 后端可以严格验证配置 +4. 支持不同类型节点的差异化配置 +5. 便于新增节点类型和执行器 \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java index c6a8d559..f23c5cc2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java @@ -26,7 +26,7 @@ import java.util.List; @Slf4j @Tag(name = "节点类型管理", description = "节点类型相关接口") @RestController -@RequestMapping("/api/v1/node-type") +@RequestMapping("/api/v1/node-types") public class NodeTypeApiController extends BaseController { @Resource diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java index 4b0ab528..2dd3dac5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java @@ -1,8 +1,10 @@ package com.qqchen.deploy.backend.workflow.converter.impl; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.exception.SystemException; import com.qqchen.deploy.backend.workflow.converter.JsonConverter; import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; @@ -30,9 +32,24 @@ public class DefaultJsonConverter implements JsonConverter { return new ArrayList<>(); } try { - return objectMapper.readValue(json, new TypeReference>() {}); + // 先将JSON解析为JsonNode,以便我们可以手动处理configSchema字段 + JsonNode rootNode = objectMapper.readTree(json); + if (!rootNode.isArray()) { + throw new SystemException(ResponseCode.WORKFLOW_CONFIG_INVALID, new Object[]{"Executors JSON must be an array"}, null); + } + + List result = new ArrayList<>(); + for (JsonNode node : rootNode) { + // 将configSchema转换为字符串 + if (node.has("configSchema")) { + ((ObjectNode) node).put("configSchema", node.get("configSchema").toString()); + } + // 将处理后的节点转换为TaskExecutorDefinition对象 + result.add(objectMapper.treeToValue(node, TaskExecutorDefinition.class)); + } + return result; } catch (JsonProcessingException e) { - throw new SystemException("Failed to parse executors JSON: " + e.getMessage(), e); + throw new SystemException(ResponseCode.WORKFLOW_CONFIG_INVALID, new Object[]{"Failed to parse executors JSON: " + e.getMessage()}, e); } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index f90413d5..e3e359f3 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -2,9 +2,9 @@ server: port: 8080 spring: datasource: - url: jdbc:mysql://127.0.0.1:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root - password: root + password: ServBay.dev driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: 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 55cf3c3b..2966d377 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 @@ -247,4 +247,179 @@ INSERT INTO wf_workflow_variable ( (2, 'admin', NOW(), 0, 'admin', NOW(), 0, 1, 'branch', 'master', 'STRING'), (3, 'admin', NOW(), 0, 'admin', NOW(), 0, - 1, 'buildNumber', '123', 'STRING'); \ No newline at end of file + 1, 'buildNumber', '123', 'STRING'); + +-- 节点类型初始化数据 +INSERT INTO wf_node_type (id, code, name, description, category, icon, color, executors, config_schema, default_config, enabled, create_time, create_by, update_time, update_by, version, deleted) +VALUES +-- 基础节点类型 +(2001, 'START', '开始节点', '工作流的开始节点', 'BASIC', 'play-circle', '#52c41a', '[]', +'{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "节点名称", + "minLength": 1, + "maxLength": 50 + }, + "description": { + "type": "string", + "title": "节点描述", + "maxLength": 200 + } + }, + "required": ["name"] +}', +'{"name": "开始", "description": ""}', +true, NOW(), 'system', NOW(), 'system', 1, false), + +(2002, 'END', '结束节点', '工作流的结束节点', 'BASIC', 'stop', '#ff4d4f', '[]', +'{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "节点名称", + "minLength": 1, + "maxLength": 50 + }, + "description": { + "type": "string", + "title": "节点描述", + "maxLength": 200 + } + }, + "required": ["name"] +}', +'{"name": "结束", "description": ""}', +true, NOW(), 'system', NOW(), 'system', 1, false), + +-- 任务节点类型 +(2003, 'SHELL', 'Shell脚本节点', '执行Shell脚本的任务节点', 'TASK', 'code', '#1890ff', +'[{ + "code": "SHELL", + "name": "Shell脚本执行器", + "description": "执行Shell脚本,支持配置超时时间和工作目录", + "configSchema": { + "type": "object", + "required": ["script"], + "properties": { + "script": { + "type": "string", + "title": "脚本内容", + "format": "shell", + "description": "需要执行的Shell脚本内容" + }, + "timeout": { + "type": "number", + "title": "超时时间", + "description": "脚本执行的最大时间(秒)", + "minimum": 1, + "maximum": 3600, + "default": 300 + }, + "workingDir": { + "type": "string", + "title": "工作目录", + "description": "脚本执行的工作目录", + "default": "/tmp" + } + } + } +}]', +'{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "节点名称", + "minLength": 1, + "maxLength": 50 + }, + "description": { + "type": "string", + "title": "节点描述", + "maxLength": 200 + }, + "executor": { + "type": "string", + "title": "执行器", + "enum": ["SHELL"], + "enumNames": ["Shell脚本执行器"] + }, + "retryTimes": { + "type": "number", + "title": "重试次数", + "minimum": 0, + "maximum": 3, + "default": 0 + }, + "retryInterval": { + "type": "number", + "title": "重试间隔(秒)", + "minimum": 1, + "maximum": 300, + "default": 60 + } + }, + "required": ["name", "executor"] +}', +'{"name": "Shell脚本", "executor": "SHELL", "retryTimes": 0, "retryInterval": 60}', +true, NOW(), 'system', NOW(), 'system', 1, false), + +-- 网关节点类型 +(2004, 'GATEWAY', '网关节点', '控制流程流转的网关节点', 'GATEWAY', 'fork', '#faad14', '[]', +'{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "节点名称", + "minLength": 1, + "maxLength": 50 + }, + "description": { + "type": "string", + "title": "节点描述", + "maxLength": 200 + }, + "type": { + "type": "string", + "title": "网关类型", + "enum": ["PARALLEL", "EXCLUSIVE", "INCLUSIVE"], + "enumNames": ["并行网关", "排他网关", "包容网关"] + } + }, + "required": ["name", "type"] +}', +'{"name": "网关", "type": "EXCLUSIVE"}', +true, NOW(), 'system', NOW(), 'system', 1, false), + +-- 事件节点类型 +(2005, 'TIMER', '定时器节点', '定时触发的事件节点', 'EVENT', 'clock-circle', '#722ed1', '[]', +'{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "节点名称", + "minLength": 1, + "maxLength": 50 + }, + "description": { + "type": "string", + "title": "节点描述", + "maxLength": 200 + }, + "cron": { + "type": "string", + "title": "CRON表达式", + "pattern": "^(\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*) (\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*) (\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*) (\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*) (\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*) (\\d+|\\*|\\*/\\d+|\\d+\\-\\d+|\\d+(,\\d+)*)$", + "description": "定时触发的CRON表达式" + } + }, + "required": ["name", "cron"] +}', +'{"name": "定时器", "cron": "0 0 * * * ?"}', +true, NOW(), 'system', NOW(), 'system', 1, false); \ No newline at end of file