Activiti 7 学习整合 BPMN.js(二)

2021-01-07  本文已影响0人  吉他手_c156

动态表当读取历史数据接口

ActivitiMapper 中编写查询表单历史数据的方法
    /**
     * 读取表单数据
     * @param PROC_INST_ID   流程实例 id
     * @return
     */
    @Select("select Control_ID_,Control_VALUE_ from formdata where PROC_INST_ID_ = #{PROC_INST_ID}")
    public List<Map<String,String>> selectFormData(@Param("PROC_INST_ID")String PROC_INST_ID);
formDataShow 方法中读取表单历史数据,并保存在数据字典中
/*
                构建表单控件历史数据字典
                key 是控件的 id
                value 是控件的值
                这里是我们根据 taskId 查询出来的所有流程的表单数据
            */
            Map<String,String> controlListMap = new HashMap<String, String>();
            // 读取数据库本流程实例的所有表单数据
            List<Map<String, String>> tempControlList = activitiMapper.selectFormData(task.getProcessInstanceId());
            // 将查询出来的控件 id 和值保存在数据字典中
            for(Map<String,String> map : tempControlList){
                controlListMap.put(map.get("Control_ID_").toString(), map.get("Control_VALUE_").toUpperCase());
            }
之前返回给前端的默认值是写死的现在需要,如果默认值保存的是之前任意环节的控件 id 时,需要读取出之前控件的值
// 如果默认值是之前任意表单控件的 id ,那么我们应该读取之前表单的值
                if(split[3].startsWith("FormProperty_")){
                    // 是表单数据  从之前的保存的所有控件历史数据字典中读取
                    // 在这里  split[3] 拿到的就是历史表单的 key
                    if(controlListMap.containsKey(split[3])){
                        form.put("controlDefValue", controlListMap.get(split[3]));
                    }else{
                        // 如果字典中不存在给出错误提示
                        form.put("controlDefValue", "读取失败,检查"+split[3]+"配置");
                    }
                }else{
                    // 不是之前表单数据
                    form.put("controlDefValue", split[3]);
                }
画 bpmn js 测试,我们需要在 A 环节填写的数据在 B 环节读取出来
image.png
image.png
bpmn js 创建好之后,上传,部署,略过......
查询流程
渲染表单 formDataShow 完整代码
    /**
     * 渲染动态表单
     * @return
     */
    @GetMapping("/formDataShow")
    public AjaxResponse formDataShow(@RequestParam("taskId")String taskId){

        try{
            // 这是 GlobalConfig 类定义的是否是测试标记,标记是测试环境使用 内存用户登录,方便测试使用
            if(GlobalConfig.Test){
                // 测试环境使用内存用户登录
                securityUtil.logInAs("zhangsan");
            }
            // 查询任务
            Task task = taskRuntime.task(taskId);

            /*
                构建表单控件历史数据字典
                key 是控件的 id
                value 是控件的值
                这里是我们根据 taskId 查询出来的所有流程的表单数据
            */
            Map<String,String> controlListMap = new HashMap<String, String>();
            // 读取数据库本流程实例的所有表单数据
            List<Map<String, String>> tempControlList = activitiMapper.selectFormData(task.getProcessInstanceId());
            // 将查询出来的控件 id 和值保存在数据字典中
            for(Map<String,String> map : tempControlList){
                controlListMap.put(map.get("Control_ID_").toString(), map.get("Control_VALUE_").toUpperCase());
            }

            /**
             * 关键代码,这里在强调一下,在 activiti6 和 5 的时候实际上是有 form 这个类的
             * 但是在 activiti7 中去掉的为了轻量化,但是我们在 7 中还是有方法可以通过流程
             * 定义的 id 和任务的 id 拿到一个叫 userTask 的类,在 userTask 类中可以拿到表单属性
             */
            UserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId())
                    /**
                     * 获取流程元素,这里是要传什么呢?这里实际上是要传任务的key的,在 task 里并没有任务的 key 的
                     * 在 activiti 6 中是有任务的 key 的,不过我们可以用另外一个方案,我们可以用表单的 key ,这里
                     * 我们可以表表单的 key 和任务的 key 启成一模一样的名字,这样你拿表单的 key 就相当于拿任务的 key
                     * 了
                     */
                    .getFlowElement(task.getFormKey());
            // 说明该环节是不需要表单的
            if(userTask == null){
                return AjaxResponse.AjaxData(
                        GlobalConfig.ResponseCode.SUCCESS.getCode(),
                        GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                        "无表单"
                );
            }

            List<FormProperty> formProperties = userTask.getFormProperties();
            // 保存分割后的格式返回给前端
            List<Map<String,Object>> listMap = new ArrayList<Map<String, Object>>();
            for (FormProperty formProperty : formProperties) {
                // 分割表单数据
                String[] split = formProperty.getId().split("-_-");
                Map<String,Object> form = new HashMap<String,Object>();
                form.put("id", split[0]);
                form.put("controlType", split[1]);
                form.put("controlLabel", split[2]);

                // 如果默认值是之前任意表单控件的 id ,那么我们应该读取之前表单的值
                if(split[3].startsWith("FormProperty_")){
                    // 是表单数据  从之前的保存的所有控件历史数据字典中读取
                    // 在这里  split[3] 拿到的就是历史表单的 key
                    if(controlListMap.containsKey(split[3])){
                        form.put("controlDefValue", controlListMap.get(split[3]));
                    }else{
                        // 如果字典中不存在给出错误提示
                        form.put("controlDefValue", "读取失败,检查"+split[3]+"配置");
                    }
                }else{
                    // 不是之前表单数据
                    form.put("controlDefValue", split[3]);
                }

                form.put("controlParam", split[4]);
                listMap.add(form);
            }
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    listMap
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "渲染动态表单失败",
                    e.toString()
            );
        }
    }

高亮历史流程渲染

效果

image.png

先画流程图,上传,部署,启动和之前的一样,这里略过

image.png

获取需要高亮的连线 id 编号方法

// 获取一条流程实例历史
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(instanceId)
                    .singleResult();
            // 根据流程定义 key 获取 BMPN
            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 获取流程
            Process process = bpmnModel.getProcesses().get(0);
            // 获取所有流程 FlowElement 的信息,就是所有bpmn的节点
            Collection<FlowElement> flowElements = process.getFlowElements();

            /**
             * 这个 map 中的 key 就是 开始节点和结束节点的编号拼起来的字符串,
             * value 就是开始节点和结束节点的连线,到时候我们根据开始节点和结束节点
             * 就可以获取到需要高亮的连线
             */
            Map<String,String> map = new HashMap<String,String>();
            for (FlowElement flowElement : flowElements) {
                // 判断是否是线条
                if(flowElement instanceof SequenceFlow){
                    SequenceFlow sequenceFlow = (SequenceFlow)flowElement;
                    String ref = sequenceFlow.getSourceRef();
                    String targetRef = sequenceFlow.getTargetRef();
                    /**
                     * 保存开始节点和结束节点,与它们之间连线的对应关系
                     * key: 开始节点 编号 + 结束节点 编号
                     * value: 连线编号
                     */
                    map.put(ref+targetRef,sequenceFlow.getId());
                }
            }

            /**
             * 获取已经完成的全部流程历史节点
             */
            List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .list();
            /**
             * 将各个历史节的开始节点和结束几点的编号两两对应起来,
             * 就可以从上面的 map 中获取到需要高亮的连线
             */
            Set<String> keyList = new HashSet<String>();
            for (HistoricActivityInstance i : list) {
                for (HistoricActivityInstance j : list) {
                    if(i != j){
                        keyList.add(i.getActivityId()+j.getActivityId());
                    }
                }
            }

            // 获取高亮连线 id
            Set<String> highLine = new HashSet<String>();
            // 根据已经完成的(开始节点编号+结束节点编号)组成的 key 获取需要高亮的连线编号
            keyList.forEach(s -> highLine.add(map.get(s)));
image.png
最终获取到的连线,因为 任务1 我已经执行过了所以获取到的是两根连线编号
image.png

获取需要高亮已经完成的任务节点编号方法

            // 获取已经完成的节点
            List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .finished()
                    .list();

            // 已经完成的节点高亮
            Set<String> highPoint = new HashSet<>();
            // 保存已经完成的流程节点编号
            listFinished.forEach(s -> highPoint.add(s.getActivityId()));
这时获取到的是 开始节点 和 任务1 节点,因为任务一我已经执行过了
image.png

获取需要高亮下一步我要代办执行的任务节点编号方法

            // 获取代办节点
            List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .unfinished()
                    .list();

            // 代办的节点高亮
            Set<String> waitingToDo = new HashSet<>();
            // 保存需要代办的节点编号
            listUnFinished.forEach(s -> waitingToDo.add(s.getActivityId()));
这时获取到的就是 任务2 节点编号,因为 任务1 执行完之后就流转到 任务2 了
image.png

获取当前用户已经完成的任务节点编号

            // 获取当前用户完成的任务
            List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
                    .taskAssignee(userInfoBean.getUsername())
                    .processInstanceId(instanceId)
                    .finished()
                    .list();

            // 当前用户完成的高亮
            Set<String> iDo = new HashSet<String>();
            // 保存用户完成的节点编号
            taskInstanceList.forEach(s -> iDo.add(s.getTaskDefinitionKey()));
当前用户完成了 任务1
image.png
最终得到的数据
{
    "status": 0,
    "msg": "成功",
    "obj": {
        "waitingToDo": [
            "Activity_2"
        ],
        "highPoint": [
            "StartEvent_1",
            "Activity_1"
        ],
        "iDo": [
            "Activity_1"
        ],
        "highLine": [
            null,
            "Flow_2",
            "Flow_1"
        ]
    }
}

完整方法

    /**
     * 高亮显示路程历史
     * @param instanceId     流程实例id
     * @param userInfoBean   用户信息
     * @return
     */
    @GetMapping("/getHighlight")
    public AjaxResponse getHighlight(@RequestParam("instanceId")String instanceId,
                                     @AuthenticationPrincipal UserInfoBean userInfoBean){
        try {
            // 获取一条流程实例历史
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(instanceId)
                    .singleResult();
            // 根据流程定义 key 获取 BMPN
            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 获取流程
            Process process = bpmnModel.getProcesses().get(0);
            // 获取所有流程 FlowElement 的信息,就是所有bpmn的节点
            Collection<FlowElement> flowElements = process.getFlowElements();

            /**
             * 这个 map 中的 key 就是 开始节点和结束节点的编号拼起来的字符串,
             * value 就是开始节点和结束节点的连线,到时候我们根据开始节点和结束节点
             * 就可以获取到需要高亮的连线
             */
            Map<String,String> map = new HashMap<String,String>();
            for (FlowElement flowElement : flowElements) {
                // 判断是否是线条
                if(flowElement instanceof SequenceFlow){
                    SequenceFlow sequenceFlow = (SequenceFlow)flowElement;
                    String ref = sequenceFlow.getSourceRef();
                    String targetRef = sequenceFlow.getTargetRef();
                    /**
                     * 保存开始节点和结束节点,与它们之间连线的对应关系
                     * key: 开始节点 编号 + 结束节点 编号
                     * value: 连线编号
                     */
                    map.put(ref+targetRef,sequenceFlow.getId());
                }
            }

            /**
             * 获取已经完成的全部流程历史节点
             */
            List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .list();
            /**
             * 将各个历史节的开始节点和结束几点的编号两两对应起来,
             * 就可以从上面的 map 中获取到需要高亮的连线
             */
            Set<String> keyList = new HashSet<String>();
            for (HistoricActivityInstance i : list) {
                for (HistoricActivityInstance j : list) {
                    if(i != j){
                        keyList.add(i.getActivityId()+j.getActivityId());
                    }
                }
            }

            // 获取高亮连线 id
            Set<String> highLine = new HashSet<String>();
            // 根据已经完成的(开始节点编号+结束节点编号)组成的 key 获取需要高亮的连线编号
            keyList.forEach(s -> highLine.add(map.get(s)));


            // 获取已经完成的节点
            List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .finished()
                    .list();

            // 已经完成的节点高亮
            Set<String> highPoint = new HashSet<>();
            // 保存已经完成的流程节点编号
            listFinished.forEach(s -> highPoint.add(s.getActivityId()));

            // 获取代办节点
            List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId)
                    .unfinished()
                    .list();

            // 代办的节点高亮
            Set<String> waitingToDo = new HashSet<>();
            // 保存需要代办的节点编号
            listUnFinished.forEach(s -> waitingToDo.add(s.getActivityId()));


            // 获取当前用户完成的任务
            List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
                    .taskAssignee(userInfoBean.getUsername())
                    .processInstanceId(instanceId)
                    .finished()
                    .list();

            // 当前用户完成的高亮
            Set<String> iDo = new HashSet<String>();
            // 保存用户完成的节点编号
            taskInstanceList.forEach(s -> iDo.add(s.getTaskDefinitionKey()));

            Map<String,Object> reMap = new HashMap<String, Object>();
            // 高亮已经完成的节点
            reMap.put("highPoint",highPoint);
            // 高亮连线节点编号
            reMap.put("highLine", highLine);
            // 高亮代办节点编号
            reMap.put("waitingToDo", waitingToDo);
            // 高亮当前用户完成的节点编号
            reMap.put("iDo",iDo);

            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    reMap
            );
        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "高亮历史任务失败",
                    e.toString()
            );
        }
    }

bpmn js 扩展下载

在 bpmnjs 目录下 app 目录下的 index.html 中添加导出按钮

    <li>
      <a id="downloadBPNM" href title="download as SVG image">
        导出
      </a>
    </li>

创建 tools.js

image.png
import $ from 'jquery'
const proHost = window.location.protocol + "//" + window.location.host;
const href = window.location.href.split("bpmnjs")[0];
const key = href.split(window.location.host)[1];
const publicurl = proHost + key;
const tools = {
    /**
     * 下载方法
     * @param bpmnModeler
     */
    download(bpmnModeler){
        var downloadLink = $("#downloadBPNM");
        bpmnModeler.saveXML({format:true},function(err,xml){
            if(err){
                return console.error("could not save bpmn",err);
            }
            tools.setEncoded(downloadLink,"digaram.bpmn",xml);
        });
    },
    /**
     *
     * @param link  下载的按钮
     * @param name  下载的名字
     * @param data  下载的数据
     */
    setEncoded(link, name, data) {
     var encodedData = encodeURIComponent(data);

        if (data) {
            link.addClass('active').attr({
                'href': 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData,
                'download': name
            });
        } else {
            link.removeClass('active');
        }
    }
}

export default tools

在 index.html 同级目录的 index.js 中引入创建的 tools.js

image.png

在 index.html 同级目录的 index.js 添加导出按钮的点击事件,bpmnModeler 就是 bpmn 的模型

  $("#downloadBPNM").on('click',function(){
    tools.download(bpmnModeler);
  });
image.png

测试 正常导出

image.png

在线部署 bpmn

index.html 中添加部署按钮

    <li>
      <a id="saveBPNM" href>
        部署
      </a>
    </li>

index.js 中添加部署的点击事件

// 部署 bpmn
  $("#saveBPNM").on('click',function(){
    tools.saveBPMN(bpmnModeler);
  });

tools.js 中添加在线部署的方法

/**
     * 部署方法
     * @param bpmnModeler
     */
    saveBPMN(bpmnModeler){
        var downloadLink = $("#downloadBPNM");
        bpmnModeler.saveXML({format:true},function(err,xml){
            if(err){
                return console.error("could not save bpmn",err);
            }
            console.info(xml)
            // 参数就是 bpmn xml 字符串
            var param = {
                "xmlBPMN": xml
            };
            // 调用后台接口上传bpmn
            $.ajax({
                url: publicurl + "processDefinition/addDeploymentByString",
                type: "post",
                dataType: "json",
                data: param,
                success: function(res){
                    if(res.status == 0){
                        alert("部署成功");
                    }else{
                        alert("部署失败");
                    }
                },
                error: function(err){
                    console.info(err);
                }
            });
        });
    }

上传 BPMN 并展示

index.html 添加导入按钮

    <!--  导入 bpmn  -->
    <li>
      <form id="form" name="myForm" onsubmit="return false" method="post" enctype="multipart/form-data" title="上传文件">
        <input type="file" name="uploadFile" id="uploadFile" accept=".bpmn" style="display:none"/>
        <label class="label" for="uploadFile">导入</label>
      </form>
    </li>

index.js 添加导入按钮变事件

  // 上传 bpmn
  $("#uploadFile").on("change",function(i){
    tools.uploadBPMN(bpmnModeler);
  });

tools.js 中编写 uploadBPMN 上传 bpmn 方法

    /**
     * 上传 bpmn
     */
    uploadBPMN(bpmnModeler){
        // 获取文件
        var fileUpload = document.myForm.uploadFile.files[0];
        // 创建 FormData 对象
        var fm = new FormData();
        fm.append("processFile",fileUpload)
        $.ajax({
            url: publicurl + "processDefinition/uploadBPMN",
            type: "post",
            data: fm,
            async: false,
            contentType: false,
            processData: false,
            success: function(res){
                if(res.status == 0){
                    var url = publicurl + "bpmn/" + res.obj;
                    // 打开上传的 bpmn
                    tools.openBPMN_URL(bpmnModeler,url);
                }else{
                    alert(res.msg);
                }
            }
        });
    },
    /**
     * 打开上传的 bpmn
     * @param url
     */
    openBPMN_URL(bpmnModeler,url){
        $.ajax(url,{dataType: "text"}).done(
            // 返回 xml 文件
            async function (xml) {
                try {
                    // 导入 xml
                    await bpmnModeler.importXML(xml);

                }catch (e) {
                    console.error(e);
                }
            });
    }

编写后台上传接口,uploadBpmnPath 是上传的文件目录在 yml 中配置的换成自己的就可以了

    /**
     * 添加流程定义通过在线提交 BPMN 的 xml
     * @param processFile
     * @return
     */
    @PostMapping("/uploadBPMN")
    public AjaxResponse addDeploymentByString(HttpServletRequest request,
                                              @RequestParam("processFile") MultipartFile processFile
                                              ){
        try{
            if(processFile.isEmpty()){
                return AjaxResponse.AjaxData(
                        GlobalConfig.ResponseCode.ERROR.getCode(),
                        GlobalConfig.ResponseCode.ERROR.getDesc(),
                        "BPMN 不能为空"
                );
            }
            // 获取原始文件名
            String originFileName = processFile.getOriginalFilename();
            // 获取文件后缀
            String suffixName = originFileName.substring(originFileName.lastIndexOf("."));
            // 新的文件名
            String fileName = UUID.randomUUID() + suffixName;
            // 上传文件的路径
            File filePath = new File(uploadBpmnPath+fileName);
            if(!filePath.getParentFile().exists()){
                filePath.getParentFile().mkdirs();
            }
            // 上传
            processFile.transferTo(filePath);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    fileName
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "上传 BPMN 失败",
                    e.getMessage()
            );
        }
    }
}

uploadBpmnPath 上传的目录,yml 中 通过 @Value("${uploadBpmnPath}") 引用

# bpmn 文件上传路径
upload:
  bpmn:
    path: G:/gu-pao/activiti7_workflow/src/main/resources/resources/bpmn/

上传的文件目录映射

@Configuration
public class PathMapping implements WebMvcConfigurer {

    @Value("${upload.bpmn.path}")
    private String uploadBpmnPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 添加默认映射
        registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/");
        // 添加 bpmn 路径映射
        registry.addResourceHandler("/bpmn/**")
                .addResourceLocations("file:"+uploadBpmnPath.replace("/","\\"));
    }
}

测试导入本地 bpmn,成功上传并回显

image.png

源码地址

https://gitee.com/jitashou18089237297/activiti-integrates-bpmnjs.git

上一篇下一篇

猜你喜欢

热点阅读