update 流程图,会签

This commit is contained in:
lijiarao
2023-09-05 17:58:13 +08:00
parent 3388e8976e
commit 9d7b57493a
19 changed files with 1676 additions and 5905 deletions
@@ -0,0 +1,34 @@
package com.jero.modules.activiti.config;
import com.jero.modules.activiti.service.impl.image.ICustomProcessDiagramGenerator;
import org.activiti.engine.impl.form.DoubleFormType;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author liJiaRao
* @date 2023-09-05 17:53
*/
public class ActivitiConfig implements ProcessEngineConfigurationConfigurer {
@Autowired
private ICustomProcessDiagramGenerator customProcessDiagramGenerator;
@Autowired
private DoubleFormType doubleFormType;
/**
* 解決工作流生成图片乱码问题
*
* @param processEngineConfiguration
*/
@Override
public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
int[] result = {};
processEngineConfiguration.setActivityFontName("宋体");
processEngineConfiguration.setAnnotationFontName("宋体");
processEngineConfiguration.setLabelFontName("宋体");
processEngineConfiguration.setProcessDiagramGenerator(customProcessDiagramGenerator);
}
}
@@ -0,0 +1,29 @@
package com.jero.modules.activiti.config;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.TaskListener;
import java.util.List;
/**
* @author liJiaRao
* @date 2023-09-05 14:54
*/
@Slf4j
public class MultiInstanceTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
DelegateExecution execution = delegateTask.getExecution();
System.out.println("流程实例总数:" + execution.getVariable("nrOfInstances"));
System.out.println("当前活动的流程实例总数:" + execution.getVariable("nrOfActiveInstances"));
System.out.println("已经完成实例的数目:" + execution.getVariable("nrOfCompletedInstances"));
System.out.println("leader" + execution.getVariable("leader"));
System.out.println("---------------------------------------分隔线----------------------------------------------------");
}
}
@@ -0,0 +1,17 @@
package com.jero.modules.activiti.config;
import org.activiti.engine.impl.cfg.IdGenerator;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author liJiaRao
* @date 2023-09-05 17:39
*/
public class MyGen implements IdGenerator {
private AtomicLong aLong = new AtomicLong(100);
@Override
public synchronized String getNextId() {
return String.valueOf(aLong.incrementAndGet());
}
}
@@ -0,0 +1,130 @@
package com.jero.modules.activiti.config;
import java.awt.*;
/**
* 常量类
*@author jjd
**/
public final class WorkflowConstants {
/**businessKey**/
public static final String WORKLOW_BUSINESS_KEY = "businessKey";
/**按钮网关**/
public static final String WAY_TYPE = "wayType";
/**按钮网关**/
public static final String WAY_TYPE_PREFIX = "way_type_";
/**项目id**/
public static final String PROJECT_ID = "projectId";
/**核心企业Id变量**/
public static final String CORE_ENTERPRISE_ID="coreEnterpriseId";
/**链属企业Id变量**/
public static final String CHAIN_ENTERPRISE_ID="chainEnterpriseId";
/**银行企业Id变量**/
public static final String BANK_ENTERPRISE_ID="bankEnterpriseId";
/**保理公司Id变量**/
public static final String BAOLI_ENTERPRISE_ID="baoliEnterpriseId";
/**立账开立企业Id变量**/
public static final String START_ENTERPRISE_ID="startEnterpriseId";
/**立账合作企业Id变量**/
public static final String PARTNER_ENTERPRISE_ID="partnerEnterpriseId";
/**母公司企业id**/
public static final String PARENT_ENTERPRISE_ID="parentEnterpriseId";
/**指定签收企业id**/
public static final String RECEIVE_ENTERPRISE_ID ="receiveEnterpriseId";
/**转出方企业Id变量**/
public static final String TRANSFER_ENTERPRISE_ID="transEnterpriseId";
/**指定签收企业id**/
public static final String AC_TASK_ID ="acTaskId";
/**企业所有角色**/
public static final String ENT_ALL_ROLE ="all";
/**运营所有角色**/
public static final String OPER_ALL_ROLE ="oper";
/**流程定义缓存时间**/
public static final int PROCESS_DEFINITION_CACHE = 60;
/**流程实例激活**/
public static final int PROCESS_INSTANCE_ACTIVE = 1;
/**流程实例挂起**/
public static final int PROCESS_INSTANCE_SUSPEND = 0;
/**读取图片**/
public static final String READ_IMAGE = "image";
/**读取xml**/
public static final String READ_XML = "xml";
/**流程激活**/
public static final Integer ACTIVE_PROCESSDEFINITION = 1;
/**流程挂起**/
public static final Integer SUSPEND_PROCESSDEFINITION = 2;
/**流程状态:0-全部,1-正常,2-已挂起**/
public static final int QUERY_ALL = 0;
public static final int QUERY_NORMAL = 1;
public static final int QUERY_SUSPENDED = 2;
/**流程实例状态:0-全部,1-正常,2-已删除**/
public static final int INSTANCE_ALL = 0;
public static final int INSTANCE_NOT_DELETED = 1;
public static final int INSTANCE_DELETED = 2;
/** 系统管理员ID **/
public static final String INTERFACE_SYSTEM_ID = "-1";
/** 系统管理员名称 **/
public static final String INTERFACE_SYSTEM_NAME = "系统操作";
/** 流程部署类型:1-启动并激活,2-启动即挂起 **/
public static final int PROCESS_START_ACTIVE = 1;
public static final int PROCESS_START_SUSPEND = 2;
/** 用于标识流程项目配置信息校验结果:1:新流程,2:新版本, 3:流程类别有误 **/
public static final int CHECK_NEW_PROCESS = 1;
public static final int CHECK_NEW_VERSION = 2;
public static final int CHECK_ERROR_PROCESS_TYPE = 3;
/** 默认网关条件值 **/
public static final Integer default_GATEWAY_CONDITION_VALUE = 1;
/** 工作流-业务状态表数据类型:1-工作流状态,2-业务状态 **/
public static final Integer PROCESS_STATUS = 1;
public static final Integer BIZNESS_STATUS = 2;
/** 新增流程时标识:1-直接保存,2-提示覆盖 **/
public static final Integer PROCESS_STATUS_SAVE = 1;
public static final Integer BIZNESS_STATUS_WARN = 2;
/** 模板类型标识:1-新创建或直接导入的模板,2-默认模板生成 **/
public static final Integer MODEL_TYPE_1 = 1;
public static final Integer MODEL_TYPE_2 = 2;
/** 查询流程定义标识:1-查询最新版本流程定义,2-查询所有版本 **/
public static final Integer QUERY_PROCESS_LATEST_VERSION = 1;
public static final Integer QUERY_PROCESS_ALL_VERSION = 2;
/** 按钮网关 通过1 */
public static final String WAY_TYPE_PASS = "1";
/** 按钮网关 驳回或结束0 */
public static final String WAY_TYPE_REJECT = "0";
/** 按钮网关 退回2 */
public static final String WAY_TYPE_BACK = "2";
/**任务参数为空**/
public static final int TASK_CHECK_PARAM_NULL = -1;
/**任务已办理**/
public static final int TASK_CHECK_COMPLETED = 1;
/**无权限办理**/
public static final int TASK_CHECK_NO_PERMISSIONS= 2;
/**任务校验通过**/
public static final int TASK_CHECK_PASS = 0;
/** 动态流程图颜色定义 **/
public static final Color COLOR_NORMAL = new Color(0, 205, 0);
public static final Color COLOR_CURRENT = new Color(255, 0, 0);
/** 定义生成流程图时的边距(像素) **/
public static final int PROCESS_PADDING = 5;
}
@@ -10,7 +10,10 @@ import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.*;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
@@ -21,12 +24,15 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
@@ -48,18 +54,28 @@ public class AController {
@GetMapping("/deploy")
public Result<Map<String, String>> deploy(@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "classpathResource") String classpathResource) {
if (StringUtils.isNotBlank(name)) {
@RequestParam(value = "classpathResource") String classpathResource,
@RequestParam(value = "resourceName") String resourceName,
@RequestPart(value = "files", required = false) MultipartFile file) {
if (StringUtils.isBlank(name)) {
//根据流程定义Key查询
name = "出差申请流程";
}
// 3、使用RepositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
//.addClasspathResource("bpmn/diagram_1.bpmn20.xml") // 添加bpmn资源
//.addClasspathResource("bpmn/business-trips.png") // 添加png资源
.addClasspathResource(classpathResource)
.name(name)
.deploy();
Deployment deployment = null;
try {
deployment = repositoryService.createDeployment()
//.addClasspathResource("bpmn/diagram_1.bpmn20.xml") // 添加bpmn资源
//.addClasspathResource("bpmn/business-trips.png") // 添加png资源
//.addClasspathResource(classpathResource)
.addInputStream(resourceName, file.getInputStream())
.key(name)
.name(name)
.deploy();
} catch (IOException e) {
e.printStackTrace();
}
// 4、输出部署信息
Map<String, String> map = new HashMap<>();
@@ -114,9 +130,12 @@ public class AController {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> vars = new HashMap<>();
vars.put("mainDrafter", "admin");
// 3、根据流程定义Id启动流程
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(key);
.startProcessInstanceByKey(key, vars);
// 输出内容
Map<String, String> map = new HashMap<>();
@@ -155,12 +174,21 @@ public class AController {
@GetMapping("/complete")
public Result<Object> complete(@RequestParam(value = "taskId") String taskId) {
taskService.complete(taskId);
Map<String, Object> vars = new HashMap<>();
List<String> otherDrafterList = new ArrayList<>();
otherDrafterList.add("zhangsan");
otherDrafterList.add("lisi");
otherDrafterList.add("wangwu");
vars.put("leaderList", otherDrafterList);
Map<String, Object> map = new HashMap<>();
map.put("status", 2);
taskService.complete(taskId, map, vars);
return Result.OK();
}
/**
* 加签
*
* @param taskId
* @param loginName
* @return
@@ -181,19 +209,25 @@ public class AController {
@GetMapping("/addTask")
// @ApiOperation("新增节点")
public Object addTask(String taskId, String assignee) {
return processService.addTask(taskId,assignee);
return processService.addTask(taskId, assignee);
}
@GetMapping(value = "/readResource")
@ApiOperation(value = "获取实时流程图", notes = "获取实时流程图,输出跟踪流程信息")
public void getFlowImgByInstanceId(@RequestParam(value = "processInstanceId") String processInstanceId, HttpServletResponse response) {
processService.getFlowImgByInstanceId(processInstanceId, response);
public void getFlowImgByInstanceId(@RequestParam(value = "processInstanceId") String
processInstanceId, HttpServletResponse response) {
try {
processService.readResource(processInstanceId, response);
} catch (Exception e) {
e.printStackTrace();
}
}
@GetMapping(value = "/export/{modelId}")
@ApiOperation(value = "将流程模型导出为 bpmn文件",notes = "将流程模型导出为 bpmn文件")
public void export(@PathVariable("modelId") @ApiParam("流程模型Id") String modelId, HttpServletResponse response) {
@ApiOperation(value = "将流程模型导出为 bpmn文件", notes = "将流程模型导出为 bpmn文件")
public void export(@PathVariable("modelId") @ApiParam("流程模型Id") String modelId, HttpServletResponse
response) {
try {
BpmnModel bpmnModel = repositoryService.getBpmnModel(modelId);
@@ -214,11 +248,12 @@ public class AController {
log.error("导出model的bpmn文件失败:modelId={}", modelId, e);
}
}
@GetMapping("test")
public Result<String> getRedisTest(){
public Result<String> getRedisTest() {
String msg1 = MessageUtils.getMessage("message");
String msg2 = MessageUtils.getMessage("message.operate.error","123456");
String msg2 = MessageUtils.getMessage("message.operate.error", "123456");
LanguageEnum language = MessageUtils.getLanguage();
return Result.OK(msg1+"\n"+msg2) ;
return Result.OK(msg1 + "\n" + msg2);
}
}
@@ -7,7 +7,7 @@ import javax.servlet.http.HttpServletResponse;
* @date 2023-08-03 15:37
*/
public interface ProcessService {
void getFlowImgByInstanceId(String pProcessInstanceId, HttpServletResponse response);
Object addTask(String taskId, String assignee);
void readResource(String pProcessInstanceId, HttpServletResponse response) throws Exception;
}
@@ -1,10 +1,11 @@
package com.jero.modules.activiti.service.impl;
import com.jero.common.exception.JeroBootException;
import com.jero.modules.activiti.config.DefaultProcessDiagramGenerator;
import com.jero.modules.activiti.config.WorkflowConstants;
import com.jero.modules.activiti.entity.ActGeBytearray;
import com.jero.modules.activiti.mapper.ActGeBytearrayMapper;
import com.jero.modules.activiti.service.ProcessService;
import com.jero.modules.activiti.service.impl.image.ICustomProcessDiagramGenerator;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.BpmnAutoLayout;
import org.activiti.bpmn.converter.BpmnXMLConverter;
@@ -13,23 +14,30 @@ import org.activiti.bpmn.model.*;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.persistence.deploy.DeploymentManager;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -54,87 +62,6 @@ public class ProcessServiceImpl implements ProcessService {
@Resource
private ActGeBytearrayMapper actGeBytearrayMapper;
@Override
public void getFlowImgByInstanceId(String processInstanceId, HttpServletResponse response) {
InputStream imageStream = null;
try {
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
if (historicProcessInstance == null){
throw new JeroBootException("历史流程不存在");
}
// 获取流程中已经执行的节点,按照执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstances = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceId()
.asc().list();
// 高亮已经执行流程节点ID集合
List<String> highLightedActivitiIds = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
// 用默认颜色
highLightedActivitiIds.add(historicActivityInstance.getActivityId());
}
// 正在执行的节点
List<Execution> runTaskList = runtimeService.createExecutionQuery()
.processInstanceId(processInstanceId)
.list();
List<String> runningActivityIdList = new ArrayList<>();
for (Execution execution : runTaskList) {
if (!StringUtils.isEmpty(execution.getActivityId())) {
runningActivityIdList.add(execution.getActivityId());
}
}
List<String> currIds = historicActivityInstances.stream()
.filter(item -> item.getEndTime() != null)
.map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
// 获得流程引擎配置
ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();
BpmnModel bpmnModel = repositoryService
.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
//
imageStream = new DefaultProcessDiagramGenerator().generateDiagram(
bpmnModel,
"png",
highLightedActivitiIds,//所有活动过的节点,包括当前在激活状态下的节点
currIds,//当前为激活状态下的节点
highLightedFlowIds,//活动过的线
"宋体",
"宋体",
"宋体",
processEngineConfiguration.getClassLoader(),
1.0,
runningActivityIdList);
OutputStream out = null;
out = response.getOutputStream();
int len = 0;
byte[] b = new byte[1024];
while ((len = imageStream.read(b)) != -1) {
out.write(b, 0, len);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (imageStream != null) {
try {
imageStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public Object addTask(String taskId, String assignee) {
// 1、创建ProcessEngine
@@ -191,154 +118,183 @@ public class ProcessServiceImpl implements ProcessService {
flow.setTargetRef(to);
return flow;
}
/**
* @param bpmnModel bpmnModel
* @param historicActivityInstanceList historicActivityInstanceList
* @return HighLightedFlows
*/
public List<String> getHighLightedFlows(BpmnModel bpmnModel,
List<HistoricActivityInstance> historicActivityInstanceList) {
//historicActivityInstanceList 是 流程中已经执行的历史活动实例
// 已经流经的顺序流,需要高亮显示
List<String> highFlows = new ArrayList<>();
private List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> allHistoricActivityNodeList = new ArrayList<>();
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
// 拥有endTime的历史活动实例,即已经完成了的节点
List<HistoricActivityInstance> finishedActivityInstancesList = new ArrayList<>();
/*
* 循环的目的:
* 获取所有的历史节点FlowNode并放入allHistoricActivityNodeList
* 获取所有确定结束了的历史节点finishedActivityInstancesList
*/
for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
// 获取流程节点
// bpmnModel.getMainProcess()获取一个Process对象
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
allHistoricActivityNodeList.add(flowNode);
// 如果结束时间不为空,表示当前节点已经完成
historicActivityNodes.add(flowNode);
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstancesList.add(historicActivityInstance);
finishedActivityInstances.add(historicActivityInstance);
}
}
FlowNode currentFlowNode;
FlowNode targetFlowNode;
HistoricActivityInstance currentActivityInstance;
// 遍历已经完成的活动实例,从每个实例的outgoingFlows中找到已经执行的
for (int k = 0; k < finishedActivityInstancesList.size(); k++) {
currentActivityInstance = finishedActivityInstancesList.get(k);
// 获得当前活动对应的节点信息以及outgoingFlows信息
FlowNode currentFlowNode = null;
FlowNode targetFlowNode = null;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
// 当前节点的所有流出线
List<SequenceFlow> outgoingFlowList = currentFlowNode.getOutgoingFlows();
/*
* 遍历outgoingFlows并找到已经流转的 满足如下条件认为已经流转:
* 1、当前节点是并行网关或者兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已经流转
* 2、当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
* (第二点有问题,有过驳回的,会只绘制驳回的流程线,通过走向下一级的流程线没有高亮显示)
/**
* 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) ||
"inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow outgoingFlow : outgoingFlowList) {
// 获取当前节点流程线对应的下一级节点
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(outgoingFlow.getTargetRef(), true);
// 如果下级节点包含在所有历史节点中,则将当前节点的流出线高亮显示
if (allHistoricActivityNodeList.contains(targetFlowNode)) {
highFlows.add(outgoingFlow.getId());
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
/*
* 2、当前节点不是并行网关或者兼容网关
* 【已解决-问题】如果当前节点有驳回功能,驳回到申请节点,
* 则因为申请节点在历史节点中,导致当前节点驳回到申请节点的流程线被高亮显示,但实际并没有进行驳回操作
*/
List<Map<String, Object>> tempMapList = new ArrayList<>();
// 当前节点ID
String currentActivityId = currentActivityInstance.getActivityId();
int size = historicActivityInstanceList.size();
boolean ifStartFind = false;
boolean ifFinded = false;
HistoricActivityInstance historicActivityInstance;
// 循环当前节点的所有流出线
// 循环所有的历史节点
// log.info("【开始】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
// log.info("循环历史节点");
for (int i = 0; i < size; i++) {
// // 如果当前节点流程线对应的下级节点在历史节点中,则该条流程线进行高亮显示(【问题】有驳回流程线时,即使没有进行驳回操作,因为申请节点在历史节点中,也会将驳回流程线高亮显示-_-||)
// if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
// Map<String, Object> map = new HashMap<>();
// map.put("highLightedFlowId", sequenceFlow.getId());
// map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
// tempMapList.add(map);
// // highFlows.add(sequenceFlow.getId());
// }
// 历史节点
historicActivityInstance = historicActivityInstanceList.get(i);
// log.info("第【{}/{}】个历史节点-ActivityId=【{}】", i + 1, size, historicActivityInstance.getActivityId());
// 如果循环历史节点中的id等于当前节点id,从当前历史节点继续先后查找是否有当前流程线等于的节点
// 历史节点的序号需要大于等于已经完成历史节点的序号,放置驳回重审一个节点经过两次时只取第一次的流出线高亮显示,第二次的不显示
if (i >= k && historicActivityInstance.getActivityId().equals(currentActivityId)) {
// log.info("第【{}】个历史节点和当前节点一致-ActivityId=【{}】", i + 1, historicActivityInstance.getActivityId());
ifStartFind = true;
// 跳过当前节点继续查找下一个节点
continue;
}
if (ifStartFind) {
// log.info("[开始]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);
ifFinded = false;
for (SequenceFlow sequenceFlow : outgoingFlowList) {
// 如果当前节点流程线对应的下级节点在其后面的历史节点中,则该条流程线进行高亮显示
// 【问题】
// log.info("当前流出线的下级节点=[{}]", sequenceFlow.getTargetRef());
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
// log.info("当前节点[{}]需高亮显示的流出线=[{}]", currentActivityId, sequenceFlow.getId());
highFlows.add(sequenceFlow.getId());
// 暂时默认找到离当前节点最近的下一级节点即退出循环,否则有多条流出线时将全部被高亮显示
ifFinded = true;
break;
}
for (SequenceFlow sequenceFlow : sequenceFlows) {
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
Map<String, Object> map = new HashMap<>();
map.put("highLightedFlowId", sequenceFlow.getId());
map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
tempMapList.add(map);
}
// log.info("[完成]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);
}
if (ifFinded) {
// 暂时默认找到离当前节点最近的下一级节点即退出历史节点循环,否则有多条流出线时将全部被高亮显示
break;
}
}
// log.info("【完成】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
// if (!CollectionUtils.isEmpty(tempMapList)) {
// // 遍历匹配的集合,取得开始时间最早的一个
// long earliestStamp = 0L;
// String highLightedFlowId = null;
// for (Map<String, Object> map : tempMapList) {
// long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
// if (earliestStamp == 0 || earliestStamp <= highLightedFlowStartTime) {
// highLightedFlowId = map.get("highLightedFlowId").toString();
// earliestStamp = highLightedFlowStartTime;
// }
// }
// highFlows.add(highLightedFlowId);
// }
if (!CollectionUtils.isEmpty(tempMapList)) {
// 遍历匹配的集合,取得开始时间最早的一个
long earliestStamp = 0L;
String highLightedFlowId = null;
for (Map<String, Object> map : tempMapList) {
long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
highLightedFlowId = map.get("highLightedFlowId").toString();
earliestStamp = highLightedFlowStartTime;
}
}
highLightedFlowIds.add(highLightedFlowId);
}
}
}
return highLightedFlowIds;
}
@Override
public void readResource(String pProcessInstanceId, HttpServletResponse response)
throws Exception {
// 设置页面不缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
String processDefinitionId = "";
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(pProcessInstanceId).singleResult();
if(processInstance == null) {
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(pProcessInstanceId).singleResult();
processDefinitionId = historicProcessInstance.getProcessDefinitionId();
} else {
processDefinitionId = processInstance.getProcessDefinitionId();
}
ProcessDefinitionQuery pdq = repositoryService.createProcessDefinitionQuery();
ProcessDefinition pd = pdq.processDefinitionId(processDefinitionId).singleResult();
String resourceName = pd.getDiagramResourceName();
if(resourceName.endsWith(".png") && StringUtils.isEmpty(pProcessInstanceId) == false)
{
getActivitiProccessImage(pProcessInstanceId,response);
//ProcessDiagramGenerator.generateDiagram(pde, "png", getRuntimeService().getActiveActivityIds(processInstanceId));
}
else
{
// 通过接口读取
InputStream resourceAsStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), resourceName);
// 输出资源内容到相应对象
byte[] b = new byte[1024];
int len = -1;
while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
}
return highFlows;
}
/**
* 获取流程图像,已执行节点和流程线高亮显示
*/
public void getActivitiProccessImage(String pProcessInstanceId, HttpServletResponse response) {
//logger.info("[开始]-获取流程图图像");
try {
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(pProcessInstanceId).singleResult();
if (historicProcessInstance == null) {
//throw new BusinessException("获取流程实例ID[" + pProcessInstanceId + "]对应的历史流程实例失败!");
}
else {
// 获取流程定义
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());
// 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(pProcessInstanceId).orderByHistoricActivityInstanceId().asc().list();
// 已执行的节点ID集合
List<String> executedActivityIdList = new ArrayList<String>();
int index = 1;
//logger.info("获取已经执行的节点ID");
for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
executedActivityIdList.add(activityInstance.getActivityId());
//logger.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName());
index++;
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
// 已执行的线集合
List<String> flowIds = new ArrayList<String>();
// 获取流程走过的线 (getHighLightedFlows是下面的方法)
flowIds = getHighLightedFlows(bpmnModel,processDefinition, historicActivityInstanceList);
// // 获取流程图图像字符流
// ProcessDiagramGenerator pec = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
// //配置字体
// InputStream imageStream = pec.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds,"宋体","微软雅黑","黑体",null,2.0);
Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(pProcessInstanceId).list()
.stream().map(e->e.getActivityId()).collect(Collectors.toSet());
ICustomProcessDiagramGenerator diagramGenerator = (ICustomProcessDiagramGenerator) processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList,
flowIds, "宋体", "宋体", "宋体", null, 1.0, new Color[] { WorkflowConstants.COLOR_NORMAL, WorkflowConstants.COLOR_CURRENT }, currIds);
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
imageStream.close();
}
//logger.info("[完成]-获取流程图图像");
} catch (Exception e) {
System.out.println(e.getMessage());
//logger.error("【异常】-获取流程图失败!" + e.getMessage());
//throw new BusinessException("获取流程图失败!" + e.getMessage());
}
}
}
@@ -1,61 +1,15 @@
package com.jero.modules.activiti.service.impl.image;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.image.ProcessDiagramGenerator;
import java.awt.*;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
/**
* bpmnModel bpmn模型
* activityFontName 活动字体
* labelFontName 标签字体
* annotationFontName 标注字体
* highLightedActivities 高亮活动节点(绿色)
* runningActivityIdList 正在运行的活动节点(红色)
* highLightedFlows 高亮顺序流(绿色)
* lastFlowIdList 最后一条执行的顺序流(红色) ——————未实现
*/
public interface ICustomProcessDiagramGenerator{
//指定字体
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> runningActivityIdList,
List<String> highLightedFlows,
List<String> runningActivityFlowsIds,
String activityFontName,
String labelFontName,
String annotationFontName);
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> runningActivityIdList,
List<String> highLightedFlows,
String activityFontName,
String labelFontName,
String annotationFontName);
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> highLightedFlows,
String activityFontName,
String labelFontName,
String annotationFontName);
//无指定字体,使用默认字体
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> runningActivityIdList,
List<String> highLightedFlows,
List<String> runningActivityFlowsIds);
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> runningActivityIdList,
List<String> highLightedFlows);
InputStream generateDiagramCustom(BpmnModel bpmnModel,
List<String> highLightedActivities,
List<String> highLightedFlows);
public interface ICustomProcessDiagramGenerator extends ProcessDiagramGenerator {
InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds);
}
@@ -0,0 +1,58 @@
package com.jero.modules.activiti.util;
/**
* @author liJiaRao
* @date 2023-09-05 17:16
*/
public enum CamundaTag {
assignee("assignee", "camunda:assignee","activiti:assignee","指派人"),
candidateUsers("candidateUsers", "camunda:candidateUsers","activiti:candidateUsers","候选人"),
candidateGroups("candidateGroups", "camunda:candidateGroups","activiti:candidateGroups","候选组"),
exclusive("exclusive", "camunda:exclusive","activiti:exclusive","独家"),
taskListener("taskListener", "camunda:taskListener","activiti:taskListener","监听"),
;
private String attribute;
private String camundaName;
private String activitiName;
private String note;
CamundaTag(String attribute, String camundaName, String activitiName, String note) {
this.attribute = attribute;
this.camundaName = camundaName;
this.activitiName = activitiName;
this.note = note;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public String getCamundaName() {
return camundaName;
}
public void setCamundaName(String camundaName) {
this.camundaName = camundaName;
}
public String getActivitiName() {
return activitiName;
}
public void setActivitiName(String activitiName) {
this.activitiName = activitiName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
@@ -0,0 +1,178 @@
package com.jero.modules.activiti.util;
import cn.hutool.core.io.FileUtil;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.dom4j.tree.AbstractAttribute;
import org.dom4j.tree.DefaultAttribute;
import org.dom4j.tree.DefaultElement;
import org.springframework.util.CollectionUtils;
import java.io.*;
import java.util.List;
/**
* 作者 CG
* 时间 2021/4/12 14:21
* 备注 camunda 和 activiti xml 互相转换
* 参数
*/
public class ConversionUtil {
public static void main(String[] args) {
File file1 = new File("E:\\Data\\Downloads\\sign.bpmn");
camundaToActiviti(file1,true);
}
/**
* 作者 CG
* 时间 2021/4/12 14:22
* 备注 判断文件名字是否是xml,不是xml转换为xml
* 参数
*/
public static File isXml(File file) {
// 判断是否为null
String name = FileUtil.mainName(file);
String parent = file.getParent();
boolean bpmn = FileUtil.pathEndsWith(file, "bpmn");
if (bpmn) {
//转换为xml
File xmlFile = FileUtil.file(parent, name + ".xml");
File copy = FileUtil.copy(file, xmlFile, true);
return copy;
}
return file;
}
/**
* 作者 CG
* 时间 2021/4/12 15:57
* 备注 属性转换
* 参数 [file, flag camunda->activiti=true activiti->camunda=false]
*/
public static void camundaToActiviti(File file, Boolean flag) {
//判断文件后缀名是否正确
try {
File xml = isXml(file);
//转换为文档
SAXReader reader = new SAXReader();
// 转换为Document
Document doc = reader.read(xml);
//获取根节点
Element rootElement = doc.getRootElement();
//获取命名空间
//flag camunda->activiti=true activiti->camunda=false
NamespaceHandler(rootElement, flag);
//处理userTask
Element process = rootElement.element("process");
List<Element> userTask = process.elements("userTask");
CamundaTag[] values = CamundaTag.values();
if (!CollectionUtils.isEmpty(userTask)) {
for (CamundaTag value : values) {
userTask(userTask, value, flag);
}
//处理监听
userTaskListener(userTask, flag);
}
//保存文档
saveDocument(doc, file);
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 作者 CG
* 时间 2021/4/12 17:45
* 备注 处理命名空间
* 参数 [rootElement, flag]
*/
public static void NamespaceHandler(Element rootElement, Boolean flag) {
NamespaceName camundaNamespace = NamespaceName.camunda;
NamespaceName activitiNamespace = NamespaceName.activiti;
Namespace activiti = rootElement.getNamespaceForPrefix(flag ? camundaNamespace.getName() : activitiNamespace.getName());
if (activiti != null) {
//删除camunda命名空间
rootElement.remove(activiti);
//添加activiti命名空间
Namespace namespace = new Namespace(flag ? activitiNamespace.getName() : camundaNamespace.getName(), flag ? activitiNamespace.getUrl() : camundaNamespace.getUrl());
rootElement.add(namespace);
}
}
/**
* 作者 CG
* 时间 2021/4/12 15:36
* 备注 userTask 参数转换
* 参数 [userTask 要处理的集合, camundaTag 要处理的参数类型, flag camunda->activiti=true activiti->camunda=false]
*/
public static void userTask(List<Element> userTask, CamundaTag camundaTag, Boolean flag) {
for (Element element : userTask) {
Attribute assignee = element.attribute(camundaTag.getAttribute());
if (assignee != null) {
element.remove(assignee);
AbstractAttribute abstractAttribute = new AbstractAttribute() {
@Override
public QName getQName() {
QName qName = new QName(flag ? camundaTag.getActivitiName() : camundaTag.getCamundaName());
return qName;
}
@Override
public String getValue() {
return assignee.getValue();
}
};
element.add(abstractAttribute);
}
}
}
/**
* 作者 CG
* 时间 2021/4/12 15:36
* 备注 userTask 参数转换
* 参数 [userTask 要处理的集合, camundaTag 要处理的参数类型, flag camunda->activiti=true activiti->camunda=false]
*/
public static void userTaskListener(List<Element> userTask, Boolean flag) {
CamundaTag listener = CamundaTag.taskListener;
for (Element element : userTask) {
Element extensionElements = element.element("extensionElements");
if (extensionElements!=null){
//是否有监听
Element taskListener = extensionElements.element(listener.getAttribute());
if (taskListener!=null){
//获取节点所有参数
List<Attribute> attributes = taskListener.attributes();
DefaultElement defaultElement = new DefaultElement(flag?listener.getActivitiName():listener.getCamundaName());
//将参数拷贝到新的节点
for (Attribute attribute : attributes) {
DefaultAttribute defaultAttribute = new DefaultAttribute(attribute.getName(), attribute.getValue());
defaultElement.add(defaultAttribute);
}
//删除原有节点
extensionElements.remove(taskListener);
//添加新构建的节点
extensionElements.add(defaultElement);
}
}
}
}
/**
* 作者 CG
* 时间 2021/4/12 15:45
* 备注 文档保存
* 参数 [document, xmlFile]
*/
public static void saveDocument(Document document, File xmlFile) throws IOException {
Writer osWrite = new OutputStreamWriter(new FileOutputStream(xmlFile));//创建输出流
OutputFormat format = OutputFormat.createPrettyPrint(); //获取输出的指定格式
format.setEncoding("UTF-8");//设置编码 ,确保解析的xml为UTF-8格式
XMLWriter writer = new XMLWriter(osWrite, format);//XMLWriter 指定输出文件以及格式
writer.write(document);//把document写入xmlFile指定的文件(可以为被解析的文件或者新创建的文件)
writer.flush();
writer.close();
}
}
@@ -0,0 +1,34 @@
package com.jero.modules.activiti.util;
/**
* @author liJiaRao
* @date 2023-09-05 17:16
*/
public enum NamespaceName {
activiti("activiti","http://activiti.org/bpmn"),
camunda("camunda","http://camunda.org/schema/1.0/bpmn"),
;
private String name;
public String url;
NamespaceName(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}