/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
*
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
*
* 1. This software is for development use only under a valid license
* from BladeX.
*
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
*
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
*
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
*
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
*
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.flow.engine.service.impl;
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 jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
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.editor.language.json.converter.BpmnJsonConverterContext;
import org.flowable.editor.language.json.converter.CustomBpmnJsonConverterContext;
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.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.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.springblade.flow.core.pojo.entity.BladeFlow;
import org.springblade.flow.core.pojo.enums.FlowModeEnum;
import org.springblade.flow.core.utils.TaskUtil;
import org.springblade.flow.engine.constant.FlowEngineConstant;
import org.springblade.flow.engine.entity.FlowExecution;
import org.springblade.flow.engine.entity.FlowModel;
import org.springblade.flow.engine.entity.FlowProcess;
import org.springblade.flow.engine.mapper.FlowMapper;
import org.springblade.flow.engine.service.FlowEngineService;
import org.springblade.flow.engine.utils.FlowCache;
import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
@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())) {
// 给节点增加序号
activityMap.computeIfAbsent(historicActivityInstance.getActivityId(), k -> 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.isEmpty()) {
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.isEmpty()) {
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