Flowable 与 Spring Boot 深度集成:从环境搭建到平台构建

发布于:2025-07-26 ⋅ 阅读:(21) ⋅ 点赞:(0)

在前三篇文章中,我们依次认识了 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=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

flowable:
  # 数据库表结构更新策略
  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 {

    @Override
    public 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 服务

  1. 下载 Flowable Modeler 的 WAR 包并部署到 Tomcat
  2. 配置跨域支持(flowable-modeler-app/WEB-INF/classes/flowable-modeler.properties):
flowable.modeler.app.cors.allowed-origins=http://localhost:8080

flowable.modeler.app.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS

flowable.modeler.app.cors.allowed-headers=Content-Type,Authorization

 3.配置与业务系统相同的数据库,实现数据共享

2.2 实现 Modeler 与业务系统的单点登录

通过自定义过滤器实现 SSO 集成:

@Component
public class ModelerSsoFilter extends OncePerRequestFilter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    protected 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 {

    @Autowired
    private RepositoryService repositoryService;
    
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 从Modeler获取流程模型并部署
     */
    public Deployment deployFromModeler(String modelId) {
        // 调用Modeler的API获取流程模型JSON
        String 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 {

    @Autowired
    private TaskService taskService;
    
    @Autowired
    private 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 {

    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private HistoryService historyService;
    
    @Autowired
    private 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();
        
        // 构建流程跟踪VO
        ProcessTraceVO 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 {

    @Autowired
    private 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);
        }
        
        // 计算平均值并转换为VO
        return 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 {

    @Bean
    public 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 {

    @Autowired
    private ProcessRepositoryService processRepositoryService;

    @Override
    public 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 的部署策略:

  1. 共享数据库模式:所有服务共享 Flowable 数据库,适合中小规模部署
  2. 独立流程服务:将 Flowable 作为独立微服务,提供 REST API 供其他服务调用
  3. 事件驱动架构:通过消息队列(如 Kafka)实现流程事件的跨服务通知

5.2 性能优化实践

  1. 历史数据归档:定期将旧的历史数据迁移到归档库
@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 的核心组件(如自定义解析器、行为处理器)

如果在集成过程中遇到特殊场景或技术难题,欢迎在评论区留言讨论!


网站公告

今日签到

点亮在社区的每一天
去签到