在前三篇文章中,我們依次認識了 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 的核心組件(如自定義解析器、行為處理器)
如果在集成過程中遇到特殊場景或技術難題,歡迎在評論區留言討論!