Administrator
发布于 2026-03-16 / 1 阅读
0
0

流程引擎与Flowable企业级面试指南

# 流程引擎与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  
**适用场景**:工作流/审批流相关面试


评论