11

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<String> processDefinitionLocationSuffixes = Arrays.asList("**.bpmn20.xml", "**.bpmn");
    private String restApiMapping = "/api/*";
    private String restApiServletName = "activitiRestApi";
    private boolean jpaEnabled = true; // true by default
    private List<String> customMybatisMappers;
    private List<String> 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<Resource> procDefResources = discoverProcessDefinitionResources(activitiProperties.getProcessDefinitionLocationPrefix(),            activitiProperties.getProcessDefinitionLocationSuffixes(),this.activitiProperties.isCheckProcessDefinitions());
        configuration.setDeploymentResources(procDefResources.toArray(new Resource[procDefResources.size()]));
        Map<String, List<ActivitiEventListener>> 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<Resource> discoverProcessDefinitionResources(String prefix, List<String> suffixes, boolean checkPDs) throws IOException {
        if (checkPDs) {
            List<Resource> 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流程执行计划


坐死等吃
190 声望183 粉丝