Flowable案例

wen java案例 2

本文目录导读:

Flowable案例

  1. 案例场景:公司差旅费用报销流程
  2. 第一步:流程定义(BPMN 2.0 XML)
  3. 第二步:核心 Java 代码实战(Spring Boot 环境)
  4. 第三步:前端/Controller 交互接口
  5. 第四步:该类案例的踩坑点与优化建议

Flowable(以及与之齐名的 Activiti、Camunda),最常见的需求是 “如何从零搭建一个请假流程”“如何实现会签/驳回”,为了给你最有价值的案例,我将以 “企业内部报销审批流程” 为例,从 数据库表设计、BPMN建模、核心Java代码、前端流程推进 四个维度进行深度拆解。

这是一个经典的“会签+条件分支+驳回”案例,能够覆盖大部分工作流开发场景。


案例场景:公司差旅费用报销流程

业务规则:

  1. 员工提交报销单,金额填写。
  2. 部门经理审批
    • 金额 ≤ 3000元:审批通过后直接结束,打款。
    • 金额 > 3000元:需要 财务总监 会签(多人必须全部同意)。
  3. 任何节点驳回,流程直接结束(或退回发起人,本案例为简单起见直接结束)。

第一步:流程定义(BPMN 2.0 XML)

这是流程图的底层灵魂,通常我们使用 Flowable Modeler 绘制,但为了理解,这里是关键的 XML 片段逻辑:

<process id="expenseProcess" name="报销流程" isExecutable="true">
  <!-- 开始事件 -->
  <startEvent id="startEvent" name="开始" flowable:initiator="applyUser"/>
  <!-- 申请节点(用户任务) -->
  <userTask id="applyTask" name="填写报销单" flowable:assignee="${applyUser}"/>
  <!-- 部门经理审批(用户任务) -->
  <userTask id="managerTask" name="部门经理审批" flowable:assignee="${managerUser}"/>
  <!-- 排他网关(判断金额) -->
  <exclusiveGateway id="amountGateway" name="金额判断"/>
  <!-- 财务总监会签(多实例任务) -->
  <userTask id="financeTask" name="财务总监审批" flowable:assignee="${assignee}">
      <multiInstanceLoopCharacteristics isSequential="false" 
                   flowable:collection="financeUsers" flowable:elementVariable="assignee">
        <completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
      </multiInstanceLoopCharacteristics>
  </userTask>
  <!-- 结束事件 -->
  <endEvent id="endEvent" name="结束"/>
  <!-- 连线逻辑(Sequence Flow) -->
  <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask"/>
  <sequenceFlow id="flow2" sourceRef="applyTask" targetRef="managerTask"/>
  <!-- 网关分支:金额小于3000 -->
  <sequenceFlow id="flow3" sourceRef="managerTask" targetRef="amountGateway"/>
  <sequenceFlow id="flow_le_3000" name="<=3000" sourceRef="amountGateway" targetRef="endEvent">
    <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[${amount <= 3000}]]>
    </conditionExpression>
  </sequenceFlow>
  <!-- 网关分支:金额大于3000 -->
  <sequenceFlow id="flow_gt_3000" name=">3000" sourceRef="amountGateway" targetRef="financeTask">
    <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[${amount > 3000}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow4" sourceRef="financeTask" targetRef="endEvent"/>
</process>

第二步:核心 Java 代码实战(Spring Boot 环境)

这是业务系统对接的关键,我们主要操作 RuntimeServiceTaskService

启动流程实例(员工提交申请)

@Service
public class ExpenseService {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private IdentityService identityService;
    /**
     * 启动报销流程
     */
    public void startExpenseProcess(String applyUserId, String managerUserId, Double amount) {
        // 设置发起人(用于在流程中获取)
        identityService.setAuthenticatedUserId(applyUserId);
        // 设置流程变量(供BPMN表达式使用)
        Map<String, Object> variables = new HashMap<>();
        variables.put("applyUser", applyUserId);
        variables.put("managerUser", managerUserId);
        variables.put("amount", amount);
        // 根据流程定义Key启动
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("expenseProcess", variables);
        System.out.println("流程启动成功,流程实例ID:" + processInstance.getId());
    }
    /**
     * 完成部门经理审批任务
     */
    public void completeManagerTask(String taskId, Boolean approved) {
        Map<String, Object> vars = new HashMap<>();
        vars.put("managerApproved", approved);
        taskService.complete(taskId, vars);
    }
    /**
     * 查询某人需要处理的任务列表
     */
    public List<Task> getTasksForUser(String assignee) {
        return taskService.createTaskQuery()
                .taskAssignee(assignee)
                .orderByTaskCreateTime()
                .desc()
                .list();
    }
}

动态设置会签人(关键难点)

在实际项目中,审批人往往是根据角色动态查出来的,不能写死在流程里,这里在启动流程时,动态注入会签人列表:

public void startExpenseProcess(..., List<String> financeUsers) {
    Map<String, Object> variables = new HashMap<>();
    // ... 其他变量
    // 这里传入List,BPMN中的【flowable:collection】会接收这个变量
    variables.put("financeUsers", financeUsers);
    runtimeService.startProcessInstanceByKey("expenseProcess", variables);
}

驳回与结束

如果经理审批不通过,我们要终止流程(或者走驳回节点),可以在 completeManagerTask 中处理:

public void completeManagerTask(String taskId, Boolean approved) {
    Map<String, Object> vars = new HashMap<>();
    vars.put("managerApproved", approved);
    // 查找当前任务所属的流程实例
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    String processInstanceId = task.getProcessInstanceId();
    if (Boolean.FALSE.equals(approved)) {
        // 如果驳回,直接删除流程实例(即终止流程)
        runtimeService.deleteProcessInstance(processInstanceId, "经理审批不通过");
    } else {
        // 正常完成,网关会根据amount变量自动判断走向
        taskService.complete(taskId, vars);
    }
}

第三步:前端/Controller 交互接口

主要是向业务系统提供 REST API。

@RestController
@RequestMapping("/expense")
public class ExpenseController {
    @Autowired
    private ExpenseService expenseService;
    // 1. 提交审批
    @PostMapping("/start")
    public Result start(@RequestBody ExpenseDTO dto) {
        // 根据部门经理角色查询出实际用户ID,以及财务总监列表
        List<String> financeUsers = roleService.getFinanceUsers();
        expenseService.startExpenseProcess(dto.getApplyUser(), dto.getManagerUser(), 
                                           dto.getAmount(), financeUsers);
        return Result.success();
    }
    // 2. 获取待办任务
    @GetMapping("/todos/{userId}")
    public Result getTodos(@PathVariable String userId) {
        List<Task> tasks = expenseService.getTasksForUser(userId);
        // 转换为前端需要的DTO
        return Result.success(tasks.stream().map(...).collect(Collectors.toList()));
    }
    // 3. 审批通过/驳回
    @PostMapping("/complete")
    public Result complete(@RequestParam String taskId, 
                           @RequestParam Boolean approved) {
        expenseService.completeManagerTask(taskId, approved);
        return Result.success();
    }
}

第四步:该类案例的踩坑点与优化建议

  1. 并行网关 vs 排他网关:本案例使用的是 排他网关(Exclusive Gateway),如果你是“会签”(多个人同时审批),必须用 多实例(Multi-Instance) 节点,否则流程会在第一个审批人完成后直接流失效。
  2. 流程变量 (Variables) 大小写:BPMN 表达式 ${amount > 3000} 中的 amount 必须与 Java Map 中 Put 的 Key 完全一致
  3. 驳回处理:本案例的驳回直接终止了流程,如果业务复杂(比如驳回后需修改重新提交),建议增加 用户任务节点 指向发起人,并设置“驳回原因”变量,而不是直接删除流程实例。
  4. 性能问题:建议使用 Flowable 自带的 ActivityBehavior 进行动态人员解析,尽量避免在 Service 层写大量的 if-else 判断谁来审批,将规则下沉到 BPMN 设计器中。

Flowable 的核心优点是将 动态路由 从 Java 代码中剥离出来,交给了 XML 模型,你的 Java 代码只需要负责:

  • 启动流程(塞入业务数据为变量)。
  • 监听任务(查询待办)。
  • 触发流程(Complete 任务)。

如果你有具体的业务逻辑(比如动态指定审批人、按部门多级审批等),可以告诉我更具体的场景,我可以为你提供更精准的 BPMN 或者代码片段。

抱歉,评论功能暂时关闭!