懒羊羊
2023-11-14 8286c62256f23bc2367a6729c0f46f84215e380b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
package cn.stylefeng.guns.workflow.modular.controller.base;
 
import cn.stylefeng.guns.sys.core.util.FileDownload;
import cn.stylefeng.guns.workflow.core.consts.ActConst;
import cn.stylefeng.guns.workflow.core.consts.BpmsActivityTypeEnum;
import cn.stylefeng.guns.workflow.core.util.DelFileUtil;
import cn.stylefeng.guns.workflow.core.util.FileUpload;
import cn.stylefeng.guns.workflow.core.util.PathUtil;
import cn.stylefeng.guns.workflow.core.util.UtilMisc;
import cn.stylefeng.roses.core.base.controller.BaseController;
import cn.stylefeng.roses.core.util.ToolUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
 
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.util.*;
import java.util.zip.ZipInputStream;
 
/**
 * 流程管理总类
 *
 * @author fengshuonan
 * @Date 2019/8/5 22:29
 */
public class ActBaseController extends BaseController {
 
    /**
     * 流程引擎对象
     */
    @Autowired
    private ProcessEngine processEngine;
 
    /**
     * 管理流程定义  与流程定义和部署对象相关的Service
     */
    @Autowired
    private RepositoryService repositoryService;
 
    /**
     * 与正在执行的流程实例和执行对象相关的Service(执行管理,包括启动、推进、删除流程实例等操作)
     */
    @Autowired
    private RuntimeService runtimeService;
 
    /**
     * 任务管理 与正在执行的任务管理相关的Service
     */
    @Autowired
    private TaskService taskService;
 
    /**
     * 历史管理(执行完的数据的管理)
     */
    @Autowired
    private HistoryService historyService;
 
    /**
     * 添加流程模型并返回modelId
     *
     * @param processId     流程唯一标识key
     * @param processAuthor 流程作者
     * @param name          流程名称
     * @param modelname     模型名称
     * @param description   模型描述
     * @param category      模型分类
     * @return
     * @throws UnsupportedEncodingException
     */
    protected String createModel(String processId, String processAuthor, String name, String modelname, String description, String category) throws UnsupportedEncodingException {
 
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvs");
        editorNode.put("resourceId", "canvs");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
 
        //命名空间(禁止修改)
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
 
        //流程节点作者
        stencilSetNode.put("author", "");
        editorNode.set("stencilset", stencilSetNode);
        ObjectNode propertiesNode = objectMapper.createObjectNode();
 
        //流程唯一标识
        propertiesNode.put("process_id", processId);
 
        //流程作者
        propertiesNode.put("process_author", processAuthor);
 
        //流程名称
        propertiesNode.put("name", name);
        editorNode.set("properties", propertiesNode);
 
        ObjectNode modelObjectNode = objectMapper.createObjectNode();
 
        //模型名称
        modelObjectNode.put("name", modelname);
 
        //模型版本
        modelObjectNode.put("revision", 1);
 
        //模型描述
        modelObjectNode.put("description", description);
        Model modelData = repositoryService.newModel();
 
        //模型分类
        modelData.setCategory(category);
        modelData.setDeploymentId(null);
        modelData.setKey(null);
        modelData.setMetaInfo(modelObjectNode.toString());
 
        //模型名称
        modelData.setName(modelname);
        modelData.setTenantId("");
        modelData.setVersion(1);
 
        //保存模型,存储数据到表:act_re_model 流程设计模型部署表
        repositoryService.saveModel(modelData);
 
        //保存资源,存储数据到表:act_ge_bytearray 二进制数据表
        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
 
        return modelData.getId();
    }
 
    /**
     * 从流程定义映射模型
     *
     * @param processDefinitionId 流程定义ID
     * @throws UnsupportedEncodingException
     * @throws XMLStreamException
     */
    protected Model saveModelFromPro(String processDefinitionId) throws Exception {
 
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
 
        BpmnJsonConverter converter = new BpmnJsonConverter();
        ObjectNode modelNode = converter.convertToJson(bpmnModel);
        Model modelData = repositoryService.newModel();
 
        //唯一标识
        modelData.setKey(processDefinition.getKey());
 
        //名称
        modelData.setName(processDefinition.getName() + "(反射)");
 
        //分类,默认行政审批分类
        modelData.setCategory("00102");
        modelData.setDeploymentId(processDefinition.getDeploymentId());
 
        //版本
        modelData.setVersion(Integer.parseInt(String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count() + 1)));
 
        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());
 
        //保存模型
        repositoryService.saveModel(modelData);
 
        //保存资源
        repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
 
        return modelData;
    }
 
    /**
     * 通过模型ID获取流程基本信息
     *
     * @param modelId 流程ID
     * @return
     * @throws JsonProcessingException
     * @throws UnsupportedEncodingException
     * @throws IOException
     */
    protected Map<String, String> getProcessProperties(String modelId) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
                new String(repositoryService.getModelEditorSource(modelId), "utf-8")).get("properties");
        Map<String, String> map = new HashMap<>();
 
        //流程唯一标识(KEY)
        map.put("process_id", editorJsonNode.get("process_id").toString());
 
        //流程作者
        map.put("process_author", editorJsonNode.get("process_author").toString());
 
        //流程名称
        map.put("name", editorJsonNode.get("name").toString());
        return map;
    }
 
    /**
     * 删除模型
     *
     * @param modelId 模型ID
     */
    protected void deleteModel(String modelId) {
 
        //act_re_model 和  act_ge_bytearray 两张表中相关数据都删除
        repositoryService.deleteModel(modelId);
    }
 
    /**
     * 根据模型ID导出xml文件
     *
     * @param modelId 模型ID
     * @throws Exception
     */
    protected void exportXmlFromModelId(HttpServletResponse response, String modelId) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(modelNode);
        BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
        byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
        ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
        //IOUtils.copy(in, response.getOutputStream());
        String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
        /*response.setHeader("Content-Disposition", "attachment; filename=" + filename);  
        response.flushBuffer(); */
        FileUpload.copyFile(in, PathUtil.getProjectpath() + ActConst.FILEACTIVITI, filename);
 
        //把文件上传到文件目录里面
        FileDownload.fileDownload(response, PathUtil.getProjectpath() + ActConst.FILEACTIVITI + filename, filename);
        in.close();
    }
 
    /**
     * 根据模型ID预览xml文件
     *
     * @param modelId 模型ID
     * @throws Exception
     */
    protected String viewXmlFromModelId(String modelId) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(modelNode);
        BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
        byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
        ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
        InputStreamReader isr = new InputStreamReader(in, "utf-8");
        BufferedReader bufferedReader = new BufferedReader(isr);
        StringBuffer xmlContent = new StringBuffer();
        String lineTxt = null;
        while ((lineTxt = bufferedReader.readLine()) != null) {
            xmlContent.append(lineTxt);
            xmlContent.append("\n");
        }
        isr.close();
        return xmlContent.toString();
    }
 
    /**
     * 判断能否正常根据模型ID导出xml文件(当没有画流程图的时候会报异常)
     *
     * @param response
     * @param modelId  模型ID
     * @throws Exception
     */
    protected void isCanexportXmlFromModelId(HttpServletResponse response, String modelId) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(modelNode);
        BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
        xmlConverter.convertToXML(bpmnModel);
    }
 
    /**
     * 部署流程定义(根据modelId部署)
     *
     * @param modelId 模型ID
     * @return 部署ID
     */
    protected String deploymentProcessDefinitionFromModelId(String modelId) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
        byte[] bpmnBytes = null;
        BpmnJsonConverter BpmnJsonConverter = new BpmnJsonConverter();
        BpmnModel model = BpmnJsonConverter.convertToBpmnModel(modelNode);
        bpmnBytes = new BpmnXMLConverter().convertToXML(model);
        String processName = modelData.getName() + ".bpmn20.xml";
 
        //部署名称
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().name(modelData.getName());
 
        //完成部署
        Deployment deployment = deploymentBuilder.addString(processName, new String(bpmnBytes, "utf-8")).deploy();
 
        //部署ID
        return deployment.getId();
    }
 
    /**
     * 部署流程定义(从Classpath)
     *
     * @param name    部署名称
     * @param xmlpath xml文件路径
     * @param pngpath png文件路径
     * @return 部署ID
     */
    protected String deploymentProcessDefinitionFromClasspath(String name, String xmlpath, String pngpath) {
 
        //创建部署对象
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
 
        //部署名称
        deploymentBuilder.name(name);
 
        //从文件中读取xml资源
        deploymentBuilder.addClasspathResource(xmlpath);
 
        //从文件中读取png资源
        deploymentBuilder.addClasspathResource(pngpath);
 
        //完成部署
        Deployment deployment = deploymentBuilder.deploy();
 
        //部署ID
        return deployment.getId();
    }
 
    /**
     * 部署流程定义(从zip压缩包)
     *
     * @param name    部署名称
     * @param zippath zip文件路径
     * @return 部署ID
     * @throws FileNotFoundException
     */
    protected String deploymentProcessDefinitionFromZip(String name, String zippath) throws Exception {
        File outfile = new File(zippath);
        FileInputStream inputStream = new FileInputStream(outfile);
        ZipInputStream ipInputStream = new ZipInputStream(inputStream);
 
        //创建部署对象
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
 
        //部署名称
        deploymentBuilder.name(name);
        deploymentBuilder.addZipInputStream(ipInputStream);
 
        //完成部署
        Deployment deployment = deploymentBuilder.deploy();
        ipInputStream.close();
        inputStream.close();
 
        //部署ID
        return deployment.getId();
    }
 
    /**
     * 部署流程定义(从InputStream)
     *
     * @param name     部署名称
     * @param filepath 文件路径
     * @return 部署ID
     * @throws FileNotFoundException
     */
    protected String deploymentProcessDefinitionFromInputStream(String name, String filepath) throws Exception {
 
        if (ToolUtil.isEmpty(name)) {
            name = "无名称流程";
        }
 
        //末尾需要加上.bpmn20.xml
        name = name + ".bpmn20.xml";
 
        InputStream inputStream = new FileInputStream(filepath);
 
        //创建部署对象
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
 
        //部署名称
        deploymentBuilder.name(name);
        deploymentBuilder.addInputStream(name, inputStream);
 
        //完成部署
        Deployment deployment = deploymentBuilder.deploy();
        inputStream.close();
 
        //部署ID
        return deployment.getId();
    }
 
    /**
     * 根据流程定义的部署ID生成XML和PNG
     *
     * @param deploymentId 部署ID
     * @throws IOException
     */
    protected void createXmlAndPng(String deploymentId) throws IOException {
 
        //生成先清空之前生成的文件
        DelFileUtil.delFolder(PathUtil.getProjectpath() + "uploadFiles/activitiFile");
 
        List<String> names = repositoryService.getDeploymentResourceNames(deploymentId);
        for (String name : names) {
            if (name.indexOf("zip") != -1) {
                continue;
            }
            InputStream in = repositoryService.getResourceAsStream(deploymentId, name);
 
            //把文件上传到文件目录里面
            FileUpload.copyFile(in, PathUtil.getProjectpath() + ActConst.FILEACTIVITI, name);
            in.close();
        }
    }
 
    /**
     * 删除部署的流程
     *
     * @param deploymentId 部署ID
     * @throws IOException
     */
    protected void deleteDeployment(String deploymentId) throws Exception {
 
        //不带级联的删除,此删除只能删除没有启动的流程,否则抛出异常 .act_re_deployment,act_re_procdef 和  act_ge_bytearray 三张表中相关数据都删除
        //repositoryService.deleteDeployment(DEPLOYMENT_ID_);
 
        //级联删除,不管流程是否启动,都可以删除
        repositoryService.deleteDeployment(deploymentId, true);
    }
 
    /**
     * 激活流程定义
     *
     * @param deploymentId 流程定义ID
     */
    protected void activateProcessDefinitionById(String deploymentId) {
        repositoryService.activateProcessDefinitionById(deploymentId, true, null);
    }
 
    /**
     * 挂起流程定义
     *
     * @param deploymentId 流程定义ID
     */
    protected void suspendProcessDefinitionById(String deploymentId) {
        repositoryService.suspendProcessDefinitionById(deploymentId, true, null);
    }
 
    /**
     * 获取发起人
     *
     * @param procInstId 流程实例ID
     */
    protected String getInitiator(String procInstId) {
 
        //获取历史流程实例
        HistoricProcessInstance hip = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
 
        //获取流程中已经执行的节点,按照执行先后顺序排序
        List<HistoricActivityInstance> hais = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId)
                .orderByHistoricActivityInstanceId().asc().list();
 
        // 获取bpmnModel
        BpmnModel bpmnModel = repositoryService.getBpmnModel(hip.getProcessDefinitionId());
 
        //全部活动实例
        List<FlowNode> historicFlowNodeList = new LinkedList<>();
        for (HistoricActivityInstance hai : hais) {
            historicFlowNodeList.add((FlowNode) bpmnModel.getMainProcess().getFlowElement(hai.getActivityId(), true));
            if (hai.getAssignee() != null) {
 
                //不为空的第一个节点办理人就是发起人
                return hai.getAssignee();
            }
        }
        return null;
    }
 
    /**
     * 设置流程变量(绑定任务)用Map形式
     *
     * @param taskId 任务ID
     */
    protected void setVariablesByTaskIdAsMap(String taskId, Map<String, Object> map) {
        taskService.setVariablesLocal(taskId, map);
    }
 
    /**
     * 指派任务的代理人
     *
     * @param Assignee 代理人
     * @param taskId   任务ID
     */
    protected void setAssignee(String taskId, String Assignee) {
        taskService.setAssignee(taskId, Assignee);
    }
 
    /**
     * 设置流程变量(不绑定任务)
     *
     * @param taskId 任务ID
     */
    protected void setVariablesByTaskId(String taskId, String key, String value) {
        taskService.setVariable(taskId, key, value);
    }
 
    /**
     * 完成任务
     *
     * @param taskId 任务ID
     */
    protected void completeMyPersonalTask(String taskId) {
        taskService.complete(taskId);
    }
 
    /**
     * 移除流程变量(从正在运行中)
     *
     * @param procInstId 流程实例ID
     */
    protected void removeVariablesByPROC_INST_ID_(String procInstId, String key) {
        runtimeService.removeVariable(procInstId, key);
    }
 
    /**
     * 获取流程变量
     *
     * @param taskId 任务ID
     * @param key    键
     */
    protected Object getVariablesByTaskIdAsMap(String taskId, String key) {
        return taskService.getVariable(taskId, key);
    }
 
    /**
     * 生成当前任务节点流程图片PNG
     *
     * @param procInstId 流程实例ID
     * @param filename   图片名称
     * @throws IOException
     */
    protected void createXmlAndPngAtNowTask(String procInstId, String filename) throws IOException {
 
        //生成先清空之前生成的文件
        DelFileUtil.delFolder(PathUtil.getProjectpath() + "uploadFiles/activitiFile");
 
        //把文件上传到文件目录里面
        InputStream in = null;
        try {
            in = getResourceDiagramInputStream(procInstId);
            FileUpload.copyFile(in, PathUtil.getProjectpath() + ActConst.FILEACTIVITI, filename);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }
 
    /**
     * 获取当前任务流程图片的输入流
     *
     * @param procInstId 流程实例ID
     * @return
     */
    private InputStream getResourceDiagramInputStream(String procInstId) {
        try {
            //获取历史流程实例
            HistoricProcessInstance hip = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
 
            //获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> hai = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId)
                    .orderByHistoricActivityInstanceId().asc().list();
 
            // 构造已执行的节点ID集合
            List<String> executedActivityIdList = new ArrayList<>();
            for (HistoricActivityInstance activityInstance : hai) {
                executedActivityIdList.add(activityInstance.getActivityId());
            }
 
            // 获取bpmnModel
            BpmnModel bpmnModel = repositoryService.getBpmnModel(hip.getProcessDefinitionId());
 
            // 获取流程已发生流转的线ID集合
            List<String> flowIds = this.getExecutedFlows(bpmnModel, hai);
            ProcessDiagramGenerator processDiagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
            return processDiagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", null, 2.0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
 
    /**
     * 作废流程
     *
     * @param processId 流程实例ID
     * @param reason    作废原因
     * @throws Exception
     */
    protected void deleteProcessInstance(String processId, String reason) {
        runtimeService.deleteProcessInstance(processId, reason);
    }
 
    /**
     * 获取流程已发生流转的线ID集合
     *
     * @param bpmnModel
     * @param historicActivityInstances 历史流程实例list
     * @return
     */
    private List<String> getExecutedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
 
        //流转线ID集合
        List<String> flowIdList = new ArrayList<>();
 
        //全部活动实例
        List<FlowNode> historicFlowNodeList = new LinkedList<>();
 
        //已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstanceList = new LinkedList<>();
 
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            historicFlowNodeList.add((FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true));
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstanceList.add(historicActivityInstance);
            }
        }
        //遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
        FlowNode currentFlowNode = null;
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstanceList) {
 
            //获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
 
            List<SequenceFlow> sequenceFlowList = currentFlowNode.getOutgoingFlows();
 
            /**
             * 遍历outgoingFlows并找到已流转的
             * 满足如下条件任务已流转:
             * 1.当前节点是并行网关或包含网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
             * 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最近的流转节点视为有效流转
             */
            FlowNode targetFlowNode = null;
            if (BpmsActivityTypeEnum.PARALLEL_GATEWAY.getType().equals(currentActivityInstance.getActivityType())
                    || BpmsActivityTypeEnum.INCLUSIVE_GATEWAY.getType().equals(currentActivityInstance.getActivityType())) {
 
                //遍历历史活动节点,找到匹配Flow目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlowList) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicFlowNodeList.contains(targetFlowNode)) {
                        flowIdList.add(sequenceFlow.getId());
                    }
                }
            } else {
                List<Map<String, String>> tempMapList = new LinkedList<>();
 
                //遍历历史活动节点,找到匹配Flow目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlowList) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            tempMapList.add(UtilMisc.toMap("flowId", sequenceFlow.getId(), "activityStartTime", String.valueOf(historicActivityInstance.getStartTime().getTime())));
                        }
                    }
                }
                String flowId = null;
                for (Map<String, String> map : tempMapList) {
                    flowId = map.get("flowId");
                    flowIdList.add(flowId);
                }
            }
        }
        return flowIdList;
    }
 
    /**
     * 删除历史流程
     *
     * @param procInstId 流程实例ID
     */
    protected void deleteHiProcessInstance(String procInstId) {
        historyService.deleteHistoricProcessInstance(procInstId);
    }
}