教程一: | 创建camunda项目=>部署流程定义=>创建流程实例=>走完流程实例 |
教程二: | camunda数据库中的48张表分别的大致含义,数据库表结构介绍 |
教程三: | 下载camunda-modeler并将其置为IDEA的外部工具详细过程,且在camunda-modeler中进行绘制流程定义图 |
教程四: | 不使用camunda-modeler,使用Fluent Builder API,代码编写流程定义并部署 |
教程五: | 代码编写流程定义,并自定义创建需要展示的流程图 |
学过工作流的应该都能分清什么叫做【流程定义】,什么叫做【流程图】吧。
严格来讲,流程图包括流程定义,但不局限于流程定义,毕竟还有个图形显示。就好比bpmn文件,虽然有着代码方式的显示,但也有着图形方式的显示方式。
之前在教程(四)中有写到一个方式:Fluent Builder API,但这个方法只推荐用于简单的、不复杂的、无循环的、分支少等等的流程定义的编写。
在下面要讲到的,同样是用代码方式去编写流程定义,但是不同的是,流程图的生成方式,也是需要自己实现的,也就是自定义化,根据不同的流程图的难度、分支数目、循环与否、等等需求,实现流程图的代码也是应该不一样的。
第一部分:
首先,学过前面的教程的话,可以知道在camunda-modeler中我们可以通过图形的方式去自定义一个流程图,并通过它自动生成一个bpmn文件。在流程图中,任何一个流通的元素(节点),我们都应该有流进or流出的顺序流;而在每个元素身上,我们可以同时有无数个流出和无数个流入的顺序流,当然,一个元素在正常情况下也不至于有过多的顺序流,顺序流的数目应该由实际需求来确定。
在上图中,我们可以看到,位于中心的Event一共有6个位置的流出点,对应的也就有六个位置的流入点,在camunda-modeler中我们当然可以很简单的得到这样的流程,但是想用代码去得到这样的流程图,就需要一番斟酌。
根据流入点和流出点,我们手中的代码,在第一步可以定义一个简单的关于流入点和流出点的类,在这个类中,我只简单的区分了上下左右四个方向的流入点和流出点(因为是图,有x和y坐标):【序列参考点类】
**
* Desc:序列参考点类
* ——可以添加更多入口和出口以提供更多选择和绘图的方式
* ——也可以添加更多的其他方面(是否是usertask,是否是网关,是否是会签等等)的参数或者新建类,去提供更多的绘图的变化的可能性
*/
public class SequenceReferencePoints {
// 左入口
private double leftEntryX;
private double leftEntryY;
// 右入口
private double rightEntryX;
private double rightEntryY;
// 上入口
private double upperEntryX;
private double upperEntryY;
// 下入口
private double lowerEntryX;
private double lowerEntryY;
// 左出口
private double leftExitX;
private double leftExitY;
// 右出口
private double rightExitX;
private double rightExitY;
// 上出口
private double upperExitX;
private double upperExitY;
// 下出口
private double lowerExitX;
private double lowerExitY;
// start和end使用这个就足够
public SequenceReferencePoints(double leftEntryX, double leftEntryY,
double rightExitX, double rightExitY) {
this.leftEntryX = leftEntryX;
this.leftEntryY = leftEntryY;
this.rightExitX = rightExitX;
this.rightExitY = rightExitY;
}
public SequenceReferencePoints(double leftEntryX, double leftEntryY,
double rightEntryX, double rightEntryY,
double upperEntryX, double upperEntryY,
double lowerEntryX, double lowerEntryY,
double leftExitX, double leftExitY,
double rightExitX, double rightExitY,
double upperExitX, double upperExitY,
double lowerExitX, double lowerExitY) {
this.leftEntryX = leftEntryX;
this.leftEntryY = leftEntryY;
this.rightEntryX = rightEntryX;
this.rightEntryY = rightEntryY;
this.upperEntryX = upperEntryX;
this.upperEntryY = upperEntryY;
this.lowerEntryX = lowerEntryX;
this.lowerEntryY = lowerEntryY;
this.leftExitX = leftExitX;
this.leftExitY = leftExitY;
this.rightExitX = rightExitX;
this.rightExitY = rightExitY;
this.upperExitX = upperExitX;
this.upperExitY = upperExitY;
this.lowerExitX = lowerExitX;
this.lowerExitY = lowerExitY;
}
// 构造函数根据需求可以有不同参数的构造函数
// ……忽略get方法,没有set方法,没有考虑单独通过set方法设置,目前考虑到的只是通过构造函数来构造
}
第二部分:
这里,我们需要一个DrawUtils,来帮助我们绘制顺序流、节点等,部分入参如BpmnModelInstance 可能一时不知道是怎么回事,不着急,往后看:
这里的两个自定义的方法分别是【绘制顺序流】和【绘制节点】,都可以自行进行改动,根据需求变动即可。
(其中最后三个create方法是camunda官方给出的方法,在这里可能稍有部分改动,但整体代码和官方的代码改动不大)
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnEdge;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnPlane;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnShape;
import org.camunda.bpm.model.bpmn.instance.dc.Bounds;
import org.camunda.bpm.model.bpmn.instance.di.Waypoint;
import org.camunda.bpm.model.xml.ModelInstance;
import org.w3c.dom.Element;
import java.util.HashMap;
/**
* Desc:绘制bpmn文件中的diagram的工具类(是较为通用的diagram工具类)
*/
public class DrawUtils {
// 节点类型
private final static double SPACE = 50;
/**
* Desc:绘制顺序流
*
* @param plane bpmn平面
* @param modelInstance bpmn模型实例
* @param sfElement 当前顺序流
* @param refPoints 所有节点的各个x、y坐标的集合
* @return bpmn平面
*/
public static BpmnPlane drawFlow(BpmnPlane plane, BpmnModelInstance modelInstance, Element sfElement, HashMap refPoints) {
String sourceRef = sfElement.getAttribute("sourceRef");
String targetRef = sfElement.getAttribute("targetRef");
// 获取源节点所有出口
SequenceReferencePoints srp = refPoints.get(sourceRef);
double leftExitX = srp.getLeftExitX();
double leftExitY = srp.getLeftExitY();
double rightExitX = srp.getRightExitX();
double rightExitY = srp.getRightExitY();
double upperExitX = srp.getUpperExitX();
double upperExitY = srp.getUpperExitY();
double lowerExitX = srp.getLowerExitX();
double lowerExitY = srp.getLowerExitY();
// 目标节点的所有入口
srp = refPoints.get(targetRef);
double leftEntryX = srp.getLeftEntryX();
double leftEntryY = srp.getLeftEntryY();
double rightEntryX = srp.getRightEntryX();
double rightEntryY = srp.getRightEntryY();
double upperEntryX = srp.getUpperEntryX();
double upperEntryY = srp.getUpperEntryY();
double lowerEntryX = srp.getLowerEntryX();
double lowerEntryY = srp.getLowerEntryY();
///
BpmnEdge bpmnEdge = modelInstance.newInstance(BpmnEdge.class);
BaseElement element = modelInstance.getModelElementById(sfElement.getAttribute("id"));
bpmnEdge.setBpmnElement(element);
Waypoint wp = modelInstance.newInstance(Waypoint.class);
///判断源节点与目标节点的位置并绘制航路点
double factor;
if (rightExitY == leftEntryY && rightExitX < leftEntryX) {// 目标节点在正右方
if (upperEntryX - upperExitX > 150) {
factor = (upperEntryX - upperExitX) / 150;
// 使用上出口、上入口,需要四个航路点
wp.setX(upperExitX);
wp.setY(upperExitY);
bpmnEdge.addChildElement(wp);
// 向上移动(factor * 15)
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperExitX);
wp.setY(upperExitY - (15 * factor));
bpmnEdge.addChildElement(wp);
//
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperEntryX);
wp.setY(upperExitY - (15 * factor));
bpmnEdge.addChildElement(wp);
// 来到目标节点上入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperEntryX);
wp.setY(upperEntryY);
bpmnEdge.addChildElement(wp);
} else {
//需要两个航路点,一个是右出口,一个是目标节点左入口
wp.setX(rightExitX);
wp.setY(rightExitY);
bpmnEdge.addChildElement(wp);
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(leftEntryX);
wp.setY(leftEntryY);
bpmnEdge.addChildElement(wp);
}
} else if (rightExitY == leftEntryY && rightExitX > leftEntryX) {// 目标节点在正左方
if (upperExitX - upperEntryX > 150) {
factor = (upperExitX - upperEntryX) / 150;
// 使用上出口、上入口,需要四个航路点
wp.setX(upperExitX);
wp.setY(upperExitY);
bpmnEdge.addChildElement(wp);
// 向上移动(factor * 20)
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperExitX);
wp.setY((upperExitY - (30 * factor)));
bpmnEdge.addChildElement(wp);
// 移动到目标节点上入口的上方
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperEntryX + (5 * factor));
wp.setY((upperExitY - (30 * factor)));
bpmnEdge.addChildElement(wp);
// 来到目标节点上入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperEntryX + (5 * factor));
wp.setY(upperEntryY);
bpmnEdge.addChildElement(wp);
} else {
// 左出口
wp.setX(leftExitX);
wp.setY(leftExitY);
bpmnEdge.addChildElement(wp);
// 目标节点右入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(rightEntryX);
wp.setY(rightEntryY);
bpmnEdge.addChildElement(wp);
}
} else if (upperExitX == upperEntryX && upperEntryY > upperExitY) {// 目标节点在正上方
//使用上出口,下入口,两个航路点
wp.setX(upperExitX);
wp.setY(upperExitY);
bpmnEdge.addChildElement(wp);
// 目标节点下入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerEntryX);
wp.setY(lowerEntryY);
bpmnEdge.addChildElement(wp);
} else if (upperExitX == upperEntryX && upperEntryY < upperExitY) {// 目标节点在正下方
//使用下出口,上入口,两个航路点
wp.setX(lowerExitX);
wp.setY(lowerExitY);
bpmnEdge.addChildElement(wp);
// 目标节点上入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(upperEntryX);
wp.setY(upperEntryY);
bpmnEdge.addChildElement(wp);
} else if (rightExitX < leftEntryX && rightExitY > leftEntryY) {// 目标节点位于右上方
// 使用下出口,下入口,四个航路点
wp.setX(lowerExitX);
wp.setY(lowerExitY);
bpmnEdge.addChildElement(wp);
// 向下移动一段距离
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerExitX);
wp.setY(lowerExitY + SPACE);
bpmnEdge.addChildElement(wp);
// 向右移动到目标节点下方
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerEntryX);
wp.setY(lowerExitY + SPACE);
bpmnEdge.addChildElement(wp);
// 目标节点下入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerEntryX);
wp.setY(lowerEntryY);
bpmnEdge.addChildElement(wp);
} else if (rightExitX < leftEntryX && rightExitY < leftEntryY) {// 目标节点位于右下方
// 使用下出口和左入口,三个航路点
wp.setX(lowerExitX);
wp.setY(lowerExitY);
bpmnEdge.addChildElement(wp);
// 向下移动到目标节点同一水平线上
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerExitX);
wp.setY(leftEntryY);
bpmnEdge.addChildElement(wp);
// 目标节点左入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(leftEntryX);
wp.setY(leftEntryY);
bpmnEdge.addChildElement(wp);
} else if (leftExitX > rightEntryX && leftExitY > rightEntryY) {// 目标节点在左上方
// 使用下出口,下入口,四个航路点
wp.setX(lowerExitX);
wp.setY(lowerExitY);
bpmnEdge.addChildElement(wp);
// 向下移动一段距离
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerExitX);
wp.setY(lowerExitY + SPACE);
bpmnEdge.addChildElement(wp);
// 向右移动到目标节点下方
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerEntryX);
wp.setY(lowerExitY + SPACE);
bpmnEdge.addChildElement(wp);
// 目标节点下入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerEntryX);
wp.setY(lowerEntryY);
bpmnEdge.addChildElement(wp);
} else if (leftExitX > rightEntryX && rightExitY < leftEntryY) {// 左下方
// 使用下出口,右入口,三个航路点
wp.setX(lowerExitX);
wp.setY(lowerExitY);
bpmnEdge.addChildElement(wp);
// 向下移动到目标节点同一水平线上
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(lowerExitX);
wp.setY(rightEntryY);
bpmnEdge.addChildElement(wp);
// 目标节点右入口
wp = modelInstance.newInstance(Waypoint.class);
wp.setX(rightEntryX);
wp.setY(rightEntryY);
bpmnEdge.addChildElement(wp);
}
plane.addChildElement(bpmnEdge);
return plane;
}
/**
* Desc:绘制节点
*
* @param plane bpmn平面
* @param modelInstance bpmn模型实例
* @param element 当前节点
* @param x x坐标
* @param y y坐标
* @param height 高
* @param width 宽
* @param setHorizontal 水平设置
* @return bpmn平面
*/
public static BpmnPlane drawShape(BpmnPlane plane, ModelInstance modelInstance, BpmnModelElementInstance element,
double x, double y,
double height, double width,
boolean setHorizontal) {
BpmnShape bpmnShape = modelInstance.newInstance(BpmnShape.class);
bpmnShape.setBpmnElement((BaseElement) element);
if (setHorizontal) {
bpmnShape.setHorizontal(true);
}
Bounds bounds = modelInstance.newInstance(Bounds.class);
bounds.setX(x);
bounds.setY(y);
bounds.setHeight(height);
bounds.setWidth(width);
if (element instanceof ExclusiveGateway) {
bpmnShape.setMarkerVisible(true);
}
bpmnShape.setBounds(bounds);
plane.addChildElement(bpmnShape);
return plane;
}
// 创建事件的帮助方法
public static T createElement(BpmnModelElementInstance parentElement, String id, Class elementClass) {
T element = parentElement.getModelInstance().newInstance(elementClass);
element.setAttributeValue("id", id, true);
parentElement.addChildElement(element);
return element;
}
public static UserTask createUserTask(Process process, String id, String name, String groups, String typeName) {
UserTask userTask = createElement(process, id, UserTask.class);
userTask.setName(name);
userTask.setCamundaCandidateGroups(groups);
userTask.setImplementation(typeName);
return userTask;
}
// 创建顺序流
public static SequenceFlow createSequenceFlow(Process process, FlowNode from, FlowNode to) {
String identifier = from.getId() + "-" + to.getId();
SequenceFlow sequenceFlow = createElement(process, identifier, SequenceFlow.class);
process.addChildElement(sequenceFlow);
sequenceFlow.setSource(from);
from.getOutgoing().add(sequenceFlow);
sequenceFlow.setTarget(to);
to.getIncoming().add(sequenceFlow);
return sequenceFlow;
}
}
第三部分:
Service And ServiceImpl:
①service:
public interface ProcessService {
/**
* Desc:修改某个流程定义
* 包括修改审批组或者增删节点
*
* @param processDefinitionName 流程定义name
* @param nodeList 节点所有信息的list(userTask节点信息)
* @return true - > 修改成功
* @throws Exception e
*/
boolean modifyProcessDefinition(String processDefinitionName, List
②impl:
@Override
@Transactional(rollbackFor = Exception.class)
public boolean modifyProcessDefinition(String processDefinitionName, List
如果想在页面上看到,和activiti不太一样的是,camunda需要通过bpmn.js绘制。
不过可以通过输出为xxx.bpmn20.xml的方式,再用camunda-modeler来打开即可,如果需要应用到项目里面,还是需要bpmn.js了,不过那块儿是前端的内容,我这边并没有去研究,具体使用方法,可以自行搜索研究,如果有机会的话,会在之后找个时间研究一下,再出一个相关的教程。
教程六目前还没有想好写哪方面的,如果有推荐写的方向可以在下面进行留言评论。