根據配置反向生BpmnModel

需求來源

有的時候我們需要動態調整流程圖,每次調整時都需要修改、部署 及發佈等操作 才能正常生成我們想要的BpmnModel。這個時候就想,我們能不能通過數據庫配置,反向生成流程圖呢?當然可以,這個也可以解決動態加節點問題。
正向: 流程設計器設計-》保存到-》BpmnModel=》部署=》發佈 適用與業務人員
反向: 數據庫=》BpmnModel 適用與對產品非常熟悉的開發人員

數據庫設計


SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for oa_node
-- ----------------------------
DROP TABLE IF EXISTS `oa_node`;
CREATE TABLE `oa_node` (
  `id` int(20) unsigned NOT NULL AUTO_INCREMENT,
  `node_id` varchar(64) DEFAULT NULL COMMENT '節點編號',
  `node_name` varchar(64) DEFAULT '' COMMENT '節點名稱',
  `process_id` int(20) DEFAULT NULL COMMENT '流程編號',
  `process_key` varchar(64) DEFAULT '' COMMENT '流程key',
  `node_type` varchar(64) DEFAULT '' COMMENT '節點類型',
  `task_type` varchar(64) DEFAULT 'approve' COMMENT '任務類型',
  `sort` int(10) unsigned DEFAULT '1' COMMENT '排序',
  `user_id_list` varchar(255) DEFAULT '' COMMENT '節點人員編號列表',
  `role_group_id` int(20) DEFAULT NULL COMMENT '節點角色組編號',
  `find_user_type` tinyint(4) DEFAULT '7' COMMENT '節點用戶類型',
  `combine_type` tinyint(2) unsigned DEFAULT '2' COMMENT '組合方式:1 正常(找不到節點人員提示異常) 2 正常(找不到節點人員就跳過當前環節) ',
  `relation_node_id` varchar(64) DEFAULT '' COMMENT '依賴節點',
  `action_types` varchar(255) DEFAULT 'pass,reject' COMMENT '動作集合',
  `skip_expression` varchar(1000) DEFAULT '' COMMENT '用戶任務條件跳過表達式',
  `expression` varchar(1000) DEFAULT '' COMMENT '連線表達式/用戶任務完成表達式',
  `source_ref` varchar(64) DEFAULT '' COMMENT '連線來源節點NodeId',
  `target_ref` varchar(64) DEFAULT '' COMMENT '連線目標節點NodeId',
  `sequential` tinyint(2) unsigned DEFAULT '1' COMMENT '1 單實例 2 串行多實例 3 並行多實例',
  `countersign_type` varchar(10) DEFAULT '1' COMMENT '會籤類型',
  `proportion` varchar(4) DEFAULT '' COMMENT '通過比例',
  `valid_state` tinyint(2) unsigned DEFAULT '1' COMMENT '狀態 1 有效 0 失效',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最後更新時間',
  `operator_id` int(11) DEFAULT NULL COMMENT '操作人工號',
  `operator_name` varchar(64) DEFAULT '' COMMENT '操作人姓名',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_nid_pid` (`node_id`,`process_id`),
  KEY `idx_process_id` (`process_id`),
  KEY `idx_process_key` (`process_key`),
  KEY `idx_pid_nid` (`node_id`,`process_id`)
) ENGINE=InnoDB AUTO_INCREMENT=136 DEFAULT CHARSET=utf8mb4 COMMENT='流程節點表';
SET FOREIGN_KEY_CHECKS=1;

引入POM

  <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.4.1</version>
        </dependency>
		<!-- 如果自動佈局,則需要引入該jar  -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-bpmn-layout</artifactId>
            <version>6.4.1</version>
        </dependency>

工具類

import lombok.Data;
import lombok.ToString;

import java.io.Serializable;

/**
 * todo:
 *
 * @author : zhoulin.zhu
 * @date : 2020/3/3 10:11
 */
@Data
@ToString
public class NodeDTO implements Serializable {

    private static final long serialVersionUID = 462441365348815087L;

    private Integer id;

    private String nodeId;

    private String nodeName;

    private Integer processId;

    private String processKey;

    private String  nodeType;

    private String taskType;

    private Integer sort;

    private String userIdList;

    private Integer roleGroupId;

    private Integer findUserType;

    private Integer combineType;

    private String relationNodeId;

    private String actionTypes;

    private String skipExpression;

    private String sourceRef;

    private String targetRef;

    private String expression;

    private Integer sequential;

    private String  countersignType;

    private String proportion;

    private Integer validState;

    private Integer operatorId;

    private String operatorName;
}
import com.alibaba.fastjson.JSONArray;
import com.zhenai.oa.flow.dto.response.NodeDTO;
import com.zhenai.oa.flow.handler.SpringContextHandler;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.RepositoryService;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * todo: 根據nodeList 動態生成BpmnModel
 *
 * @author : zhoulin.zhu
 * @date : 2020/4/21 14:51
 */
public class AutoBuildModelUtils {


    private static final String ELEMENT_TYPE_PREFIX = "bpmn:";
    private static final String DEFAULT_ASSIGNEE_LIST_EXP = "${assigneeList}";
    private static final String ASSIGNEE_USER_EXP = "assignee";
    private static final String DEFAULT_ASSIGNEE_USER_EXP = "assigneeExp";
    //即 只要有一個人完成任務,則當前任務也算完成
    private static final String COMPLETION_CONDITION = "${nrOfCompletedInstances/nrOfInstances >= 0}";
    private static final String DEFAULT_SKIP_EXPRESSION ="${approveAction == \"approveFail\"}";
    private static final String ELEMENT_TYPE_USER_TASK = "UserTask";
    private static final String ELEMENT_TYPE_START_EVENT = "StartEvent";
    private static final String ELEMENT_TYPE_END_EVENT = "EndEvent";
    private static final String ELEMENT_TYPE_EXCLUSIVE_GATEWAY = "ExclusiveGateway";
    private static final String ELEMENT_TYPE_PARALLEL_GATEWAY = "ParallelGateway";
    private static final String ELEMENT_TYPE_SEQUENCE_FLOW = "SequenceFlow";
    private static final String ELEMENT_TYPE_LANE = "Lane";
    private static final String ELEMENT_TYPE_SUB_PROCESS = "SubProcess";



    /**
     * 功能描述: 根據數據庫配置nodeDTO,反向生成流程圖模型
     *
     * 
     * @param processKey 流程KEY
     * @param processName 流程Name
     * @param nodeList nodeList
     * @param autoDeploy 是否自動部署
     * @return : org.flowable.bpmn.model.BpmnModel
     * @author : zhoulin.zhu
     * @date : 2020/4/21 15:21
     */
    public static BpmnModel autoBulidBpmnModel(String processKey, String processName, List<NodeDTO> nodeList, boolean autoDeploy){

        BpmnModel bpmnModel = new BpmnModel();
        Process process = new Process();
        for (NodeDTO nodeDTO : nodeList) {
            if (StringUtils.isEmpty(nodeDTO.getNodeType())) {
                continue;
            }
            /* 可根據實際需求拓展 */
            switch (nodeDTO.getNodeType().replaceAll(ELEMENT_TYPE_PREFIX, "")) {
                case ELEMENT_TYPE_START_EVENT:
                    process.addFlowElement(createStartEvent(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
                    break;
                case ELEMENT_TYPE_END_EVENT:
                    process.addFlowElement(createEndEvent(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
                    break;
                case ELEMENT_TYPE_USER_TASK:
                    process.addFlowElement(createUserTask(nodeDTO));
                    break;
                case ELEMENT_TYPE_EXCLUSIVE_GATEWAY:
                    process.addFlowElement(createExclusiveGateway(nodeDTO.getNodeId(), nodeDTO.getNodeName(), null));
                    break;
                case ELEMENT_TYPE_PARALLEL_GATEWAY:
                    process.addFlowElement(createParallelGateway(nodeDTO.getNodeId(), nodeDTO.getNodeName(), null));
                    break;
                case ELEMENT_TYPE_SEQUENCE_FLOW:
                    process.addFlowElement(createSequenceFlow(nodeDTO));
                    break;
                case ELEMENT_TYPE_LANE:
                    // not support type
                    //createLane(nodeDTO.getNodeId(),nodeDTO.getNodeName());
                    break;
                case ELEMENT_TYPE_SUB_PROCESS:
                    process.addFlowElement(createSubProcess(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
                    break;
                default:
                    // logger not support type
                    break;
            }
        }

        bpmnModel.addProcess(process);
        /* 生成BPMN自動佈局 */
        BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModel);
        bpmnAutoLayout.execute();

        //驗證組裝bpmnmodel是否正確
        ProcessValidator defaultProcessValidator = new ProcessValidatorFactory().createDefaultProcessValidator();
        List<ValidationError> validate = defaultProcessValidator.validate(bpmnModel);
        if (validate.size() > 0) {
            throw  new FlowableException(JSONArray.toJSON(validate).toString());
        }

        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);

        if(autoDeploy){
            SpringContextHandler.getBean(RepositoryService.class).createDeployment()
                    .name(processName)
                    .key(processKey)
                    .addBytes(processName + ".bpmn20.xml", bpmnBytes)
                    .deploy();
        }
        return bpmnModel;
    }

    //開始節點
    private static StartEvent createStartEvent(String nodeId, String nodeName) {
        StartEvent startEvent = new StartEvent();
        startEvent.setId(nodeId);
        if (!StringUtils.isEmpty(nodeName)) {
            startEvent.setName(nodeName);
        } else {
            startEvent.setName("開始");
        }
        return startEvent;
    }

    /*結束節點*/
    private static EndEvent createEndEvent(String nodeId, String nodeName) {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(nodeId);
        if (!StringUtils.isEmpty(nodeName)) {
            endEvent.setName(nodeName);
        } else {
            endEvent.setName("結束");
        }
        return endEvent;
    }


    //排他網關
    private static ExclusiveGateway createExclusiveGateway(String nodeId, String nodeName, String defaultFlow) {
        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
        exclusiveGateway.setId(nodeId);
        exclusiveGateway.setName(nodeName);

        if (!StringUtils.isEmpty(defaultFlow)) {
            exclusiveGateway.setDefaultFlow(defaultFlow);
        }
        return exclusiveGateway;
    }

    //並行網關
    private static ParallelGateway createParallelGateway(String nodeId, String nodeName, String defaultFlow) {
        ParallelGateway parallelGateway = new ParallelGateway();
        parallelGateway.setId(nodeId);
        parallelGateway.setName(nodeName);
        if (!StringUtils.isEmpty(defaultFlow)) {
            parallelGateway.setDefaultFlow(defaultFlow);
        }
        return parallelGateway;
    }

    private static UserTask createUserTask(NodeDTO nodeDTO) {
        UserTask userTask = new UserTask();
        userTask.setName(nodeDTO.getNodeName());
        userTask.setId(nodeDTO.getNodeId());
        if (nodeDTO.getSequential() > 1) {
            MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
            multiInstanceLoopCharacteristics.setSequential(nodeDTO.getSequential() == 2);
            multiInstanceLoopCharacteristics.setInputDataItem(DEFAULT_ASSIGNEE_LIST_EXP);
            multiInstanceLoopCharacteristics.setElementVariable(DEFAULT_ASSIGNEE_USER_EXP);

            if(nodeDTO.getSequential() > 1 && !StringUtils.isEmpty(nodeDTO.getProportion())  ){
                userTask.setSkipExpression(nodeDTO.getSkipExpression());
            }
            if(!StringUtils.isEmpty(nodeDTO.getExpression())){
                multiInstanceLoopCharacteristics.setCompletionCondition(nodeDTO.getExpression());
            } else if(!StringUtils.isEmpty(nodeDTO.getProportion())) {
                multiInstanceLoopCharacteristics.setCompletionCondition(COMPLETION_CONDITION.replaceAll("0",nodeDTO.getProportion()));
            }
            userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
        }
        if (!StringUtils.isEmpty(nodeDTO.getSkipExpression())) {
            userTask.setSkipExpression(nodeDTO.getSkipExpression());
        }
        return userTask;
    }

    private static SequenceFlow createSequenceFlow(NodeDTO nodeDTO) {
        SequenceFlow sequenceFlow = new SequenceFlow();
        sequenceFlow.setName(nodeDTO.getNodeName());
        sequenceFlow.setId(nodeDTO.getNodeId());
        sequenceFlow.setTargetRef(nodeDTO.getTargetRef());
        sequenceFlow.setSourceRef(nodeDTO.getSourceRef());
        if (!StringUtils.isEmpty(nodeDTO.getSkipExpression())) {
            sequenceFlow.setSkipExpression(nodeDTO.getSkipExpression());
        }
        if (!StringUtils.isEmpty(nodeDTO.getExpression())) {
            sequenceFlow.setConditionExpression(nodeDTO.getExpression());
        }
        return sequenceFlow;
    }

    //設置泳道
    private static Lane createLane(String nodeId, String nodeName) {
        Lane lane = new Lane();
        lane.setId(nodeId);
        lane.setName(nodeName);
        return lane;
    }

    //設置子流程
    private static SubProcess createSubProcess(String nodeId, String nodeName) {
        SubProcess subProcess = new SubProcess();
        subProcess.setId(nodeId);
        subProcess.setName(nodeName);
        return subProcess;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章