From f69897a9d0c59b71c7ac16a126ed5d4e53ac11d5 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 11 Dec 2024 18:16:47 +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 --- .../workflow/dto/graph/EdgeConfig.java | 18 +++ .../workflow/dto/graph/NodeConfig.java | 12 ++ .../workflow/entity/WorkflowDefinition.java | 10 +- .../backend/workflow/enums/NodeType.java | 12 ++ .../workflow/hibernate/WorkflowGraphType.java | 122 ++++++++++++++++++ .../db/migration/V1.0.0__init_schema.sql | 2 +- .../db/migration/V1.0.1__init_data.sql | 15 +-- 7 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java index ad1a90f2..327d192f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.graph; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; /** @@ -8,6 +9,7 @@ import lombok.Data; * @date 2024-12-11 */ @Data +@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段 public class EdgeConfig { /** * 条件 @@ -19,8 +21,24 @@ public class EdgeConfig { */ private String expression; + /** + * 条件表达式 + */ + private String conditionExpression; + /** * 类型(sequence/message/association) */ private String type; + + public void setConditionExpression(String conditionExpression) { + this.conditionExpression = conditionExpression; + // 同时设置 condition 字段,保持兼容性 + this.condition = conditionExpression; + } + + public String getConditionExpression() { + // 如果 conditionExpression 为空但 condition 不为空,返回 condition + return conditionExpression != null ? conditionExpression : condition; + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java index c334700c..5f2244aa 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.graph; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import java.util.List; import java.util.Map; @@ -10,6 +11,7 @@ import java.util.Map; * @date 2024-12-11 */ @Data +@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段 public class NodeConfig { /** * 配置类型 @@ -70,4 +72,14 @@ public class NodeConfig { * 是否排他 */ private Boolean exclusive; + + /** + * 脚本格式 + */ + private String scriptFormat; + + /** + * 脚本内容 + */ + private String script; } 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 index b1d7dde1..3355c7a7 100644 --- 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 @@ -2,9 +2,11 @@ package com.qqchen.deploy.backend.workflow.entity; import com.fasterxml.jackson.databind.JsonNode; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowGraph; import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; -import com.vladmihalcea.hibernate.type.json.JsonType; +import com.qqchen.deploy.backend.workflow.hibernate.WorkflowGraphType; import com.qqchen.deploy.backend.framework.domain.Entity; +import com.vladmihalcea.hibernate.type.json.JsonType; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -50,9 +52,9 @@ public class WorkflowDefinition extends Entity { /** * 流程图数据,包含节点和连线的位置、样式等信息 */ - @Type(JsonType.class) - @Column(name = "graph_data", columnDefinition = "json") - private JsonNode graphData; + @Type(WorkflowGraphType.class) + @Column(name = "graph", columnDefinition = "json") + private WorkflowGraph graph; /** * 表单配置 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeType.java index 2fd15980..8fd5b43c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeType.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeType.java @@ -1,5 +1,7 @@ package com.qqchen.deploy.backend.workflow.enums; +import com.fasterxml.jackson.annotation.JsonValue; + /** * 节点类型枚举 * @author cascade @@ -16,6 +18,7 @@ public enum NodeType { SUBPROCESS("subProcess"), CALL_ACTIVITY("callActivity"); + @JsonValue private final String value; NodeType(String value) { @@ -25,4 +28,13 @@ public enum NodeType { public String getValue() { return value; } + + public static NodeType fromValue(String value) { + for (NodeType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown NodeType value: " + value); + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java new file mode 100644 index 00000000..07d0dc05 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java @@ -0,0 +1,122 @@ +package com.qqchen.deploy.backend.workflow.hibernate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowGraph; +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.usertype.UserType; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +/** + * 自定义 Hibernate 类型,用于处理 WorkflowGraph 的序列化和反序列化 + */ +public class WorkflowGraphType implements UserType { + private final ObjectMapper objectMapper; + + public WorkflowGraphType() { + this.objectMapper = new ObjectMapper(); + // 配置 ObjectMapper + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public int getSqlType() { + return Types.VARCHAR; + } + + @Override + public Class returnedClass() { + return WorkflowGraph.class; + } + + @Override + public boolean equals(WorkflowGraph x, WorkflowGraph y) { + if (x == y) { + return true; + } + if (x == null || y == null) { + return false; + } + return x.equals(y); + } + + @Override + public int hashCode(WorkflowGraph x) { + return x == null ? 0 : x.hashCode(); + } + + @Override + public WorkflowGraph nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) + throws SQLException { + String value = rs.getString(position); + if (value == null) { + return null; + } + try { + return objectMapper.readValue(value, WorkflowGraph.class); + } catch (JsonProcessingException e) { + throw new HibernateException("Failed to convert String to WorkflowGraph: " + value, e); + } + } + + @Override + public void nullSafeSet(PreparedStatement st, WorkflowGraph value, int index, SharedSessionContractImplementor session) + throws SQLException { + if (value == null) { + st.setNull(index, Types.VARCHAR); + return; + } + try { + st.setString(index, objectMapper.writeValueAsString(value)); + } catch (JsonProcessingException e) { + throw new HibernateException("Failed to convert WorkflowGraph to String: " + value, e); + } + } + + @Override + public WorkflowGraph deepCopy(WorkflowGraph value) { + if (value == null) { + return null; + } + try { + return objectMapper.readValue(objectMapper.writeValueAsString(value), WorkflowGraph.class); + } catch (JsonProcessingException e) { + throw new HibernateException("Failed to deep copy WorkflowGraph", e); + } + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(WorkflowGraph value) { + try { + return value == null ? null : objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new HibernateException("Failed to disassemble WorkflowGraph", e); + } + } + + @Override + public WorkflowGraph assemble(Serializable cached, Object owner) { + try { + return cached == null ? null : objectMapper.readValue(cached.toString(), WorkflowGraph.class); + } catch (JsonProcessingException e) { + throw new HibernateException("Failed to assemble WorkflowGraph", e); + } + } + + @Override + public WorkflowGraph replace(WorkflowGraph detached, WorkflowGraph managed, Object owner) { + return deepCopy(detached); + } +} 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 ef621a91..b479cadb 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 @@ -397,7 +397,7 @@ CREATE TABLE workflow_definition -- 流程配置 bpmn_xml TEXT COMMENT 'BPMN XML内容', - graph_data JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息', + graph JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息', form_config JSON COMMENT '表单配置', tags JSON COMMENT '流程标签', 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 c8320a0c..56029c8e 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 @@ -159,7 +159,7 @@ INSERT INTO sys_external_system ( -- 工作流定义测试数据 INSERT INTO workflow_definition ( name, `key`, flow_version, description, category, - bpmn_xml, graph_data, form_config, tags, + bpmn_xml, graph, form_config, tags, status, is_executable, target_namespace, created_at, updated_at, created_by, updated_by, is_deleted ) VALUES @@ -167,7 +167,7 @@ INSERT INTO workflow_definition ( ( '简单服务任务流程', 'simple_service_flow', 1, '一个包含服务任务的简单流程', 'test', '', - '{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"service1","type":"serviceTask","position":{"x":300,"y":100}},{"id":"end1","type":"end","position":{"x":500,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"service1"},{"id":"flow2","source":"service1","target":"end1"}]}', + '{"nodes":[{"id":"start1","type":"start","name":"开始","position":{"x":100,"y":100},"config":{"type":"start"},"properties":{},"size":{"width":40,"height":40}},{"id":"service1","type":"serviceTask","name":"服务任务","position":{"x":300,"y":100},"config":{"type":"serviceTask","implementation":"shell","fields":{"script":{"string":"echo \\"Hello World\\""}}},"properties":{},"size":{"width":100,"height":60}},{"id":"end1","type":"end","name":"结束","position":{"x":500,"y":100},"config":{"type":"end"},"properties":{},"size":{"width":40,"height":40}}],"edges":[{"id":"flow1","source":"start1","target":"service1","name":"","config":{"type":"sequenceFlow"},"properties":{}},{"id":"flow2","source":"service1","target":"end1","name":"","config":{"type":"sequenceFlow"},"properties":{}}],"properties":{}}', '{"formItems":[]}', '["simple","test","service"]', 'PUBLISHED', TRUE, 'http://www.flowable.org/test', @@ -177,10 +177,8 @@ INSERT INTO workflow_definition ( -- 用户任务流程:开始 -> 用户任务 -> 结束 ( '用户审批流程', 'user_approval_flow', 1, '一个简单的用户审批流程', 'approval', - '', - '{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"user1","type":"userTask","position":{"x":300,"y":100}},{"id":"end1","type":"end","position":{"x":500,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"user1","label":"提交审批"},{"id":"flow2","source":"user1","target":"end1","label":"审批完成"}]}', + '', + '{"nodes":[{"id":"start1","type":"start","name":"开始","position":{"x":100,"y":100},"config":{"type":"start"},"properties":{},"size":{"width":40,"height":40}},{"id":"user1","type":"userTask","name":"审批任务","position":{"x":300,"y":100},"config":{"type":"userTask","assignee":"user1"},"properties":{},"size":{"width":100,"height":60}},{"id":"end1","type":"end","name":"结束","position":{"x":500,"y":100},"config":{"type":"end"},"properties":{},"size":{"width":40,"height":40}}],"edges":[{"id":"flow1","source":"start1","target":"user1","name":"提交审批","config":{"type":"sequenceFlow"},"properties":{}},{"id":"flow2","source":"user1","target":"end1","name":"审批完成","config":{"type":"sequenceFlow"},"properties":{}}],"properties":{}}', '{"formItems":[{"type":"input","label":"意见","name":"comment","required":true}]}', '["approval","user-task"]', 'PUBLISHED', TRUE, 'http://www.flowable.org/test', @@ -190,9 +188,8 @@ flowable:priority="1"> 服务任务 -> 用户任务 -> 脚本任务 -> 结束 ( '复杂业务流程', 'complex_business_flow', 1, '包含多种任务节点的复杂业务流程', 'business', - '$${condition}', - '{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"service1","type":"serviceTask","position":{"x":300,"y":100}},{"id":"user1","type":"userTask","position":{"x":500,"y":100}},{"id":"script1","type":"scriptTask","position":{"x":700,"y":100}},{"id":"end1","type":"end","position":{"x":900,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"service1"},{"id":"flow2","source":"service1","target":"user1","label":"条件分支"},{"id":"flow3","source":"user1","target":"script1"},{"id":"flow4","source":"script1","target":"end1"}]}', + '$${condition}', + '{"nodes":[{"id":"start1","type":"start","name":"开始","position":{"x":100,"y":100},"config":{"type":"start"},"properties":{},"size":{"width":40,"height":40}},{"id":"service1","type":"serviceTask","name":"服务任务","position":{"x":300,"y":100},"config":{"type":"serviceTask","implementation":"com.example.ServiceTask"},"properties":{},"size":{"width":100,"height":60}},{"id":"user1","type":"userTask","name":"用户任务","position":{"x":500,"y":100},"config":{"type":"userTask","assignee":"user1"},"properties":{},"size":{"width":100,"height":60}},{"id":"script1","type":"scriptTask","name":"脚本任务","position":{"x":700,"y":100},"config":{"type":"scriptTask","scriptFormat":"groovy","script":"println \\"Hello\\""},"properties":{},"size":{"width":100,"height":60}},{"id":"end1","type":"end","name":"结束","position":{"x":900,"y":100},"config":{"type":"end"},"properties":{},"size":{"width":40,"height":40}}],"edges":[{"id":"flow1","source":"start1","target":"service1","name":"","config":{"type":"sequenceFlow"},"properties":{}},{"id":"flow2","source":"service1","target":"user1","name":"条件分支","config":{"type":"sequenceFlow","conditionExpression":"$${condition}"},"properties":{}},{"id":"flow3","source":"user1","target":"script1","name":"","config":{"type":"sequenceFlow"},"properties":{}},{"id":"flow4","source":"script1","target":"end1","name":"","config":{"type":"sequenceFlow"},"properties":{}}],"properties":{}}', '{"formItems":[{"type":"input","label":"业务参数","name":"businessParam","required":true},{"type":"select","label":"审批结果","name":"approvalResult","options":[{"label":"同意","value":"approve"},{"label":"拒绝","value":"reject"}]}]}', '["complex","business","multi-task"]', 'PUBLISHED', TRUE, 'http://www.flowable.org/test',