引言
在上一篇我们手动构建了一个流程对象并简单打印执行,其构建流程对象的方式并不是很友好。为了更方便的构建流程对象,我们采用全新的方式,即解析基础篇提到的流程定义文件,并将其转成流程模型。 以下是要解析的样例文件:
src/test/resources/leave.json
{ "name": "leave", "displayName": "请假", "instanceUrl": "leaveForm", "nodes": [ { "id": "start", "type": "snaker:start", "x": 340, "y": 160, "properties": { "width": "120", "height": "80" }, "text": { "x": 340, "y": 200, "value": "开始" } }, { "id": "apply", "type": "snaker:task", "x": 520, "y": 160, "properties": { "assignee": "approve.operator", "taskType": "Major", "performType": "ANY", "autoExecute": "N", "width": "120", "height": "80", "field": { "userKey": "1" } }, "text": { "x": 520, "y": 160, "value": "请假申请" } }, { "id": "approveDept", "type": "snaker:task", "x": 740, "y": 160, "properties": { "assignmentHandler": "com.mldong.config.FlowAssignmentHandler", "taskType": "Major", "performType": "ANY", "autoExecute": "N", "width": "120", "height": "80" }, "text": { "x": 740, "y": 160, "value": "部门领导审批" } }, { "id": "end", "type": "snaker:end", "x": 980, "y": 160, "properties": { "width": "120", "height": "80" }, "text": { "x": 980, "y": 200, "value": "结束" } } ], "edges": [ { "id": "t1", "type": "snaker:transition", "sourceNodeId": "start", "targetNodeId": "apply", "startPoint": { "x": 358, "y": 160 }, "endPoint": { "x": 460, "y": 160 }, "properties": { "height": 80, "width": 120 }, "pointsList": [ { "x": 358, "y": 160 }, { "x": 460, "y": 160 } ] }, { "id": "t2", "type": "snaker:transition", "sourceNodeId": "apply", "targetNodeId": "approveDept", "startPoint": { "x": 580, "y": 160 }, "endPoint": { "x": 680, "y": 160 }, "properties": { "height": 80, "width": 120 }, "pointsList": [ { "x": 580, "y": 160 }, { "x": 680, "y": 160 } ] }, { "id": "t3", "type": "snaker:transition", "sourceNodeId": "approveDept", "targetNodeId": "end", "startPoint": { "x": 800, "y": 160 }, "endPoint": { "x": 962, "y": 160 }, "properties": { "height": 80, "width": 120 }, "pointsList": [ { "x": 800, "y": 160 }, { "x": 830, "y": 160 }, { "x": 830, "y": 160 }, { "x": 932, "y": 160 }, { "x": 932, "y": 160 }, { "x": 962, "y": 160 } ] } ] }
类图
流程图
代码实现
model/logicflow/LfPoint.java
package com.mldong.flow.engine.model.logicflow; import lombok.Data; import java.io.Serializable; /** * * logicFlow坐标 * @author mldong * @date 2023/4/26 */ @Data public class LfPoint implements Serializable { private int x; // x轴坐标 private int y; // y轴坐标 }
LogicFlow模型对象
model/logicflow/LfNode.java
package com.mldong.flow.engine.model.logicflow; import cn.hutool.core.lang.Dict; import lombok.Data; import java.io.Serializable; /** * * logicFlow节点 * @author mldong * @date 2023/4/26 */ @Data public class LfNode implements Serializable { private String id; // 节点唯一id private String type; // 节点类型 private int x; // 节点中心点x轴坐标 private int y; // 节点中心点y轴坐标 Dict properties; // 节点属性 Dict text; // 节点文本 }
model/logicflow/LfEdge.java
package com.mldong.flow.engine.model.logicflow; import cn.hutool.core.lang.Dict; import lombok.Data; import java.io.Serializable; import java.util.List; /** * * LogicFlow边 * @author mldong * @date 2022/6/12 */ @Data public class LfEdge implements Serializable { private String id; // 边唯一id private String type; // 边类型 private String sourceNodeId; // 源节点id private String targetNodeId; // 目标节点id private Dict properties; // 边属性 private Dict text; // 边文本 private LfPoint startPoint; // 边开始点坐标 private LfPoint endPoint; // 边结束点坐标 private List<LfPoint> pointsList; // 边所有点集合 }
model/logicflow/LfModel.java
package com.mldong.flow.engine.model.logicflow; import com.mldong.flow.engine.model.BaseModel; import lombok.Data; import java.util.List; /** * * logicFlow模型 * @author mldong * @date 2023/4/26 */ @Data public class LfModel extends BaseModel { private String type; // 流程定义分类 private String expireTime;// 过期时间(常量或变量) private String instanceUrl; // 启动实例的url,前后端分离后,定义为路由名或或路由地址 private String instanceNoClass; // 启动流程时,流程实例的流水号生成类 private List nodes; // 节点集合 private List edges; // 边集合 }
解析类
parser/NodeParser.java
package com.mldong.flow.engine.parser; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.logicflow.LfEdge; import com.mldong.flow.engine.model.logicflow.LfNode; import java.util.List; /** * * 节点解析接口 * @author mldong * @date 2023/4/26 */ public interface NodeParser { String NODE_NAME_PREFIX="snaker:"; // 节点名称前辍 String TEXT_VALUE_KEY = "value"; // 文本值 String WIDTH_KEY = "width"; // 节点宽度 String HEIGHT_KEY = "height"; // 节点高度 String PRE_INTERCEPTORS_KEY = "preInterceptors"; // 前置拦截器 String POST_INTERCEPTORS_KEY = "postInterceptors"; // 后置拦截器 String EXPR_KEY = "expr"; // 表达式key String HANDLE_CLASS_KEY = "handleClass"; // 表达式处理类 String FORM_KEY = "form"; // 表单标识 String ASSIGNEE_KEY = "assignee"; // 参与人 String ASSIGNMENT_HANDLE_KEY = "assignmentHandler"; // 参与人处理类 String TASK_TYPE_KEY = "taskType"; // 任务类型(主办/协办) String PERFORM_TYPE_KEY = "performType"; // 参与类型(普通参与/会签参与) String REMINDER_TIME_KEY = "reminderTime"; // 提醒时间 String REMINDER_REPEAT_KEY = "reminderRepeat"; // 重复提醒间隔 String EXPIRE_TIME_KEY = "expireTime"; // 期待任务完成时间变量key String AUTH_EXECUTE_KEY = "autoExecute"; // 到期是否自动执行Y/N String CALLBACK_KEY = "callback"; // 自动执行回调类 String EXT_FIELD_KEY = "field"; // 自定义扩展属性 /** * 节点属性解析方法,由解析类完成解析 * @param lfNode LogicFlow节点对象 * @param edges 所有边对象 */ void parse(LfNode lfNode, List edges); /** * 解析完成后,提供返回NodeModel对象 * @return 节点模型 */ NodeModel getModel(); }
parser/AbstractNodeParser.java
package com.mldong.flow.engine.parser; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.TransitionModel; import com.mldong.flow.engine.model.logicflow.LfEdge; import com.mldong.flow.engine.model.logicflow.LfNode; import java.util.List; import java.util.stream.Collectors; /** * * 通用属性解析(基本属性和边) * @author mldong * @date 2023/4/26 */ public abstract class AbstractNodeParser implements NodeParser { // 节点模型对象 protected NodeModel nodeModel; @Override public void parse(LfNode lfNode, List<LfEdge> edges) { nodeModel = newModel(); // 解析基本信息 nodeModel.setName(lfNode.getId()); if(ObjectUtil.isNotNull(lfNode.getText())) { nodeModel.setDisplayName(lfNode.getText().getStr(TEXT_VALUE_KEY)); } Dict properties = lfNode.getProperties(); // 解析布局属性 int x = lfNode.getX(); int y = lfNode.getY(); int w = Convert.toInt(properties.get(WIDTH_KEY),0); int h = Convert.toInt(properties.get(HEIGHT_KEY),0); nodeModel.setLayout(StrUtil.format("{},{},{},{}",x,y,w,h)); // 解析拦截器 nodeModel.setPreInterceptors(properties.getStr(PRE_INTERCEPTORS_KEY)); nodeModel.setPostInterceptors(properties.getStr(POST_INTERCEPTORS_KEY)); // 解析输出边 List<LfEdge> nodeEdges = getEdgeBySourceNodeId(lfNode.getId(), edges); nodeEdges.forEach(edge->{ TransitionModel transitionModel = new TransitionModel(); transitionModel.setName(edge.getId()); transitionModel.setTo(edge.getTargetNodeId()); transitionModel.setSource(nodeModel); transitionModel.setExpr(edge.getProperties().getStr(EXPR_KEY)); if(CollectionUtil.isNotEmpty(edge.getPointsList())) { // x1,y1;x2,y2;x3,y3…… transitionModel.setG(edge.getPointsList().stream().map(point->{ return point.getX()+","+point.getY(); }).collect(Collectors.joining(";"))); } else { if(ObjectUtil.isNotNull(edge.getStartPoint()) && ObjectUtil.isNotNull(edge.getEndPoint())) { int startPointX = edge.getStartPoint().getX(); int startPointY = edge.getStartPoint().getY(); int endPointX = edge.getEndPoint().getX(); int endPointY = edge.getEndPoint().getY(); transitionModel.setG(StrUtil.format("{},{};{},{}", startPointX, startPointY, endPointX, endPointY)); } } nodeModel.getOutputs().add(transitionModel); }); // 调用子类特定解析方法 parseNode(lfNode); } /** * 子类实现此类完成特定解析 * @param lfNode */ public abstract void parseNode(LfNode lfNode); /** * 由子类各自创建节点模型对象 * @return */ public abstract NodeModel newModel(); @Override public NodeModel getModel() { return nodeModel; } /** * 获取节点输入 * @param targetNodeId 目标节点id * @param edges * @return */ private List<LfEdge> getEdgeByTargetNodeId(String targetNodeId,List<LfEdge> edges) { return edges.stream().filter(edge->{ return edge.getTargetNodeId().equals(targetNodeId); }).collect(Collectors.toList()); } /** * 获取节点输出 * @param sourceNodeId 源节点id * @param edges * @return */ private List<LfEdge> getEdgeBySourceNodeId(String sourceNodeId,List<LfEdge> edges) { return edges.stream().filter(edge->{ return edge.getSourceNodeId().equals(sourceNodeId); }).collect(Collectors.toList()); } }
parser/impl/StartParser.java
package com.mldong.flow.engine.parser.impl; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.StartModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 开始节点解析类 * @author mldong * @date 2023/4/26 */ public class StartParser extends AbstractNodeParser { @Override public void parseNode(LfNode lfNode) { } @Override public NodeModel newModel() { return new StartModel(); } }
parser/impl/EndParser.java
package com.mldong.flow.engine.parser.impl; import com.mldong.flow.engine.model.EndModel; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 结束节点解析类 * @author mldong * @date 2023/4/26 */ public class EndParser extends AbstractNodeParser { @Override public void parseNode(LfNode lfNode) { } @Override public NodeModel newModel() { return new EndModel(); } }
parser/impl/TaskParser.java
package com.mldong.flow.engine.parser.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Dict; import com.mldong.flow.engine.enums.TaskPerformTypeEnum; import com.mldong.flow.engine.enums.TaskTypeEnum; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.TaskModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 任务节点解析类 * @author mldong * @date 2023/4/26 */ public class TaskParser extends AbstractNodeParser { /** * 解析task节点特有属性 * @param lfNode */ @Override public void parseNode(LfNode lfNode) { TaskModel taskModel = (TaskModel)nodeModel; Dict properties = lfNode.getProperties(); taskModel.setForm(properties.getStr(FORM_KEY)); taskModel.setAssignee(properties.getStr(ASSIGNEE_KEY)); taskModel.setAssignmentHandler(properties.getStr(ASSIGNMENT_HANDLE_KEY)); taskModel.setTaskType(TaskTypeEnum.codeOf(properties.getInt(TASK_TYPE_KEY))); taskModel.setPerformType(TaskPerformTypeEnum.codeOf(properties.getInt(PERFORM_TYPE_KEY))); taskModel.setReminderTime(properties.getStr(REMINDER_TIME_KEY)); taskModel.setReminderRepeat(properties.getStr(REMINDER_REPEAT_KEY)); taskModel.setExpireTime(properties.getStr(EXPIRE_TIME_KEY)); taskModel.setAutoExecute(properties.getStr(AUTH_EXECUTE_KEY)); taskModel.setCallback(properties.getStr(CALLBACK_KEY)); // 自定义扩展属性 Object field = properties.get(EXT_FIELD_KEY); if(field!=null) { taskModel.setExt(Convert.convert(Dict.class, field)); } } @Override public NodeModel newModel() { return new TaskModel(); } }
parser/impl/ForkParser.java
package com.mldong.flow.engine.parser.impl; import com.mldong.flow.engine.model.ForkModel; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 分支节点解析类 * @author mldong * @date 2023/4/26 */ public class ForkParser extends AbstractNodeParser { @Override public void parseNode(LfNode lfNode) { } @Override public NodeModel newModel() { return new ForkModel(); } }
parser/impl/JoinParser.java
package com.mldong.flow.engine.parser.impl; import com.mldong.flow.engine.model.JoinModel; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 合并节点解析器 * @author mldong * @date 2023/4/26 */ public class JoinParser extends AbstractNodeParser { @Override public void parseNode(LfNode lfNode) { } @Override public NodeModel newModel() { return new JoinModel(); } }
parser/impl/DecisionParser.java
package com.mldong.flow.engine.parser.impl; import cn.hutool.core.lang.Dict; import com.mldong.flow.engine.model.DecisionModel; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.logicflow.LfNode; import com.mldong.flow.engine.parser.AbstractNodeParser; /** * * 决策节点解析类 * @author mldong * @date 2023/4/26 */ public class DecisionParser extends AbstractNodeParser { /** * 解析decision节点特有属性 * @param lfNode */ @Override public void parseNode(LfNode lfNode) { DecisionModel decisionModel = (DecisionModel) nodeModel; Dict properties = lfNode.getProperties(); decisionModel.setExpr(properties.getStr(EXPR_KEY)); decisionModel.setHandleClass(properties.getStr(HANDLE_CLASS_KEY)); } @Override public NodeModel newModel() { return new DecisionModel(); } }
服务上下文相关类
Context.java
package com.mldong.flow.engine; import java.util.List; /** * * 服务上下文接口,类似spring的ioc * @author mldong * @date 2023/4/26 */ public interface Context { /** * 根据服务名称、实例向服务工厂注册 * @param name 服务名称 * @param object 服务实例 */ void put(String name, Object object); /** * 根据服务名称、类型向服务工厂注册 * @param name 服务名称 * @param clazz 类型 */ void put(String name, Class<?> clazz); /** * 判断是否存在给定的服务名称 * @param name 服务名称 * @return */ boolean exist(String name); /** * 根据给定的类型查找服务实例 * @param clazz 类型 * @return */ <T> T find(Class<T> clazz); /** * 根据给定的类型查找所有此类型的服务实例 * @param clazz 类型 * @return */ <T> List<T> findList(Class<T> clazz); /** * 根据给定的服务名称、类型查找服务实例 * @param name 服务名称 * @param clazz 类型 * @return */ <T> T findByName(String name, Class<T> clazz); }
impl/SimpleContext.java
package com.mldong.flow.engine.impl; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import com.mldong.flow.engine.Context; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * * 简单的上下文发现实现类 * @author mldong * @date 2023/4/26 */ public class SimpleContext implements Context { private Dict dict = Dict.create(); @Override public void put(String name, Object object) { dict.put(name, object); } @Override public void put(String name, Class<?> clazz) { dict.put(name, ReflectUtil.newInstance(clazz)); } @Override public boolean exist(String name) { return ObjectUtil.isNotNull(dict.getObj(name)); } @Override public <T> T find(Class<T> clazz) { for (Map.Entry<String, Object> entry : dict.entrySet()) { if (clazz.isInstance(entry.getValue())) { return clazz.cast(entry.getValue()); } } return null; } @Override public <T> List<T> findList(Class<T> clazz) { List<T> res = new ArrayList<>(); for (Map.Entry<String, Object> entry : dict.entrySet()) { if (clazz.isInstance(entry.getValue())) { res.add(clazz.cast(entry.getValue())); } } return res; } @Override public <T> T findByName(String name, Class<T> clazz) { for (Map.Entry<String, Object> entry : dict.entrySet()) { if (entry.getKey().equals(name) && clazz.isInstance(entry.getValue())) { return clazz.cast(entry.getValue()); } } return null; } }
core/ServiceContext.java
package com.mldong.flow.engine.core; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ReflectUtil; import com.mldong.flow.engine.Context; import java.util.List; /** * * 单例服务上下文 * @author mldong * @date 2022/6/12 */ public class ServiceContext { private static Context context; public static void setContext(Context context) { ServiceContext.context = context; } public static void put(String name, Object object) { Assert.notNull(context,"未注册服务上下文"); context.put(name, object); } public static void put(String name, Class<?> clazz) { Assert.notNull(context,"未注册服务上下文"); context.put(name, ReflectUtil.newInstance(clazz)); } public static boolean exist(String name) { Assert.notNull(context,"未注册服务上下文"); return context.exist(name); } public static <T> T find(Class<T> clazz) { Assert.notNull(context,"未注册服务上下文"); return context.find(clazz); } public static <T> List<T> findList(Class<T> clazz) { Assert.notNull(context,"未注册服务上下文"); return context.findList(clazz); } public static <T> T findByName(String name, Class<T> clazz) { Assert.notNull(context,"未注册服务上下文"); return context.findByName(name, clazz); } }
解析入口类
parser/ModelParser.java
package com.mldong.flow.engine.parser; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.json.JSONUtil; import com.mldong.flow.engine.core.ServiceContext; import com.mldong.flow.engine.model.NodeModel; import com.mldong.flow.engine.model.ProcessModel; import com.mldong.flow.engine.model.TaskModel; import com.mldong.flow.engine.model.TransitionModel; import com.mldong.flow.engine.model.logicflow.LfEdge; import com.mldong.flow.engine.model.logicflow.LfModel; import com.mldong.flow.engine.model.logicflow.LfNode; import java.io.ByteArrayInputStream; import java.util.List; public class ModelParser { private ModelParser(){} /** * 将json定义文件解析成流程模型对象 * @param bytes * @return */ public static ProcessModel parse(byte [] bytes) { String json = IoUtil.readUtf8(new ByteArrayInputStream(bytes)); LfModel lfModel = JSONUtil.parse(json).toBean(LfModel.class); ProcessModel processModel = new ProcessModel(); List nodes = lfModel.getNodes(); List edges = lfModel.getEdges(); if(CollectionUtil.isEmpty(nodes) || CollectionUtil.isEmpty(edges) ) { return processModel; } // 流程定义基本信息 processModel.setName(lfModel.getName()); processModel.setDisplayName(lfModel.getDisplayName()); processModel.setType(lfModel.getType()); processModel.setInstanceUrl(lfModel.getInstanceUrl()); processModel.setInstanceNoClass(lfModel.getInstanceNoClass()); // 流程节点信息 nodes.forEach(node->{ String type = node.getType().replace(NodeParser.NODE_NAME_PREFIX,""); NodeParser nodeParser = ServiceContext.findByName(type,NodeParser.class); if(nodeParser!=null) { nodeParser.parse(node, edges); NodeModel nodeModel = nodeParser.getModel(); processModel.getNodes().add(nodeParser.getModel()); if (nodeModel instanceof TaskModel) { processModel.getTasks().add((TaskModel) nodeModel); } } }); // 循环节点模型,构造输入边、输出边的source、target for(NodeModel node : processModel.getNodes()) { for(TransitionModel transition : node.getOutputs()) { String to = transition.getTo(); for(NodeModel node2 : processModel.getNodes()) { if(to.equalsIgnoreCase(node2.getName())) { node2.getInputs().add(transition); transition.setTarget(node2); } } } } return processModel; } }
配置类
cfg/Configuration.java
package com.mldong.flow.engine.cfg; import com.mldong.flow.engine.Context; import com.mldong.flow.engine.core.ServiceContext; import com.mldong.flow.engine.impl.SimpleContext; import com.mldong.flow.engine.parser.impl.*; public class Configuration { public Configuration() { this(new SimpleContext()); } public Configuration(Context context) { ServiceContext.setContext(context); ServiceContext.put("decision", DecisionParser.class); ServiceContext.put("end", EndParser.class); ServiceContext.put("fork", ForkParser.class); ServiceContext.put("join", JoinParser.class); ServiceContext.put("start", StartParser.class); ServiceContext.put("task", TaskParser.class); } }
单元测试类
ModelParserTest.java
package com.mldong.flow; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Dict; import com.mldong.flow.engine.cfg.Configuration; import com.mldong.flow.engine.core.Execution; import com.mldong.flow.engine.model.ProcessModel; import com.mldong.flow.engine.parser.ModelParser; import org.junit.Test; /** * * 模型解析单元测试 * @author mldong * @date 2023/4/26 */ public class ModelParserTest { @Test public void parseTest() { new Configuration(); ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave.json"))); Execution execution = new Execution(); execution.setArgs(Dict.create()); processModel.getStart().execute(execution); } }
运行结果
model:StartModel,name:start,displayName:开始,time:2023-04-26 21:32:40
model:TaskModel,name:apply,displayName:请假申请,time:2023-04-26 21:32:41
model:TaskModel,name:approveDept,displayName:部门领导审批,time:2023-04-26 21:32:42
model:EndModel,name:end,displayName:结束,time:2023-04-26 21:32:42
相关源码 mldong-flow-demo-03
流程设计器 在线体验
以上就是java 工作流引擎设计实现解析流程定义文件的详细内容,更多关于java 工作流引擎的资料请关注IT俱乐部其它相关文章!