1.瞎说说为啥要写
这个项目是最近公司的新需求,搞一个类似于企业微信的审批流系统,因为觉得flowable比较成熟稳定,功能齐全,所以选择使用这个框架。
因为我们的需要是可以让用户自己配置流程,因此还需要前端的配置,我是后端,之后的笔记就不贴前端代码是如何实现的了,只说明后端思路以及流程xml的相关实现。
内容仅是公司需要用到的功能,flowable有很多强大的功能,笔记说明的仅是框架的一小部分。
注意:flowable有特别多的概念性的东西我就不多说了,直接上内容,按照功能来说明。
内容需要对审批流有一定的了解,没接触过可能会有点蒙蔽。
个人风格就是瞎jm乱说,有什么不对可以留言一起探讨。
最后说明一下,我用的版本是6.8.1
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.1</version>
</dependency>
2.流程定义
说白了就是将流程定义到框架中去(ps:存到数据库)
流程定义的方式最常见的无非就是两种。通过xml文件、通过xml内容(字符串)
插入一个知识点:flowable通过xml的process节点的id来判断是否是同一个流程,如果id已存在,就是插入一条新数据,版本号在原基础上自增。换句话说就是,不影响旧的流程使用,新的流程版本号加1。
不变的是:流程定义key 变得是:流程定义key对应的版本号、流程定义id(流程的唯一标识)、部署id(每次部署都会生成一个新的)
再插入一个知识点,程定义id的组成:(流程定义key:版本号:UUID)
process节点如下:
<process id="flow_***name="变更申请" flowable:category="*">
2.1 流程部署
//通过xml内容(字符串)
Deployment deploy = repositoryService.createDeployment()
.name(param.getProcessDefinitionName())
.category(param.getCategory())
.addInputStream(param.getProcessDefinitionName() + FlowConstant.BPMN_FILE_SUFFIX, inputStream)
.deploy();
```
//通过文件(xml文件转化为输入流)
Deployment deploy = repositoryService.createDeployment()
.name(param.getProcessDefinitionName())
.category(param.getCategory())
.addInputStream(param.getProcessDefinitionName() + FlowConstant.BPMN_FILE_SUFFIX, inputStream)
.deploy();
```
2.2 其他操作
// 级联删除, true - 表示会连带删除所有相关的任务信息
repositoryService.deleteDeployment(deploymentId, true);
//激活(流程定义id,是否同时激活已挂起的实例,激活时间(null=立即))
repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
// 挂起
repositoryService.suspendProcessDefinitionById(processDefinitionId, false, null);
//读取xml内容
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
3. 待办、已办、我的申请列表
这些列表数据,一般可以通过两种方式获取,一种是通过flowable的api,一种就是写sql查表。 如果对于flowable的数据库表不够熟悉,我建议还是使用api,避免错误,而且方便。
注意: 通过一次api查询出来的数据,是没法过滤掉已挂起的流程数据,因此如果需要过滤,需要先查询出来已已经挂起的流程实例id,然后根据这些id去条件查询。
如果流程中需要条件查询流程相关的东西,最简单的方法就是将变量放入流程实例中,也就是通过variables存入流程,查询的时候通过api去查询,下面会有相关例子说明。
3.1 待办列表
TaskQuery query = taskService.createTaskQuery()
.taskAssignee(loginUserId.toString()) // 当前办理人
.includeProcessVariables() // 带出流程级变量,带出后才可以查询流程标量
.active() // 获取已激活的流程实例
.orderByTaskCreateTime().desc();
if (StrUtil.isNotBlank(param.getProcessCategory())) {
query.processDefinitionKey(param.getProcessCategory());
}
if (StrUtil.isNotBlank(param.getDesc())) {
query.processVariableValueLike("processDesc", "%" + param.getDesc() + "%");
}
```
long total = query.count();
List<Task> tasks = query.listPage(pageHelper.getPageSize() * (pageHelper.getPageNo() - 1), pageHelper.getPageSize());
//根据任务的流程实例id获取实例关联的所有用户任务(多实例)
Set<String> processInstanceIds = tasks.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
List<Task> currentTaskList = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds)
.active()
.list();
3.2 我的申请列表
// 所有挂起的运行实例 ID
Set<String> suspendedIds = runtimeService.createProcessInstanceQuery()
.startedBy(loginUserId.toString())
.suspended() // 只取挂起
.list()
.stream()
.map(ProcessInstance::getProcessInstanceId)
.collect(Collectors.toSet());
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
.startedBy(loginUserId.toString()) // 我发起的
.includeProcessVariables() // 带出流程级变量
.orderByProcessInstanceStartTime().desc();
// 手动 NOT IN 挂起实例
if (CollUtil.isNotEmpty(suspendedIds)) {
query.processInstanceIds(
historyService.createHistoricProcessInstanceQuery()
.startedBy(loginUserId.toString())
.list()
.stream()
.map(HistoricProcessInstance::getId)
.filter(id -> !suspendedIds.contains(id))
.collect(Collectors.toSet())
);
}
long total = query.count();
List<HistoricProcessInstance> instances = query.listPage(pageHelper.getPageSize() * (pageHelper.getPageNo() - 1), pageHelper.getPageSize());
3.3 已办列表
/ 所有【挂起】的运行实例 ID
Set<String> suspendedIds = runtimeService.createProcessInstanceQuery()
.suspended()
.list()
.stream()
.map(ProcessInstance::getProcessInstanceId)
.collect(Collectors.toSet());
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
.taskAssignee(loginUserId.toString()) // 已完成办理人
.finished() // 已完成
.includeProcessVariables()
.orderByTaskCreateTime()// 带出流程级变量
.desc();
// 手动 NOT IN 挂起实例
if (CollUtil.isNotEmpty(suspendedIds)) {
query.processInstanceIdIn(
historyService.createHistoricTaskInstanceQuery()
.taskAssignee(loginUserId.toString()) // 已完成办理人
.finished() // 已完成
.list()
.stream()
.map(HistoricTaskInstance::getProcessInstanceId)
.filter(id -> !suspendedIds.contains(id))
.collect(Collectors.toSet())
);
}
long total = query.count();
List<HistoricTaskInstance> tasks = query.listPage(pageHelper.getPageSize() * (pageHelper.getPageNo() - 1), pageHelper.getPageSize());
4.完成任务、挂起流程
顾名思义就是完成当前任务,让流程走到下一个节点。
// 需要加 —— 任何 写操作(setAssignee、complete、addComment …)
//只要 会写 USER_ID_ 字段,就必须 setAuthenticatedUserId,否则 办理人/评论人 = null。
identityService.setAuthenticatedUserId(userId + "")
//完成任务,需要先判断当前任务是否被转交过,如果转交过,需要执行resolveTask
taskService.complete(flowRequestParamsDTO.getTaskId());
//挂起当前流程
runtimeService.suspendProcessInstanceById(processInstanceId);