在前三篇文章中,我们依次认识了 Flowable 的基础概念、用 Modeler 设计流程,以及通过 API 控制流程运行。但在实际项目中,我们更需要将 Flowable 与 Spring Boot 深度融合,构建完整的工作流平台。本文将从环境配置、设计器集成、权限控制等方面,分享 Flowable 与 Spring Boot 集成的实战经验。
一、Flowable 与 Spring Boot 的无缝对接
1.1 基础环境配置
Spring Boot 对 Flowable 提供了自动配置支持,只需引入相关依赖即可快速集成:
<dependencies><!-- Flowable核心依赖 --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.8.0</version></dependency><!-- Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 数据库依赖 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- 安全框架(用于权限控制) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
</dependencies>
在application.yml中配置数据库和 Flowable 参数:
spring:datasource:url: jdbc:mysql://localhost:3306/flowable_boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverflowable:# 数据库表结构更新策略database-schema-update: true# 异步执行器配置async-executor-activate: true# 历史记录级别history-level: full# 流程部署路径process-definition-location-prefix: classpath:/processes/
1.2 自定义 Flowable 配置
通过ProcessEngineConfigurationConfigurer可自定义流程引擎配置:
@Configuration
public class FlowableConfig implements ProcessEngineConfigurationConfigurer {@Overridepublic void configure(SpringProcessEngineConfiguration configuration) {// 配置邮件服务器(用于任务通知)configuration.setMailServerHost("smtp.example.com");configuration.setMailServerPort(587);configuration.setMailServerUsername("notifications@example.com");configuration.setMailServerPassword("password");// 配置自定义表单类型List<AbstractFormType> customFormTypes = new ArrayList<>();customFormTypes.add(new PhoneFormType()); // 自定义手机号表单类型customFormTypes.add(new IdCardFormType()); // 自定义身份证表单类型configuration.setCustomFormTypes(customFormTypes);// 配置流程自动部署器configuration.setDeploymentName("spring-boot-deployment");}
}
二、Flowable Modeler 与业务系统集成
将 Flowable Modeler 嵌入业务系统,实现流程设计与业务的无缝衔接。
2.1 部署独立的 Modeler 服务
- 下载 Flowable Modeler 的 WAR 包并部署到 Tomcat
- 配置跨域支持(flowable-modeler-app/WEB-INF/classes/flowable-modeler.properties):
flowable.modeler.app.cors.allowed-origins=http://localhost:8080flowable.modeler.app.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONSflowable.modeler.app.cors.allowed-headers=Content-Type,Authorization
3.配置与业务系统相同的数据库,实现数据共享
2.2 实现 Modeler 与业务系统的单点登录
通过自定义过滤器实现 SSO 集成:
@Component
public class ModelerSsoFilter extends OncePerRequestFilter {@Autowiredprivate AuthenticationManager authenticationManager;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 从请求头获取令牌String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {try {// 验证令牌并创建认证对象UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(token, token);Authentication authentication = authenticationManager.authenticate(authToken);// 设置安全上下文SecurityContextHolder.getContext().setAuthentication(authentication);} catch (AuthenticationException e) {SecurityContextHolder.clearContext();}}filterChain.doFilter(request, response);}
}
2.3 流程部署与版本管理
实现流程设计完成后自动部署到业务系统:
@Service
public class ProcessDeploymentService {@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RestTemplate restTemplate;/*** 从Modeler获取流程模型并部署*/public Deployment deployFromModeler(String modelId) {// 调用Modeler的API获取流程模型JSONString modelUrl = "http://localhost:8080/flowable-modeler/app/rest/models/" + modelId + "/source";String bpmnXml = restTemplate.getForObject(modelUrl, String.class);// 部署流程定义return repositoryService.createDeployment().name("model-" + modelId).addString("process-" + modelId + ".bpmn20.xml", bpmnXml).deploy();}/*** 流程版本管理*/public List<ProcessDefinition> getProcessVersions(String processKey) {return repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey).orderByProcessDefinitionVersion().asc().list();}/*** 激活指定版本的流程*/public void activateProcessVersion(String processDefinitionId) {repositoryService.activateProcessDefinitionById(processDefinitionId);// 停用其他版本String processKey = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult().getKey();List<ProcessDefinition> otherVersions = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey).processDefinitionIdNotEquals(processDefinitionId).list();otherVersions.forEach(def -> repositoryService.suspendProcessDefinitionById(def.getId()));}
}
三、基于 Flowable 的工作流平台搭建
构建包含待办任务、流程监控、报表分析的完整工作流平台。
3.1 待办任务中心
实现个人待办、已办任务的统一管理:
@RestController
@RequestMapping("/workflow/task")
public class TaskController {@Autowiredprivate TaskService taskService;@Autowiredprivate IdentityService identityService;/*** 获取当前用户的待办任务*/@GetMapping("/pending")public Page<TaskVO> getPendingTasks(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size) {String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();// 查询待办任务(包含候选人任务)List<Task> tasks = taskService.createTaskQuery().taskCandidateOrAssigned(currentUser).orderByTaskCreateTime().desc().listPage(page * size, size);// 转换为VO对象List<TaskVO> taskVOs = tasks.stream().map(this::convertToVO).collect(Collectors.toList());// 计算总条数long total = taskService.createTaskQuery().taskCandidateOrAssigned(currentUser).count();return new Page<>(taskVOs, page, size, total);}/*** 认领任务*/@PostMapping("/claim/{taskId}")public ResponseEntity<Void> claimTask(@PathVariable String taskId) {String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();// 验证任务是否可认领Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(currentUser).singleResult();if (task == null) {return ResponseEntity.badRequest().build();}// 认领任务taskService.claim(taskId, currentUser);return ResponseEntity.ok().build();}/*** 完成任务*/@PostMapping("/complete/{taskId}")public ResponseEntity<Void> completeTask(@PathVariable String taskId,@RequestBody TaskCompleteRequest request) {String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();// 验证任务归属Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(currentUser).singleResult();if (task == null) {return ResponseEntity.badRequest().build();}// 完成任务taskService.complete(taskId, request.getVariables());return ResponseEntity.ok().build();}// 其他方法:获取已办任务、委托任务、转办任务等
}
3.2 流程监控与分析
实现流程运行状态的实时监控和数据分析:
@RestController
@RequestMapping("/workflow/monitor")
public class MonitorController {@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate HistoryService historyService;@Autowiredprivate ManagementService managementService;/*** 流程运行状态统计*/@GetMapping("/statistics")public WorkflowStatisticsVO getStatistics() {WorkflowStatisticsVO stats = new WorkflowStatisticsVO();// 运行中流程数量stats.setRunningProcessCount(runtimeService.createProcessInstanceQuery().active().count());// 已完成流程数量stats.setCompletedProcessCount(historyService.createHistoricProcessInstanceQuery().finished().count());// 平均流程完成时间HistoricProcessInstanceStatistics avgStats = historyService.createHistoricProcessInstanceStatisticsQuery(null).singleResult();if (avgStats != null) {stats.setAvgCompletionTime(avgStats.getAverageDurationInMillis());}// 各流程定义的运行数量List<ProcessDefinitionCountVO> definitionCounts = runtimeService.createProcessInstanceQuery().groupByProcessDefinitionKey().countByProcessDefinitionKey().entrySet().stream().map(entry -> {ProcessDefinitionCountVO vo = new ProcessDefinitionCountVO();vo.setProcessKey(entry.getKey());vo.setCount(entry.getValue());return vo;}).collect(Collectors.toList());stats.setProcessDefinitionCounts(definitionCounts);return stats;}/*** 流程实例跟踪*/@GetMapping("/trace/{processInstanceId}")public ProcessTraceVO traceProcess(@PathVariable String processInstanceId) {// 获取流程实例ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();// 获取历史活动实例List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();// 构建流程跟踪VOProcessTraceVO traceVO = new ProcessTraceVO();traceVO.setProcessInstanceId(processInstanceId);traceVO.setProcessDefinitionId(instance.getProcessDefinitionId());traceVO.setStartTime(instance.getStartTime());traceVO.setStatus(instance.isEnded() ? "completed" : "running");// 转换活动实例List<ActivityTraceVO> activityTraces = activities.stream().map(activity -> {ActivityTraceVO activityVO = new ActivityTraceVO();activityVO.setActivityId(activity.getActivityId());activityVO.setActivityName(activity.getActivityName());activityVO.setAssignee(activity.getAssignee());activityVO.setStartTime(activity.getStartTime());activityVO.setEndTime(activity.getEndTime());activityVO.setDuration(activity.getDurationInMillis());return activityVO;}).collect(Collectors.toList());traceVO.setActivities(activityTraces);return traceVO;}
}
3.3 流程报表与分析
通过报表分析流程瓶颈,优化业务流程:
@Service
public class WorkflowReportService {@Autowiredprivate HistoryService historyService;/*** 流程环节耗时分析*/public List<ActivityDurationVO> analyzeActivityDurations(String processKey) {// 查询指定流程的所有历史活动List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery().processDefinitionKey(processKey).activityTypeIn("userTask", "serviceTask").list();// 按活动ID分组计算平均耗时Map<String, List<Long>> durationMap = new HashMap<>();for (HistoricActivityInstance activity : activities) {String activityId = activity.getActivityId();long duration = activity.getDurationInMillis() == null ? 0 : activity.getDurationInMillis();durationMap.computeIfAbsent(activityId, k -> new ArrayList<>()).add(duration);}// 计算平均值并转换为VOreturn durationMap.entrySet().stream().map(entry -> {ActivityDurationVO vo = new ActivityDurationVO();vo.setActivityId(entry.getKey());// 获取活动名称String activityName = activities.stream().filter(a -> a.getActivityId().equals(entry.getKey())).findFirst().map(HistoricActivityInstance::getActivityName).orElse(entry.getKey());vo.setActivityName(activityName);// 计算平均耗时List<Long> durations = entry.getValue();long avgDuration = durations.stream().mapToLong(Long::longValue).average().orElse(0);vo.setAvgDuration(avgDuration);vo.setCount(durations.size());return vo;}).sorted(Comparator.comparingLong(ActivityDurationVO::getAvgDuration).reversed()).collect(Collectors.toList());}
}
四、安全与权限控制
集成 Spring Security 实现流程操作的权限管控。
4.1 基于角色的权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth// 公开接口.requestMatchers("/workflow/public/**", "/actuator/health").permitAll()// 管理员接口.requestMatchers("/workflow/admin/**", "/flowable-ui/**").hasRole("ADMIN")// 流程设计接口.requestMatchers("/workflow/model/**").hasAnyRole("ADMIN", "PROCESS_DESIGNER")// 其他接口需认证.anyRequest().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))).csrf(csrf -> csrf.disable());return http.build();}/*** JWT认证转换器(将JWT声明转换为Spring Security权限)*/private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");JwtAuthenticationConverter converter = new JwtAuthenticationConverter();converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);return converter;}
}
4.2 流程权限的细粒度控制
通过AccessControlProvider控制流程实例的访问权限:
@Component
public class CustomAccessControlProvider implements AccessControlProvider {@Autowiredprivate ProcessRepositoryService processRepositoryService;@Overridepublic boolean isAuthorized(UserDetails user, String processInstanceId) {String username = user.getUsername();// 管理员拥有所有权限if (user.getAuthorities().stream().anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"))) {return true;}// 流程发起人拥有权限ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();if (instance != null && username.equals(instance.getStartUserId())) {return true;}// 参与过流程的用户拥有权限long participatedCount = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).taskAssignee(username).count();if (participatedCount > 0) {return true;}return false;}
}
五、实际项目中的架构设计与踩坑经验
5.1 分布式环境下的 Flowable 部署
在微服务架构中,Flowable 的部署策略:
- 共享数据库模式:所有服务共享 Flowable 数据库,适合中小规模部署
- 独立流程服务:将 Flowable 作为独立微服务,提供 REST API 供其他服务调用
- 事件驱动架构:通过消息队列(如 Kafka)实现流程事件的跨服务通知
5.2 性能优化实践
- 历史数据归档:定期将旧的历史数据迁移到归档库
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void archiveHistoricData() {// 归档30天前的历史数据Date cutoffDate = new Date(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000);historyService.createHistoricProcessInstanceQuery().finished().processInstanceEndTimeBefore(cutoffDate).list().forEach(instance -> {// 迁移到归档表archiveService.archiveInstance(instance.getId());// 删除原表数据historyService.deleteHistoricProcessInstance(instance.getId());});
}
2.流程变量优化:
- 避免存储大对象(超过 1KB)
- 使用runtimeService.setVariableLocal设置局部变量
- 敏感信息加密存储
3.缓存策略:缓存流程定义和常用流程数据
@Bean
public CacheManager flowableCacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(1000));return cacheManager;
}
5.3 常见问题及解决方案
问题 | 解决方案 |
流程实例查询性能差 | 1. 减少返回字段 2. 分页查询 3. 添加索引 4. 使用缓存 |
分布式环境下流程事件重复处理 | 1. 使用幂等设计 2. 事件去重 3. 分布式锁控制 |
大并发下任务创建慢 | 1. 异步创建任务 2. 批量处理 3. 数据库优化 |
流程版本升级困难 | 1. 设计兼容的流程变更 2. 流程实例迁移工具 3. 灰度发布策略 |
六、小结与下一篇预告
本文我们实现了 Flowable 与 Spring Boot 的深度集成,包括:
- 环境搭建与自定义配置
- Modeler 与业务系统的无缝对接
- 工作流平台核心功能(待办任务、流程监控)
- 安全权限控制与分布式部署策略
这些内容足以支撑企业级工作流平台的构建。下一篇文章,我们将探讨 Flowable 的高级特性与扩展,包括:
- 动态流程生成(无需预先设计 BPMN 文件)
- Flowable 与决策引擎 DMN 的集成
- 复杂场景的流程设计模式(如子流程嵌套、事件子流程)
- 自定义 Flowable 的核心组件(如自定义解析器、行为处理器)
如果在集成过程中遇到特殊场景或技术难题,欢迎在评论区留言讨论!