什么是Activiti?
Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于 Java 的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。
说白了activiti是一个业务流程管理引擎,会沿着设计者设计好的流程,一步一步的执行下去,直到终点。

依赖:

新建springBoot项目时勾选activiti,或者在已建立的springBoot项目添加以下依赖:

  1. <dependency>
  2. <groupId>org.activiti</groupId>
  3. <artifactId>activiti-spring-boot-starter-basic</artifactId>
  4. <version>6.0.0</version>
  5. </dependency>

配置:

数据源和activiti配置:

  1. server:
  2. port: 8081
  3. spring:
  4. datasource:
  5. url: jdbc:mysql://localhost:3306/act5?useSSL=true
  6. driver-class-name: com.mysql.jdbc.Driver
  7. username: root
  8. password: root
  9. # activiti default configuration
  10. activiti:
  11. database-schema-update: true
  12. check-process-definitions: true
  13. process-definition-location-prefix: classpath:/processes/
  14. # process-definition-location-suffixes:
  15. # - **.bpmn
  16. # - **.bpmn20.xml
  17. history-level: full

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

springMVC配置:

  1. package com.yawn.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.format.FormatterRegistry;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.web.servlet.config.annotation.*;
  6. /**
  7. * Created by yawn on 2017/8/5.
  8. */
  9. @EnableWebMvc
  10. @Configuration
  11. public class MvcConfig extends WebMvcConfigurerAdapter {
  12. @Override
  13. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  14. registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
  15. registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
  16. super.addResourceHandlers(registry);
  17. }
  18. @Override
  19. public void addViewControllers(ViewControllerRegistry registry) {
  20. registry.addViewController("/index");
  21. registry.addViewController("/user");
  22. registry.addRedirectViewController("/","/templates/login.html");
  23. // registry.addStatusController("/403", HttpStatus.FORBIDDEN);
  24. super.addViewControllers(registry);
  25. }
  26. }

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

使用activiti:

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

案例:
以以下请假流程为例:

1657246960331.jpg

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

  1. private static final String PROCESS_DEFINE_KEY = "vacationProcess";
  2. public Object startVac(String userName, Vacation vac) {
  3. identityService.setAuthenticatedUserId(userName);
  4. // 开始流程
  5. ProcessInstance vacationInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINE_KEY);
  6. // 查询当前任务
  7. Task currentTask = taskService.createTaskQuery().processInstanceId(vacationInstance.getId()).singleResult();
  8. // 申明任务
  9. taskService.claim(currentTask.getId(), userName);
  10. Map<String, Object> vars = new HashMap<>(4);
  11. vars.put("applyUser", userName);
  12. vars.put("days", vac.getDays());
  13. vars.put("reason", vac.getReason());
  14. // 完成任务
  15. taskService.complete(currentTask.getId(), vars);
  16. return true;
  17. }

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

2. 审批请假(老板)

(1)查询需要自己审批的请假

  1. public Object myAudit(String userName) {
  2. List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(userName)
  3. .orderByTaskCreateTime().desc().list();
  4. // / 多此一举 taskList中包含了以下内容(用户的任务中包含了所在用户组的任务)
  5. // Group group = identityService.createGroupQuery().groupMember(userName).singleResult();
  6. // List<Task> list = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
  7. // taskList.addAll(list);
  8. List<VacTask> vacTaskList = new ArrayList<>();
  9. for (Task task : taskList) {
  10. VacTask vacTask = new VacTask();
  11. vacTask.setId(task.getId());
  12. vacTask.setName(task.getName());
  13. vacTask.setCreateTime(task.getCreateTime());
  14. String instanceId = task.getProcessInstanceId();
  15. ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
  16. Vacation vac = getVac(instance);
  17. vacTask.setVac(vac);
  18. vacTaskList.add(vacTask);
  19. }
  20. return vacTaskList;
  21. }
  22. private Vacation getVac(ProcessInstance instance) {
  23. Integer days = runtimeService.getVariable(instance.getId(), "days", Integer.class);
  24. String reason = runtimeService.getVariable(instance.getId(), "reason", String.class);
  25. Vacation vac = new Vacation();
  26. vac.setApplyUser(instance.getStartUserId());
  27. vac.setDays(days);
  28. vac.setReason(reason);
  29. Date startTime = instance.getStartTime(); // activiti 6 才有
  30. vac.setApplyTime(startTime);
  31. vac.setApplyStatus(instance.isEnded() ? "申请结束" : "等待审批");
  32. return vac;
  33. }
  1. package com.yawn.entity;
  2. import java.util.Date;
  3. /**
  4. * @author Created by yawn on 2018-01-09 14:31
  5. */
  6. public class VacTask {
  7. private String id;
  8. private String name;
  9. private Vacation vac;
  10. private Date createTime;
  11. // getter setter ...
  12. }

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

(2)审批请假

  1. public Object passAudit(String userName, VacTask vacTask) {
  2. String taskId = vacTask.getId();
  3. String result = vacTask.getVac().getResult();
  4. Map<String, Object> vars = new HashMap<>();
  5. vars.put("result", result);
  6. vars.put("auditor", userName);
  7. vars.put("auditTime", new Date());
  8. taskService.claim(taskId, userName);
  9. taskService.complete(taskId, vars);
  10. return true;
  11. }

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

3. 查询记录

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

(1) 查询请假记录

  1. public Object myVacRecord(String userName) {
  2. List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
  3. .processDefinitionKey(PROCESS_DEFINE_KEY).startedBy(userName).finished()
  4. .orderByProcessInstanceEndTime().desc().list();
  5. List<Vacation> vacList = new ArrayList<>();
  6. for (HistoricProcessInstance hisInstance : hisProInstance) {
  7. Vacation vacation = new Vacation();
  8. vacation.setApplyUser(hisInstance.getStartUserId());
  9. vacation.setApplyTime(hisInstance.getStartTime());
  10. vacation.setApplyStatus("申请结束");
  11. List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
  12. .processInstanceId(hisInstance.getId()).list();
  13. ActivitiUtil.setVars(vacation, varInstanceList);
  14. vacList.add(vacation);
  15. }
  16. return vacList;
  17. }

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

  1. package com.yawn.util;
  2. import org.activiti.engine.history.HistoricVariableInstance;
  3. import java.lang.reflect.Field;
  4. import java.util.List;
  5. /**
  6. * activiti中使用得到的工具方法
  7. * @author Created by yawn on 2018-01-10 16:32
  8. */
  9. public class ActivitiUtil {
  10. /**
  11. * 将历史参数列表设置到实体中去
  12. * @param entity 实体
  13. * @param varInstanceList 历史参数列表
  14. */
  15. public static <T> void setVars(T entity, List<HistoricVariableInstance> varInstanceList) {
  16. Class<?> tClass = entity.getClass();
  17. try {
  18. for (HistoricVariableInstance varInstance : varInstanceList) {
  19. Field field = tClass.getDeclaredField(varInstance.getVariableName());
  20. if (field == null) {
  21. continue;
  22. }
  23. field.setAccessible(true);
  24. field.set(entity, varInstance.getValue());
  25. }
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }

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

4. 前端展示和操作

(1)审批列表和审批操作示例

  1. <div ng-controller="myAudit">
  2. <h2 ng-init="myAudit()">待我审核的请假</h2>
  3. <table border="0">
  4. <tr>
  5. <td>任务名称</td>
  6. <td>任务时间</td>
  7. <td>申请人</td>
  8. <td>申请时间</td>
  9. <td>天数</td>
  10. <td>事由</td>
  11. <td>操作</td>
  12. </tr>
  13. <tr ng-repeat="vacTask in vacTaskList">
  14. <td>{{vacTask.name}}</td>
  15. <td>{{vacTask.createTime | date:'yyyy-MM-dd HH:mm:ss'}}</td>
  16. <td>{{vacTask.vac.applyUser}}</td>
  17. <td>{{vacTask.vac.applyTime | date:'yyyy-MM-dd HH:mm:ss'}}</td>
  18. <td>{{vacTask.vac.days}}</td>
  19. <td>{{vacTask.vac.reason}}</td>
  20. <td>
  21. <button type="button" ng-click="passAudit(vacTask.id, 1)">审核通过</button>
  22. <button type="button" ng-click="passAudit(vacTask.id, 0)">审核拒绝</button>
  23. </td>
  24. </tr>
  25. </table>
  26. </div>
  1. app.controller("myAudit", function ($scope, $http, $window) {
  2. $scope.vacTaskList = [];
  3. $scope.myAudit = function () {
  4. $http.get(
  5. "/myAudit"
  6. ).then(function (response) {
  7. $scope.vacTaskList = response.data;
  8. })
  9. };
  10. $scope.passAudit = function (taskId, result) {
  11. $http.post(
  12. "/passAudit",
  13. {
  14. "id": taskId,
  15. "vac": {
  16. "result": result >= 1 ? "审核通过" : "审核拒绝"
  17. }
  18. }
  19. ).then(function (response) {
  20. if (response.data === true) {
  21. alert("操作成功!");
  22. $window.location.reload();
  23. } else {
  24. alert("操作失败!");
  25. }
  26. })
  27. }
  28. });