Spring Boot + Flowable 工作流开发教程:整合 BPMN 和 CMMN 实战

Spring Boot + Flowable 工作流开发教程:整合 BPMN 和 CMMN 实战

本教程将详细介绍如何在 Spring Boot 应用中整合 Flowable 工作流引擎,同时使用 BPMN(业务流程模型与标记)和 CMMN(案例管理模型与标记)两种模型实现复杂业务场景。

一、环境准备与技术栈

技术栈

  • Spring Boot 3.1+
  • Flowable 7.0+
  • MySQL 8.0+(或 H2 内存数据库)
  • Lombok(简化代码)
  • Thymeleaf(视图模板)
  • Spring Security(安全控制)

Maven 依赖

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- Flowable 依赖 -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>7.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-cmmn-spring-boot-starter</artifactId>
        <version>7.0.0</version>
    </dependency>
    
    <!-- 数据库 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- 其他工具 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

二、项目配置

application.yml 配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/flowable_db?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

flowable:
  # 数据库配置
  database-schema-update: true
  # 自动部署配置
  bpmn:
    check-process-definitions: true
    process-definition-location-prefix: classpath:/processes/
    process-definition-location-suffixes: **/*.bpmn20.xml
  cmmn:
    check-case-definitions: true
    case-definition-location-prefix: classpath:/cases/
    case-definition-location-suffixes: **/*.cmmn.xml
  # 异步执行器
  async-executor-activate: true
  async-executor:
    core-pool-size: 10
    max-pool-size: 50
    queue-size: 100

三、BPMN 流程设计(请假审批流程)

leave-process.bpmn20.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://www.flowable.org/processdef">
  
  <process id="leaveProcess" name="请假审批流程" isExecutable="true">
    
    <!-- 开始事件 -->
    <startEvent id="start" />
    
    <!-- 用户任务:提交请假申请 -->
    <userTask id="applyLeave" name="提交请假申请" flowable:assignee="${applicant}" />
    
    <!-- 排他网关:决定审批路径 -->
    <exclusiveGateway id="decisionGateway" />
    
    <!-- 用户任务:经理审批 -->
    <userTask id="managerApproval" name="经理审批" flowable:assignee="${manager}" />
    
    <!-- 用户任务:总监审批 -->
    <userTask id="directorApproval" name="总监审批" flowable:assignee="${director}" />
    
    <!-- 服务任务:发送通知 -->
    <serviceTask id="sendNotification" name="发送审批结果通知" 
                 flowable:class="com.example.workflow.service.NotificationService" />
    
    <!-- 结束事件 -->
    <endEvent id="end" />
    
    <!-- 顺序流连接 -->
    <sequenceFlow sourceRef="start" targetRef="applyLeave" />
    <sequenceFlow sourceRef="applyLeave" targetRef="decisionGateway" />
    
    <sequenceFlow sourceRef="decisionGateway" targetRef="managerApproval">
      <conditionExpression xsi:type="tFormalExpression">${days &lt; 3}</conditionExpression>
    </sequenceFlow>
    
    <sequenceFlow sourceRef="decisionGateway" targetRef="directorApproval">
      <conditionExpression xsi:type="tFormalExpression">${days &gt;= 3}</conditionExpression>
    </sequenceFlow>
    
    <sequenceFlow sourceRef="managerApproval" targetRef="sendNotification" />
    <sequenceFlow sourceRef="directorApproval" targetRef="sendNotification" />
    <sequenceFlow sourceRef="sendNotification" targetRef="end" />
  </process>
</definitions>

四、CMMN 案例设计(客户支持案例)

customer-support.cmmn.xml

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
             xmlns:flowable="http://flowable.org/cmmn"
             targetNamespace="http://www.flowable.org/casedef">
  
  <case id="customerSupportCase" name="客户支持案例">
    <casePlanModel id="casePlanModel">
      
      <!-- 第一阶段:初始支持 -->
      <planItem id="initialSupport" definitionRef="initialSupportStage" />
      <stage id="initialSupportStage" name="初始支持阶段">
        <planItem id="ticketCreation" definitionRef="createTicketTask" />
        <planItem id="firstResponse" definitionRef="firstResponseTask" />
        
        <exitCriterion id="escalateCriterion" sentryRef="escalateSentry" />
      </stage>
      
      <!-- 任务:创建工单 -->
      <humanTask id="createTicketTask" name="创建支持工单">
        <flowable:assignee>${supportAgent}</flowable:assignee>
      </humanTask>
      
      <!-- 任务:首次响应 -->
      <humanTask id="firstResponseTask" name="首次响应客户">
        <flowable:assignee>${supportAgent}</flowable:assignee>
        <entryCriterion id="ticketCreatedCriterion" sentryRef="ticketCreatedSentry" />
      </humanTask>
      
      <!-- 哨兵:工单已创建 -->
      <sentry id="ticketCreatedSentry">
        <planItemOnPart sourceRef="ticketCreation">
          <standardEvent>complete</standardEvent>
        </planItemOnPart>
      </sentry>
      
      <!-- 哨兵:需要升级 -->
      <sentry id="escalateSentry">
        <planItemOnPart sourceRef="firstResponse">
          <standardEvent>complete</standardEvent>
        </planItemOnPart>
        <ifPart>
          <condition>${escalationRequired}</condition>
        </ifPart>
      </sentry>
      
      <!-- 第二阶段:升级支持 -->
      <planItem id="escalatedSupport" definitionRef="escalatedSupportStage" />
      <stage id="escalatedSupportStage" name="升级支持阶段">
        <planItem id="expertReview" definitionRef="expertReviewTask" />
        <planItem id="solutionDelivery" definitionRef="solutionDeliveryTask" />
      </stage>
      
      <!-- 任务:专家评审 -->
      <humanTask id="expertReviewTask" name="专家评审问题">
        <flowable:assignee>${expert}</flowable:assignee>
      </humanTask>
      
      <!-- 任务:解决方案交付 -->
      <humanTask id="solutionDeliveryTask" name="交付解决方案">
        <flowable:assignee>${supportAgent}</flowable:assignee>
        <entryCriterion id="expertReviewCriterion" sentryRef="expertReviewSentry" />
      </humanTask>
      
      <!-- 哨兵:专家评审完成 -->
      <sentry id="expertReviewSentry">
        <planItemOnPart sourceRef="expertReview">
          <standardEvent>complete</standardEvent>
        </planItemOnPart>
      </sentry>
    </casePlanModel>
  </case>
</definitions>

五、核心业务逻辑实现

1. 流程启动服务

@Service
@RequiredArgsConstructor
public class WorkflowService {
    
    private final RuntimeService runtimeService;
    private final CmmnRuntimeService cmmnRuntimeService;
    private final TaskService taskService;
    private final IdentityService identityService;
    
    /**
     * 启动请假流程
     */
    @Transactional
    public ProcessInstance startLeaveProcess(LeaveRequest leaveRequest, String applicant) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", applicant);
        variables.put("manager", "manager_user");
        variables.put("director", "director_user");
        variables.put("days", leaveRequest.getDays());
        variables.put("reason", leaveRequest.getReason());
        
        return runtimeService.startProcessInstanceByKey(
            "leaveProcess", 
            variables
        );
    }
    
    /**
     * 启动客户支持案例
     */
    @Transactional
    public CaseInstance startCustomerSupportCase(SupportTicket ticket, String agent) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("supportAgent", agent);
        variables.put("expert", "expert_user");
        variables.put("customerId", ticket.getCustomerId());
        variables.put("issueDescription", ticket.getDescription());
        
        return cmmnRuntimeService.createCaseInstanceBuilder()
            .caseDefinitionKey("customerSupportCase")
            .variables(variables)
            .start();
    }
    
    /**
     * 获取用户任务
     */
    public List<Task> getUserTasks(String userId) {
        return taskService.createTaskQuery()
            .taskAssignee(userId)
            .includeProcessVariables()
            .includeCaseVariables()
            .list();
    }
    
    /**
     * 完成任务
     */
    @Transactional
    public void completeTask(String taskId, Map<String, Object> variables) {
        // 模拟真实用户操作
        identityService.setAuthenticatedUserId(SecurityContextHolder.getContext().getAuthentication().getName());
        
        if (variables != null && !variables.isEmpty()) {
            taskService.complete(taskId, variables);
        } else {
            taskService.complete(taskId);
        }
    }
    
    /**
     * 处理升级逻辑
     */
    @Transactional
    public void handleEscalation(String caseId, boolean escalationRequired) {
        cmmnRuntimeService.setVariable(caseId, "escalationRequired", escalationRequired);
    }
}

2. 通知服务

@Service
public class NotificationService implements JavaDelegate {
    
    private static final Logger logger = LoggerFactory.getLogger(NotificationService.class);
    
    @Override
    public void execute(DelegateExecution execution) {
        String applicant = (String) execution.getVariable("applicant");
        String status = (Boolean) execution.getVariable("approved") ? "批准" : "拒绝";
        String reason = (String) execution.getVariable("reason");
        
        String message = String.format(
            "用户 %s 的请假申请已%s。原因:%s",
            applicant, status, reason
        );
        
        logger.info("发送通知: {}", message);
        // 实际项目中可集成邮件、短信等通知方式
    }
}

六、控制器层实现

WorkflowController.java

@Controller
@RequestMapping("/workflow")
@RequiredArgsConstructor
public class WorkflowController {
    
    private final WorkflowService workflowService;
    
    // 启动请假流程
    @PostMapping("/leave/start")
    public String startLeaveProcess(
            @ModelAttribute LeaveRequest leaveRequest,
            Principal principal) {
        
        workflowService.startLeaveProcess(leaveRequest, principal.getName());
        return "redirect:/tasks";
    }
    
    // 启动客户支持案例
    @PostMapping("/support/start")
    public String startSupportCase(
            @ModelAttribute SupportTicket ticket,
            Principal principal) {
        
        workflowService.startCustomerSupportCase(ticket, principal.getName());
        return "redirect:/tasks";
    }
    
    // 获取用户任务列表
    @GetMapping("/tasks")
    public String getUserTasks(Model model, Principal principal) {
        List<Task> tasks = workflowService.getUserTasks(principal.getName());
        model.addAttribute("tasks", tasks);
        return "task-list";
    }
    
    // 完成任务
    @PostMapping("/task/complete/{taskId}")
    public String completeTask(
            @PathVariable String taskId,
            @RequestParam(required = false) Boolean approved,
            @RequestParam(required = false) Boolean escalation) {
        
        Map<String, Object> variables = new HashMap<>();
        if (approved != null) {
            variables.put("approved", approved);
        }
        if (escalation != null) {
            variables.put("escalationRequired", escalation);
        }
        
        workflowService.completeTask(taskId, variables);
        return "redirect:/tasks";
    }
    
    // 处理升级
    @PostMapping("/support/escalate/{caseId}")
    public String escalateCase(
            @PathVariable String caseId,
            @RequestParam Boolean escalation) {
        
        workflowService.handleEscalation(caseId, escalation);
        return "redirect:/tasks";
    }
}

七、安全配置

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/home").permitAll()
                .requestMatchers("/tasks", "/workflow/**").authenticated()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
                .permitAll()
            );
        
        return http.build();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails employee = User.withUsername("employee")
            .password("{noop}password")
            .roles("USER")
            .build();
        
        UserDetails manager = User.withUsername("manager_user")
            .password("{noop}password")
            .roles("MANAGER")
            .build();
        
        UserDetails director = User.withUsername("director_user")
            .password("{noop}password")
            .roles("DIRECTOR")
            .build();
        
        UserDetails support = User.withUsername("support_agent")
            .password("{noop}password")
            .roles("SUPPORT")
            .build();
        
        UserDetails expert = User.withUsername("expert_user")
            .password("{noop}password")
            .roles("EXPERT")
            .build();
        
        return new InMemoryUserDetailsManager(employee, manager, director, support, expert);
    }
}

八、前端页面示例

task-list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>任务列表</title>
</head>
<body>
    <h1>我的任务</h1>
    
    <!-- 请假申请表单 -->
    <div>
        <h2>提交请假申请</h2>
        <form th:action="@{/workflow/leave/start}" method="post">
            请假天数: <input type="number" name="days" required><br>
            请假原因: <textarea name="reason" required></textarea><br>
            <button type="submit">提交申请</button>
        </form>
    </div>
    
    <!-- 客户支持表单 -->
    <div>
        <h2>创建客户支持工单</h2>
        <form th:action="@{/workflow/support/start}" method="post">
            客户ID: <input type="text" name="customerId" required><br>
            问题描述: <textarea name="description" required></textarea><br>
            <button type="submit">创建工单</button>
        </form>
    </div>
    
    <!-- 任务列表 -->
    <div th:if="${not #lists.isEmpty(tasks)}">
        <h2>待处理任务</h2>
        <table border="1">
            <tr>
                <th>任务ID</th>
                <th>任务名称</th>
                <th>创建时间</th>
                <th>操作</th>
            </tr>
            <tr th:each="task : ${tasks}">
                <td th:text="${task.id}"></td>
                <td th:text="${task.name}"></td>
                <td th:text="${#temporals.format(task.createTime, 'yyyy-MM-dd HH:mm')}"></td>
                <td>
                    <!-- 审批任务 -->
                    <div th:if="${task.taskDefinitionKey == 'managerApproval' or 
                                  task.taskDefinitionKey == 'directorApproval'}">
                        <form th:action="@{/workflow/task/complete/{taskId}(taskId=${task.id})}" method="post">
                            <button type="submit" name="approved" value="true">批准</button>
                            <button type="submit" name="approved" value="false">拒绝</button>
                        </form>
                    </div>
                    
                    <!-- 支持任务 -->
                    <div th:if="${task.taskDefinitionKey == 'firstResponseTask'}">
                        <form th:action="@{/workflow/support/escalate/{caseId}(caseId=${task.caseInstanceId})}" method="post">
                            是否需要升级:
                            <input type="radio" name="escalation" value="true" required><input type="radio" name="escalation" value="false"><button type="submit">提交</button>
                        </form>
                    </div>
                    
                    <!-- 其他任务 -->
                    <div th:unless="${task.taskDefinitionKey == 'managerApproval' or 
                                     task.taskDefinitionKey == 'directorApproval' or 
                                     task.taskDefinitionKey == 'firstResponseTask'}">
                        <form th:action="@{/workflow/task/complete/{taskId}(taskId=${task.id})}" method="post">
                            <button type="submit">完成任务</button>
                        </form>
                    </div>
                </td>
            </tr>
        </table>
    </div>
</body>
</html>

九、BPMN 与 CMMN 结合的最佳实践

1. 使用场景区分

  • BPMN 适用场景

    • 结构化、可预测的流程
    • 顺序化任务执行
    • 需要严格审批流程的业务
  • CMMN 适用场景

    • 非结构化、动态性强的流程
    • 事件驱动的任务
    • 需要根据条件动态调整执行路径的案例

2. 混合使用策略

客户提交问题
BPMN: 创建工单
CMMN: 客户支持案例
一线支持处理
是否解决?
结束
升级到二线支持
专家评审
解决方案交付

3. 交互机制

  • BPMN 启动 CMMN:在 BPMN 服务任务中启动 CMMN 案例
  • CMMN 触发 BPMN:在 CMMN 任务完成时启动 BPMN 子流程
  • 共享变量:通过流程/案例变量在两个引擎间传递数据

十、高级功能与优化

1. 历史数据管理

@RestController
@RequestMapping("/history")
public class HistoryController {
    
    private final HistoryService historyService;
    private final CmmnHistoryService cmmnHistoryService;
    
    @GetMapping("/process/{instanceId}")
    public List<HistoricActivityInstance> getProcessHistory(
            @PathVariable String instanceId) {
        return historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(instanceId)
            .orderByHistoricActivityInstanceStartTime().asc()
            .list();
    }
    
    @GetMapping("/case/{caseId}")
    public List<HistoricCaseInstance> getCaseHistory(
            @PathVariable String caseId) {
        return cmmnHistoryService.createHistoricCaseInstanceQuery()
            .caseInstanceId(caseId)
            .orderByStartTime().asc()
            .list();
    }
}

2. 流程监控与管理

@Service
public class AdminService {
    
    private final ManagementService managementService;
    private final RuntimeService runtimeService;
    private final CmmnManagementService cmmnManagementService;
    
    // 获取所有运行中流程
    public List<ProcessInstance> getActiveProcesses() {
        return runtimeService.createProcessInstanceQuery().list();
    }
    
    // 获取所有运行中案例
    public List<CaseInstance> getActiveCases() {
        return cmmnRuntimeService.createCaseInstanceQuery().list();
    }
    
    // 挂起流程实例
    public void suspendProcess(String instanceId) {
        runtimeService.suspendProcessInstanceById(instanceId);
    }
    
    // 激活案例实例
    public void activateCase(String caseId) {
        cmmnRuntimeService.activateCaseInstanceById(caseId);
    }
    
    // 查看作业队列
    public List<Job> getPendingJobs() {
        return managementService.createJobQuery().list();
    }
}

3. 性能优化策略

  1. 启用异步执行器:处理耗时任务

    flowable:
      async-executor-activate: true
      async-executor:
        core-pool-size: 10
        max-pool-size: 50
    
  2. 历史数据归档:定期清理历史表

    historyService.createHistoricProcessInstanceDeleteQuery()
        .finishedBefore(new Date(System.currentTimeMillis() - (30 * 24 * 3600 * 1000)))
        .delete();
    
  3. 批量处理:减少数据库交互

    taskService.complete(taskIds, variables);
    

十一、部署与测试

部署步骤

  1. 创建数据库:CREATE DATABASE flowable_db;
  2. 启动应用:mvn spring-boot:run
  3. 访问:http://localhost:8080/tasks

测试场景

  1. 请假流程测试

    • 以员工身份登录
    • 提交请假申请(3天以下)
    • 以经理身份审批
    • 查看通知结果
  2. 客户支持案例测试

    • 以支持人员身份登录
    • 创建客户支持工单
    • 完成首次响应
    • 选择升级案例
    • 以专家身份评审问题
    • 交付解决方案

十二、常见问题解决

  1. 流程无法启动

    • 检查 BPMN/CMMN 文件位置
    • 验证 XML 语法是否正确
    • 检查流程定义 KEY 是否匹配
  2. 任务分配失败

    • 确保 assignee 变量已设置
    • 检查用户是否存在
  3. 变量传递问题

    • 使用 execution.setVariable() 设置流程变量
    • 使用 cmmnRuntimeService.setVariable() 设置案例变量
  4. 性能问题

    • 启用异步执行器
    • 优化数据库查询
    • 增加连接池大小

总结

本教程详细介绍了如何在 Spring Boot 应用中整合 Flowable 工作流引擎,同时使用 BPMN 和 CMMN 两种模型:

  1. 环境配置:设置 Spring Boot 和 Flowable 依赖
  2. 流程设计:创建 BPMN 请假流程和 CMMN 客户支持案例
  3. 业务实现:服务层、控制层和安全配置
  4. 前端交互:任务列表和表单处理
  5. 高级功能:历史数据管理、监控和性能优化
  6. 混合策略:BPMN 与 CMMN 的最佳结合方式

通过本教程,可以构建出能够处理复杂业务流程的企业级应用,结合 BPMN 的结构化特性和 CMMN 的灵活性,满足多样化的业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值