反序列化问题。

This commit is contained in:
dengqichen 2024-12-11 18:16:47 +08:00
parent b5861a1bfe
commit f69897a9d0
7 changed files with 177 additions and 14 deletions

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.graph; package com.qqchen.deploy.backend.workflow.dto.graph;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data; import lombok.Data;
/** /**
@ -8,6 +9,7 @@ import lombok.Data;
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段
public class EdgeConfig { public class EdgeConfig {
/** /**
* 条件 * 条件
@ -19,8 +21,24 @@ public class EdgeConfig {
*/ */
private String expression; private String expression;
/**
* 条件表达式
*/
private String conditionExpression;
/** /**
* 类型sequence/message/association * 类型sequence/message/association
*/ */
private String type; 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;
}
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.graph; package com.qqchen.deploy.backend.workflow.dto.graph;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -10,6 +11,7 @@ import java.util.Map;
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段
public class NodeConfig { public class NodeConfig {
/** /**
* 配置类型 * 配置类型
@ -70,4 +72,14 @@ public class NodeConfig {
* 是否排他 * 是否排他
*/ */
private Boolean exclusive; private Boolean exclusive;
/**
* 脚本格式
*/
private String scriptFormat;
/**
* 脚本内容
*/
private String script;
} }

View File

@ -2,9 +2,11 @@ package com.qqchen.deploy.backend.workflow.entity;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete; 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.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.qqchen.deploy.backend.framework.domain.Entity;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
@ -50,9 +52,9 @@ public class WorkflowDefinition extends Entity<Long> {
/** /**
* 流程图数据包含节点和连线的位置样式等信息 * 流程图数据包含节点和连线的位置样式等信息
*/ */
@Type(JsonType.class) @Type(WorkflowGraphType.class)
@Column(name = "graph_data", columnDefinition = "json") @Column(name = "graph", columnDefinition = "json")
private JsonNode graphData; private WorkflowGraph graph;
/** /**
* 表单配置 * 表单配置

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.workflow.enums; package com.qqchen.deploy.backend.workflow.enums;
import com.fasterxml.jackson.annotation.JsonValue;
/** /**
* 节点类型枚举 * 节点类型枚举
* @author cascade * @author cascade
@ -16,6 +18,7 @@ public enum NodeType {
SUBPROCESS("subProcess"), SUBPROCESS("subProcess"),
CALL_ACTIVITY("callActivity"); CALL_ACTIVITY("callActivity");
@JsonValue
private final String value; private final String value;
NodeType(String value) { NodeType(String value) {
@ -25,4 +28,13 @@ public enum NodeType {
public String getValue() { public String getValue() {
return value; 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);
}
} }

View File

@ -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<WorkflowGraph> {
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<WorkflowGraph> 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);
}
}

View File

@ -397,7 +397,7 @@ CREATE TABLE workflow_definition
-- 流程配置 -- 流程配置
bpmn_xml TEXT COMMENT 'BPMN XML内容', bpmn_xml TEXT COMMENT 'BPMN XML内容',
graph_data JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息', graph JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息',
form_config JSON COMMENT '表单配置', form_config JSON COMMENT '表单配置',
tags JSON COMMENT '流程标签', tags JSON COMMENT '流程标签',

View File

@ -159,7 +159,7 @@ INSERT INTO sys_external_system (
-- 工作流定义测试数据 -- 工作流定义测试数据
INSERT INTO workflow_definition ( INSERT INTO workflow_definition (
name, `key`, flow_version, description, category, name, `key`, flow_version, description, category,
bpmn_xml, graph_data, form_config, tags, bpmn_xml, graph, form_config, tags,
status, is_executable, target_namespace, status, is_executable, target_namespace,
created_at, updated_at, created_by, updated_by, is_deleted created_at, updated_at, created_by, updated_by, is_deleted
) VALUES ) VALUES
@ -167,7 +167,7 @@ INSERT INTO workflow_definition (
( (
'简单服务任务流程', 'simple_service_flow', 1, '一个包含服务任务的简单流程', 'test', '简单服务任务流程', 'simple_service_flow', 1, '一个包含服务任务的简单流程', 'test',
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="simple_service_flow" name="简单服务任务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="script"><flowable:string><![CDATA[echo "Hello World"]]></flowable:string></flowable:field></extensionElements></serviceTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="end1"></sequenceFlow></process></definitions>', '<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="simple_service_flow" name="简单服务任务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="script"><flowable:string><![CDATA[echo "Hello World"]]></flowable:string></flowable:field></extensionElements></serviceTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="end1"></sequenceFlow></process></definitions>',
'{"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":[]}', '{"formItems":[]}',
'["simple","test","service"]', '["simple","test","service"]',
'PUBLISHED', TRUE, 'http://www.flowable.org/test', 'PUBLISHED', TRUE, 'http://www.flowable.org/test',
@ -177,10 +177,8 @@ INSERT INTO workflow_definition (
-- 用户任务流程:开始 -> 用户任务 -> 结束 -- 用户任务流程:开始 -> 用户任务 -> 结束
( (
'用户审批流程', 'user_approval_flow', 1, '一个简单的用户审批流程', 'approval', '用户审批流程', 'user_approval_flow', 1, '一个简单的用户审批流程', 'approval',
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="user_approval_flow" name="用户审批流程" '<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="user_approval_flow" name="用户审批流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><userTask id="user1" name="审批任务" flowable:assignee="$${initiator}" flowable:candidateUsers="user1,user2" flowable:candidateGroups="group1,group2" flowable:dueDate="$${dueDate}" flowable:formKey="form1" flowable:priority="1"></userTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" name="提交审批" sourceRef="start1" targetRef="user1"></sequenceFlow><sequenceFlow id="flow2" name="审批完成" sourceRef="user1" targetRef="end1"></sequenceFlow></process></definitions>',
isExecutable="true"><startEvent id="start1" name="开始"></startEvent><userTask id="user1" name="审批任务" flowable:assignee="$${initiator}" flowable:candidateUsers="user1,user2" flowable:candidateGroups="group1,group2" flowable:dueDate="$${dueDate}" flowable:formKey="form1" '{"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":{}}',
flowable:priority="1"></userTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" name="提交审批" sourceRef="start1" targetRef="user1"></sequenceFlow><sequenceFlow id="flow2" name="审批完成" sourceRef="user1" targetRef="end1"></sequenceFlow></process></definitions>',
'{"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":"审批完成"}]}',
'{"formItems":[{"type":"input","label":"意见","name":"comment","required":true}]}', '{"formItems":[{"type":"input","label":"意见","name":"comment","required":true}]}',
'["approval","user-task"]', '["approval","user-task"]',
'PUBLISHED', TRUE, 'http://www.flowable.org/test', 'PUBLISHED', TRUE, 'http://www.flowable.org/test',
@ -190,9 +188,8 @@ flowable:priority="1"></userTask><endEvent id="end1" name="结束"></endEvent><s
-- 复杂流程:开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束 -- 复杂流程:开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
( (
'复杂业务流程', 'complex_business_flow', 1, '包含多种任务节点的复杂业务流程', 'business', '复杂业务流程', 'complex_business_flow', 1, '包含多种任务节点的复杂业务流程', 'business',
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="complex_business_flow" '<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="complex_business_flow" name="复杂业务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="implementation"><flowable:string><![CDATA[com.example.ServiceTask]]></flowable:string></flowable:field></extensionElements></serviceTask><userTask id="user1" name="用户任务" flowable:assignee="user1"></userTask><scriptTask id="script1" name="脚本任务" scriptFormat="groovy"><script><![CDATA[println "Hello"]]></script></scriptTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="user1"><conditionExpression xsi:type="tFormalExpression">$${condition}</conditionExpression></sequenceFlow><sequenceFlow id="flow3" sourceRef="user1" targetRef="script1"></sequenceFlow><sequenceFlow id="flow4" sourceRef="script1" targetRef="end1"></sequenceFlow></process></definitions>',
name="复杂业务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="implementation"><flowable:string><![CDATA[com.example.ServiceTask]]></flowable:string></flowable:field></extensionElements></serviceTask><userTask id="user1" name="用户任务" flowable:assignee="user1"></userTask><scriptTask id="script1" name="脚本任务" scriptFormat="groovy"><script><![CDATA[println "Hello"]]></script></scriptTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="user1"><conditionExpression xsi:type="tFormalExpression">$${condition}</conditionExpression></sequenceFlow><sequenceFlow id="flow3" sourceRef="user1" targetRef="script1"></sequenceFlow><sequenceFlow id="flow4" sourceRef="script1" targetRef="end1"></sequenceFlow></process></definitions>', '{"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":{}}',
'{"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"}]}',
'{"formItems":[{"type":"input","label":"业务参数","name":"businessParam","required":true},{"type":"select","label":"审批结果","name":"approvalResult","options":[{"label":"同意","value":"approve"},{"label":"拒绝","value":"reject"}]}]}', '{"formItems":[{"type":"input","label":"业务参数","name":"businessParam","required":true},{"type":"select","label":"审批结果","name":"approvalResult","options":[{"label":"同意","value":"approve"},{"label":"拒绝","value":"reject"}]}]}',
'["complex","business","multi-task"]', '["complex","business","multi-task"]',
'PUBLISHED', TRUE, 'http://www.flowable.org/test', 'PUBLISHED', TRUE, 'http://www.flowable.org/test',