# 流程引擎与Flowable企业级面试指南
> 🎯 不背书,讲原理,重实战
> 涵盖:工作流核心概念、Flowable实战、审批流设计、企业级应用
---
## 📚 目录
1. [流程引擎是什么?解决什么问题?](#一流程引擎是什么)
2. [核心概念(用人话解释)](#二核心概念)
3. [Flowable实战应用](#三flowable实战应用)
4. [企业级审批流设计](#四企业级审批流设计)
5. [常见问题与解决方案](#五常见问题与解决方案)
6. [面试问答](#六面试问答)
---
# 一、流程引擎是什么?
## 1.1 一句话解释
**流程引擎 = 帮你管理「谁在什么时候做什么事」的框架**
没有流程引擎时,你要自己写一堆状态判断:
```java
// ❌ 没有流程引擎:代码里全是状态判断
public void approve(Long orderId, Long userId) {
Order order = orderDao.getById(orderId);
if (order.getStatus() == 0) { // 待主管审批
if (isManager(userId)) {
order.setStatus(1);
// 判断金额决定下一步
if (order.getAmount() > 10000) {
order.setStatus(2); // 需要总监审批
} else {
order.setStatus(3); // 审批通过
}
}
} else if (order.getStatus() == 2) { // 待总监审批
if (isDirector(userId)) {
order.setStatus(3);
}
}
// ... 还有驳回、转办、加签、会签...
// 代码越来越乱,改一个流程要改代码
}
```
有了流程引擎:
```java
// ✅ 有流程引擎:只关心业务,流程交给引擎
public void approve(Long taskId, Long userId, boolean approved) {
// 完成当前任务,引擎自动流转到下一步
taskService.complete(taskId, Map.of("approved", approved));
// 流程怎么走?画图配置,不用改代码
}
```
## 1.2 流程引擎解决的痛点
| 痛点 | 没有流程引擎 | 有了流程引擎 |
|------|-------------|-------------|
| **流程变更** | 改代码、测试、发版 | 改流程图,热部署 |
| **流程追踪** | 自己写日志、查数据库 | 自动记录,可视化查看 |
| **并行审批** | 自己写多线程、状态同步 | 配置并行网关即可 |
| **超时提醒** | 自己写定时任务 | 配置边界事件 |
| **动态审批人** | 写一堆if-else | 配置表达式或监听器 |
## 1.3 主流流程引擎对比
| 引擎 | 特点 | 适用场景 |
|------|------|---------|
| **Flowable** | 轻量、活跃、功能全 | 中大型项目首选 |
| **Activiti** | Flowable的前身,社区分裂 | 老项目在用 |
| **Camunda** | 功能强大,偏重型 | 复杂流程、外企 |
| **jBPM** | RedHat出品,复杂 | 用的较少 |
**为什么推荐Flowable?**
- Activiti核心团队出来做的,更活跃
- 轻量,容易集成Spring Boot
- 功能齐全:流程、表单、决策表、Case管理
---
# 二、核心概念(用人话解释)
## 2.1 BPMN 2.0 核心元素
**把流程想象成一条河流:**
```
开始 ──→ 任务 ──→ 网关 ──→ 任务 ──→ 结束
○ □ ◇ □ ◎
○ 开始事件:河流的源头
□ 任务:河中的水车(要干活的地方)
◇ 网关:河流的分叉口(决定往哪走)
◎ 结束事件:河流入海口
```
### 核心元素说明
**1. 事件(Event)- 什么时候**
```
○ 开始事件:流程的入口
◎ 结束事件:流程的出口
⏰ 定时事件:到点触发
✉ 消息事件:收到消息触发
⚠ 错误事件:出错时触发
```
**2. 任务(Task)- 做什么**
```
👤 用户任务:需要人处理(审批)
⚙ 服务任务:系统自动执行(调接口)
📧 发送任务:发邮件/短信
📝 脚本任务:执行脚本
```
**3. 网关(Gateway)- 怎么走**
```
◇ 排他网关(XOR):只走一条路(if-else)
◇ 并行网关(AND):所有路同时走(fork-join)
◇ 包容网关(OR):满足条件的都走
```
## 2.2 用一个请假流程理解
```
场景:员工请假
- 3天以内:主管审批
- 3天以上:主管审批 + 总监审批
- 审批通过:发邮件通知
- 审批拒绝:流程结束
流程图:
○ 开始
│
▼
┌────────┐
│ 提交申请 │ (用户任务)
└────┬───┘
│
▼
┌────────┐
│ 主管审批 │ (用户任务)
└────┬───┘
│
▼
◇ 排他网关
╱ ╲
[通过] [拒绝]
│ │
▼ ▼
◇ 排他网关 ◎ 结束
╱ ╲
[≤3天] [>3天]
│ │
│ ┌─────▼────┐
│ │ 总监审批 │
│ └─────┬────┘
│ │
└────┬─────┘
▼
┌────────┐
│ 发送邮件 │ (服务任务)
└────┬───┘
│
▼
◎ 结束
```
## 2.3 核心表结构
Flowable运行时会用到这些表(面试常问):
| 表前缀 | 用途 | 重要的表 |
|--------|------|---------|
| **ACT_RE_** | 流程定义(静态) | `ACT_RE_DEPLOYMENT`(部署)、`ACT_RE_PROCDEF`(流程定义) |
| **ACT_RU_** | 运行时数据(动态) | `ACT_RU_EXECUTION`(执行实例)、`ACT_RU_TASK`(待办任务) |
| **ACT_HI_** | 历史数据 | `ACT_HI_PROCINST`(历史流程)、`ACT_HI_TASKINST`(历史任务) |
| **ACT_ID_** | 用户身份 | `ACT_ID_USER`(用户)、`ACT_ID_GROUP`(组) |
```
关系:
流程定义(ACT_RE_PROCDEF) 1:N 流程实例(ACT_RU_EXECUTION) 1:N 任务(ACT_RU_TASK)
类比:
流程定义 = 模板(请假流程模板)
流程实例 = 具体的一次申请(张三的请假申请)
任务 = 待办事项(等待主管审批)
```
---
# 三、Flowable实战应用
## 3.1 Spring Boot集成
```xml
<!-- pom.xml -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.0</version>
</dependency>
```
```yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?useSSL=false&characterEncoding=UTF-8
username: root
password: root
flowable:
# 自动创建表
database-schema-update: true
# 异步执行器(生产环境建议开启)
async-executor-activate: true
# 历史记录级别
history-level: full
```
## 3.2 流程定义(BPMN XML)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="http://flowable.org/test">
<process id="leaveProcess" name="请假流程" isExecutable="true">
<!-- 开始事件 -->
<startEvent id="startEvent"/>
<!-- 提交申请 -->
<userTask id="submitTask" name="提交申请"
flowable:assignee="${initiator}"/>
<!-- 主管审批 -->
<userTask id="managerTask" name="主管审批"
flowable:assignee="${manager}">
<!-- 超时提醒:3天没处理发邮件 -->
<boundaryEvent id="timeout" attachedToRef="managerTask">
<timerEventDefinition>
<timeDuration>P3D</timeDuration>
</timerEventDefinition>
</boundaryEvent>
</userTask>
<!-- 排他网关:判断是否通过 -->
<exclusiveGateway id="approveGateway"/>
<!-- 排他网关:判断天数 -->
<exclusiveGateway id="daysGateway"/>
<!-- 总监审批 -->
<userTask id="directorTask" name="总监审批"
flowable:assignee="${director}"/>
<!-- 发送通知(服务任务,自动执行)-->
<serviceTask id="notifyTask" name="发送通知"
flowable:delegateExpression="${notifyService}"/>
<!-- 结束事件 -->
<endEvent id="endEvent"/>
<endEvent id="rejectEnd"/>
<!-- 连线 -->
<sequenceFlow sourceRef="startEvent" targetRef="submitTask"/>
<sequenceFlow sourceRef="submitTask" targetRef="managerTask"/>
<sequenceFlow sourceRef="managerTask" targetRef="approveGateway"/>
<!-- 通过/拒绝分支 -->
<sequenceFlow sourceRef="approveGateway" targetRef="daysGateway">
<conditionExpression>${approved == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="approveGateway" targetRef="rejectEnd">
<conditionExpression>${approved == false}</conditionExpression>
</sequenceFlow>
<!-- 天数分支 -->
<sequenceFlow sourceRef="daysGateway" targetRef="directorTask">
<conditionExpression>${days > 3}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="daysGateway" targetRef="notifyTask">
<conditionExpression>${days <= 3}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="directorTask" targetRef="notifyTask"/>
<sequenceFlow sourceRef="notifyTask" targetRef="endEvent"/>
</process>
</definitions>
```
## 3.3 核心API使用
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class LeaveService {
// 核心服务
private final RepositoryService repositoryService; // 流程定义管理
private final RuntimeService runtimeService; // 流程实例管理
private final TaskService taskService; // 任务管理
private final HistoryService historyService; // 历史记录
private final IdentityService identityService; // 用户身份
/**
* 1. 部署流程定义
*/
public void deployProcess() {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/leave.bpmn20.xml")
.name("请假流程")
.deploy();
log.info("流程部署成功,ID: {}", deployment.getId());
}
/**
* 2. 发起流程(启动流程实例)
*/
public String startProcess(LeaveRequest request) {
// 设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("initiator", request.getUserId());
variables.put("days", request.getDays());
variables.put("reason", request.getReason());
variables.put("manager", getManager(request.getUserId()));
variables.put("director", getDirector(request.getUserId()));
// 设置发起人(用于权限控制)
identityService.setAuthenticatedUserId(request.getUserId());
// 启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveProcess", // 流程定义Key
request.getBusinessKey(), // 业务Key(如请假单ID)
variables // 流程变量
);
log.info("流程启动成功,实例ID: {}", instance.getId());
return instance.getId();
}
/**
* 3. 查询待办任务
*/
public List<TaskVO> getMyTasks(String userId) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(userId) // 指定处理人
// .taskCandidateUser(userId) // 候选人
// .taskCandidateGroup("managers") // 候选组
.orderByTaskCreateTime().desc()
.list();
return tasks.stream().map(task -> {
TaskVO vo = new TaskVO();
vo.setTaskId(task.getId());
vo.setTaskName(task.getName());
vo.setProcessInstanceId(task.getProcessInstanceId());
vo.setCreateTime(task.getCreateTime());
// 获取业务数据
String businessKey = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.singleResult()
.getBusinessKey();
vo.setBusinessKey(businessKey);
return vo;
}).collect(Collectors.toList());
}
/**
* 4. 完成任务(审批)
*/
@Transactional
public void completeTask(String taskId, boolean approved, String comment) {
Task task = taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task == null) {
throw new BusinessException("任务不存在");
}
// 添加审批意见
taskService.addComment(taskId, task.getProcessInstanceId(),
approved ? "同意" : "拒绝", comment);
// 设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("approved", approved);
// 完成任务,流程自动流转到下一步
taskService.complete(taskId, variables);
log.info("任务完成,taskId: {}, approved: {}", taskId, approved);
}
/**
* 5. 查询流程历史
*/
public List<HistoricTaskVO> getProcessHistory(String processInstanceId) {
List<HistoricTaskInstance> tasks = historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceEndTime().asc()
.list();
return tasks.stream().map(task -> {
HistoricTaskVO vo = new HistoricTaskVO();
vo.setTaskId(task.getId());
vo.setTaskName(task.getName());
vo.setAssignee(task.getAssignee());
vo.setStartTime(task.getStartTime());
vo.setEndTime(task.getEndTime());
vo.setDuration(task.getDurationInMillis());
// 获取审批意见
List<Comment> comments = taskService.getTaskComments(task.getId());
if (!comments.isEmpty()) {
vo.setComment(comments.get(0).getFullMessage());
}
return vo;
}).collect(Collectors.toList());
}
/**
* 6. 撤回流程
*/
@Transactional
public void cancelProcess(String processInstanceId, String reason) {
// 删除流程实例
runtimeService.deleteProcessInstance(processInstanceId, reason);
log.info("流程已撤回,instanceId: {}", processInstanceId);
}
}
```
## 3.4 服务任务(自动执行)
```java
/**
* 发送通知服务任务
* 流程流转到这个节点时自动执行
*/
@Component("notifyService")
@Slf4j
public class NotifyServiceTask implements JavaDelegate {
@Autowired
private EmailService emailService;
@Autowired
private SmsService smsService;
@Override
public void execute(DelegateExecution execution) {
// 获取流程变量
String initiator = (String) execution.getVariable("initiator");
Integer days = (Integer) execution.getVariable("days");
String reason = (String) execution.getVariable("reason");
log.info("发送通知,申请人: {}, 天数: {}", initiator, days);
// 发送邮件
emailService.send(initiator, "请假审批通过",
String.format("您的%d天请假申请已通过", days));
// 发送短信
smsService.send(initiator, "您的请假申请已通过");
// 可以设置流程变量,传递给下一个节点
execution.setVariable("notifyTime", new Date());
}
}
```
## 3.5 任务监听器
```java
/**
* 任务监听器:任务创建/完成时触发
*/
@Component
@Slf4j
public class TaskNotifyListener implements TaskListener {
@Autowired
private MessageService messageService;
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
String assignee = delegateTask.getAssignee();
String taskName = delegateTask.getName();
if (EVENTNAME_CREATE.equals(eventName)) {
// 任务创建时,发送待办通知
log.info("任务创建,发送待办通知给: {}", assignee);
messageService.sendTodo(assignee,
String.format("您有新的待办任务:%s", taskName));
} else if (EVENTNAME_COMPLETE.equals(eventName)) {
// 任务完成时
log.info("任务完成: {}", taskName);
}
}
}
```
---
# 四、企业级审批流设计
## 4.1 动态审批人
**场景**:审批人不是固定的,需要根据规则动态获取
```java
/**
* 动态审批人分配
*/
@Component("dynamicAssignee")
public class DynamicAssigneeService {
@Autowired
private UserService userService;
@Autowired
private OrganizationService orgService;
/**
* 获取直属主管
*/
public String getDirectManager(String userId) {
return userService.getDirectManager(userId);
}
/**
* 获取部门经理
*/
public String getDeptManager(String userId) {
String deptId = userService.getDeptId(userId);
return orgService.getDeptManager(deptId);
}
/**
* 根据金额获取审批人
*/
public String getApproverByAmount(BigDecimal amount) {
if (amount.compareTo(new BigDecimal("10000")) > 0) {
return "CFO";
} else if (amount.compareTo(new BigDecimal("5000")) > 0) {
return "财务经理";
} else {
return "财务主管";
}
}
}
```
**在BPMN中使用**:
```xml
<userTask id="approveTask" name="审批"
flowable:assignee="${dynamicAssignee.getDirectManager(initiator)}"/>
```
## 4.2 会签(多人并行审批)
**场景**:需要多个人同时审批,全部通过才能继续
```xml
<!-- 会签任务 -->
<userTask id="multiApprove" name="会签审批">
<multiInstanceLoopCharacteristics
isSequential="false" <!-- false=并行,true=串行 -->
flowable:collection="${approvers}" <!-- 审批人列表 -->
flowable:elementVariable="approver"> <!-- 循环变量 -->
<!-- 完成条件:全部通过 -->
<completionCondition>
${nrOfCompletedInstances == nrOfInstances}
</completionCondition>
</multiInstanceLoopCharacteristics>
<extensionElements>
<flowable:taskListener event="create"
delegateExpression="${taskNotifyListener}"/>
</extensionElements>
</userTask>
```
```java
// 启动流程时设置会签人
Map<String, Object> variables = new HashMap<>();
variables.put("approvers", Arrays.asList("user1", "user2", "user3"));
runtimeService.startProcessInstanceByKey("process", variables);
```
**会签变量说明**:
```
nrOfInstances - 总实例数
nrOfActiveInstances - 活动实例数
nrOfCompletedInstances - 已完成实例数
loopCounter - 当前循环次数
```
## 4.3 驳回与回退
```java
/**
* 驳回到指定节点
*/
public void rejectToNode(String taskId, String targetNodeId, String comment) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
String processInstanceId = task.getProcessInstanceId();
// 添加驳回意见
taskService.addComment(taskId, processInstanceId, "驳回", comment);
// 获取当前执行实例
List<Execution> executions = runtimeService.createExecutionQuery()
.processInstanceId(processInstanceId)
.list();
// 跳转到目标节点
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveActivityIdTo(task.getTaskDefinitionKey(), targetNodeId)
.changeState();
log.info("任务驳回,从 {} 到 {}", task.getTaskDefinitionKey(), targetNodeId);
}
/**
* 驳回到上一步
*/
public void rejectToPrevious(String taskId, String comment) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 获取历史任务,找到上一步
List<HistoricTaskInstance> historyTasks = historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.orderByHistoricTaskInstanceEndTime().desc()
.list();
if (historyTasks.size() < 2) {
throw new BusinessException("已是第一步,无法驳回");
}
// 上一步的任务定义Key
String previousNodeId = historyTasks.get(1).getTaskDefinitionKey();
rejectToNode(taskId, previousNodeId, comment);
}
```
## 4.4 转办与委托
```java
/**
* 转办:把任务交给别人处理
* 原处理人不再参与
*/
public void transfer(String taskId, String targetUserId, String comment) {
taskService.addComment(taskId, null, "转办",
String.format("转办给 %s: %s", targetUserId, comment));
// 直接修改处理人
taskService.setAssignee(taskId, targetUserId);
log.info("任务转办,taskId: {}, targetUser: {}", taskId, targetUserId);
}
/**
* 委托:把任务委托给别人处理
* 被委托人处理后,还会回到原处理人
*/
public void delegate(String taskId, String targetUserId, String comment) {
taskService.addComment(taskId, null, "委托",
String.format("委托给 %s: %s", targetUserId, comment));
// 委托任务
taskService.delegateTask(taskId, targetUserId);
log.info("任务委托,taskId: {}, targetUser: {}", taskId, targetUserId);
}
/**
* 被委托人处理完成后,归还任务
*/
public void resolveTask(String taskId, String comment) {
taskService.addComment(taskId, null, "处理完成", comment);
// 归还给原处理人
taskService.resolveTask(taskId);
}
```
## 4.5 加签
```java
/**
* 加签:在当前节点加入新的审批人
*
* 前加签:在我审批之前,先让别人审批
* 后加签:我审批之后,再让别人审批
*/
public void addSign(String taskId, String targetUserId, boolean before) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (before) {
// 前加签:当前任务委托给新人,新人处理完再回来
taskService.delegateTask(taskId, targetUserId);
} else {
// 后加签:在流程中动态添加任务(复杂,一般用子流程实现)
// 简化方案:创建一个待办任务记录
Task newTask = taskService.newTask();
newTask.setAssignee(targetUserId);
newTask.setName(task.getName() + "(加签)");
newTask.setProcessInstanceId(task.getProcessInstanceId());
taskService.saveTask(newTask);
}
}
```
## 4.6 与业务系统集成
```java
/**
* 业务表和流程的关联
*/
@Service
public class BusinessProcessService {
/**
* 提交业务申请(启动流程)
*/
@Transactional
public void submitApplication(LeaveApplication application) {
// 1. 保存业务数据
application.setStatus("审批中");
applicationDao.save(application);
// 2. 启动流程,关联业务ID
Map<String, Object> variables = Map.of(
"businessId", application.getId(),
"businessType", "LEAVE",
"initiator", application.getUserId(),
"days", application.getDays()
);
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveProcess",
"LEAVE:" + application.getId(), // businessKey
variables
);
// 3. 保存流程实例ID到业务表
application.setProcessInstanceId(instance.getId());
applicationDao.update(application);
}
/**
* 审批完成后,更新业务状态
*/
@TransactionalEventListener
public void onProcessComplete(ProcessCompletedEvent event) {
String businessKey = event.getProcessInstance().getBusinessKey();
// 解析业务Key
String[] parts = businessKey.split(":");
String businessType = parts[0];
Long businessId = Long.parseLong(parts[1]);
// 更新业务状态
if ("LEAVE".equals(businessType)) {
LeaveApplication application = applicationDao.getById(businessId);
application.setStatus("已完成");
applicationDao.update(application);
}
}
}
```
---
# 五、常见问题与解决方案
## 5.1 流程部署后如何热更新?
```java
/**
* 流程版本管理
*
* Flowable支持同一流程多版本并存:
* - 新启动的流程使用最新版本
* - 已运行的流程继续使用原版本
*/
public void upgradeProcess() {
// 部署新版本(版本号自动递增)
repositoryService.createDeployment()
.addClasspathResource("processes/leave_v2.bpmn20.xml")
.name("请假流程")
.deploy();
// 查询所有版本
List<ProcessDefinition> definitions = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("leaveProcess")
.orderByProcessDefinitionVersion().desc()
.list();
// 可以挂起旧版本(不再允许启动新实例)
ProcessDefinition oldVersion = definitions.get(1);
repositoryService.suspendProcessDefinitionById(oldVersion.getId());
}
/**
* 流程迁移(将运行中的流程迁移到新版本)
*/
public void migrateProcess(String processInstanceId, String newProcessDefinitionId) {
runtimeService.createProcessInstanceMigrationBuilder()
.migrateToProcessDefinition(newProcessDefinitionId)
.migrate(processInstanceId);
}
```
## 5.2 如何处理长时间未处理的任务?
```xml
<!-- 方式1:边界定时事件 -->
<userTask id="approveTask" name="审批">
<boundaryEvent id="timeout" attachedToRef="approveTask" cancelActivity="false">
<timerEventDefinition>
<timeDuration>P3D</timeDuration> <!-- 3天后触发 -->
</timerEventDefinition>
</boundaryEvent>
</userTask>
<!-- 超时后执行服务任务 -->
<serviceTask id="remindTask" name="发送提醒"
flowable:delegateExpression="${reminderService}"/>
<sequenceFlow sourceRef="timeout" targetRef="remindTask"/>
<sequenceFlow sourceRef="remindTask" targetRef="approveTask"/>
```
```java
// 方式2:定时任务扫描
@Scheduled(cron = "0 0 9 * * ?") // 每天9点
public void remindOverdueTasks() {
// 查询超过3天未处理的任务
Date overdueTime = DateUtils.addDays(new Date(), -3);
List<Task> overdueTasks = taskService.createTaskQuery()
.taskCreatedBefore(overdueTime)
.list();
for (Task task : overdueTasks) {
// 发送提醒
messageService.sendReminder(task.getAssignee(),
String.format("您有待办任务[%s]已超过3天未处理", task.getName()));
}
}
```
## 5.3 高并发下的性能优化
```yaml
# 1. 开启异步执行器
flowable:
async-executor-activate: true
async-executor:
core-pool-size: 8
max-pool-size: 16
queue-capacity: 100
# 2. 历史数据分库
flowable:
history-level: activity # 减少历史记录(full/audit/activity/none)
```
```java
// 3. 定期清理历史数据
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void cleanHistory() {
// 删除30天前已完成的流程历史
Date deleteTime = DateUtils.addDays(new Date(), -30);
historyService.createHistoricProcessInstanceQuery()
.finished()
.finishedBefore(deleteTime)
.list()
.forEach(instance -> {
historyService.deleteHistoricProcessInstance(instance.getId());
});
}
```
---
# 六、面试问答
## Q1:你们为什么要用流程引擎?
**答**:
我们有很多审批流程(请假、报销、采购等),最开始是硬编码:
```java
if (status == 1) {
// 主管审批逻辑
} else if (status == 2) {
// 总监审批逻辑
}
```
**痛点**:
1. 流程变更要改代码、重新发版
2. 并行审批、会签实现复杂
3. 流程追踪困难,出问题难排查
4. 超时提醒要自己写定时任务
**用了Flowable后**:
1. 流程可视化设计,改流程不用改代码
2. 会签、并行网关等开箱即用
3. 完整的历史记录,可以追溯每一步
4. 边界事件处理超时,优雅
---
## Q2:流程引擎的核心表有哪些?
**答**:
主要分4类:
1. **RE表(Repository)**:流程定义
- `ACT_RE_DEPLOYMENT`:部署信息
- `ACT_RE_PROCDEF`:流程定义
2. **RU表(Runtime)**:运行时数据
- `ACT_RU_EXECUTION`:流程实例
- `ACT_RU_TASK`:待办任务
- `ACT_RU_VARIABLE`:流程变量
3. **HI表(History)**:历史数据
- `ACT_HI_PROCINST`:历史流程实例
- `ACT_HI_TASKINST`:历史任务
- `ACT_HI_ACTINST`:历史节点
4. **ID表(Identity)**:用户身份
- `ACT_ID_USER`、`ACT_ID_GROUP`
---
## Q3:会签是怎么实现的?
**答**:
用BPMN的多实例任务(Multi-Instance):
```xml
<userTask id="multiApprove">
<multiInstanceLoopCharacteristics
isSequential="false" <!-- 并行 -->
collection="${approvers}" <!-- 审批人列表 -->
elementVariable="approver">
<completionCondition>
${nrOfCompletedInstances == nrOfInstances}
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
```
- `isSequential=false`:并行执行
- `collection`:审批人列表
- `completionCondition`:完成条件
可以配置:
- 全部通过:`nrOfCompletedInstances == nrOfInstances`
- 一票通过:`approveCount >= 1`
- 一票否决:`rejectCount >= 1`
- 比例通过:`approveCount / nrOfInstances >= 0.5`
---
## Q4:如何实现动态审批人?
**答**:
三种方式:
**1. 表达式**:
```xml
<userTask flowable:assignee="${dynamicAssignee.getManager(initiator)}"/>
```
**2. 任务监听器**:
```java
@Component
public class DynamicAssigneeListener implements TaskListener {
@Override
public void notify(DelegateTask task) {
String initiator = (String) task.getVariable("initiator");
String manager = userService.getManager(initiator);
task.setAssignee(manager);
}
}
```
**3. 候选人/候选组**:
```xml
<userTask flowable:candidateGroups="managers"/>
```
然后用户从待办列表中"认领"任务。
---
## Q5:如何处理驳回?
**答**:
```java
public void reject(String taskId, String targetNodeId) {
// 使用Flowable的changeActivityState API
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveActivityIdTo(currentNodeId, targetNodeId)
.changeState();
}
```
也可以通过流程设计实现:在每个审批节点加一条驳回的连线,指向需要退回的节点。
---
## Q6:流程引擎的性能问题怎么解决?
**答**:
1. **异步执行器**:开启async-executor,异步执行服务任务
2. **历史级别调整**:
- `full`:记录所有细节(开发测试)
- `audit`:记录关键节点(生产)
- `activity`:只记录活动(高性能需求)
3. **定期清理历史**:已完成的流程定期归档或删除
4. **分库分表**:RU表和HI表分开,历史表可以按时间分表
5. **缓存**:流程定义缓存,减少数据库查询
---
## 📝 面试速记卡
```
【核心概念】
流程引擎 = 管理「谁在什么时候做什么事」
BPMN三要素 = 事件(什么时候) + 任务(做什么) + 网关(怎么走)
【核心表】
RE = 流程定义 | RU = 运行时 | HI = 历史 | ID = 用户
【核心API】
RepositoryService - 部署管理
RuntimeService - 流程实例
TaskService - 任务管理
HistoryService - 历史查询
【高级特性】
会签 = MultiInstance + completionCondition
驳回 = changeActivityStateBuilder
动态审批人 = 表达式/监听器/候选人
【性能优化】
异步执行器 + 历史级别 + 定期清理 + 缓存
```
---
**文档版本**:v1.0
**创建时间**:2026-01-19
**适用场景**:工作流/审批流相关面试