add 添加通过流程实例id获取历史流程图,添加flowable配置,调整流程办理

This commit is contained in:
songgaoshuai 2023-06-08 18:18:48 +08:00
parent e69e9d3087
commit fa3e9f82bd
8 changed files with 1444 additions and 8 deletions

View File

@ -15,4 +15,9 @@ public interface FlowConstant {
String MESSAGE_CURRENT_TASK_IS_NULL = "当前任务不存在或你不是任务办理人";
/**
* 连线
*/
String SEQUENCE_FLOW = "sequenceFlow";
}

View File

@ -0,0 +1,35 @@
package org.dromara.workflow.controller;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 流程实例管理 控制层
*
* @author may
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/processInstance")
public class ActProcessInstanceController extends BaseController {
private final IActProcessInstanceService iActProcessInstanceService;
/**
* 通过流程实例id获取历史流程图
*
* @param processInstanceId 流程实例id
* @param response 响应
*/
@GetMapping("/getHistoryProcessImage/{processInstanceId}")
public void getHistoryProcessImage(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId,
HttpServletResponse response) {
iActProcessInstanceService.getHistoryProcessImage(processInstanceId, response);
}
}

View File

@ -0,0 +1,108 @@
package org.dromara.workflow.flowable;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
public class CustomDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
//设置高亮线的颜色 这里我设置成绿色
protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN;
public CustomDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
}
/**
* 画线颜色设置
*/
public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType,
AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(CONNECTION_COLOR);
if (connectionType.equals("association")) {
g.setStroke(ASSOCIATION_STROKE);
} else if (highLighted) {
//设置线的颜色
g.setPaint(HIGHLIGHT_SEQUENCEFLOW_COLOR);
g.setStroke(HIGHLIGHT_FLOW_STROKE);
}
for (int i = 1; i < xPoints.length; i++) {
Integer sourceX = xPoints[i - 1];
Integer sourceY = yPoints[i - 1];
Integer targetX = xPoints[i];
Integer targetY = yPoints[i];
Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
g.draw(line);
}
if (isDefault) {
Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
drawDefaultSequenceFlowIndicator(line, scaleFactor);
}
if (conditional) {
Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
drawConditionalSequenceFlowIndicator(line, scaleFactor);
}
if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) {
Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
drawArrowHead(line, scaleFactor);
}
if (associationDirection == AssociationDirection.BOTH) {
Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
drawArrowHead(line, scaleFactor);
}
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
/**
* 高亮节点设置
*/
public void drawHighLight(int x, int y, int width, int height) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
//设置高亮节点的颜色
g.setPaint(HIGHLIGHT_COLOR);
g.setStroke(THICK_TASK_BORDER_STROKE);
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
g.draw(rect);
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
/**
* @description: 高亮节点红色
* @param: x
* @param: y
* @param: width
* @param: height
* @return: void
* @author: gssong
* @date: 2022/4/12
*/
public void drawHighLightRed(int x, int y, int width, int height) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
//设置高亮节点的颜色
g.setPaint(Color.green);
g.setStroke(THICK_TASK_BORDER_STROKE);
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
g.draw(rect);
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
}

View File

@ -0,0 +1,20 @@
package org.dromara.workflow.flowable.config;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
/**
* flowable配置
*
* @author may
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
processEngineConfiguration.setIdGenerator(IdWorker::getIdStr);
}
}

View File

@ -0,0 +1,18 @@
package org.dromara.workflow.service;
import jakarta.servlet.http.HttpServletResponse;
/**
* 流程实例 服务层
*
* @author may
*/
public interface IActProcessInstanceService {
/**
* 通过流程实例id获取历史流程图
*
* @param processInstanceId 流程实例id
* @param response 响应
*/
void getHistoryProcessImage(String processInstanceId, HttpServletResponse response);
}

View File

@ -0,0 +1,132 @@
package org.dromara.workflow.service.impl;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.flowable.CustomDefaultProcessDiagramGenerator;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 流程实例 服务层实现
*
* @author may
*/
@RequiredArgsConstructor
@Service
public class ActProcessInstanceServiceImpl implements IActProcessInstanceService {
private final RepositoryService repositoryService;
private final RuntimeService runtimeService;
private final HistoryService historyService;
@Value("${flowable.activity-font-name}")
private String activityFontName;
@Value("${flowable.label-font-name}")
private String labelFontName;
@Value("${flowable.annotation-font-name}")
private String annotationFontName;
/**
* 通过流程实例id获取历史流程图
*
* @param processInstanceId 流程实例id
* @param response 响应
*/
@Override
public void getHistoryProcessImage(String processInstanceId, HttpServletResponse response) {
// 设置页面不缓存
response.setHeader("Pragma", "no-cache");
response.addHeader("Cache-Control", "must-revalidate");
response.addHeader("Cache-Control", "no-cache");
response.addHeader("Cache-Control", "no-store");
response.setDateHeader("Expires", 0);
InputStream inputStream = null;
try {
String processDefinitionId;
// 获取当前的流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
// 如果流程已经结束则得到结束节点
if (Objects.isNull(processInstance)) {
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
} else {
// 根据流程实例ID获得当前处于活动状态的ActivityId合集
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
}
// 获得活动的节点
List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
List<String> highLightedFlows = new ArrayList<>();
List<String> highLightedNodes = new ArrayList<>();
//高亮
for (HistoricActivityInstance tempActivity : highLightedFlowList) {
if (FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType())) {
//高亮线
highLightedFlows.add(tempActivity.getActivityId());
} else {
//高亮节点
if (tempActivity.getEndTime() == null) {
highLightedNodes.add(Color.RED.toString() + tempActivity.getActivityId());
} else {
highLightedNodes.add(tempActivity.getActivityId());
}
}
}
List<String> highLightedNodeList = new ArrayList<>();
//运行中的节点
List<String> redNodeCollect = highLightedNodes.stream().filter(e -> e.contains(Color.RED.toString())).collect(Collectors.toList());
//排除与运行中相同的节点
for (String nodeId : highLightedNodes) {
if (!nodeId.contains(Color.RED.toString()) && !redNodeCollect.contains(Color.RED + nodeId)) {
highLightedNodeList.add(nodeId);
}
}
highLightedNodeList.addAll(redNodeCollect);
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
CustomDefaultProcessDiagramGenerator diagramGenerator = new CustomDefaultProcessDiagramGenerator();
inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodeList, highLightedFlows, activityFontName, labelFontName, annotationFontName, null, 1.0, true);
// 响应相关图片
response.setContentType("image/png");
byte[] bytes = IOUtils.toByteArray(inputStream);
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -53,6 +53,7 @@ public class ActTaskServiceImpl implements IActTaskService {
* @param startProcessBo 启动流程参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> startWorkFlow(StartProcessBo startProcessBo) {
Map<String, Object> map = new HashMap<>();
if (StringUtils.isBlank(startProcessBo.getBusinessKey())) {
@ -74,12 +75,7 @@ public class ActTaskServiceImpl implements IActTaskService {
Map<String, Object> variables = startProcessBo.getVariables();
// 启动跳过表达式
variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true);
ProcessInstance pi;
if (CollUtil.isNotEmpty(variables)) {
pi = runtimeService.startProcessInstanceByKeyAndTenantId(startProcessBo.getProcessKey(), startProcessBo.getBusinessKey(), variables, TenantHelper.getTenantId());
} else {
pi = runtimeService.startProcessInstanceByKeyAndTenantId(startProcessBo.getProcessKey(), startProcessBo.getBusinessKey(), TenantHelper.getTenantId());
}
ProcessInstance pi = runtimeService.startProcessInstanceByKeyAndTenantId(startProcessBo.getProcessKey(), startProcessBo.getBusinessKey(), variables, TenantHelper.getTenantId());
// 将流程定义名称 作为 流程实例名称
runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName());
// 申请人执行流程
@ -116,10 +112,10 @@ public class ActTaskServiceImpl implements IActTaskService {
if (task == null) {
throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
}
//办理任务
taskService.complete(completeTaskBo.getTaskId(), completeTaskBo.getVariables());
//办理意见
taskService.addComment(completeTaskBo.getTaskId(), task.getProcessInstanceId(), StringUtils.isBlank(completeTaskBo.getMessage()) ? "同意" : completeTaskBo.getMessage());
//办理任务
taskService.complete(completeTaskBo.getTaskId(), completeTaskBo.getVariables());
return true;
} catch (Exception e) {
throw new ServiceException(e.getMessage());