Minutes of exploration Activiti6.0

Foreword

A year to do the operation and maintenance automation platform, we need to use process engine, the project was going to write golang, but golang process engine function is too simple it is not used to, finally selection of java + activiti. Activiti official website to see, hey document is a result of a 7.0 just wrote is not full, we java or 8, 7.0 is matched java11, ultimately had to give up too many problems with activiti6.0 up.

Touch the stones

Although there are many online tutorials, but to really run up really is not easy, some elements such as the meaning of the parameters or look at the manual to figure out the 5.0.

installation

Resolve dependencies Xianpao up

Built environment: java8 + springboot2.1.3 + activiti6.0 + mysql

pom-dependent:

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter-basic</artifactId>
	<version>${activiti.version}</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>
复制代码

Configuration database:

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/activiti?nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
复制代码

Then start, congratulations, did not use it, it will appear the following error:

Caused by: java.io.FileNotFoundException: class path resource [org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.class] cannot be opened because it does not exist:

The reason is activiti6.0 development time springboot2.0 not come out so disrepair, the file path wrong, solution:

Class front row start to get rid of SecurityAutoConfiguration

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)

Start again, if you come across the table to create a database table but can not find the problem, two solutions:

  • Manually create a table, the table pathactiviti-engine-6.0.0.jar/org/activiti/db/create
  • Database configuration plusnullCatalogMeansCurrent=true

This is because the org/activiti/engine/impl/db/DbSqlSessionuse activiti databaseMetaData.getTablesfind the library table exists, and dbSqlSessionFactoryacquired catalogempty because mysql use schemato identify library name instead catalog, leading to mysql libraries scan all come to the table, once the library has with other activiti thought the watches found In fact, the table does not exist. nullCatalogMeansCurrentThe significance lies in the mysql default current library, the mysql-connector-java 5.xdefault is true of the argument, but it defaults to false 6.x or more.

Start again, still not up:

Caused by: java.io.FileNotFoundException: class path resource [processes/] cannot be resolved to URL because it does not exist

This is because activiti will go to the resource / processes the following procedure to find files, create the directory.

Paint plug-in configuration

activiti offers a variety of designer to draw a flow chart, plug-ins and eclipse the IDEA plugin I installed, or relatively easy to use the next eclipse, so only describes the installation process eclipse plugin.

  1. The next eclipse
  2. Download plug-in Help -> Install New Software -> Add:
Names : Activiti BPMN 2.0 designer
Location : https://www.activiti.org/designer/update/
复制代码
  1. New next over a diagram

4. drawing a flow chart of a flowchart of a standard start event and an end event, save for the unfinished .bpmn20.xml .bpmn file or files, and placed under resource / processes directory, an example of xml file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test" id="m1551347612734" name="">
  <process id="machine_expansion" name="Machine Expansion" isExecutable="true" isClosed="false" processType="None">
    <startEvent id="start" name="开始"></startEvent>
    ...
复制代码

run

Use runtimeservice create a createProcessInstanceBuilder, set processDefinitionKey to process id xml, and then start it.

    @Autowired
    private RuntimeService runtimeService;
    @Override
    public String start(BaseParamDTO baseParamDTO) {
        String currentUserName = CurrentUserUtil.getCurrentUserName();
        identityService.setAuthenticatedUserId(currentUserName);
        ExpansionParamDTO expansionParamDTO = (ExpansionParamDTO) baseParamDTO.getParams();
        ProcessInstance machineExpansion = runtimeService.createProcessInstanceBuilder().processDefinitionKey(WorksheetTypeEnum.EXPANSION.getActivitiDefineName())
                .businessKey(expansionParamDTO.getAppName()).name(WorksheetTypeEnum.EXPANSION.name()).start();
        baseParamDTO.setId(machineExpansion.getProcessInstanceId());
        submit(baseParamDTO);
        return machineExpansion.getProcessInstanceId();
    }
复制代码

To set startuser processes must use identityService.setAuthenticatedUserId setting because the process is to obtain the user's current thread.

Database Table

To be familiar with a project, the easiest way is to see his design of the database table is how the table which have saved something, activiti database a total of 28 tables

  • ACT_GE_ *: common table, generally do not focus on, if the binary data id appear in other tables, such as throwing error, went to act_ge_bytearray query
  • ACT_HI_ *: historical information, activiti history information storage division four levels, can be used spring.activiti.historyLevelto configure the corresponding HistoryService
    • none: save nothing
    • activity: to save all of the process instances, task, event information;
    • audit: the default level, save all process instances, tasks, activities, form properties;
    • full: Process process to save all these details and more than audit and variables;
  • ACT_ID_ *: user authentication information corresponding to IdentityService;
  • ACT_RE_ *: deployment and process definitions, corresponding to the RepositoryService;
  • ACT_RU_ *: runtime information, the process level corresponding to the related tables RuntimeService, task-level table corresponds TaskService, job level table corresponding to ManagementService.
Table Name description
act_evt_log Event log table
act_ge_bytearray Binary data table, such as throwing error
act_ge_property activiti attribute table
act_hi_actinst Historical examples of process activities
act_hi_attachment Historical process attachments
act_hi_comment Historical descriptions
act_hi_detail Details the history of the process running
act_hi_identitylink History of the relationship between the user information
act_hi_procinst History of process instances
act_hi_taskinst usertask examples of history
act_hi_varinst History of the process parameters
act_id_group user group
act_id_info User Details information
act_id_membership Users and user groups associated with relations
act_id_user User Info
act_procdef_info Process definition extension table
act_re_deployment Process Deployment Information
act_re_model Process Model
act_re_procdef It has deployed processes
act_ru_deadletter_job Runtime dead job
act_ru_event_subscr Runtime event listener
act_ru_execution Examples of process runtime execution
act_ru_identitylink User relationship information at runtime
act_ru_job job runtime
act_ru_suspended_job Run-time job paused
act_ru_task usertask instance runtime
act_ru_timer_job Timer job runtime
act_ru_variable Run-time parameter

To describe what example

Basic definitions

There are a few basic definitions in the activiti:

  • process: a process instance, for example, defines an approval process bpmn, now run up, this is a process instance.
  • exection: execution instance process, a process has many steps, each step will trigger the execution of a process instance.
  • task: specifically refers to usertask, when creating a usertask, activiti creates a task in exection in.
  • job: the implementation of steps, each exection will generate job to jobExecutor to perform, if the general job in act_ru_job, the timing task in act_ru_timer_job, the job goes into suspended act_ru_suspended_job, failed to run to act_ru_deadletter_job.
  • activity: the word appears in the history table, understanding and exection similar, except that exection is an example of execution, an execution instance can execute multiple activity, while activity refers to a step in the process, it will correspond to a certain step in bpmn .

Several common task of difference

Designer total eclipse There are eight task, now I only use four kinds of them

User task (User Task)

User tasks require people to set the operation to complete tasks such as approval, execution to usertask, the process will be stuck, only the user manually trigger the process will continue. Examples are as follows, there is no hard-coded user when drawing bpmn, but by taskService set assigness in the process, usertask completed only be complete, the follow-up process can be used to control parameters and exclusive gateway.

    @Override
    public void submit(BaseParamDTO baseParamDTO) {
        String currentUserName = CurrentUserUtil.getCurrentUserName();
        ExpansionParamDTO expansionParamDTO = (ExpansionParamDTO) baseParamDTO.getParams();
        Task submit = taskService.createTaskQuery().processInstanceId(baseParamDTO.getId()).taskDefinitionKey(ExpansionDisplayEnum.SUBMIT.getFlow()).singleResult();
        taskService.setAssignee(submit.getId(), currentUserName);
        taskService.setVariable(submit.getId(), INPUT_PARAM, expansionParamDTO);
        taskService.setVariable(submit.getId(), INPUT_PRIORITY, baseParamDTO.getPriority());
        taskService.setPriority(submit.getId(), baseParamDTO.getPriority());
        if (StringUtils.isNotBlank(baseParamDTO.getComment())){
            taskService.addComment(submit.getId(), null, baseParamDTO.getComment());
        }
        taskService.complete(submit.getId());
    }
复制代码

User tasks involved several concepts:

  • assigness: executor of the task, who wrote who performed
  • candidateUsers: candidates for the task, which requires people who write execution
  • candidateGroups: The task of a candidate group, which group of people need to write the implementation of which groups
  • comment: Note, this information will be written act_hi_comment table, and only usertask or processes associated

Script task (Script Task)

Write a script to automatically execute the script when performing this step. (Not used)

Service tasks (Service Task)

The most common task, performed automatically when executed java class corresponding to the step

@Component
public class ApplyMachineTask implements JavaDelegate {

    public static final Logger LOGGER = LoggerFactory.getLogger(ApplyMachineTask.class);

    public static final String MACHINE_APPLY_ID = "applyId";
    public static final String MACHINE_APPLY_ID_LOCAL = "localApplyId";
    public static final String MACHINE_APPLY_CHECK = "applyCheckCount";

    @Autowired
    private MachineService machineService;

    @Override
    public void execute(DelegateExecution delegateExecution){
        ExpansionParamDTO param = delegateExecution.getVariable(INPUT_PARAM, ExpansionParamDTO.class);
        LOGGER.debug("start apply machine, param {}", param);
        param.setWorksheetId(delegateExecution.getProcessInstanceId());
        ApiResponseDTO<String> stringApiResponseDTO = machineService.applyMachine(param);
        if (stringApiResponseDTO.isSuccess()){
            delegateExecution.setVariable(MACHINE_APPLY_ID, stringApiResponseDTO.getBody());
            delegateExecution.setVariableLocal(MACHINE_APPLY_ID_LOCAL, stringApiResponseDTO.getBody());
            delegateExecution.setVariable(MACHINE_APPLY_CHECK, 0);
        } else {
            delegateExecution.setVariableLocal(ERROR_LOCAL, stringApiResponseDTO.getError());
            throw new BpmnError(ERROR_RETRY, stringApiResponseDTO.getError());
        }
    }
}
复制代码

Here referring to the next parameter setting, setVariablesets the process of global variables, that is, any one process step can be acquired parameter for parameter passing, and setVariableLocalset up local variables, parameters, only the current step in order to obtain and use local variables in that state when the flow proceeds to step can be recorded, the parameters related to the coverage problem, so do the phenomenon global and local parameters of the same name (except subprocess).

Business Rules Tasks (BusinessRule Task)

User business rules used to synchronize one or more rules. activiti use drools rules engine enforce business rules. (Not used)

Mail tasks (Mail Task)

Automatic e-mail task, it can send messages to one or more participants. (Not used)

Manual tasks (Manual Task)

Manual tasks defined outside of the BPM engine task. Need someone to represent work completed, and the engine does not need to know, there is no corresponding system and UI interface. For the engine, manual tasks directly through activities performed automatically after the process down to reach it. (Not used)

Receiving task (Receive Task)

Receiving task is a simple task, it waits for arrival of the corresponding message. Process After reaching this step would have been to wait until the message is triggered. As shown below, a receive task hung in a timing boundaries event, event timing boundaries inspection intervals triggered service task status when the status message to the receiving task, the reception task is completed, the timing to stop border gateway.

Timing border gateway configuration
Message triggers:

 Execution receiveTask = runtimeService.createExecutionQuery().processInstanceId(execution.getProcessInstanceId())
    .activityId(ExpansionDisplayEnum.WAIT_INIT.getFlow()).singleResult();
runtimeService.trigger(receiveTask.getId());
复制代码

Calls sub-processes (CallActivity)

Two activity subprocess A is CallActivity A is The subprocess, except that the main flow CallActivity external call is a process, generates a new runtime processId, completely independent of the scope, other processes commonly used to duplicate or need the situation of independent processId; and subprocess is nested sub-processes, he does not generate a new processId, but the definition of an independent event field, usually used to capture the same type of event.

  1. Draw a sub-process, there can not choose async, chose not to run up, my understanding is to call CallActivity and implementation of the new sub-process is performed by a different thread, just to generate calls CallActivity create a sub-process job, and the child whether the process is asynchronous execution in multi instance configuration page.
  2. main config page layout sub-process to be invoked the process id and the need to pass parameters, the meaning of this parameter is the name of the main process parameters privilege biography called the parameter name after the last param
  3. multi instance multi-instance configuration page, sequential configuration that is synchronous or asynchronous place, loop to specify the number of instances of direct, here solved through mass participation, there will be a list of defined collection represents a global parameter name privilegeList of the list for each parameter the instance name called privilege, completion condition set closing conditions. activiti will automatically create multiple parameters when creating multi-instance:
  • nrOfInstances: Total number of instances
  • Examples of currently active: nrOfActiveInstances
  • nrOfCompletedInstances: Examples of completed

Conditions set here is completed when the main flow continues.

Mom, I want to try catch

activiti two events, one is the process events, in the process, one is border incidents, hung on a step due to the operation of indirect flow trigger, and two events are divided into two categories, one It is to capture one is triggered.

  • Capture (Catching): when process execution to the event, it waits to be triggered.
  • Trigger (Throwing): when process execution to the event, will trigger an event.

Here say is a boundary error trapping event.

As shown, all the steps to start the application are placed in a sub-process embedded subprocess, and then mount a boundary error in the capture event subprocess, a procedure to capture any false triggering retry button when throwing error when the child process If the restart is re-execute the entire sub-processes, If you skip this process to continue.

There is a boundary error event errorCode parameter, errorCode to match error trapping:

  • If no errorRef, boundary error event will capture all error events, no matter what the error errorCode Yes.
  • If you set errorRef, and cited a mistake has been saved, the border incidents will only capture the error code with the same error.
  • If you set errorRef, but BPMN 2.0 is not defined error, errorRef as errorCode will use (and similar use of the wrong end of the event).

A gateway configured exclusively, the conditions stated in each path

Sub-processes each step try catch live bugs and throw

delegateExecution.setVariableLocal(ERROR_LOCAL,stringApiResponseDTO.getError());
throw new BpmnError(ERROR_RETRY, stringApiResponseDTO.getError());
复制代码

Note that throwing error in the error text seems to get less, they still use local variables to store it.

How do approve and reject

Because I want to record all the steps, so I put each operating division are carried out, resulting in the approval process as shown in the figure below.

The first submission tasks are performed directly at the start of the process, and then use the listener is set for approval before approving candidates step begins, if the current user is a candidate for approval to skip the approval process, whether through approval by an exclusive gateway plus the parameter control.

Set a listener on the approval usertask, used to set the task of approving candidates

@Component
public class ApprovalTaskListener implements TaskListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApprovalTaskListener.class);

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private ApprovalTask approvalTask;

    @Autowired
    private ApplicationService applicationService;

    @Autowired
    private TaskService taskService;

    @Override
    public void notify(DelegateTask delegateTask) {
        String processInstanceId = delegateTask.getProcessInstanceId();
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String appName = processInstance.getBusinessKey();
        String owner = processInstance.getStartUserId();
        Integer priority = delegateTask.getVariable(INPUT_PRIORITY, Integer.class);
        taskService.setPriority(delegateTask.getId(), priority);
        ApiResponseDTO<ApplicationDTO> application = applicationService.getApplication(appName);
        if (application.isSuccess()){
            List<String> bizMaintainer = application.getBody().getBizMaintainer();
            delegateTask.addCandidateUsers(bizMaintainer);
            //申请人是审批人时直接处理
            if (bizMaintainer.contains(owner)){
                taskService.setAssignee(delegateTask.getId(), owner);
            }
        } else {
            LOGGER.error("get application info error, pId: {}, app: {}", processInstanceId, application);
            throw new RuntimeException(application.getError());
        }
    }
}
复制代码

In approving usertask outer layer is located exection listener approvalSkipListener, as I said before exection is performed prior to usertask, each usertask outer layer has a exection, so the listener can control the behavior of usertask.

@Component
public class ApprovalSkipListener implements ExecutionListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApprovalSkipListener.class);

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private ApplicationService applicationService;

    @Override
    public void notify(DelegateExecution execution) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult();
        String appName = processInstance.getBusinessKey();
        String owner = processInstance.getStartUserId();
        ApiResponseDTO<ApplicationDTO> application = applicationService.getApplication(appName);
        if (application.isSuccess()){
            List<String> bizMaintainer = application.getBody().getBizMaintainer();
            if (bizMaintainer.contains(owner)){
                execution.setVariable(SKIP_ENABLE,true);
                execution.setVariable(APPROVAL_PASS, true);
            }
        } else {
            LOGGER.error("get application info error, pId: {}, app: {}", processInstance.getId(), application);
            throw new RuntimeException(application.getError());
        }
    }
}
复制代码

Skip condition is provided

If rejected, the process will return to submit the task that requires the user to re-submit a new record in the database will appear.

Said rollback to roll back

activiti has a very interesting feature is the compensation, compensation rollback is to use a trigger event and boundary compensation capture events completed. When compensation is triggered, activiti in turn will forward the compensation capture events triggered from.

  1. Draw a compensation trigger event
  2. Draw a boundary compensation capture event
    The figure means that when the process went silent on this task when receiving the trigger and get stuck in a border timed event, if the timing to take the machine off the assembly line will be the end of the process, but if the timing is not the message will trigger a silent period take the compensation trigger events, trigger the application starts, the rollback is successful.

Slowly a lot of problems solution

How did the recording task

When it comes to this issue, we should mention asynchronous tasks:

  • async: Identifies the task is not executed asynchronously, if true means that the task is not jobExecutor executed directly, but rather to generate additional job into the work performed by the worker thread pool.

The official plans to borrow a brief description of it. activiti process is executed by way of a transaction, when the process begins, activit will always promote the process until it encounters a trigger wait state, then the current state to the database, and wait for the next. FIG usertask following a wait state is the first, the second timer is a wait state, and completed usertask service tasks during the same work unit, the same unit of work are success and failure atoms. If the service is successful task database to see is done usertask and complete service task, service task if a mistake can lead to activti transaction is rolled back, and this time it will return to the original state, a phenomenon that is not only still in the database completed usertask record, and see the service task record.

If I want to see the implementation status of the service tasks it, solution is to set async to true, the service task to the background, job periodically scan the database JobExecutor background submitted to the pool of worker threads. As shown below, the service task async set to true, this time there are three wait states, the first one is userTask, a second service task, a third timer. The process starts now, the service will create a job task wait state after usertask completed and put him to save the database, and then executed by the worker thread pool, if the service fails to perform the task job, the job is still in the database. And if that fails the mail task will be the same as before the service is rolled back to the initial state of the task.

Why do not run the job

It is also often encountered a pit, it is still not resolved, which involves exclusive task.

  • exclusive: it means that the task is not an exclusive task, if true, would work with the thread of a process instance exclusive task execution order out all at once.

Or borrow the official chart, assume the following scenario, three service tasks are asynchronous, jobExecutor put this three minutes to perform the job to the worker thread pool, if they arrive at the same time complicated by the convergence gateways that will be consistency, because each branch checks whether the other branches reaching've been waiting for will appear.

To solve this problem, activiti use optimistic locking, when brought together they would go all branches update the version number of the process instance, if the update fails branch will be locked for some time and try again. But the problem is that optimistic locking failure to retry the job by default only three times and the job can lead to non-transactional data duplication. So appeared exclusive task to ensure that when true exclusive activiti for not executing the same process instance while two rows of his task, that is to say async and exclusive at the same time is not really true parallelism, and 6.0 exclusive default is true.

Logically only async is true, exclusive to false locks will not run the job, but the fact is not the case, so I suspect that when multiple machines simultaneously acquire job due to this problem, but also to be the follow-up investigation.

postscript

Finally eloquent summary of the activiti finished, but go beyond that, there are many features not tried, bug did not check out. Revolution is not successful, comrades still work.

Guess you like

Origin juejin.im/post/5d10540851882532bb57b4bc