1.前言
本文内容主要为以下两点,因为内容有交叉,所以会放在一起介绍。
- 1.以自由跳转为基础实现不改变原先任务id的驳回
关于Activiti6动态跳转可以查看我的另一篇文章Activiti6实现自由跳转 - 2.java类方式进行Activiti6配置、spring boot集成
因为有一些自定义的需求,如流程字体、自动部署、自定义监听器等,直接引入[activit-spring-boot]又没有必要,所以参考activit6源码中[activit-spring-boot]模块的代码完成。
2.实现介绍
关于自由跳转的内容我就不再多说,主要介绍如何修改Activiti生成的实体的id,以达到驳回时重新生成的任务id与原先的任务id一致。(某些业务场景下可能会用到,例如某流程中A环节提交的表单与task id绑定,当环节提交又被驳回时,为保证表单内容与任务关系不变,驳回后的任务id与原先任务id要一致)
2.1前提知识
- 1.Activiti持久化实体的过程时先创建实体对象,记录到缓存中,在完成执行后统一进行缓存对象的持久化,并清空缓存。
- 2.Activiti采用命令模式执行操作,所有操作都时一个CMD。执行一个CMD的时候会创建一个上下文环境,包含待持久化的实体缓存等,如果在CMD中嵌套执行CMD,新的CMD默认会使用上级上下文环境。当一个根级的CMD结束时,Activiti就会进行上述的缓存对象统一的持久化。
- 3.Activiti有丰富的事件类型(具体可以查看事件枚举类ActivitiEventType)供我们实现相应监听器,进行特殊业务处理。例如ENTITY_CREATED——实体创建完成(task、activity、Execution等所有实体)、TASK_CREATED——任务创建完成(针对task)、TASK_COMPLETED——任务完成等等。
2.2关于修改任务id
结合上述内容我们就可以知道,只要在TASK_CREATED进行监听,直接在监听器中将id改为需要的值即可。理论上是这样,但是需要注意,Activiti6中历史任务实体创建是在TASK_CREATED之前的,如果你在TASK_CREATED中修改任务id,实际上历史任务实体创建时是获取不到的,这样就会导致历史任务的id与运行时任务id不一致。解决的办法也很简单,改为监听ENTITY_CREATED,判断是否时需要修改id的任务实体即可。
实现代码
properties配置文件
# 是否更新数据库表
spring.activiti.databaseSchemaUpdate=true
# 是否激活异步执行器
spring.activiti.asyncExecutorActivate=false
# 流程历史记录登录
spring.activiti.historyLevel=audit
# 是否检查更新流程定义
spring.activiti.checkProcessDefinitions=false
# 流程定义所在前缀
spring.activiti.processDefinitionLocationPrefix=classpath*:/procDef/
# 流程定义后缀
spring.activiti.processDefinitionLocationSuffixes=**.bpmn
# 部署流程定义时是否生成图片
spring.activiti.createDiagramOnDeploy=false
# 字体 下面内容为转成unicode的'宋体'
spring.activiti.activityFontName=\u5b8b\u4f53
spring.activiti.labelFontName=\u5b8b\u4f53
解析Properties类
@ConfigurationProperties("spring.activiti")
public class ActivitiProperties {
private boolean checkProcessDefinitions = true;
private boolean asyncExecutorActivate = true;
private boolean restApiEnabled;
private String deploymentName;
private String mailServerHost = "localhost";
private int mailServerPort = 1025;
private String mailServerUserName;
private String mailServerPassword;
private String mailServerDefaultFrom;
private boolean mailServerUseSsl;
private boolean mailServerUseTls;
private String databaseSchemaUpdate = "true";
private String databaseSchema;
private boolean isDbIdentityUsed = true;
private boolean isDbHistoryUsed = true;
private HistoryLevel historyLevel = HistoryLevel.AUDIT;
private String processDefinitionLocationPrefix = "classpath:/processes/";
private List processDefinitionLocationSuffixes = Arrays.asList("**.bpmn20.xml", "**.bpmn");
private String restApiMapping = "/api/*";
private String restApiServletName = "activitiRestApi";
private boolean jpaEnabled = true; // true by default
private List customMybatisMappers;
private List customMybatisXMLMappers;
private boolean createDiagramOnDeploy;
private String activityFontName;
private String labelFontName;
//省略getter、setter
}
spring boot配置类
@Configuration
@EnableConfigurationProperties(ActivitiProperties.class)
public class ActivitiConfig {
private static final Logger logger = LoggerFactory.getLogger(ActivitiConfig.class);
@Autowired
private ActivitiProperties activitiProperties;
@Autowired
private DataSource dataSource;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TaskCreatedListener taskCreatedListener;
@Autowired
private TaskCompletedListener taskCompletedListener;
@Autowired
private EntityCreatedListener entityCreatedListener;
@Autowired
private ResourcePatternResolver resourceLoader;
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration() throws IOException {
SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
configuration.setDataSource(dataSource);
configuration.setTransactionManager(transactionManager);
configuration.setDatabaseSchemaUpdate(activitiProperties.getDatabaseSchemaUpdate());
configuration.setAsyncExecutorActivate(activitiProperties.isAsyncExecutorActivate());
configuration.setHistory(activitiProperties.getHistoryLevel().getKey());
configuration.setCreateDiagramOnDeploy(activitiProperties.isCreateDiagramOnDeploy());
configuration.setActivityFontName(activitiProperties.getActivityFontName());
configuration.setLabelFontName(activitiProperties.getLabelFontName());
//todo 修改自动部署,当前自动部署直接搬自[activit-spring-boot]
//如果checkProcessDefinitions为true,则发布新版流程定义,后续可能根据流程定义文件MD5等判断是否真正变化而进行发布
List procDefResources = discoverProcessDefinitionResources(activitiProperties.getProcessDefinitionLocationPrefix(), activitiProperties.getProcessDefinitionLocationSuffixes(),this.activitiProperties.isCheckProcessDefinitions());
configuration.setDeploymentResources(procDefResources.toArray(new Resource[procDefResources.size()]));
Map> typedListeners = new HashMap<>();
typedListeners.put("ENTITY_CREATED", Collections.singletonList(entityCreatedListener));
typedListeners.put("TASK_CREATED", Collections.singletonList(taskCreatedListener));
typedListeners.put("TASK_COMPLETED", Collections.singletonList(taskCompletedListener));
configuration.setTypedEventListeners(typedListeners);
return configuration;
}
private List discoverProcessDefinitionResources(String prefix, List suffixes, boolean checkPDs) throws IOException {
if (checkPDs) {
List result = new ArrayList<>();
for (String suffix : suffixes) {
String path = prefix + suffix;
Resource[] resources = resourceLoader.getResources(path);
if (resources != null && resources.length > 0) {
CollectionUtils.mergeArrayIntoCollection(resources, result);
}
}
if (result.isEmpty()) {
logger.info("No process definitions were found for autodeployment");
}
return result;
}
return new ArrayList<>();
}
@Bean
public ProcessEngineFactoryBean processEngine() throws IOException {
ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean();
factoryBean.setProcessEngineConfiguration(processEngineConfiguration());
return factoryBean;
}
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine) {
return processEngine.getRuntimeService();
}
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine) {
return processEngine.getRepositoryService();
}
@Bean
public TaskService taskService(ProcessEngine processEngine) {
return processEngine.getTaskService();
}
@Bean
public HistoryService historyService(ProcessEngine processEngine) {
return processEngine.getHistoryService();
}
@Bean
public ManagementService managementService(ProcessEngine processEngine) {
return processEngine.getManagementService();
}
@Bean
public IdentityService identityService(ProcessEngine processEngine) {
return processEngine.getIdentityService();
}
实体创建完成监听器
@Component
public class EntityCreatedListener implements ActivitiEventListener {
public void onEvent(ActivitiEvent event){
Object entity = ((ActivitiEntityEvent)event).getEntity();
if(entity instanceof TaskEntity){
TaskEntity taskEntity = (TaskEntity)entity;
// 这个要改变的id值,可以在上篇文章中的SetFLowNodeAndGoCmd中设置相应流程变量即可。
String changeTaskId = (String)taskEntity.getVariable("changeTaskIdVarKey");
if(!StringUtils.isEmpty(changeTaskId)){
taskEntity.setId(changeTaskId);
taskEntity.setVariable("changeTaskIdKey","");
}
}
}
public boolean isFailOnException(){
return true;
}
}
2.3关于如何获取当前任务的来源任务,以进行驳回
我们知道Activiti中有TASK_CREATED和TASK_COMPLETED事件,在同一个流程实例中,一个任务A如果不是最后的结束任务,那么在它完成后,必定会有一个新的任务B创建,而我们简单理解为A为B的来源任务。(假设A是申请任务,B就时审批任务,B的处理人对当前审批不同意要驳回时,流程就要回退到任务A。)
这样一来,我们可以监听TASK_COMPLETED,在此时为流程设置一个变量fromTaskId,值为任务A的id,当任务A的TASK_COMPLETED结束后,就来到的了任务B的TASK_CREATED中,我们此时从流程变量中获取fromTaskId,并将次id作为任务B的来源id持久化到一张自己创建的任务关系表中。这样后面要进行驳回时,只要通过这样关系表,马上就可以定位到要驳回到的任务id了。
实现代码
任务完成监听器
// 关于监听器的注册看上面配置类中typedListeners部分已有
@Component
public class TaskCompletedListener implements ActivitiEventListener {
public void onEvent(ActivitiEvent event){
TaskEntity taskEntity = (TaskEntity)((ActivitiEntityEvent)event).getEntity();
taskEntity.setVariable("fromTaskIdVarKey", taskEntity.getId());
}
public boolean isFailOnException(){
return true;
}
}
任务创建完成监听器
@Component
public class TaskCreatedListener implements ActivitiEventListener {
public void onEvent(ActivitiEvent event){
TaskEntity taskEntity = (TaskEntity)((ActivitiEntityEvent)event).getEntity();
String fromTaskId = (String)taskEntity.getVariable(WfVarKeyConstants.fromTaskId);
if(StringUtils.isEmpty(fromTaskId)) return;
xxxTaskInfo info = new xxxTaskInfo();
info.setId(taskEntity.getId());
info.setFromId(fromTaskId);
//此处进行任务关系持久化,自行实现
xxxTaskInfoRepository.save(info);
}
public boolean isFailOnException(){
return true;
}
}
3.最后
本来打算做一个Activiti小贴士列表,不过看篇幅已经很长了,小贴士好像也凑不齐一篇文章,而且还没人看:)
那就放到下次来说
todo
1.Activiti命令执行模式
2.持久化过程与会话缓存(CRUD)
3.BPMN流程执行计划