热点新闻
仿钉钉流程轻松实现JSON转BPMN
2023-07-07 01:54  浏览:4151  搜索引擎搜索“手机速企网”
温馨提示:信息一旦丢失不一定找得到,请务必收藏信息以备急用!本站所有信息均是注册会员发布如遇到侵权请联系文章中的联系方式或客服删除!
联系我时,请说明是在手机速企网看到的信息,谢谢。
展会发布 展会网站大全 报名观展合作 软文发布

前言

写过工作流都会遇到这样的难题,希望流程的设计可以类似钉钉一样简单明了,而不是超级不有好的bpmn设计器,上网大概搜了一下实现方案,前端仿钉钉设计器一大堆,例如wflow,smart-flow-design,参照这些源码前端设计器不成问题

问题在于这样的设计器数据是json格式,不符合bpmn协议,就无法和activiti,flowable等工作流直接对接

如果自己开发工作流引擎,但开发成本肯定比较大,所以还是希望能实现自定义的json和xml可以转换

方案

转换这个活可以前端干,也可以后端干,如果前端干可以使用bpmn-moddle,bpmn.js就是使用它生成的xml,但大概看了一下发现文档稀缺,使用很起来很难

最终决定使用java转换,因为发现activiti包中的BpmnModel可以很轻松画出xml,而且基本不用看文档,.方法名基本就能和bpmn协议对上号

json协议

前后端使用json来表达流程设计,那一定要订一套自己的协议,大概按照smart-flow-design写一个简版的





smart-flow-design

{ "id": "节点id", "name": "节点名称", "type": "申请节点/审核节点/分支节点/抄送节点", "next": "下一节点", "exclusive": [// 排他条件 { // 条件 "condition": "条件表达式", //分支节点内部流程 "process": {} } ], // 委派人 "assignee": { "users": [], "multiMode": "会签/顺序审批" }, // 表单权限 "formPerms": [ { "key": "字段key", "perm": "权限类型 编辑/只读/隐藏", "required": true } ] }

节点类型简单实现几个:申请节点/审核节点/分支节点/抄送节点

通过next指向下一节点,实现一个链表结构

如一个简单的流程设计如下





简单工作流

对应的json数据如下

{ "id": "1", "name": "申请节点", "type": "ROOT", "next": { "id": "2", "name": "审批节点", "type": "APPROVAL", "next": { "id": "3", "name": "抄送节点", "type": "CC" } } }

带分支的设计如下





分支工作流


对应的json:

{ "id": "1", "name": "申请节点", "type": "ROOT", "next": { "id": "2", "name": "条件节点", "type": "EXCLUSIVE", "exclusive": [ { "condition": "amount>=100" }, { "condition": "amount<100", "process": { "id": "4", "name": "审批人1", "type": "APPROVAL", "next": null } } ], "next": { "id": "3", "name": "审批人2", "type": "APPROVAL" } } }

基本上这个json数据结构就足够标识很多场景了,分支条件可以自己再写复杂一点,如果需要扩展新增属性即可

java

java 创建一些实体来接受json,很简单就不详细写了,大概如下

@Data public class ProcessNode { @ApiModelProperty(value = "节点ID") private String id; @ApiModelProperty(value = "节点名称") private String name; @ApiModelProperty(value = "节点类型") private String type; @ApiModelProperty(value = "下一节点") private ProcessNode next; @ApiModelProperty(value = "分支") private List<ExclusiveBranch> exclusive; @ApiModelProperty(value = "委托人") private Assignee assignee; @ApiModelProperty(value = "表单权限") private List<FormPerm> formPerms; } @Data public class ExclusiveBranch { @ApiModelProperty(value = "id") private String id; @ApiModelProperty(value = "分支条件") private String condition; @ApiModelProperty(value = "分支内部流程") private ProcessNode process; } @Data public class Assignee { @ApiModelProperty(value = "委托人列表") private List<String> users; @ApiModelProperty(value = "多人审批方式") private String multiMode; }

在controller使用@RequestBody接受一下前端传来的json即可

转BPMN

接下来就把这个java实体转成xml,引入今天的主角:BpmnModel

引入依赖

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-model</artifactId> <version>7.1.0.M1</version> </dependency>

即可开始使用BpmnModel开始绘制bpmn协议的工作流

初始化

首先准备工作流

BpmnModel model = new BpmnModel(); Process process = new Process(); model.addProcess(process); process.setId("Process_"+UUID.randomUUID()); process.setExecutable(true);

其中process就相当于我们的图纸,后续工作就是往这个图纸上画节点和线

绘制开始结束

由于json协议中不包含开始结束节点,所以首先要绘制出两个节点

开始节点

// 新建开始节点 StartEvent startEvent = new StartEvent(); startEvent.setId("_start"); // 绘制到图纸 process.addFlowElement(startEvent)

结束节点

// 新建结束节点 EndEvent endEvent = new EndEvent(); endEvent.setId("_end"); // 绘制到图纸 process.addFlowElement(endEvent)

到此两个节点就画出来了,但是还没有任何线

绘制bpmn

接下来就根据json的节点来绘制bpmn节点,同时还要考虑线的绘制节点的连接线

json协议中是next指向下一节点,所以绘制节点的方法一定是要使用递归的画法,为了处理画线问题,可以在绘制方法中添加两个参数preId(上一节点ID)和endId(结束节点ID)

这样逻辑为如下:

  • 绘制bpmn节点
  • 绘制上一节点与当前节点的连线
  • 如果有next,递归绘制下一节点
  • 如果没有next,绘制当前节点与结束节点的连接线

考虑到上一根线可能有条件,所以再加入参数preexpression(上一根线的条件),最终方法如下

public void drawNode(Process process, ProcessNode node, String preId, String endId, String preexpression) { // 根据type绘制不同种类的节点 Inout inout = drawNodeByType(process, node); // 绘制前一根线 process.addFlowElement(createSequenceFlow(preId, inout.getIn(), preexpression)); if (node.getNext() == null) { // 没有下一步,绘制指向结束的线 process.addFlowElement(createSequenceFlow(inout.getOut(), endId, null)); } else { // 有下一步,递归绘制下一个节点 drawNode(process, node.getNext(), inout.getOut(), endId, null); } }

其中drawNodeByType(process, node)方法根据不同的种类画不通过的节点,反回是一个Inout

@Data @AllArgsConstructor public class Inout { private String in; private String out; }

代表进入节点的id和出节点的id,这是因为json的节点和bpmn的节点不是一一对应的,普通的审核节点,in和out都是审核节点id,而如果是分支节点,in代表分支的开始网关id,out代表分支结束网关的id,接下来分别以两种节点类型举例来实现

private Inout drawNodeByType(Process process, ProcessNode node) { if (node.getType().equals("审核节点")) { return drawAuditNode(process, node); } else if (node.getType().equals("分支节点")) { return drawExclusiveNode(process, node); } else { throw new IllegalArgumentException(); } }

审核节点

private Inout drawAuditNode(Process process, ProcessNode node) { // 绘制节点 String id = "Node_"+UUID.randomUUID(); UserTask userTask = new UserTask(); userTask.setId(id); userTask.setName(node.getName()); // 设置多实例 userTask.setAssignee("${user}"); MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics(); if (node.getAssignee().getMultiMode().equals("顺序审批")) { multiInstanceLoopCharacteristics.setSequential(true); } multiInstanceLoopCharacteristics.setElementVariable("user"); // 完成条件 multiInstanceLoopCharacteristics.setCompletionCondition("${nrOfInstances == nrOfCompletedInstances}"); multiInstanceLoopCharacteristics.setInputDataItem("${users}"); userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics); // 保存json节点配置到扩展属性 Map<String, Object> extensions = new HashMap<>(); extensions.put("node", node); BpmnUtil.addExtensionProperty(userTask, extensions); // 只有一个节点,in&out相同 return new Inout(id, id); }

分支节点

private Inout drawExclusiveNode(Process process, ProcessNode node) { // 开始网关 String startId = "Exclusive_"+UUID.randomUUID(); ExclusiveGateway startGateway = new ExclusiveGateway(); startGateway.setId(startId); process.addFlowElement(startGateway); // 结束网关 String endId = "Exclusive_"+UUID.randomUUID(); ExclusiveGateway endGateway = new ExclusiveGateway(); endGateway.setId(endId); process.addFlowElement(endGateway); // 绘制分支 List<ExclusiveBranch> branches = node.getExclusive(); for (ExclusiveBranch branch : branches) { String expression = branch.getCondition(); if (branch.getProcess()==null) { // 没有子流程,直接绘制结束线 process.addFlowElement(createSequenceFlow(startId, endId, expression)); } else { // 有子流程,递归绘制子流程 drawNode(process, branch.getProcess(), startId, endId, expression); } } // int和out不一样 return new Inout(startId, endId); }

注意:绘制分支时如果有子流程,又回调用了drawNode,这是preId为开始网关id,endId是结束网关id,并且携带了表达式

其他类型的节点都类似,很简单,不写了

bpmn绘制完了,如果使用activiti就可以直接部署BpmnModel对象了

Deployment deployment = repositoryService .createDeployment() .addBpmnModel("test", bpmnModel) .deploy();

自动布局

如果要转换xml,上面的bpmnModel只有节点和线,并没有布局,可以使用第三方轻松布局

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-layout</artifactId> <version>7.1.0.M1</version> <scope>compile</scope> </dependency>

代码一行就够了

// 四.自动布局 new BpmnAutoLayout(bpmnModel).execute();

转xml

如果想把BpmnModel转换为xml,也很简单,引入依赖

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-converter</artifactId> <version>7.1.0.M1</version> </dependency>

转换代码

// 五.转xml BpmnXMLConverter bpmnXMLConverter=new BpmnXMLConverter(); byte[] convertToXML = bpmnXMLConverter.convertToXML(bpmnModel); String xml=new String(convertToXML); xml = xml.replaceAll("&lt;","<").replaceAll("&gt;",">");

最终

贴一下完整实例代码(代码只是简版,只为提供思路

@SuppressWarnings("ALL") public class BpmnConvert { public String toBpmn(ProcessNode node) { // 一.准备工作 BpmnModel bpmnModel = new BpmnModel(); Process process = new Process(); // 相当于图纸 bpmnModel.addProcess(process); process.setId("Process_"+UUID.randomUUID()); process.setExecutable(true); // 二.开始结束节点 StartEvent startEvent = new StartEvent();// 新建开始节点 startEvent.setId("_start"); process.addFlowElement(startEvent);// 绘制到图纸 EndEvent endEvent = new EndEvent(); // 新建结束节点 endEvent.setId("_end");// 绘制到图纸 process.addFlowElement(endEvent); // 三.递归绘制节点 drawNode(process, node, "_start", "_end", null); // 四.自动布局 new BpmnAutoLayout(bpmnModel).execute(); // 五.转xml BpmnXMLConverter bpmnXMLConverter=new BpmnXMLConverter(); byte[] convertToXML = bpmnXMLConverter.convertToXML(bpmnModel); String xml=new String(convertToXML); xml = xml.replaceAll("&lt;","<").replaceAll("&gt;",">"); return xml; } public void drawNode(Process process, ProcessNode node, String preId, String endId, String preexpression) { // 根据type绘制不同种类的节点 Inout inout = drawNodeByType(process, node); // 绘制前一根线 process.addFlowElement(createSequenceFlow(preId, inout.getIn(), preexpression)); if (node.getNext() == null) { // 没有下一步, 绘制指向结束的线 process.addFlowElement(createSequenceFlow(inout.getOut(), endId, null)); } else { // 有下一步, 递归绘制下一个节点 drawNode(process, node.getNext(), inout.getOut(), endId, null); } } private Inout drawNodeByType(Process process, ProcessNode node) { if (node.getType().equals("审核节点")) { return drawAuditNode(process, node); } else if (node.getType().equals("分支节点")) { return drawExclusiveNode(process, node); } else { throw new IllegalArgumentException(); } } private Inout drawAuditNode(Process process, ProcessNode node) { // 绘制节点 String id = "Node_"+UUID.randomUUID(); UserTask userTask = new UserTask(); userTask.setId(id); userTask.setName(node.getName()); // 设置多实例 userTask.setAssignee("${user}"); MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics(); if (node.getAssignee().getMultiMode().equals("顺序审批")) { multiInstanceLoopCharacteristics.setSequential(true); } multiInstanceLoopCharacteristics.setElementVariable("user"); // 完成条件 multiInstanceLoopCharacteristics.setCompletionCondition("${nrOfInstances == nrOfCompletedInstances}"); multiInstanceLoopCharacteristics.setInputDataItem("${users}"); userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics); // 保存json节点配置到扩展属性 Map<String, Object> extensions = new HashMap<>(); extensions.put("node", node); BpmnUtil.addExtensionProperty(userTask, extensions); return new Inout(id, id); } private Inout drawExclusiveNode(Process process, ProcessNode node) { // 开始网关 String startId = "Exclusive_"+UUID.randomUUID(); ExclusiveGateway startGateway = new ExclusiveGateway(); startGateway.setId(startId); process.addFlowElement(startGateway); // 结束网关 String endId = "Exclusive_"+UUID.randomUUID(); ExclusiveGateway endGateway = new ExclusiveGateway(); endGateway.setId(endId); process.addFlowElement(endGateway); // 绘制分支 List<ExclusiveBranch> branches = node.getExclusive(); for (ExclusiveBranch branch : branches) { String expression = branch.getCondition(); if (branch.getProcess()==null) { // 没有子流程,直接绘制结束线 process.addFlowElement(createSequenceFlow(startId, endId, expression)); } else { // 有子流程,递归绘制子流程 drawNode(process, branch.getProcess(), startId, endId, expression); } } // int和out不一样 return new Inout(startId, endId); } public SequenceFlow createSequenceFlow(String from, String to, String conditionexpression) { SequenceFlow flow = new SequenceFlow(); flow.setId(from + "-" + to); flow.setSourceRef(from); flow.setTargetRef(to); if (conditionexpression != null) { flow.setConditionexpression(conditionexpression); } return flow; } }

核心代码真的没几行,细节自己完善即可

我自己做了个相对复杂的json,转换为xml最终在bpmn.js展示效果如下





bpmn.js

功能都没大问题,就是自动布局的线有点扭曲

发布人:ed8c****    IP:117.173.23.***     举报/删稿
展会推荐
让朕来说2句
评论
收藏
点赞
转发