这是系列搭建springcloud基础框架的文章,内容包括集成shiro、Mysql主从、seata、activiti、drools、hadoop大数据常用组件、keepalive+nginx https配置等;
创建 chen-activiti:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chen</groupId>
<artifactId>chen-frame-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.chen</groupId>
<artifactId>chen-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Activity -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>6.0.0</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Activiti modeler 可视化依赖 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-modeler</artifactId>
<version>5.23.0</version>
</dependency>
<!-- Activity6 集成 Modeler 需要 jar 包 -->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
<!-- activiti整合动态流程图所需引入的pom文件-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>5.18.0</version> <!-- 5.23.0 报错 com.github.jgraph:jgraphx:pom:v3.9.10 -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
ActivitiApplication.java
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class, SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
@EnableFeignClients(value = {"com.chen.frame.api"})
@ComponentScan(basePackages = {"com.chen.frame.common", "com.chen.frame.api", "com.chen.frame.activiti"})
public class ActivitiApplication {
public static void main(String ...args) {
//SpringApplication.run(ManagerApplication.class, args);
new SpringApplicationBuilder(ActivitiApplication.class)
.properties(new ConcurrentHashMap<String, Object>(){{
put("frame.module.basePackages", "com.chen.frame.activiti.mapper");
}}).run(args);
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
@Bean
@Primary
public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
// 解析器
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 注册XSS SQL 解析器
SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
xssModule.addSerializer(new XssSqlStringJsonSerializer());
objectMapper.registerModule(xssModule);
return objectMapper;
}
}
ProcessEngineConfig ,activiti引擎配置
package com.chen.frame.activiti.configuration;
import com.alibaba.druid.filter.config.ConfigTools;
import lombok.Data;
import org.activiti.engine.*;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 流程引擎配置文件
**/
@Configuration
@ConfigurationProperties(prefix = "spring.shardingsphere")
@Data
public class ProcessEngineConfig {
private Logger logger = LoggerFactory.getLogger(ProcessEngineConfig.class);
// 从 yml 配置文件中获取 mysql 配置信息
@Value("${spring.shardingsphere.datasource.master.url}")
private String url;
@Value("${spring.shardingsphere.datasource.master.driver-class-name}")
private String driverClassName;
@Value("${spring.shardingsphere.datasource.master.username}")
private String username;
@Value("${spring.shardingsphere.datasource.master.password}")
private String password;
//@Value("${spring.datasource.publicKey}")
private String publicKey;
/**
* 初始化流程引擎
* @return
*/
@Primary
@Bean
public ProcessEngine initProcessEngine() {
logger.info("=============================ProcessEngineBegin=============================");
// 流程引擎配置
ProcessEngineConfiguration cfg = null;
try {
cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl(url)
.setJdbcUsername(username)
// 对密码进行解密
//.setJdbcPassword(ConfigTools.decrypt(publicKey, password))
.setJdbcPassword(password)
.setJdbcDriver(driverClassName)
// 初始化基础表,不需要的可以改为 DB_SCHEMA_UPDATE_FALSE
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)
// 默认邮箱配置
// 发邮件的主机地址,先用 QQ 邮箱
.setMailServerHost("smtp.qq.com")
// POP3/SMTP服务的授权码
.setMailServerPassword("3676383abc")
// 默认发件人
.setMailServerDefaultFrom("345268267@qq.com")
// 设置发件人用户名
.setMailServerUsername("管理员");
} catch (Exception e) {
e.printStackTrace();
}
// 初始化流程引擎对象
ProcessEngine processEngine = cfg.buildProcessEngine();
logger.info("=============================ProcessEngineEnd=============================");
return processEngine;
}
//八大接口
// 业务流程的定义相关服务
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine){
return processEngine.getRepositoryService();
}
// 流程对象实例相关服务
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine){
return processEngine.getRuntimeService();
}
// 流程任务节点相关服务
@Bean
public TaskService taskService(ProcessEngine processEngine){
return processEngine.getTaskService();
}
// 流程历史信息相关服务
@Bean
public HistoryService historyService(ProcessEngine processEngine){
return processEngine.getHistoryService();
}
// 表单引擎相关服务
@Bean
public FormService formService(ProcessEngine processEngine){
return processEngine.getFormService();
}
// 用户以及组管理相关服务
@Bean
public IdentityService identityService(ProcessEngine processEngine){
return processEngine.getIdentityService();
}
// 管理和维护相关服务
@Bean
public ManagementService managementService(ProcessEngine processEngine){
return processEngine.getManagementService();
}
// 动态流程服务
@Bean
public DynamicBpmnService dynamicBpmnService(ProcessEngine processEngine){
return processEngine.getDynamicBpmnService();
}
//八大接口 end
}
注:可以运行 下项目,让创建 activiti 相关表;
生成后,把上面的配置 “setDatabaseSchemaUpdate” 改为 false ;
bootstrap.yml
server:
port: 8055
spring:
application:
name: chen-activiti
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
group: DEFAULT_GROUP
shared-configs[0]:
data-id: application-dev.yml
group: DEFAULT_GROUP
refresh: true
shared-configs[1]:
data-id: chen-druid-dev.yml
group: DEFAULT_GROUP
refresh: true
profiles:
active: dev
main:
allow-bean-definition-overriding: true
redis:
host: 127.0.0.1
port: 6379
password: 123456
timeout: 3000
max-wait: 30000
max-active: 100
max-idle: 20
min-idle: 0
mybatis:
mapperLocations: classpath:mapper/**Mapper.xml
type-aliases-package: com.chen.frame.activiti.mapper
configuration:
map-underscore-to-camel-case: true
# shardingsphere 数据库安全检测
management:
health:
db:
enabled: false
第一步,集成设计器;
参考:https://blog.csdn.net/qq_37143673/article/details/102667824
资源下载: https://github.com/Activiti/Activiti/releases/tag/5.23.0/
Activiti-5.23.0.tar.gz
解压到当前目录下: tar -xvf Activiti-5.23.0.tar.gz
基础包 com.chen.frame.activiti 下,创建 editor 包;
在 resources 下创建 static 目录 ;
将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-webapp-explorer2/src/main/webapp/ 下
diagram-viewer、editor-app、modeler.html 拷到 static 目录下 ;
将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-modeler/src/main/java/org/activiti/rest/editor/ 下
main、model两个目录下的三个文件,拷到 com.chen.frame.activiti 基础包的 editor 子包下;
ModelEditorJsonRestResource、ModelSaveRestResource、StencilsetRestResource;
将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-webapp-explorer2/src/main/resources/stencilset.json
拷到 chen-activiti/resources 下;
打开 staitc/editor-app/app-cfg.js
ACTIVITI.CONFIG = {
'contextRoot' : '/activiti-explorer/service',
};
按上面的contextRoot 要在“ModelEditorJsonRestResource、ModelSaveRestResource、StencilsetRestResource” 的类上都添加 requestmapping 注解:
@RestController
@RequestMapping(value = "/activiti-explorer/service")
public class ModelSaveRestResource implements ModelDataJsonConstants
修改相关方法:
修改 “ModelSaveRestResource” 中的 saveModel 方法;
@RequestMapping(value="/model/{modelId}/save", method = RequestMethod.PUT)
@ResponseStatus(value = HttpStatus.OK)
public void saveModel(@PathVariable String modelId, String name, String description, String json_xml, String svg_xml) {
try {
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, name);
modelJson.put(MODEL_DESCRIPTION, description);
model.setMetaInfo(modelJson.toString());
model.setName(name);
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8"));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
// Setup output
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
// Do the transformation
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();
} catch (Exception e) {
LOGGER.error("Error saving model", e);
throw new ActivitiException("Error saving model", e);
}
}
创建 activitiController.java ;
/**
* 工作流代码示例
* @author: linjinp
* @create: 2019-10-22 10:13
**/
@RestController
@RequestMapping("/activiti-explorer/model")
public class ActivitiController {
/**
* 创建基本模型
* @param request
* @param response
*/
@RequestMapping("/create")
public void createModel(HttpServletRequest request, HttpServletResponse response){
try{
String modelName = "modelName";
String modelKey = "modelKey";
String description = "description";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", "canvas");
editorNode.put("resourceId", "canvas");
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
editorNode.put("stencilset", stencilSetNode);
// 定义新模型
Model modelData = repositoryService.newModel();
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, modelName);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
modelData.setMetaInfo(modelObjectNode.toString());
modelData.setName(modelName);
modelData.setKey(modelKey);
//保存模型
repositoryService.saveModel(modelData);
repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
response.sendRedirect(request.getContextPath() + "/modeler.html?modelId=" + modelData.getId());
}catch (Exception e){
}
}
}
运行 chen-activiti;
http://localhost:8055/activiti-explorer/model/create
后会转向到 :http://localhost:8055/modeler.html?modelId=1
设计好流程图后,点击保存,创错 avg相关的错误;
-重新 reimport chen-activiti
-再执行 mvn -U idea:idea, 重启 idea
后再执行 chen-activiti 后再保存,提示与 xml 有关的错误 ;
java.lang.ClassNotFoundException: org.w3c.dom.ElementTraversal,添加 maven 依赖
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency
reimport 后保存正常;
==========================================
1.布署模型
@PostMapping(value = "/deployProcess")
public ResultData deployProcess(String modelKey) throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
Model modelData = repositoryService.createModelQuery().latestVersion()
.modelKey(modelKey).singleResult();
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (null == bytes) {
return new ResultData(ResultStatus.FAILURE, "模型不存在");
}
JsonNode modelNode = new ObjectMapper().readTree(bytes);
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
model.setTargetNamespace(modelData.getCategory());
if (model.getProcesses().size() == 0) {
return new ResultData(ResultStatus.FAILURE,"数据模型不符要求,请至少设计一条主线流程。");
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
// 发布流程
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment().name(modelData.getName())
.category(modelData.getCategory()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
modelData.setDeploymentId(deployment.getId());
repositoryService.saveModel(modelData);
return new ResultData(ResultStatus.SUCCESS, "模型布暑成功");
}
2. 发起一个流程 :
/**
* 启动一个实例
*/
@PostMapping(value = "/submitProcess")
public ResultData submitProcess(String defineKey) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().latestVersion()
.processDefinitionKey(defineKey).singleResult();
Map<String, Object> varaibles = new HashMap<>();
ProcessInstance instance = runtimeService.startProcessInstanceById(pd.getId(), varaibles);
return new ResultData(ResultStatus.SUCCESS, "", instance.getProcessInstanceId());
}
3. 查询当前用户在流程定义下的待办任务
@PostMapping(value = "/queryTaskByUser")
public ResultData queryTaskByUser(String defineKey, String user) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RepositoryService
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(defineKey)
.includeProcessVariables()
.taskAssignee(user).list();
List<TaskVO> taskVOList = new ArrayList<>();
for(Task task: taskList) {
TaskVO taskVO = new TaskVO();
taskVO.setAssignee(task.getAssignee());
taskVO.setInstanceId(task.getProcessInstanceId());
taskVO.setTaskId(task.getId());
taskVO.setTaskName(task.getName());
taskVOList.add(taskVO);
}
return new ResultData(ResultStatus.SUCCESS, "", taskVOList);
}
完成当前待办任务:
@PostMapping(value = "/finishUserTask")
public ResultData finishUserTask(String taskId) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RepositoryService
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task != null) {
taskService.complete(task.getId());
}
return new ResultData(ResultStatus.SUCCESS, "ok");
}
绘制流程实例的审批过程图;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(value = "/draw")
public class ActivitiDrawController {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
//@Autowired
ProcessEngineConfiguration processEngineConfiguration;
@GetMapping(value = "/showProcessStatus")
public void showProcessStatus(HttpServletRequest request, HttpServletResponse response, String instanceId) throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取历史流程实例
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
if (processEngine == null) {
return;
}
//获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
processEngineConfiguration = processEngine.getProcessEngineConfiguration();
Context.setProcessEngineConfiguration((ProcessEngineConfigurationImpl) processEngineConfiguration);
ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId).list();
//高亮环节id集合
List<String> highLightedActivitis = new ArrayList<String>();
for(HistoricActivityInstance tempActivity : highLightedActivitList){
String activityId = tempActivity.getActivityId();
highLightedActivitis.add(activityId);
}
//高亮线路id集合
//List<String> highLightedFlows = getHighLightedFlows(bpmnModel,highLightedActivitList);
List<String> highLightedFlows = getHighLightedFlows(bpmnModel,definitionEntity,highLightedActivitList);
//中文显示的是口口口,设置字体就好了
InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel,"png",highLightedActivitis,highLightedFlows,"宋体","微软雅黑","黑体",null,2.0);
//单独返回流程图,不高亮显示
//InputStream imageStream = diagramGenerator.generatePngDiagram(bpmnModel);
// 输出资源内容到相应对象
byte[] b = new byte[1024];
int len;
while ((len = imageStream.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
}
/**
* 获取需要高亮的线
*
* @param historicActivityInstances
* @return
*/
private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstances.add(historicActivityInstance);
}
}
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();
/**
* 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
List<Map<String, Object>> tempMapList = new ArrayList<>();
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);
}
}
}
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;
}
public List<String> getHighLightedFlows(BpmnModel bpmnModel,ProcessDefinitionEntity processDefinitionEntity,List<HistoricActivityInstance> historicActivityInstances)
{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //24小时制
List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId
for (int i = 0; i < historicActivityInstances.size() - 1; i++)
{
// 对历史流程节点进行遍历
// 得到节点定义的详细信息
FlowNode activityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();// 用以保存后续开始时间相同的节点
FlowNode sameActivityImpl1 = null;
HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);// 第一个节点
HistoricActivityInstance activityImp2_ ;
for(int k = i + 1 ; k <= historicActivityInstances.size() - 1; k++)
{
activityImp2_ = historicActivityInstances.get(k);// 后续第1个节点
if ( activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") &&
df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime())) ) //都是usertask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点
{
}
else
{
sameActivityImpl1 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());//找到紧跟在后面的一个节点
break;
}
}
sameStartTimeNodes.add(sameActivityImpl1); // 将后面第一个节点放在时间相同节点的集合里
for (int j = i + 1; j < historicActivityInstances.size() - 1; j++)
{
HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
if (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime())) )
{// 如果第一个节点和第二个节点开始时间相同保存
FlowNode sameActivityImpl2 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
sameStartTimeNodes.add(sameActivityImpl2);
}
else
{// 有不相同跳出循环
break;
}
}
List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows() ; // 取出节点的所有出去的线
for (SequenceFlow pvmTransition : pvmTransitions)
{// 对所有的线进行遍历
FlowNode pvmActivityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement( pvmTransition.getTargetRef());// 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示
if (sameStartTimeNodes.contains(pvmActivityImpl)) {
highFlows.add(pvmTransition.getId());
}
}
}
return highFlows;
}
}
运行:http://localhost:8055/draw/showProcessStatus?instanceId=7501
其他相关参考;
https://blog.csdn.net/weixin_49891230/article/details/124349446 (设计器 )
https://zhuanlan.zhihu.com/p/593527050?utm_id=0 (不错,详细)
https://blog.csdn.net/m0_71777195/article/details/128032767 (详细流程实例及情况)
https://blog.csdn.net/m0_49605579/article/details/125395515 (也不错,最详细)
https://blog.csdn.net/weixin_48351067/article/details/123821155
https://vimsky.com/zh-tw/examples/detail/java-class-org.activiti.engine.repository.ModelQuery.html (查询实例)
https://blog.csdn.net/zhuchunyan_aijia/article/details/101285564 (绘制实例的审批过程图)
https://blog.csdn.net/yabushandaxue/article/details/119297080 (监听器)
https://blog.csdn.net/qq_33333654/article/details/101424876 (监听器,详细,不错)
https://bbs.huaweicloud.com/blogs/302002 (监听器,看得明白)
========================
监听器 :
1)任务监听器
三种类型 :create、assignment、complete (顺序:assignment->create->complete)
实例:
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:taskListener event="create"
class="org.crazyit.activiti.PropertyConfigListener" />
</extensionElements>
</userTask>
监听器代码:
public class PropertyConfigListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
System.out.println("执行任务监听器");
}
}
表达式形式: (expression)================================
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:taskListener event="create" expression="${myBean.testBean(task)}"/>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
在流程启动时,设置 myBean 参数;需要实现 “Serializable”
public class ExpressionBean implements Serializable {
public void testBean(DelegateTask task) {
System.out.println("执行ExpressionBean的 testBean方法: " + task.getId());
}
}
//=======================
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
// 部署流程文件
repositoryService.createDeployment()
.addClasspathResource("bpmn/ExpressionTaskListener.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("myBean", new ExpressionBean());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
表达式形式: (delegateExpression)================================
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:taskListener event="create"
delegateExpression="${myDelegate}"></activiti:taskListener>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
代码调用, 监听器需要实现 TaskListener, Serializable;
public class DelegateBean implements TaskListener, Serializable {
public void notify(DelegateTask delegateTask) {
System.out.println("使用DelegateBean");
}
}
public class DelegateExpressionTaskListener {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
// 部署流程文件
repositoryService
.createDeployment()
.addClasspathResource(
"bpmn/DelegateExpressionTaskListener.bpmn")
.deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("myDelegate", new DelegateBean());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey(
"process1", vars);
}
}
任务创建后,会调用myDelegate 的 notify 方法;
==============================================
流程监听器:
触发流程监听事件有以下几种:开始事件,结束事件,流程之间的过渡,流程活动的开始和结束,流程网关的开始,中间事件的开始和结束,开始事件的结束和结束事件的开始。只需要将扩展节点放到对应的位置即可。下面示例演示 开始节点,结束节点,第一个流转的监听事件。
bpmn文件
<process id="myProcess" name="My process" isExecutable="true">
<extensionElements>
<activiti:executionListener event="end" class="org.scf.act.api.event.listener.EndListener">
<activiti:field name="message">
<activiti:string>流程结束</activiti:string>
</activiti:field>
</activiti:executionListener>
<activiti:executionListener event="start" class="org.scf.act.api.event.listener.StartListener">
<activiti:field name="message">
<activiti:string>流程开始</activiti:string>
</activiti:field>
</activiti:executionListener>
</extensionElements>
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<userTask id="usertask1" name="User Task"></userTask>
<userTask id="usertask2" name="User Task"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1">
<extensionElements>
<activiti:executionListener class="org.scf.act.api.event.listener.FlowListener">
<activiti:field name="message">
<activiti:string>开始到task1</activiti:string>
</activiti:field>
</activiti:executionListener>
</extensionElements>
</sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
</process>
以上三个监听类的代码,相同;
public class StartListener implements ExecutionListener{
private FixedValue message;
public FixedValue getMessage() {
return message;
}
public void setMessage(FixedValue message) {
this.message = message;
}
@Override
public void notify(DelegateExecution execution) {
System.out.println(message.getExpressionText());
}
}
运行启动
Deployment deploye = rs.createDeployment().addClasspathResource("event_listener.bpmn").deploy();
ProcessDefinition pdf = rs.createProcessDefinitionQuery().deploymentId(deploye.getId()).singleResult();
ProcessInstance pi = runS.startProcessInstanceById(pdf.getId());
System.out.println(pi.getId());
Task task = ts.createTaskQuery().processInstanceId(pi.getId()).singleResult();
ts.complete(task.getId());
task = ts.createTaskQuery().processInstanceId(pi.getId()).singleResult();
ts.complete(task.getId());
运行结果:
流程开始
开始到task1
332505
流程结束