依赖

新建 spring Boot 项目时勾选 Activiti,或者在已建立的 Spring Boot 项目添加以下依赖:

org.activitiactiviti-spring-boot-starter-basic6.0.0

配置

数据源和 Activiti 配置:

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

在 Activiti 的默认配置中,process-definition-location-prefix 是指定 Activiti 流程描述文件的前缀(即路径),启动时,Activiti 就会去寻找此路径下的流程描述文件,并且自动部署;suffix 是一个 String 数组,表示描述文件的默认后缀名,默认以上两种。

Spring MVC 配置

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.*;

/**
 * Created by yawn on 2017/8/5.
 */
@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);
    }
}

这里配置静态资源和直接访问的页面: 在本示例项目中,添加了 thymeleaf 依赖解析视图,主要采用异步方式获取数据,通过 AngularJS 进行前端数据的处理和展示。

使用 Activiti

配置了数据源和 Activiti 后启动项目,activiti 的各个服务组件就已经被加入到 Spring 容器中了,所以就可以直接注入使用了。如果在未自动配置的 Spring 环境中,可以使用通过指定 bean 的 init-method 来配置 activiti 的服务组件。

案例

下面以请假流程为例说明:

在Spring Boot项目中整合使用Activiti_Spring Boot

1. 开始流程并“申请请假”(员工)

privatestaticfinal String PROCESS_DEFINE_KEY = "vacationProcess";public Object startVac(String userName, Vacation vac) {
    identityService.setAuthenticatedUserId(userName);// 开始流程
    ProcessInstance vacationInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINE_KEY);// 查询当前任务
    Task currentTask = taskService.createTaskQuery().processInstanceId(vacationInstance.getId()).singleResult();// 申明任务
    taskService.claim(currentTask.getId(), userName);

    Mapvars = new HashMap<>(4);
    vars.put("applyUser", userName);
    vars.put("days", vac.getDays());
    vars.put("reason", vac.getReason());
    // 完成任务
    taskService.complete(currentTask.getId(), vars);

    returntrue;
}

在此方法中,Vacation 是申请时的具体信息,在完成“申请请假”任务时,可以将这些信息设置成参数。

2. 审批请假(老板)


2.1 查询需要自己审批的请假

public Object myAudit(String userName) {
    ListtaskList = taskService.createTaskQuery().taskCandidateUser(userName)
                      .orderByTaskCreateTime().desc().list();
    // 多此一举 taskList中包含了以下内容(用户的任务中包含了所在用户组的任务)
    // Group group = identityService.createGroupQuery().groupMember(userName).singleResult();
    // Listlist = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
    // taskList.addAll(list);
    ListvacTaskList = 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() ? "申请结束" : "等待审批");
    return vac;
}

package com.yawn.entity;
import java.util.Date;
/**
 * @author Created by yawn on 2018-01-09 14:31
 */
public class VacTask {
    private String id;
    private String name;
    private Vacation vac;
    private Date createTime;
    // getter setter ...
}

老板查询自己当前需要审批的任务,并且将任务和参数设置到一个 VacTask 对象,用于页面的展示。

2.2 审批请假

public Object passAudit(String userName, VacTask vacTask) {
    String taskId = vacTask.getId();
    String result = vacTask.getVac().getResult();
    Mapvars = 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;
}

同理,result 是审批的结果,也是在完成审批任务时需要传入的参数;taskId 是刚才老板查询到的当前需要自己完成的审批任务 ID。(如果流程在这里设置分支,可以通过判断 result 的值来跳转到不同的任务)

3.  查询记录

由于已完成的请假在数据库 runtime 表中查不到(runtime 表只保存正在进行的流程示例信息),所以需要在 history 表中查询。


3.1 查询请假记录

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

请假记录即查出历史流程实例,再查出关联的历史参数,将历史流程实例和历史参数设置到 Vocation 对象(VO对象)中去,即可返回,用来展示。

package com.yawn.util;
import org.activiti.engine.history.HistoricVariableInstance;
import java.lang.reflect.Field;
import java.util.List;
/**
 * activiti中使用得到的工具方法
 * @author Created by yawn on 2018-01-10 16:32
 */
public class ActivitiUtil {
    /**
     * 将历史参数列表设置到实体中去
     * @param entity 实体
     * @param varInstanceList 历史参数列表
     */
    public staticvoid setVars(T entity, ListvarInstanceList) {
        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();
        }
    }
}

此外,以上是查询历史流程实例和历史参数后,设置VO对象的通用方法:可以根据参数列表中的参数,将与VO对象属性同名的参数设置到VO对象中去。

4. 前端展示和操作


4.1 审批列表和审批操作示例

在Spring Boot项目中整合使用Activiti_Spring Boot_02

待我审核的请假任务名称任务时间申请人申请时间天数事由操作{{vacTask.name}}{{vacTask.createTime | date:'yyyy-MM-dd HH:mm:ss'}}{{vacTask.vac.applyUser}}{{vacTask.vac.applyTime | date:'yyyy-MM-dd HH:mm:ss'}}{{vacTask.vac.days}}{{vacTask.vac.reason}}审核通过审核拒绝

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 ? "审核通过" : "审核拒绝"
                }
            }
        ).then(function (response) {
            if (response.data === true) {
                alert("操作成功!");
                $window.location.reload();
            } else {
                alert("操作失败!");
            }
        })
    }
});

以上是一个 Spring Boot 与 Activiti 6.0 整合的示例项目的部分代码与说明,完整的项目代码在:

gitee.com/yawensilence/activiti-demo6-springboot

在Spring Boot项目中整合使用Activiti_Activiti_03