【大白话说flowable】基础篇

53 阅读4分钟

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