Activiti6經過監聽修改實體id、springboot集成配置

1.前言

本文內容主要爲如下兩點,由於內容有交叉,因此會放在一塊兒介紹。java

  • 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要一致)spring

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了。segmentfault

實現代碼

任務完成監聽器

// 關於監聽器的註冊看上面配置類中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流程執行計劃api

相關文章
相關標籤/搜索