⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 jvm123.com/2019/08/springboot-activiti/ 「yawn Lau」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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

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

数据源和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数组,表示描述文件的默认后缀名,默认以上两种。

springMVC配置:

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 的各个服务组件就已经被加入到spring容器中了,所以就可以直接注入使用了。如果在未自动配置的spring环境中,可以使用通过指定bean的init-method来配置activiti的服务组件。

以以下请假流程为例:

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

private static final 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);

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

return true;
}

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

2. 审批请假(老板)

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

public Object myAudit(String userName) {
List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(userName)
.orderByTaskCreateTime().desc().list();
// / 多此一举 taskList中包含了以下内容(用户的任务中包含了所在用户组的任务)
// 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() ? "申请结束" : "等待审批");
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)审批请假

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;
}

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

3. 查询记录

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

(1)查询请假记录

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("申请结束");
List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(hisInstance.getId()).list();
ActivitiUtil.setVars(vacation, varInstanceList);
vacList.add(vacation);
}
return vacList;
}

请假记录即查出历史流程实例,再查出关联的历史参数,将历史流程实例和历史参数设置到Vcation对象(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 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();
}
}
}

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

4. 前端展示和操作

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

<div ng-controller="myAudit">
<h2 ng-init="myAudit()">待我审核的请假</h2>
<table border="0">
<tr>
<td>任务名称</td>
<td>任务时间</td>
<td>申请人</td>
<td>申请时间</td>
<td>天数</td>
<td>事由</td>
<td>操作</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)">审核通过</button>
<button type="button" ng-click="passAudit(vacTask.id, 0)">审核拒绝</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 ? "审核通过" : "审核拒绝"
}
}
).then(function (response) {
if (response.data === true) {
alert("操作成功!");
$window.location.reload();
} else {
alert("操作失败!");
}
})
}
});

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

https://gitee.com/yawensilence/activiti-demo6-springboot 。

文章目录
  1. 1. springMVC配置:
  2. 2. 1. 开始流程并“申请请假”(员工)
  3. 3. 2. 审批请假(老板)
  4. 4. 3. 查询记录
  5. 5. 4. 前端展示和操作