From 0e430c30365bc3990a43fa5803a9438404ad8f31 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 23 Dec 2024 10:41:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...askDelegate.java => BaseNodeDelegate.java} | 2 +- .../delegate/JenkinsNodeDelegate.java | 176 ++++++++++++++++++ ...skDelegate.java => ShellNodeDelegate.java} | 8 +- .../JenkinsNodeLocalVariables.java | 19 ++ .../ScriptNodeLocalVariables.java | 2 +- .../JenkinsNodePanelVariables.java | 66 +++++++ .../db/migration/V1.0.1__init_data.sql | 45 ++++- 7 files changed, 301 insertions(+), 17 deletions(-) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/{BaseTaskDelegate.java => BaseNodeDelegate.java} (97%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsNodeDelegate.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/{ShellTaskDelegate.java => ShellNodeDelegate.java} (96%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/JenkinsNodeLocalVariables.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/panelVariables/JenkinsNodePanelVariables.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java similarity index 97% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java index ffba312b..b0631455 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java @@ -16,7 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @param Local变量类型 */ @Slf4j -public abstract class BaseTaskDelegate implements JavaDelegate { +public abstract class BaseNodeDelegate implements JavaDelegate { @Autowired private ObjectMapper objectMapper; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsNodeDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsNodeDelegate.java new file mode 100644 index 00000000..ade727b6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsNodeDelegate.java @@ -0,0 +1,176 @@ +package com.qqchen.deploy.backend.workflow.delegate; + +import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants; +import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.ScriptNodeLocalVariables; +import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.ScriptNodePanelVariables; +import com.qqchen.deploy.backend.workflow.enums.NodeLogTypeEnums; +import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.BpmnError; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Shell脚本任务的委派者实现 + */ +@Slf4j +@Component +public class JenkinsNodeDelegate extends BaseNodeDelegate { + + @Resource + private ApplicationEventPublisher eventPublisher; + + // 用于存储实时输出的Map + private static final Map outputMap = new ConcurrentHashMap<>(); + + private static final Map errorMap = new ConcurrentHashMap<>(); + + @Override + protected Class getPanelVariablesClass() { + return ScriptNodePanelVariables.class; + } + + @Override + protected Class getLocalVariablesClass() { + return ScriptNodeLocalVariables.class; + } + + @Override + protected void executeInternal(DelegateExecution execution, + ScriptNodePanelVariables panelVariables, + ScriptNodeLocalVariables localVariables) { + if (panelVariables == null || panelVariables.getScript() == null) { + throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided"); + } + +// try { +// log.info("准备执行脚本: {}", panelVariables.getScript()); +// // 使用processInstanceId而不是executionId +// String processInstanceId = execution.getProcessInstanceId(); +// outputMap.put(processInstanceId, new StringBuilder()); +// errorMap.put(processInstanceId, new StringBuilder()); +// +// // 创建进程构建器 +// ProcessBuilder processBuilder = new ProcessBuilder(); +// +// // 根据操作系统选择合适的shell +// String os = System.getProperty("os.name").toLowerCase(); +// if (os.contains("win")) { +// // Windows系统使用cmd +// processBuilder.command("cmd", "/c", panelVariables.getScript()); +// } else { +// // Unix-like系统使用bash +// processBuilder.command("bash", "-c", panelVariables.getScript()); +// } +// +// // 设置工作目录 +// if (StringUtils.hasText(localVariables.getWorkDir())) { +// // Windows系统路径处理 +// String workDirValue = localVariables.getWorkDir(); +// if (os.contains("win")) { +// // 确保使用Windows风格的路径分隔符 +// workDirValue = workDirValue.replace("/", "\\"); +// // 如果路径以\开头,去掉第一个\ +// if (workDirValue.startsWith("\\")) { +// workDirValue = workDirValue.substring(1); +// } +// } +// File workDirFile = new File(workDirValue); +// if (!workDirFile.exists()) { +// workDirFile.mkdirs(); +// } +// processBuilder.directory(workDirFile); +// } +// +// // 设置环境变量 +// if (localVariables.getEnv() != null) { +// processBuilder.environment().putAll(localVariables.getEnv()); +// } +// +// // 执行命令 +// log.info("执行shell脚本: {}", panelVariables.getScript()); +// Process process = processBuilder.start(); +// +// // 创建线程池处理输出 +// ExecutorService executorService = Executors.newFixedThreadPool(2); +// +// // 处理标准输出 +// Future outputFuture = executorService.submit(() -> +// processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT)); +// +// // 处理错误输出 +// Future errorFuture = executorService.submit(() -> +// processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR)); +// +// // 等待进程完成 +// int exitCode = process.waitFor(); +// +// // 等待输出处理完成 +// outputFuture.get(5, TimeUnit.SECONDS); +// errorFuture.get(5, TimeUnit.SECONDS); +// +// // 关闭线程池 +// executorService.shutdown(); +// +// // 设置最终结果 +// StringBuilder finalOutput = outputMap.get(processInstanceId); +// StringBuilder finalError = errorMap.get(processInstanceId); +// +// execution.setVariable("shellOutput", finalOutput.toString()); +// execution.setVariable("shellError", finalError.toString()); +// execution.setVariable("shellExitCode", exitCode); +// +// // 清理缓存 +// outputMap.remove(processInstanceId); +// errorMap.remove(processInstanceId); +// +// if (exitCode != 0) { +// log.error("Shell脚本执行失败,退出码: {}", exitCode); +// execution.setVariable("errorDetail", "Shell脚本执行失败,退出码: " + exitCode); +// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败,退出码: " + exitCode); +// } +// log.info("Shell脚本执行成功"); +// log.debug("脚本输出: {}", finalOutput); +// +// } catch (Exception e) { +// log.error("Shell脚本执行失败", e); +// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, e.getMessage()); +// } + } + + private void processInputStream(InputStream inputStream, String processInstanceId, NodeLogTypeEnums logType) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + // 发布日志事件 + eventPublisher.publishEvent(new ShellLogEvent(processInstanceId, line, logType)); + + // 同时保存到StringBuilder中 + if (logType == NodeLogTypeEnums.STDOUT) { + StringBuilder output = outputMap.get(processInstanceId); + synchronized (output) { + output.append(line).append("\n"); + } +// log.info("Shell output: {}", line); + } else { + StringBuilder error = errorMap.get(processInstanceId); + synchronized (error) { + error.append(line).append("\n"); + } +// log.error("Shell error: {}", line); + } + } + } catch (IOException e) { + log.error("Error reading process output", e); + } + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellNodeDelegate.java similarity index 96% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellNodeDelegate.java index f3e9d7c4..95256fe1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellNodeDelegate.java @@ -11,26 +11,20 @@ import org.flowable.engine.delegate.BpmnError; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; /** * Shell脚本任务的委派者实现 */ @Slf4j @Component -public class ShellTaskDelegate extends BaseTaskDelegate { +public class ShellNodeDelegate extends BaseNodeDelegate { @Resource private ApplicationEventPublisher eventPublisher; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/JenkinsNodeLocalVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/JenkinsNodeLocalVariables.java new file mode 100644 index 00000000..7a2b6b16 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/JenkinsNodeLocalVariables.java @@ -0,0 +1,19 @@ +package com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables; + +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class JenkinsNodeLocalVariables extends BaseNodeLocalVariables { + + @SchemaProperty( + title = "委派者", + description = "委派者", + defaultValue = "${jenkinsNodeDelegate}", + required = true + ) + private String delegate; + +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java index bf60d5c5..0ec57e95 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java @@ -11,7 +11,7 @@ public class ScriptNodeLocalVariables extends BaseNodeLocalVariables { @SchemaProperty( title = "委派者", description = "委派者", - defaultValue = "${shellTaskDelegate}", + defaultValue = "${shellNodeDelegate}", required = true ) private String delegate; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/panelVariables/JenkinsNodePanelVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/panelVariables/JenkinsNodePanelVariables.java new file mode 100644 index 00000000..569f5a6c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/panelVariables/JenkinsNodePanelVariables.java @@ -0,0 +1,66 @@ +package com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables; + +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 脚本执行器配置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class JenkinsNodePanelVariables extends BaseNodePanelVariables { + + + @SchemaProperty( + title = "脚本代码", + description = "脚本代码", + 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 = "脚本执行成功时的标识", + required = true + ) + private Integer successExitCode; + + /** + * 支持的脚本语言列表 + */ + @SchemaProperty( + title = "支持的脚本语言", + enumValues = {"shell", "python", "javascript", "groovy"} + ) + private List supportedLanguages; + +} 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 908effef..a65658d7 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 @@ -35,21 +35,50 @@ VALUES -- 初始化权限管理数据 -- -------------------------------------------------------------------------------------- --- 初始化菜单数据 +-- -------------------------------------------------------------------------------------- +-- 初始化权限管理数据 +-- -------------------------------------------------------------------------------------- + +-- -------------------------------------------------------------------------------------- +-- 初始化权限管理数据 +-- -------------------------------------------------------------------------------------- + +-- 先清理已有数据 +DELETE FROM sys_menu; + INSERT INTO sys_menu (id, name, path, component, icon, type, parent_id, sort, hidden, enabled, create_by, create_time, version, deleted) -VALUES +VALUES +-- 首页 +(99, '首页', '/dashboard', '/src/pages/dashboard/index', 'DashboardOutlined', 2, NULL, 0, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), + -- 系统管理 -(1, '系统管理', '/system', 'LAYOUT', 'setting', 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(1, '系统管理', '/system', 'Layout', 'SettingOutlined', 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 用户管理 -(2, '用户管理', '/system/user', '/System/User/index', 'user', 2, 1, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(2, '用户管理', '/system/user', '/src/pages/system/user/index', 'UserOutlined', 2, 1, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 角色管理 -(3, '角色管理', '/system/role', '/System/Role/index', 'peoples', 2, 1, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(3, '角色管理', '/system/role', '/src/pages/system/role/index', 'TeamOutlined', 2, 1, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 菜单管理 -(4, '菜单管理', '/system/menu', '/System/Menu/index', 'tree-table', 2, 1, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(4, '菜单管理', '/system/menu', '/src/pages/system/menu/index', 'MenuOutlined', 2, 1, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 部门管理 -(5, '部门管理', '/system/department', '/System/Department/index', 'tree', 2, 1, 40, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(5, '部门管理', '/system/department', '/src/pages/system/department/index', 'ApartmentOutlined', 2, 1, 40, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 三方系统 -(70, '三方系统', '/system/external', '/System/External/index', 'api', 2, 1, 70, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE); +(70, '三方系统', '/system/external', '/src/pages/system/external/index', 'ApiOutlined', 2, 1, 70, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), + +-- 工作流管理 +(100, '工作流管理', '/workflow', 'Layout', 'DeploymentUnitOutlined', 1, NULL, 2, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 工作流设计 +(101, '工作流设计', '/workflow/definition', '/src/pages/workflow/definition/index', 'EditOutlined', 2, 100, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 工作流实例 +(102, '工作流实例', '/workflow/instance', '/src/pages/workflow/instance/index', 'BranchesOutlined', 2, 100, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 节点管理 +(103, '节点管理', '/workflow/node-design', '/src/pages/workflow/nodedesign/design/index', 'ControlOutlined', 2, 100, 40, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), + +-- 应用管理 +(200, '应用管理', '/application', 'Layout', 'AppstoreOutlined', 1, NULL, 3, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 应用列表 +(201, '应用列表', '/application/list', '/src/pages/application/list/index', 'UnorderedListOutlined', 2, 200, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 环境管理 +(202, '环境管理', '/application/environment', '/src/pages/application/environment/index', 'CloudOutlined', 2, 200, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE); -- 初始化角色数据 INSERT INTO sys_role (id, create_time, code, name, type, description, sort)