查看原文
其他

在Spring Boot项目中整合使用Activiti

    点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约


每日英文

Don't put your happiness in the hands of someone else. If you really want to be happy, you have to find this happiness within yourself.

不要把自己的快乐寄托在别人身上,真正的快乐,必须来源于自己。


每日掏心

每一次的容忍,都是一次爱的消耗。请珍稀我的每一次容忍与退让,因为那是我对你爱的信用额度,但再高的额度也终有透支的一天。




来自yawn Lau | 责编:乐乐

链接:jvm123.com/2019/08/springboot-activiti.html

程序员小乐(ID:study_tech)第 786 次推文   图片来自网络


往日回顾:大公司为什么都有API网关?聊聊API网关的作用



   正文   


依赖


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


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


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 的服务组件。


案例


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


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

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

returntrue;
}


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


2. 审批请假(老板)


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.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 表中查询。


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

请假记录即查出历史流程实例,再查出关联的历史参数,将历史流程实例和历史参数设置到 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 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. 前端展示和操作


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



<html>
<head></head>
<body>
<div ng-controller="myAudit">
<h2 ng-init="myAudit()">待我审核的请假</h2>
<table border="0">
<tbody>
<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>
</tbody>
</table>
</div>
</body>
</html>



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

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

Java 多态的实现机制,看了都说好!

中央政治局会议:疫情防控工作取得阶段性成效,但拐点尚未到来

SpringBoot实现过滤器、拦截器与切片

关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存