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 < 3}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decisionGateway" targetRef="directorApproval">
<conditionExpression xsi:type="tFormalExpression">${days >= 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. 混合使用策略
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. 性能优化策略
-
启用异步执行器:处理耗时任务
flowable: async-executor-activate: true async-executor: core-pool-size: 10 max-pool-size: 50
-
历史数据归档:定期清理历史表
historyService.createHistoricProcessInstanceDeleteQuery() .finishedBefore(new Date(System.currentTimeMillis() - (30 * 24 * 3600 * 1000))) .delete();
-
批量处理:减少数据库交互
taskService.complete(taskIds, variables);
十一、部署与测试
部署步骤
- 创建数据库:
CREATE DATABASE flowable_db;
- 启动应用:
mvn spring-boot:run
- 访问:
http://localhost:8080/tasks
测试场景
-
请假流程测试:
- 以员工身份登录
- 提交请假申请(3天以下)
- 以经理身份审批
- 查看通知结果
-
客户支持案例测试:
- 以支持人员身份登录
- 创建客户支持工单
- 完成首次响应
- 选择升级案例
- 以专家身份评审问题
- 交付解决方案
十二、常见问题解决
-
流程无法启动:
- 检查 BPMN/CMMN 文件位置
- 验证 XML 语法是否正确
- 检查流程定义 KEY 是否匹配
-
任务分配失败:
- 确保 assignee 变量已设置
- 检查用户是否存在
-
变量传递问题:
- 使用
execution.setVariable()
设置流程变量 - 使用
cmmnRuntimeService.setVariable()
设置案例变量
- 使用
-
性能问题:
- 启用异步执行器
- 优化数据库查询
- 增加连接池大小
总结
本教程详细介绍了如何在 Spring Boot 应用中整合 Flowable 工作流引擎,同时使用 BPMN 和 CMMN 两种模型:
- 环境配置:设置 Spring Boot 和 Flowable 依赖
- 流程设计:创建 BPMN 请假流程和 CMMN 客户支持案例
- 业务实现:服务层、控制层和安全配置
- 前端交互:任务列表和表单处理
- 高级功能:历史数据管理、监控和性能优化
- 混合策略:BPMN 与 CMMN 的最佳结合方式
通过本教程,可以构建出能够处理复杂业务流程的企业级应用,结合 BPMN 的结构化特性和 CMMN 的灵活性,满足多样化的业务需求。