此文章将描述activiti的使用,activiti的集成,部署、启动、节点流转、驳回、批注等功能的基本使用。
目录
activiti的使用
背景
技术栈
参考
接口
先接入activiti吧
a、添加依赖
b、项目resources目录下新建activiti.cfg.xml
c、application.yml中srping下添加:
emm~~现在来绘制activiti流程图
1、绘制工具
2、我们会用到的图形
3、按照图形进行绘制,连线
4、设置process的id、name
5、设置每个节点的id、name
6、设置顺序流的name、condition
7、设置UserTask的候选人Canditate Users
8、生成xml、png文件
接口详解
1、创建数据库表
2、流程部署
3、流程删除
4、查询已部署的流程
5、启动流程
6、查询待处理的任务
7、查询处理中的任务
8、查询已处理的任务
9、任务处理(签收/退回/审核)
10、查询历史备注
我把activiti集成在了spring-boot项目中,首先就是新建一个springboot项目,这里不再赘述springboot项目的搭建。
在平常的业务开发中,遇到了一些审核流程,而且比较多,自己设计了几个数据库表,基本能满足自己的需求,但由于业务可能会越来越大,越来越复杂,就想到用activiti来解决我的问题,于是学习了activiti。
springboot 1.X + mybatis 3.X + activiti6.0.0
在学习过程中从网上找了一些博客进行参考,我也有一份电子书,文件太大就不上传了。
- 部署流程资源的三种方式 https://blog.csdn.net/zjx86320/article/details/50234707
- Activiti数据库表结构 https://blog.csdn.net/hj7jay/article/details/51302829
- IntelliJ IDEA安装Activiti插件并使用 https://blog.csdn.net/gozhuyinglong/article/details/80336765
- activiti 任务节点 处理人设置 https://blog.csdn.net/qq_30739519/article/details/51225067
- Activiti6实现自由跳转 https://segmentfault.com/a/1190000013952695 我应用此代码时加入了comment备注
- Activiti工作流的流转任务和结束任务 https://blog.csdn.net/liuming134/article/details/80453907
- Activiti添加批注(comment)信息 https://www.cnblogs.com/cxyj/p/3898535.html
- Activitivi之启动流程/完成任务的时候设置流程变量 https://www.cnblogs.com/shyroke/p/8000757.html
- Activiti任务认领 https://www.cnblogs.com/boulder/p/3658528.html
- Activiti之历史活动查询和历史任务查询和流程状态查询 https://www.cnblogs.com/shyroke/p/7994931.html
我的项目完成了以下接口:
1、创建数据库表
2、流程部署
3、流程删除
4、查询已部署的流程
5、启动流程
6、查询待处理的任务
7、查询处理中的任务
8、查询已处理的任务
9、任务处理(签收/退回/审核)
10、查询历史备注
6.0和5.21有什么区别?
在应用当中发现:
1)6.0删除了pvm包,ActivityImpl用不了了,实现节点跳转时就不能用这个了
别的区别,em~~~自己去看吧
org.activiti
activiti-spring-boot-starter-basic
6.0.0
spring:
jpa:
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
format_sql: true
use_sql_comments: true
hibernate:
ddl-auto: update
我用的是idea的actiBPM,虽然有点缺点,但是总归还能用,没有安装的请安装此plugin。
1.StartEvent 开始事件
2.EndEvent 结束事件
3.UserTask 用户任务
4.ExclusiveAateway 排他网关
5.sequenceFlow 顺序流
其它的图形我没用到,有需要的小伙伴可以自己去研究~~
先上传一个成品截图:
这是我从实际业务中拿出来的流程图,我也做了牺牲了~
点击bpmn画布空白处,即可看到属性设置,见上图
点击每个节点,即可看到属性设置
点击每个连线,即可看到属性设置
condition的表达式格式为:${pass == true}
结合业务,Canditate Users的表达式格式为:${face_audit},表示面审候选人,其他UserTask候选人可按照各自功能进行命名:
${face_audit} 面审
${final_first_audit} 终审1
${final_second_audit} 终审2审
${final_third_audit} 终审3审
${res_invest} 尽调
${loan_apply} 放款申请
${loan_audit} 放款审核
为什么设置Canditate Users?
为了结合业务,实现任务的节点的签收/退回,
当Canditate Users有值而assignee为空,则为待处理;
当Canditate Users有值而assignee不为空,则为处理中。
assignee为处理人。
xml文件:直接复制demo.bpmn文件,并且把文件名改为demo.xml文件即可,可以以xml格式看到这个流程。
png文件:右键demo.xml,Diagrams-->show BPMN2.0 Designer,右键空白处-->Export to file 即可。
上传一张png给大家看:
好了,demo绘制完毕,接下来看代码~
我贴出来的是service的代码,要看完整代码,请看文章末尾gitee地址。
/**
* 创建数据库表
* 根据配置文件activiti.cfg.xml创建ProcessEngine
*/
@Override
public void createProcessEngine() {
ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine engine = cfg.buildProcessEngine();
}
执行上述接口后,对应的数据库里会自动生成activiti相关的数据库表。
需要传入参数deploymentName 部署名称,force是否强制部署,error是个map。
其中参数deploymentName为xml文件中process标签的id。
/**
* 流程部署
*
* @param deploymentName 流程部署名称
* @param force 是否强制部署,true强制,false非强制
* @param error
*/
@Override
public void deployProcess(String deploymentName, boolean force, ErrorInfo error) {
//判断流程是否已存在
Deployment deploymentExist = processEngine.getRepositoryService().createDeploymentQuery().deploymentName(deploymentName).singleResult();
//流程已存在
if (null != deploymentExist) {
if (force) {
error.msg = "流程已存在,进行强制部署。";
//强制部署时,先删除原有的部署流程
processEngine.getRepositoryService()//true:级联删除
.deleteDeployment(deploymentExist.getId(), true);
log.info("【%s】流程强制部署,删除部署id【%d】", deploymentName, deploymentExist.getId());
} else {
error.msg = "流程已存在,不更新部署";
log.info("【%s】流程已存在,不更新部署", deploymentName);
return;
}
}
//流程不存在,直接部署
Deployment deployment = processEngine.getRepositoryService().createDeployment()
.name(deploymentName)
.addClasspathResource("processes/" + deploymentName + ".bpmn")
.addClasspathResource("processes/" + deploymentName + ".png")
.deploy();
error.msg += "流程已部署";
log.info("流程已部署,部署名称:" + deploymentName + ",部署ID:" + deployment.getId() + ",部署时间:" + deployment.getDeploymentTime());
}
有流程部署,就会有流程删除,看代码:
/**
* 删除流程定义
*
* @param deploymentId 流程部署ID_
* @param error
*/
@Override
public void deleteDeployment(String deploymentId, ErrorInfo error) {
//流程定义名称
String deploymentName = "";
List deploymentList = processEngine.getRepositoryService().createDeploymentQuery().list();
if (null != deploymentList && deploymentList.size() > 0) {
for (Deployment de : deploymentList) {
if (de.getId().equalsIgnoreCase(deploymentId)) {
deploymentName = de.getName();
break;
}
}
}
//级联删除流程
processEngine.getRepositoryService().deleteDeployment(deploymentId, true);
error.code = 1;
error.msg = "流程定义[" + deploymentName + "]已级联删除";
log.info("流程定义[%s]已级联删除", deploymentName);
}
已成功部署的流程定义会放在act_re_deployment里。
/**
* 查询全部流程定义
*
* @return
*/
@Override
public List getDeploymentList() {
//结果集
List list = new ArrayList<>();
//deployment结果集
List deploymentList = processEngine.getRepositoryService().createDeploymentQuery().list();
if (null != deploymentList && deploymentList.size() > 0) {
for (Deployment de : deploymentList) {
DeploymentDto dto = new DeploymentDto();
dto.setId(de.getId());
dto.setName(de.getName());
dto.setCreateTime(de.getDeploymentTime());
list.add(dto);
}
}
return list;
}
启动流程时,除了传业务所需的参数外,第一个节点所涉及到的参数变量也必须传,否则流程无法启动:
variables.put("face_audit", "faceAudit");
我把节点必传的参数写在了CarProcessCanditateUsersEnum.java
/**
* 启动流程
*
* @param instanceKey 流程process的id,例:demo
*/
@Override
public ProcessInstance startProcess(String instanceKey, Map variables) {
RuntimeService runtimeService = processEngine.getRuntimeService();
//可根据id、key、message启动流程
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey(instanceKey, variables);
//流程实例ID
log.info("流程实例ID:" + myProcess_1.getId());
//流程实例ProcessInstanceId
log.info("流程实例ProcessInstanceId:" + myProcess_1.getProcessInstanceId());
//流程实例ProcessDefinitionId
log.info("流程实例ProcessDefinitionId:" + myProcess_1.getProcessDefinitionId());
return myProcess_1;
}
我在activiti的使用中,UserTask任务用到了两个处理字段:Canditate Users 和 assignee
简单理解:当Canditate Users有值,assignee为null时,任务为待处理
/**
* 查询当前任务办理人的当前任务--待处理
*
* 两个参数都不传,则查询所有节点待办任务
*
* @param candidateUser 当前任务候选角色变量
* @return
*/
@Override
public List pendingTask(String candidateUser) {
//task list结果集
List tasks = new ArrayList<>();
//封装成dto list
List personnelTaskDtoList = new ArrayList<>();
if (StringUtils.isNotBlank(candidateUser)) {
//与任务相关的Service
tasks = processEngine.getTaskService()
.createTaskQuery()//创建一个任务查询对象
.taskCandidateUser(candidateUser)
.orderByTaskCreateTime()
.desc()
.list();
} else {
//查询所有待办任务
//与任务相关的Service
tasks = processEngine.getTaskService()
.createTaskQuery()//创建一个任务查询对象
.orderByTaskCreateTime()
.desc()
.list();
}
if (tasks != null && tasks.size() > 0) {
for (Task task : tasks) {
//待处理
if (StringUtils.isBlank(task.getAssignee())) {
PersonnelTaskDto entity = new PersonnelTaskDto();
entity.setTaskId(task.getId());
entity.setAssignee(task.getAssignee());
entity.setTaskName(task.getName());
entity.setCreateTime(task.getCreateTime());
entity.setProcessInstanceId(task.getProcessInstanceId());
personnelTaskDtoList.add(entity);
}
}
}
//将结果放到pageDto
return personnelTaskDtoList;
}
我在activiti的使用中,UserTask任务用到了两个处理字段:Canditate Users 和 assignee
简单理解:当Canditate Users有值,assignee也有值时,任务为处理中
/**
* 查询当前任务办理人的当前任务--处理中
* Constants.NODE_STATUS_HANDLING
* 两个参数都不传,则查询所有节点处理中任务
*
* @param candidateUser
* @return
*/
@Override
public List handlingTask(String candidateUser) {
//task list结果集
List tasks = new ArrayList<>();
//封装成dto list
List personnelTaskDtoList = new ArrayList<>();
TaskService taskService = processEngine.getTaskService();
if (StringUtils.isNotBlank(candidateUser)) {
//与任务相关的Service
tasks = taskService
.createTaskQuery()//创建一个任务查询对象
.taskCandidateUser(candidateUser)
.orderByTaskCreateTime()
.desc()
.list();
} else {
//查询所有待办任务
//与任务相关的Service
tasks = taskService
.createTaskQuery()//创建一个任务查询对象
.orderByTaskCreateTime()
.desc()
.list();
}
if (tasks != null && tasks.size() > 0) {
for (Task task : tasks) {
//处理中
if (StringUtils.isNotBlank(task.getAssignee())) {
PersonnelTaskDto entity = new PersonnelTaskDto();
entity.setTaskId(task.getId());
entity.setAssignee(task.getAssignee());
entity.setTaskName(task.getName());
entity.setCreateTime(task.getCreateTime());
entity.setProcessInstanceId(task.getProcessInstanceId());
personnelTaskDtoList.add(entity);
}
}
}
return personnelTaskDtoList;
}
历史活动涉及到的表为act_hi_*
各种历史活动查询可参考资料:历史活动查询
我这里用了:
HistoryService historyService = processEngine.getHistoryService();
historyService.createHistoricActivityInstanceQuery()
可根据自己需求进行自行修改。
/**
* 查询已处理的任务
*
* @param assignee 处理人
* @return
*/
@Override
public List handledTask(String assignee) {
//封装成dto list
List personnelTaskDtoList = new ArrayList<>();
HistoryService historyService = processEngine.getHistoryService();
List list = new ArrayList<>();
//处理人不为空时查询处理人所属的已处理任务
if (StringUtils.isNotBlank(assignee)) {
list = historyService.createHistoricActivityInstanceQuery()
.taskAssignee(assignee)
.finished()
.desc()
.list();
} else {
//处理人为空时查询所有已处理任务
list = historyService.createHistoricActivityInstanceQuery()
.list();
}
if (null != list && list.size() > 0) {
for (HistoricActivityInstance instance : list) {
PersonnelTaskDto entity = new PersonnelTaskDto();
entity.setTaskId(instance.getId());
entity.setAssignee(instance.getAssignee());
entity.setTaskName(instance.getActivityName());
entity.setCreateTime(instance.getStartTime());
entity.setProcessInstanceId(instance.getProcessInstanceId());
personnelTaskDtoList.add(entity);
}
}
return personnelTaskDtoList;
}
流程启动之后,整个流程会停留在第一个节点,以demo为例,会停留在【面审】
然后此节点的候选人进行任务签收
当有此节点权限的人签收此任务后,可以用代码禁止再被其他人签收(我这里没做)
/**
* 节点处理,0:审核,1:签收,2:退回
*
* @param user
* @param jumpToNodeDto
* @param error
*/
@Override
public void handleNode(User user, JumpToNodeDto jumpToNodeDto, ErrorInfo error) {
ActivitiServiceImpl impl = new ActivitiServiceImpl();
//审核
if (jumpToNodeDto.getIsClaim().equals(Constants.NODE_AUDIT)) {
Map variables = prepareNodeJumpVaria(jumpToNodeDto);
//流转
impl.jumpToNode(jumpToNodeDto.isInOrder(), jumpToNodeDto.getTaskId(), jumpToNodeDto.getTargetFlowElementId(), jumpToNodeDto.getComment(), variables, error);
} else {
//签收/退回
impl.claimActivity(jumpToNodeDto, user, error);
}
}
节点跳转,我这里做了顺序的的流转和跳转,因为在审核流程中任务有可能被驳回,也有可能最高权限的人一键审核通过。
跳转的代码,可以下载我的代码看一下,这里就不贴出来了。
/**
* 节点跳转/流转
*
* @param isInOrder 是否是顺序执行,true:下一节点;false:任意节点(包括驳回和最高权限的审核通过)
* @param taskId 任务id,对应act_ru_task中的ID_
* @param targetFlowElementId 目标的节点id
* @param comment 审批意见
* @param variables 传入下一节点时所需参数
*/
public void jumpToNode(boolean isInOrder, String taskId, String targetFlowElementId, String comment, Map variables, ErrorInfo error) {
//获取taskService
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (StringUtils.isBlank(task.getAssignee())) {
error.code = -1;
error.msg = "任务未签收,不允许跳转/流转";
return;
}
//当前任务节点顺序流转
if (isInOrder) {
//添加审批意见
taskService.addComment(taskId, null, comment);
try {
taskService.complete(taskId, variables);
} catch (Exception e) {
error.code = -1;
error.msg = "节点流转失败taskId[" + taskId + "]";
e.printStackTrace();
log.error("节点流转失败taskId[%s]", taskId);
}
error.code = 1;
error.msg = "节点流转成功";
}
//任意节点
if (!isInOrder) {
if (StringUtils.isBlank(targetFlowElementId)) {
error.code = -1;
error.msg = "节点跳转targetFlowElementId不能为空";
log.error(error.msg);
return;
}
ActivitiJump activitiJump = new ActivitiJump();
try {
activitiJump.jump(taskId, targetFlowElementId, comment, variables);
} catch (Exception e) {
e.printStackTrace();
log.error("节点跳转失败taskId[%s]", taskId);
error.code = -1;
error.msg = "节点跳转失败taskId[" + taskId + "]";
}
error.code = 1;
error.msg = "节点跳转成功";
}
}
任务的签收和退回
/**
* 签收/退回任务
*
* @param jumpToNodeDto
* @param user
* @param error
*/
public void claimActivity(JumpToNodeDto jumpToNodeDto, User user, ErrorInfo error) {
TaskService taskService = processEngine.getTaskService();
String isClaim = jumpToNodeDto.getIsClaim();
//签收
if (isClaim.equals(Constants.NODE_CLAIM)) {
taskService.setAssignee(jumpToNodeDto.getTaskId(), user.getName());
//添加备注
taskService.addComment(jumpToNodeDto.getTaskId(), null, jumpToNodeDto.getComment());
error.code = 1;
error.msg = "签收成功";
}
//退回
if (isClaim.equals(Constants.NODE_BACK)) {
taskService.setAssignee(jumpToNodeDto.getTaskId(), null);
taskService.addComment(jumpToNodeDto.getTaskId(), null, jumpToNodeDto.getComment());
error.code = 1;
error.msg = "退回成功";
}
}
在节点流转/跳转、签收、退后等操作过程中,肯定会填写一些批注、原因,那么怎么查询这些批注呢?
要用HistoryService
/**
* 查询历史备注
*
* @param candidateUser
* @return
*/
public List getHistoryTaskComments(String candidateUser) {
List taskComments = new ArrayList<>();
HistoryService historyService = processEngine.getHistoryService();
TaskService taskService = processEngine.getTaskService();
List list = new ArrayList<>();
//处理人不为空时查询处理人所属的已处理任务
if (StringUtils.isNotBlank(candidateUser)) {
list = historyService.createHistoricTaskInstanceQuery()
.taskCandidateUser(candidateUser)
.orderByHistoricTaskInstanceStartTime()
.desc()
.list();
} else {
//处理人为空时查询所有已处理任务
list = historyService.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceStartTime()
.desc()
.list();
}
if (null != list && list.size() > 0) {
for (HistoricTaskInstance instance : list) {
String hisTaskId = instance.getId();
List comments = taskService.getTaskComments(hisTaskId);
if (null != comments && comments.size() > 0) {
taskComments.addAll(comments);
}
}
}
return taskComments;
}
至此,基本讲述完了,贴上我的gitee代码:
https://gitee.com/dayydream/activiti.git
我只是一个小菜鸟,欢迎各位大神来沟通