精彩文章免费看

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

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

  • 我们在上面的渲染动态表单接口 (formDataShow)方法中进行扩充,大致思路是在渲染表单之前会根据 taskId 查出流程 id,并且根据流程实例 id 查出所有表单填写的值,将这些值构建成一个 hashMap 字典,这样就是一个 key-value 的形式,那么接下来如果我们的表单默认值如果不是一个字符串而是一个以 FormProperty 开头的其他表单的名称,我们就从这个字典里面通过 key 将之前存的值读取出来并且渲染出来,这就是我们的大致思路
  • 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 环节读取出来
    李四自己的表单
    FormProperty_0gvpb9n-_-string-_-姓名-_-我是张三-_-f
    读取张三表单填写的内容,FormProperty_0c4etf8 这个保存的就是张三表单的key 通过它就可以读取到张三填写的值
    FormProperty_11muvfh-_-string-_-姓别-_-FormProperty_0c4etf8-_-f
    bpmn js 创建好之后,上传,部署,略过......

    张三填写数据的任务

    @GetMapping("/formDataShow") public AjaxResponse formDataShow(@RequestParam("taskId")String taskId){ // 这是 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()

    高亮历史流程渲染

    获取需要高亮的连线 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)));
    

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

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

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

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

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

                // 获取当前用户完成的任务
                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
    @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 中添加导出按钮

    <a id="downloadBPNM" href title="download as SVG image">

    创建 tools.js

    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

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

      $("#downloadBPNM").on('click',function(){
        tools.download(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  -->
          <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=""/>
            <label class="label" for="uploadFile">导入</label>
          </form>
    

    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 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,成功上传并回显

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

    最后编辑于:2021-01-14 16:07