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 getProcessProperties(String modelId) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree( new String(repositoryService.getModelEditorSource(modelId), "utf-8")).get("properties"); Map 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 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 hais = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId) .orderByHistoricActivityInstanceId().asc().list(); // 获取bpmnModel BpmnModel bpmnModel = repositoryService.getBpmnModel(hip.getProcessDefinitionId()); //全部活动实例 List 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 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 hai = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId) .orderByHistoricActivityInstanceId().asc().list(); // 构造已执行的节点ID集合 List executedActivityIdList = new ArrayList<>(); for (HistoricActivityInstance activityInstance : hai) { executedActivityIdList.add(activityInstance.getActivityId()); } // 获取bpmnModel BpmnModel bpmnModel = repositoryService.getBpmnModel(hip.getProcessDefinitionId()); // 获取流程已发生流转的线ID集合 List 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 getExecutedFlows(BpmnModel bpmnModel, List historicActivityInstances) { //流转线ID集合 List flowIdList = new ArrayList<>(); //全部活动实例 List historicFlowNodeList = new LinkedList<>(); //已完成的历史活动节点 List 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 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> 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 map : tempMapList) { flowId = map.get("flowId"); flowIdList.add(flowId); } } } return flowIdList; } /** * 删除历史流程 * * @param procInstId 流程实例ID */ protected void deleteHiProcessInstance(String procInstId) { historyService.deleteHistoricProcessInstance(procInstId); } }