/* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the dreamlu.net developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: Chill 庄骞 (smallchill@163.com) */ package com.vci.ubcs.flow.engine.service.impl; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.vci.ubcs.flow.core.entity.BladeFlow; import com.vci.ubcs.flow.core.enums.FlowModeEnum; import com.vci.ubcs.flow.core.utils.TaskUtil; import com.vci.ubcs.flow.engine.constant.FlowEngineConstant; import com.vci.ubcs.flow.engine.entity.FlowExecution; import com.vci.ubcs.flow.engine.entity.FlowModel; import com.vci.ubcs.flow.engine.entity.FlowProcess; import com.vci.ubcs.flow.engine.mapper.FlowMapper; import com.vci.ubcs.flow.engine.service.FlowEngineService; import com.vci.ubcs.flow.engine.utils.FlowCache; import com.vci.ubcs.system.user.cache.UserCache; import com.vci.ubcs.system.user.entity.User; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.Process; import org.flowable.common.engine.impl.util.IoUtil; import org.flowable.common.engine.impl.util.io.StringStreamSource; import org.flowable.editor.language.json.converter.BpmnJsonConverter; import org.flowable.engine.*; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.DeploymentQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.repository.ProcessDefinitionQuery; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstanceQuery; import org.flowable.engine.task.Comment; import org.flowable.image.ProcessDiagramGenerator; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.support.Condition; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.FileUtil; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import org.w3c.dom.*; import org.xml.sax.InputSource; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.util.*; /** * 工作流服务实现类 * * @author Chill */ @Slf4j @Service @AllArgsConstructor public class FlowEngineServiceImpl extends ServiceImpl implements FlowEngineService { private static final String ALREADY_IN_STATE = "already in state"; private static final String USR_TASK = "userTask"; private static final String IMAGE_NAME = "image"; private static final String XML_NAME = "xml"; private static final Integer INT_1024 = 1024; private static final BpmnJsonConverter BPMN_JSON_CONVERTER = new BpmnJsonConverter(); private static final BpmnXMLConverter BPMN_XML_CONVERTER = new BpmnXMLConverter(); private final ObjectMapper objectMapper; private final RepositoryService repositoryService; private final RuntimeService runtimeService; private final HistoryService historyService; private final TaskService taskService; private final ProcessEngine processEngine; /** * 数据操作对象 */ @Resource private FlowMapper flowMapper; @Override public IPage selectFlowPage(IPage page, FlowModel flowModel) { return page.setRecords(baseMapper.selectFlowPage(page, flowModel)); } @Override public IPage selectProcessPage(IPage page, String category, Integer mode) { ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion().orderByProcessDefinitionKey().asc(); // 通用流程 if (mode == FlowModeEnum.COMMON.getMode()) { processDefinitionQuery.processDefinitionWithoutTenantId(); } // 定制流程 else if (!AuthUtil.isAdministrator()) { processDefinitionQuery.processDefinitionTenantId(AuthUtil.getTenantId()); } if (StringUtils.isNotEmpty(category)) { processDefinitionQuery.processDefinitionCategory(category); } List processDefinitionList = processDefinitionQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize())); List flowProcessList = new ArrayList<>(); processDefinitionList.forEach(processDefinition -> { String deploymentId = processDefinition.getDeploymentId(); Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult(); FlowProcess flowProcess = new FlowProcess((ProcessDefinitionEntityImpl) processDefinition); flowProcess.setDeploymentTime(deployment.getDeploymentTime()); flowProcessList.add(flowProcess); }); page.setTotal(processDefinitionQuery.count()); page.setRecords(flowProcessList); return page; } @Override public IPage selectFollowPage(IPage page, String processInstanceId, String processDefinitionKey) { ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery(); if (StringUtil.isNotBlank(processInstanceId)) { processInstanceQuery.processInstanceId(processInstanceId); } if (StringUtil.isNotBlank(processDefinitionKey)) { processInstanceQuery.processDefinitionKey(processDefinitionKey); } List flowList = new ArrayList<>(); List procInsList = processInstanceQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize())); procInsList.forEach(processInstance -> { ExecutionEntityImpl execution = (ExecutionEntityImpl) processInstance; FlowExecution flowExecution = new FlowExecution(); flowExecution.setId(execution.getId()); flowExecution.setName(execution.getName()); flowExecution.setStartUserId(execution.getStartUserId()); User taskUser = UserCache.getUserByTaskUser(execution.getStartUserId()); if (taskUser != null) { flowExecution.setStartUser(taskUser.getName()); } flowExecution.setStartTime(execution.getStartTime()); flowExecution.setExecutionId(execution.getId()); flowExecution.setProcessInstanceId(execution.getProcessInstanceId()); flowExecution.setProcessDefinitionId(execution.getProcessDefinitionId()); flowExecution.setProcessDefinitionKey(execution.getProcessDefinitionKey()); flowExecution.setSuspensionState(execution.getSuspensionState()); FlowProcess processDefinition = FlowCache.getProcessDefinition(execution.getProcessDefinitionId()); flowExecution.setCategory(processDefinition.getCategory()); flowExecution.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory())); flowList.add(flowExecution); }); page.setTotal(processInstanceQuery.count()); page.setRecords(flowList); return page; } @Override public List historyFlowList(String processInstanceId, String startActivityId, String endActivityId) { List flowList = new LinkedList<>(); List historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list(); boolean start = false; Map activityMap = new HashMap<>(16); for (int i = 0; i < historicActivityInstanceList.size(); i++) { HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i); // 过滤开始节点前的节点 if (StringUtil.isNotBlank(startActivityId) && startActivityId.equals(historicActivityInstance.getActivityId())) { start = true; } if (StringUtil.isNotBlank(startActivityId) && !start) { continue; } // 显示开始节点和结束节点,并且执行人不为空的任务 if (StringUtils.equals(USR_TASK, historicActivityInstance.getActivityType()) || FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType()) || FlowEngineConstant.END_EVENT.equals(historicActivityInstance.getActivityType())) { // 给节点增加序号 Integer activityNum = activityMap.get(historicActivityInstance.getActivityId()); if (activityNum == null) { activityMap.put(historicActivityInstance.getActivityId(), activityMap.size()); } BladeFlow flow = new BladeFlow(); flow.setHistoryActivityId(historicActivityInstance.getActivityId()); flow.setHistoryActivityName(historicActivityInstance.getActivityName()); flow.setCreateTime(historicActivityInstance.getStartTime()); flow.setEndTime(historicActivityInstance.getEndTime()); String durationTime = DateUtil.secondToTime(Func.toLong(historicActivityInstance.getDurationInMillis(), 0L) / 1000); flow.setHistoryActivityDurationTime(durationTime); // 获取流程发起人名称 if (FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())) { List processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).orderByProcessInstanceStartTime().asc().list(); if (processInstanceList.size() > 0) { if (StringUtil.isNotBlank(processInstanceList.get(0).getStartUserId())) { String taskUser = processInstanceList.get(0).getStartUserId(); User user = UserCache.getUser(TaskUtil.getUserId(taskUser)); if (user != null) { flow.setAssignee(historicActivityInstance.getAssignee()); flow.setAssigneeName(user.getName()); } } } } // 获取任务执行人名称 if (StringUtil.isNotBlank(historicActivityInstance.getAssignee())) { User user = UserCache.getUser(TaskUtil.getUserId(historicActivityInstance.getAssignee())); if (user != null) { flow.setAssignee(historicActivityInstance.getAssignee()); flow.setAssigneeName(user.getName()); } } // 获取意见评论内容 if (StringUtil.isNotBlank(historicActivityInstance.getTaskId())) { List commentList = taskService.getTaskComments(historicActivityInstance.getTaskId()); if (commentList.size() > 0) { flow.setComment(commentList.get(0).getFullMessage()); } } flowList.add(flow); } // 过滤结束节点后的节点 if (StringUtils.isNotBlank(endActivityId) && endActivityId.equals(historicActivityInstance.getActivityId())) { boolean temp = false; Integer activityNum = activityMap.get(historicActivityInstance.getActivityId()); // 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在 for (int j = i + 1; j < historicActivityInstanceList.size(); j++) { HistoricActivityInstance hi = historicActivityInstanceList.get(j); Integer activityNumA = activityMap.get(hi.getActivityId()); boolean numberTemp = activityNumA != null && activityNumA < activityNum; boolean equalsTemp = StringUtils.equals(hi.getActivityId(), historicActivityInstance.getActivityId()); if (numberTemp || equalsTemp) { temp = true; } } if (!temp) { break; } } } return flowList; } @Override public String changeState(String state, String processId) { try { if (state.equals(FlowEngineConstant.ACTIVE)) { repositoryService.activateProcessDefinitionById(processId, true, null); return StringUtil.format("激活ID为 [{}] 的流程成功", processId); } else if (state.equals(FlowEngineConstant.SUSPEND)) { repositoryService.suspendProcessDefinitionById(processId, true, null); return StringUtil.format("挂起ID为 [{}] 的流程成功", processId); } else { return "暂无流程变更"; } } catch (Exception e) { if (e.getMessage().contains(ALREADY_IN_STATE)) { return StringUtil.format("ID为 [{}] 的流程已是此状态,无需操作", processId); } return e.getMessage(); } } @Override public boolean deleteDeployment(String deploymentIds) { Func.toStrList(deploymentIds).forEach(deploymentId -> repositoryService.deleteDeployment(deploymentId, true)); return true; } @Override public boolean deployUpload(List files, String category, List tenantIdList) { files.forEach(file -> { try { String fileName = file.getOriginalFilename(); InputStream fileInputStream = file.getInputStream(); byte[] bytes = FileUtil.copyToByteArray(fileInputStream); if (Func.isNotEmpty(tenantIdList)) { tenantIdList.forEach(tenantId -> { Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).tenantId(tenantId).deploy(); deploy(deployment, category); }); } else { Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).deploy(); deploy(deployment, category); } } catch (IOException e) { e.printStackTrace(); } }); return true; } @Override public boolean deployModel(String modelId, String category, List tenantIdList) { FlowModel model = this.getById(modelId); if (model == null) { throw new ServiceException("未找到模型 id: " + modelId); } byte[] bytes = getBpmnXML(model); String processName = model.getName(); if (!StringUtil.endsWithIgnoreCase(processName, FlowEngineConstant.SUFFIX)) { processName += FlowEngineConstant.SUFFIX; } String finalProcessName = processName; if (Func.isNotEmpty(tenantIdList)) { tenantIdList.forEach(tenantId -> { Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).tenantId(tenantId).deploy(); deploy(deployment, category); }); } else { Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).deploy(); deploy(deployment, category); } return true; } @Override public boolean deleteProcessInstance(String processInstanceId, String deleteReason) { runtimeService.deleteProcessInstance(processInstanceId, deleteReason); return true; } private void deploy(Deployment deployment, String category) { log.debug("流程部署--------deploy: " + deployment + " 分类---------->" + category); List list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list(); StringBuilder logBuilder = new StringBuilder(500); List logArgs = new ArrayList<>(); // 设置流程分类 for (ProcessDefinition processDefinition : list) { if (StringUtil.isNotBlank(category)) { repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category); } logBuilder.append("部署成功,流程ID={} \n"); logArgs.add(processDefinition.getId()); } if (list.size() == 0) { throw new ServiceException("部署失败,未找到流程"); } else { log.info(logBuilder.toString(), logArgs.toArray()); } } @Override public FlowModel submitModel(FlowModel model) { //验证重复key if(StringUtils.isEmpty(model.getId())&&getFlowModelByKey(model.getModelKey())!=null){ throw new ServiceException("you cannot definition repetition 流程key!"); } FlowModel flowModel = new FlowModel(); flowModel.setId(model.getId()); flowModel.setVersion(Func.toInt(model.getVersion(), 0) + 1); flowModel.setName(model.getName()); flowModel.setModelKey(model.getModelKey()); flowModel.setModelType(FlowModel.MODEL_TYPE_BPMN); flowModel.setCreatedBy(TaskUtil.getTaskUser()); flowModel.setDescription(model.getDescription()); flowModel.setLastUpdated(Calendar.getInstance().getTime()); flowModel.setLastUpdatedBy(TaskUtil.getTaskUser()); flowModel.setTenantId(AuthUtil.getTenantId()); flowModel.setModelEditorXml(model.getModelEditorXml()); if (StringUtil.isBlank(model.getId())) { flowModel.setCreated(Calendar.getInstance().getTime()); } if (StringUtil.isNotBlank(model.getModelEditorXml())) { flowModel.setModelEditorJson(getBpmnJson(model.getModelEditorXml())); } this.saveOrUpdate(flowModel); return flowModel; } @Override public Map modelView(String processDefinitionId, String processInstanceId) { Map result = new HashMap<>(); // 节点标记 if (StringUtil.isNotBlank(processInstanceId)) { result.put("flow", this.historyFlowList(processInstanceId, null, null)); HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); processDefinitionId = processInstance.getProcessDefinitionId(); } BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); // 流程图展示 result.put("xml", new String(new BpmnXMLConverter().convertToXML(bpmnModel))); return result; } @Override public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) { // 获得当前活动的节点 String processDefinitionId; // 如果流程已经结束,则得到结束节点 if (this.isFinished(processInstanceId)) { HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); processDefinitionId = pi.getProcessDefinitionId(); } else { // 如果流程没有结束,则取当前活动节点 // 根据流程实例ID获得当前处于活动状态的ActivityId合集 ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); processDefinitionId = pi.getProcessDefinitionId(); } List highLightedActivities = new ArrayList<>(); // 获得活动的节点 List highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list(); for (HistoricActivityInstance tempActivity : highLightedActivityList) { String activityId = tempActivity.getActivityId(); highLightedActivities.add(activityId); } List flows = new ArrayList<>(); // 获取流程图 BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration(); ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator(); InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, flows, engConf.getActivityFontName(), engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true); OutputStream out = null; byte[] buf = new byte[1024]; int length; try { out = httpServletResponse.getOutputStream(); while ((length = in.read(buf)) != -1) { out.write(buf, 0, length); } } catch (IOException e) { log.error("操作异常", e); } finally { IoUtil.closeSilently(out); IoUtil.closeSilently(in); } } @Override public void resourceView(String processDefinitionId, String processInstanceId, String resourceType, HttpServletResponse response) { if (StringUtil.isAllBlank(processDefinitionId, processInstanceId)) { return; } if (StringUtil.isBlank(processDefinitionId)) { ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); processDefinitionId = processInstance.getProcessDefinitionId(); } ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult(); String resourceName = ""; if (resourceType.equals(IMAGE_NAME)) { resourceName = processDefinition.getDiagramResourceName(); } else if (resourceType.equals(XML_NAME)) { resourceName = processDefinition.getResourceName(); } try { InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName); byte[] b = new byte[1024]; int len; while ((len = resourceAsStream.read(b, 0, INT_1024)) != -1) { response.getOutputStream().write(b, 0, len); } } catch (Exception exception) { exception.printStackTrace(); } } @Override public byte[] getModelEditorXML(FlowModel model) { return getBpmnXML(model); } /** * 根据流程模型key获取XML中节点配置的处理人 * * @param key * @return */ @Override public List> getNodeByFlowableKey(String key){ String processDefinitionId = repositoryService.createProcessDefinitionQuery().latestVersion().processDefinitionKey(key).singleResult().getId(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); Process process = bpmnModel.getProcesses().get(0);//只有一个 List> ll = new ArrayList<>(); if (CollectionUtils.isNotEmpty(process.getFlowElements())) { for (FlowElement flowElement : process.getFlowElements()) { if (flowElement instanceof UserTask) { Map mi = new HashMap<>(); mi.put("taskId",flowElement.getId()); mi.put("taskName",flowElement.getName()); ll.add(mi); } } } return ll; } /** * 是否已完结 * * @param processInstanceId 流程实例id * @return bool */ private boolean isFinished(String processInstanceId) { return historyService.createHistoricProcessInstanceQuery().finished() .processInstanceId(processInstanceId).count() > 0; } /** * xml转bpmn json * * @param xml xml * @return json */ private String getBpmnJson(String xml) { return BPMN_JSON_CONVERTER.convertToJson(getBpmnModel(xml)).toString(); } /** * xml转bpmnModel * * @param xml xml * @return bpmnModel */ private BpmnModel getBpmnModel(String xml) { return BPMN_XML_CONVERTER.convertToBpmnModel(new StringStreamSource(xml), false, false); } private byte[] getBpmnXML(FlowModel model) { BpmnModel bpmnModel = getBpmnModel(model); return getBpmnXML(bpmnModel); } private byte[] getBpmnXML(BpmnModel bpmnModel) { for (Process process : bpmnModel.getProcesses()) { if (StringUtils.isNotEmpty(process.getId())) { char firstCharacter = process.getId().charAt(0); if (Character.isDigit(firstCharacter)) { process.setId("a" + process.getId()); } } } return BPMN_XML_CONVERTER.convertToXML(bpmnModel); } private BpmnModel getBpmnModel(FlowModel model) { BpmnModel bpmnModel; try { Map formMap = new HashMap<>(16); Map decisionTableMap = new HashMap<>(16); List referencedModels = baseMapper.findByParentModelId(model.getId()); for (FlowModel childModel : referencedModels) { if (FlowModel.MODEL_TYPE_FORM == childModel.getModelType()) { formMap.put(childModel.getId(), childModel); } else if (FlowModel.MODEL_TYPE_DECISION_TABLE == childModel.getModelType()) { decisionTableMap.put(childModel.getId(), childModel); } } bpmnModel = getBpmnModel(model, formMap, decisionTableMap); } catch (Exception e) { log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e); throw new ServiceException("Could not generate BPMN 2.0 model"); } return bpmnModel; } private BpmnModel getBpmnModel(FlowModel model, Map formMap, Map decisionTableMap) { try { ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson()); Map formKeyMap = new HashMap<>(16); for (FlowModel formModel : formMap.values()) { formKeyMap.put(formModel.getId(), formModel.getModelKey()); } Map decisionTableKeyMap = new HashMap<>(16); for (FlowModel decisionTableModel : decisionTableMap.values()) { decisionTableKeyMap.put(decisionTableModel.getId(), decisionTableModel.getModelKey()); } return BPMN_JSON_CONVERTER.convertToBpmnModel(editorJsonNode, formKeyMap, decisionTableKeyMap); } catch (Exception e) { log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e); throw new ServiceException("Could not generate BPMN 2.0 model"); } } public FlowModel getFlowModelByKey(String key){ Map kv = new HashMap(); kv.put("model_key",key); return baseMapper.selectOne(Condition.getQueryWrapper(kv, FlowModel.class)); } }