【审批工作流camunda教程】(五):代码编写流程定义,并自定义创建需要展示的流程图

教程一:创建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<String, SequenceReferencePoints> 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 extends BpmnModelElementInstance> T createElement(BpmnModelElementInstance parentElement, String id, Class<T> 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<Map<String, Object>> nodeList) throws Exception;

}

②impl:

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modifyProcessDefinition(String processDefinitionName, List<Map<String, Object>> nodeList) throws Exception {
        // ProcessDefinition processDefinition = checkProcessDefinitionByName(processDefinitionName);这个方法是为了校验是否存在该流程定义,此处我将其注释
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionName(processDefinitionName)
                .latestVersion()
                .list();
        // ①获取节点信息(按顺序),这里是转换为dto,UserTaskDTO中的属性为:
        // private String userTaskId;
        // private String userTaskName;
        // private List<String> groupList;
        // private String type;
        // 我这里只写了这四个属性,map中当然也就是就这四个
        List<UserTaskDTO> list = nodeList.stream()
                .map(this::toUserTaskDTO)
                .collect(Collectors.toList());
        // 简单再校验一下流程结点的名称
        List<String> nameList = list.stream()
                .map(UserTaskDTO::getUserTaskName)
                .collect(Collectors.toList());
        if (nameList.size() != new HashSet<>(nameList).size()) {
            throw new RuntimeException("编辑流程结点名称不允许重名,请修改后保存");
        }
        //以上就是平凡无奇的一些操作了

        // ②准备工作,下面要进行一些对流程编写的准备工作,首先就是要创建一个BpmnModelInstance,bpmn模型实例,然后是在bpmn文件中的<definitions>这个标签
        BpmnModelInstance modelInstance = Bpmn.createEmptyModel();
        Definitions definitions = modelInstance.newInstance(Definitions.class);
        definitions.setTargetNamespace(BPMN20_NS);
        definitions.getDomElement().registerNamespace("camunda", CAMUNDA_NS);
        modelInstance.setDefinitions(definitions);
        // Process也是在bpmn文件中的一个标签(可以打开一个bpmn文件进行查看),节点
        Process process = DrawUtils.createElement(definitions, processDefinitionName, Process.class);
        // process的id就是processDefinitionKey,process的name就是processDefinitionName
        process.setId(processDefinition.getKey());
        process.setName(processDefinitionName);
        // 必须设置为可执行的,否则在流程定义表中无法生成流程定义
        process.setExecutable(true);
        // 接下来是两个必须具备的两个节点,也是准备工作之一
        StartEvent startEvent = DrawUtils.createElement(process, "start", StartEvent.class);
        EndEvent endEvent = DrawUtils.createElement(process, "end", EndEvent.class);

        // 这里新建了两个在我的流程里面不会重复的任务节点,一个是录入节点,一个是修改节点,在我的流程里都只会出现一次,我的所有的复核不通过,都会回到修改节点。
        UserTaskDTO inputDTO = null;
        UserTaskDTO modifyDTO = null;
        // 未知数量的复核节点
        List<UserTaskDTO> reviewList = new ArrayList<>();
        // 这里根据task的类型置入(具体实现可以更改)
        for (UserTaskDTO userTaskDTO : list) {
            if (userTaskDTO.getType().equals("复核")) {
                reviewList.add(userTaskDTO);
            }
            if (userTaskDTO.getType().equals("录入")) {
                inputDTO = userTaskDTO;
            }
            if (userTaskDTO.getType().equals("修改")) {
                modifyDTO = userTaskDTO;
            }
        }
        
        if (Objects.isNull(inputDTO)) {
            throw new RuntimeException("inputDTO不存在");
        }
        // 先删除所有,再添加保存(此处的功能自己实现吧,我这里写的是简略版,具体功能是根据流程定义name删除当前它所有的审批组)
        // CandidateGroupDbo中有四个属性:流程定义name,userTaskId,userTaskGroupId(某个节点的写死的审批组名称),groupList(这个节点的实际审批组列表)
        candidateGroupRepo.deleteAll();
        CandidateGroupDbo inputCandidateGroupDbo = new CandidateGroupDbo(
                processDefinition.getName(),
                inputDTO.getUserTaskId(),
                processDefinition.getName() + inputDTO.getUserTaskId(),
                inputDTO.getGroupList());
        UserTask inputNode = DrawUtils.createUserTask(
                process, inputDTO.getUserTaskId(), inputDTO.getUserTaskName(),
                inputCandidateGroupDbo.getProcessDefinitionName() + inputCandidateGroupDbo.getUserTaskId(),
                inputDTO.getType());
        candidateGroupRepo.save(inputCandidateGroupDbo);
        // 这个方法的实际功能是创建一个group,group的id就是流程定义name+usertaskId的合体,然后group的users就是groupList中所有group的user,这样之后就可以通过流程定义name+usertaskId的方式寻找所有的该节点的审批组及审批用户(不确定是否有更好的方法,此处先这样写)
        processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + inputDTO.getUserTaskId(), inputDTO.getGroupList());
        // start --> 录入
        DrawUtils.createSequenceFlow(process, startEvent, inputNode);

        if (Objects.isNull(modifyDTO)) {
            throw new RuntimeException("modifyDTO不存在");
        }
        // 获取修改节点
        CandidateGroupDbo modifyCandidateGroupDbo = new CandidateGroupDbo(
                processDefinition.getName(),
                modifyDTO.getUserTaskId(),
                processDefinition.getName() + modifyDTO.getUserTaskId(),
                modifyDTO.getGroupList());
        UserTask modifyNode = DrawUtils.createUserTask(
                process, modifyDTO.getUserTaskId(), modifyDTO.getUserTaskName(),
                modifyCandidateGroupDbo.getProcessDefinitionName() + modifyCandidateGroupDbo.getUserTaskId(),
                modifyDTO.getType());
        candidateGroupRepo.save(modifyCandidateGroupDbo);
        // 同上面input
        processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + modifyDTO.getUserTaskId(), modifyDTO.getGroupList());

        // 获取第一个复核结点,用于修改节点 -> 第一个复核结点
        boolean first = true;
        UserTask review1DTO = null;
        // 如果一个复核节点都没有,则抛出错误
        if (reviewList.isEmpty()) {
            throw new RuntimeException("不存在复核结点!");
        }
        for (UserTaskDTO u : reviewList) {
            CandidateGroupDbo candidateGroupDbo = new CandidateGroupDbo(
                    processDefinition.getName(),
                    u.getUserTaskId(),
                    processDefinition.getName() + u.getUserTaskId(),
                    u.getGroupList());
            UserTask userTask = DrawUtils.createUserTask(
                    process, u.getUserTaskId(), u.getUserTaskName(),
                    candidateGroupDbo.getProcessDefinitionName() + candidateGroupDbo.getUserTaskId(),
                    u.getType());
            candidateGroupRepo.save(candidateGroupDbo);
            processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + u.getUserTaskId(), u.getGroupList());
            // 只在第一次将录入与第一个复核节点连接起来
            if (first) {
                DrawUtils.createSequenceFlow(process, inputNode, userTask);
                review1DTO = userTask;
                first = false;
            }
            List<ExclusiveGateway> gateways = (List<ExclusiveGateway>) modelInstance.getModelElementsByType(ExclusiveGateway.class);
            // 如果有网关的话,将网关与当前的节点进行连线(这种连线都是根据实际情况,因为我的流程图就是一个节点后面跟着一个网关这样子,所以就直接这样写在for里面了,如果你们不是,那么根据实际需求进行改动)
            if (gateways.size() >= 1) {
                ExclusiveGateway last = gateways.get(gateways.size() - 1);
                SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, last, userTask);
                // 设置顺序流条件(复核通过)#和$好像都可以,很久以前写的了,有点忘了
                ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
                conditionExpression.setTextContent("#{confirm==true}");
                sequenceFlow.setConditionExpression(conditionExpression);
            }
            ExclusiveGateway exclusiveGateway = DrawUtils.createElement(process, u.getUserTaskId() + "gateway", ExclusiveGateway.class);
            // 将节点指向新建的网关
            DrawUtils.createSequenceFlow(process, userTask, exclusiveGateway);
        }
        //获取最后一个网关,并指向end节点(此处是排他网关)
        List<ExclusiveGateway> gateways = (List<ExclusiveGateway>) modelInstance.getModelElementsByType(ExclusiveGateway.class);
        if (gateways.size() >= 1) {
            ExclusiveGateway last = gateways.get(gateways.size() - 1);
            SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, last, endEvent);
            // 设置顺序流条件(复核不通过)
            ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
            conditionExpression.setTextContent("#{confirm==false}");
            sequenceFlow.setConditionExpression(conditionExpression);
        } else {
            //如果没有网关
            throw new RuntimeException("网关构建失败!");
        }

        // 必须【先】将复核和网关相连,之后再才能将所有的网关指向修改节点,否则流程图diagram会变得很奇怪(实测)
        for (ExclusiveGateway gateway : gateways) {
            SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, gateway, modifyNode);
            // 设置顺序流条件(复核不通过)
            ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
            conditionExpression.setTextContent("#{confirm==false}");
            sequenceFlow.setConditionExpression(conditionExpression);
        }

        DrawUtils.createSequenceFlow(process, modifyNode, review1DTO);

        //
        // 读取文档以进行Xpath搜索
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new InputSource(new ByteArrayInputStream(Bpmn.convertToString(modelInstance).getBytes(StandardCharsets.UTF_8))));

        doc.getDocumentElement().normalize();

        // 对于图,需要创建一个图和一个平面元素。平面被设置在一个图对象中,图被添加为子元素
        BpmnDiagram bpmnDiagram = modelInstance.newInstance(BpmnDiagram.class);
        BpmnPlane plane = modelInstance.newInstance(BpmnPlane.class);

        bpmnDiagram.setBpmnPlane(plane);
        definitions.addChildElement(bpmnDiagram);

        // process
        plane.setBpmnElement(process);

        // 创建每个形状的起点和终点的x和y坐标的散列映射,以便稍后作为序列流的引用。
        // 当我们添加形状时,我们将使用每个元素的id作为键并保存坐标
        HashMap<String, SequenceReferencePoints> refPoints = new HashMap<>();

        // 形状源引用的数组,用于按相对顺序放置进程形状
        ArrayList<String> sourceRefs = new ArrayList<>();
        ArrayList<String> nextSourceRefs = new ArrayList<>();

        // 要搜索和保存引用和坐标的对象
        XPath xpath = XPathFactory.newInstance().newXPath();

        // 获取开始事件
        XPathExpression searchRequest = xpath.compile("//*[contains(name(),'startEvent')]");
        NodeList eventNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

        int x = 0;
        // 通过绘制开始事件开始图表
        for (int i = 0; i < eventNodes.getLength(); i++) {
            Element eElement = (Element) eventNodes.item(i);
            BpmnModelElementInstance element = modelInstance.getModelElementById(eElement.getAttribute("id"));
            // startEvent的(x,y)==(200,200)
            DrawUtils.drawShape(plane, modelInstance, element, 200, 200, 36, 36, false);
            refPoints.put(eElement.getAttribute("id"), new SequenceReferencePoints(
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238,
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238
            ));
            sourceRefs.add(eElement.getAttribute("id"));
        }
        // 开始节点与下一个节点之间的距离,不能删除
        x += 150;
        // 绘制下一个形状
        while (sourceRefs.size() > 0) {
            // 横向移动150像素以绘制下一组形状
            x += 150;
            // y将确定形状放置的y轴,并在每次运行开始时设置为零
            int yOffset = 0;

            for (String sourceRef : sourceRefs) {
                searchRequest = xpath.compile("//*[@sourceRef='" + sourceRef + "']");
                NodeList nextShapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                for (int y = 0; y < nextShapes.getLength(); y++) {
                    Element tElement = (Element) nextShapes.item(y);
                    xpath = XPathFactory.newInstance().newXPath();
                    searchRequest = xpath.compile("//*[@id='" + tElement.getAttribute("targetRef") + "']");
                    NodeList shapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                    for (int z = 0; z < shapes.getLength(); z++) {
                        Element sElement = (Element) shapes.item(z);
                        if (!refPoints.containsKey(sElement.getAttribute("id"))) {
                            nextSourceRefs.add(sElement.getAttribute("id"));

                            String type = sElement.getNodeName();

                            switch (type) {
                                case ("userTask"):
                                case ("bpmn:userTask"):
                                case ("task"):
                                case ("bpmn:task"):
                                    BpmnModelElementInstance element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, (180 + yOffset) + y * 200, 80, 100, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,//左入口
                                            x + 100, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 40,//下入口
                                            x, (220 + yOffset) + y * 200,//左出口
                                            x + 100, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 40//下出口
                                    ));
                                    break;
                                case ("exclusiveGateway"):
                                case ("bpmn:exclusiveGateway"):
                                case ("inclusiveGateway"):
                                case ("bpmn:inclusiveGateway"):
                                case ("parallelGateway"):
                                case ("bpmn:parallelGateway"):
                                case ("eventBasedGateway"):
                                case ("bpmn:eventBasedGateway"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x + 25, ((195 + yOffset) + y * 200), 50, 50, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x + 25, (220 + yOffset) + y * 200,//左入口
                                            x + 75, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 25,//下入口
                                            x + 25, (220 + yOffset) + y * 200,//左出口
                                            x + 75, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 25//下出口
                                    ));
                                    break;
                                case ("endEvent"):
                                case ("bpmn:endEvent"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 200), 36, 36, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18,
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18
                                    ));
                                    break;
                                case ("textAnnotation"):
                                case ("bpmn:textAnnotation"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 80), 200, 200, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(x, ((220 + yOffset) + y * 80), (x + 36), ((220 + yOffset) + y * 80)));
                                    break;
                            }
                        }
                    }
                }
            }
            sourceRefs.clear();
            sourceRefs.addAll(nextSourceRefs);
            nextSourceRefs.clear();
        }
        // 找到并绘制序列流,现在形状已经绘制,序列流的参考点已经建立
        searchRequest = xpath.compile("//*[contains(name(),'sequenceFlow')]");
        NodeList sfNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);
        for (int i = 0; i < sfNodes.getLength(); i++) {
            Element sfElement = (Element) sfNodes.item(i);
            DrawUtils.drawFlow(plane, modelInstance, sfElement, refPoints);
        }
        // 验证(官方方法)(实测的时候发现好像几乎没什么用处)
        Bpmn.validateModel(modelInstance);
        // 最后一步:部署
        repositoryService.createDeployment()
                .addModelInstance(processDefinitionName + ".bpmn20.xml", modelInstance)
                .name(processDefinitionName)
                .deploy();
        // 将建好的流程定义输出为格式为[xxx.bpmn20.xml]的文件
//         Bpmn.writeModelToFile(new File("D:\\"+processDefinitionName+"bpmn20.xml"), modelInstance);
        return true;
    }

如果想在页面上看到,和activiti不太一样的是,camunda需要通过bpmn.js绘制。

不过可以通过输出为xxx.bpmn20.xml的方式,再用camunda-modeler来打开即可,如果需要应用到项目里面,还是需要bpmn.js了,不过那块儿是前端的内容,我这边并没有去研究,具体使用方法,可以自行搜索研究,如果有机会的话,会在之后找个时间研究一下,再出一个相关的教程。

教程六目前还没有想好写哪方面的,如果有推荐写的方向可以在下面进行留言评论。

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
首先,需要Camunda Modeler 中创建一个简单的请假流程。在 Modeler 中,创建一个新的 BPMN 文件并添加以下元素: 1. 开始事件(Start Event) 2. 用户任务(User Task) 3. 审批人角色(Approval Role) 4. 结束事件(End Event) 然后,将用户任务分配给审批人角色,以便审批人可以审批请假请求。 在代码中,需要使用 Camunda Java API 创建实例化流程并完成用户任务。以下是一个简单的Java代码示例: ``` public void startLeaveProcess(String employeeName, Date startDate, Date endDate) { // 获取 Camunda Process Engine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 开始流程实例 Map<String, Object> variables = new HashMap<>(); variables.put("employeeName", employeeName); variables.put("startDate", startDate); variables.put("endDate", endDate); ProcessInstance processInstance = processEngine.getRuntimeService() .startProcessInstanceByKey("leaveProcess", variables); // 完成用户任务 TaskService taskService = processEngine.getTaskService(); List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list(); for (Task task : tasks) { taskService.complete(task.getId()); } } ``` 在这个示例中,`startLeaveProcess` 方法会启动一个名为 `leaveProcess` 的流程实例,并将一些变量传递给流程实例。然后,它会使用 Camunda Java API 查找与该流程实例相关的所有用户任务,并完成这些任务,即由审批审批请假请求。 需要注意的是,这只是一个简单的示例,实际情况中可能需要更多的代码来处理异常情况、获取表单数据等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值