Spring boot integrates Activiti workflow (source code attached)

What is Activiti?

Activiti is an open source workflow engine, which implements BPMN 2.0 specification, can publish the designed process definition, and schedule the process through api. Activiti, as an open-source platform for workflow and business process management licensed by Apache, its core is the super fast and super stable BPMN 2.0 process engine based on Java, which emphasizes the embeddability and scalability of process services, and emphasizes business oriented personnel.

To put it bluntly, activiti is a business process management engine, which will follow the designer's designed process step by step to the end.

Dependence:

Check activiti when creating a new springBoot project, or add the following dependencies to an established springBoot project:

<dependency>
	<groupId>org.activiti</groupId>
	<artifactId>activiti-spring-boot-starter-basic</artifactId>
	<version>6.0.0</version>
</dependency>

To configure:

Data source and activiti configuration:

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/act5?useSSL=true
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
  # activiti default configuration
  activiti:
    database-schema-update: true
    check-process-definitions: true
    process-definition-location-prefix: classpath:/processes/
#    process-definition-location-suffixes:
#      - **.bpmn
#      - **.bpmn20.xml
    history-level: full

In the default configuration of activiti, process definition location prefix specifies the prefix (path) of the process description file of activiti. When starting, activiti will search for the process description file under this path and deploy it automatically. Suffix is a String array, indicating the default suffix name of the description file. The above two are the default.

Spring MVC configuration:

package com.yawn.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.config.annotation.*;
@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");         registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
        super.addResourceHandlers(registry);
    }
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index");
        registry.addViewController("/user");
        registry.addRedirectViewController("/","/templates/login.html");
//        registry.addStatusController("/403", HttpStatus.FORBIDDEN);
        super.addViewControllers(registry);
    }
}

Configure static resources and directly accessed pages here: in this example project, we add the thymeleaf dependency resolution view, which mainly obtains data asynchronously, and processes and displays the front-end data through angularJS.

Using activiti:

After configuring the data source and activiti, start the project, and the service components of activiti have been added to the spring container, so they can be injected directly. If you are in a spring environment that is not automatically configured, you can use the init method of the specified bean to configure the service components of activiti.

Case study:

Take the leave process as an example:

1. Start the process and "apply for leave" (employee)

    private static final String PROCESS_DEFINE_KEY = "vacationProcess";
    public Object startVac(String userName, Vacation vac) {
        identityService.setAuthenticatedUserId(userName);
        // Start process
        ProcessInstance vacationInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINE_KEY);
        // Query current task
        Task currentTask = taskService.createTaskQuery().processInstanceId(vacationInstance.getId()).singleResult();
        // Affirming mission
        taskService.claim(currentTask.getId(), userName);
        Map<String, Object> vars = new HashMap<>(4);
        vars.put("applyUser", userName);
        vars.put("days", vac.getDays());
        vars.put("reason", vac.getReason());
        // Finish the task
        taskService.complete(currentTask.getId(), vars);
        return true;
    }

In this method, Vaction is the specific information at the time of application, which can be set as parameters when completing the task of "applying for leave".

2. Approve leave (boss)

(1) Query leave requests that need self approval

    public Object myAudit(String userName) {
        List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(userName)
                .orderByTaskCreateTime().desc().list();
//        /The task list contains the following contents (tasks of the user include tasks of the user group)
//        Group group = identityService.createGroupQuery().groupMember(userName).singleResult();
//        List<Task> list = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
//        taskList.addAll(list);
        List<VacTask> vacTaskList = new ArrayList<>();
        for (Task task : taskList) {
            VacTask vacTask = new VacTask();
            vacTask.setId(task.getId());
            vacTask.setName(task.getName());
            vacTask.setCreateTime(task.getCreateTime());
            String instanceId = task.getProcessInstanceId();
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
            Vacation vac = getVac(instance);
            vacTask.setVac(vac);
            vacTaskList.add(vacTask);
        }
        return vacTaskList;
    }
    private Vacation getVac(ProcessInstance instance) {
        Integer days = runtimeService.getVariable(instance.getId(), "days", Integer.class);
        String reason = runtimeService.getVariable(instance.getId(), "reason", String.class);
        Vacation vac = new Vacation();
        vac.setApplyUser(instance.getStartUserId());
        vac.setDays(days);
        vac.setReason(reason);
        Date startTime = instance.getStartTime(); // activiti 6
        vac.setApplyTime(startTime);
        vac.setApplyStatus(instance.isEnded() ? "End of application" : "Waiting for approval");
        return vac;
    }
package com.yawn.entity;
import java.util.Date;
public class VacTask {
    private String id;
    private String name;
    private Vacation vac;
    private Date createTime;
    // getter setter ...
}

The boss queries the tasks that he needs to approve at present, and sets the tasks and parameters to a VacTask object for the display of the page.

(2) Approval of leave

    public Object passAudit(String userName, VacTask vacTask) {
        String taskId = vacTask.getId();
        String result = vacTask.getVac().getResult();
        Map<String, Object> vars = new HashMap<>();
        vars.put("result", result);
        vars.put("auditor", userName);
        vars.put("auditTime", new Date());
        taskService.claim(taskId, userName);
        taskService.complete(taskId, vars);
        return true;
    }

Similarly, result is the result of approval and the parameter that needs to be passed in when the approval task is completed. taskId is the ID of the approval task that the boss has just queried and needs to complete by himself. (if the process sets branches here, you can jump to different tasks by judging the value of result.)

3. Query record

Because the completed leave can not be found in the database runtime table (the runtime table only stores the process sample information in progress), you need to query in the history table.

(1) Query leave records

    public Object myVacRecord(String userName) {
        List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey(PROCESS_DEFINE_KEY).startedBy(userName).finished()
                .orderByProcessInstanceEndTime().desc().list();
        List<Vacation> vacList = new ArrayList<>();
        for (HistoricProcessInstance hisInstance : hisProInstance) {
            Vacation vacation = new Vacation();
            vacation.setApplyUser(hisInstance.getStartUserId());
            vacation.setApplyTime(hisInstance.getStartTime());
            vacation.setApplyStatus("End of application");
            List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisInstance.getId()).list();
            ActivitiUtil.setVars(vacation, varInstanceList);
            vacList.add(vacation);
        }
        return vacList;
    }

Leave record is to find out the historical process instance, find out the associated historical parameters, set the historical process instance and historical parameters to the Vcation object (VO object), and then return to display.

package com.yawn.util;
import org.activiti.engine.history.HistoricVariableInstance;
import java.lang.reflect.Field;
import java.util.List;
//Tools and methods used in activiti
public class ActivitiUtil {

    //Set history parameter list to entity
    public static <T> void setVars(T entity, List<HistoricVariableInstance> varInstanceList) {
        Class<?> tClass = entity.getClass();
        try {
            for (HistoricVariableInstance varInstance : varInstanceList) {
                Field field = tClass.getDeclaredField(varInstance.getVariableName());
                if (field == null) {
                    continue;
                }
                field.setAccessible(true);
                field.set(entity, varInstance.getValue());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

In addition, the above is a general method for setting VO objects after querying historical process instances and historical parameters: parameters with the same name as VO object attributes can be set to VO objects according to the parameters in the parameter list.

4. Front end display and operation

(1) Approval list and approval operation example

<div ng-controller="myAudit">
        <h2 ng-init="myAudit()">Leave to be reviewed by me</h2>
        <table border="0">
            <tr>
                <td>Task name</td>
                <td>Mission time</td>
                <td>Applicant</td>
                <td>Application time</td>
                <td>Days</td>
                <td>Cause</td>
                <td>operation</td>
            </tr>
            <tr ng-repeat="vacTask in vacTaskList">
                <td>{{vacTask.name}}</td>
                <td>{{vacTask.createTime | date:'yyyy-MM-dd HH:mm:ss'}}</td>
                <td>{{vacTask.vac.applyUser}}</td>
                <td>{{vacTask.vac.applyTime | date:'yyyy-MM-dd HH:mm:ss'}}</td>
                <td>{{vacTask.vac.days}}</td>
                <td>{{vacTask.vac.reason}}</td>
                <td>
                    <button type="button" ng-click="passAudit(vacTask.id, 1)">Audit pass</button>
                    <button type="button" ng-click="passAudit(vacTask.id, 0)">Audit reject</button>
                </td>
            </tr>
        </table>
    </div>
app.controller("myAudit", function ($scope, $http, $window) {
    $scope.vacTaskList = [];
    $scope.myAudit = function () {
        $http.get(
            "/myAudit"
        ).then(function (response) {
            $scope.vacTaskList = response.data;
        })
    };
    $scope.passAudit = function (taskId, result) {
        $http.post(
            "/passAudit",
            {
                "id": taskId,
                "vac": {
                    "result": result >= 1 ? "Audit pass" : "Audit reject"
                }
            }
        ).then(function (response) {
            if (response.data === true) {
                alert("Operation succeeded!");
                $window.location.reload();
            } else {
                alert("Operation failed!");
            }
        })
    }
});

The above is part of the code and description of a sample project integrated with springBoot and activiti 6.0. The complete project code is as follows:

https://gitee.com/LiuAustin/activiti-demo6-springboot

Published 15 original articles, praised 0, visited 2092
Private letter follow

Tags: Spring Java SpringBoot JDBC

Posted on Sun, 02 Feb 2020 10:06:40 -0500 by Arsenal Rule