Flowable 工作流引擎

一、工作流引擎简介

工作流引擎的核心是将业务逻辑与流程控制逻辑分离。开发者无需在代码中硬编码复杂的流程跳转和状态判断,而是通过流程定义文件(如 BPMN 2.0 标准)来描述业务流程,由引擎来解析和执行这个流程。

1.1 核心价值:
  • 可维护性:业务流程变更时,通常只需修改流程定义文件,无需改动代码。
  • 可视化:使用 BPMN 2.0 等标准图表来描述流程,业务人员和技术人员可以更好地协作。
  • 可追溯性:引擎会记录流程实例的完整执行路径,便于审计和问题排查。
  • 灵活性:支持动态调整流程、跳转、回退等复杂场景。
1.2 主流 Java 工作流引擎
Activiti:
  • 背景:由 Alfresco 公司开发,是 jBPM 的一个分支。
  • 版本:目前主要有 Activiti 5/6/7 三个大版本。Activiti 7 与 Spring Boot 深度集成,提供了更云原生的部署方式。
  • 特点:严格遵循 BPMN 2.0 标准,与 Spring 生态集成度极高,社区庞大,文档丰富。
Flowable:
  • 背景:由 Activiti 的核心贡献者从 Activiti 项目 fork 出来,旨在提供一个更轻量、更快速、功能更丰富的版本。
  • 特点:
    • 性能优于 Activiti。
    • 提供了更多开箱即用的高级功能,如历史数据归档、动态表格等。
    • 同样严格遵循 BPMN 2.0 标准,并支持 CMMN(案例管理)和 DMN(决策规则)。
    • 社区非常活跃,迭代速度快。
1.3 核心概念

无论选择哪个引擎,你都会接触到以下核心概念:

  1. 流程定义:业务流程的蓝图,通常是一个 .bpmn20.xml 文件,用 BPMN 2.0 XML 描述。
  2. 流程实例:一个正在运行的流程定义的实例。例如,一个请假流程定义,每个员工的每一次请假申请就是一个流程实例。
  3. 活动:流程中的一个步骤,如"用户任务"、"服务任务"、"网关"等。
  4. 用户任务:需要人工参与的任务,例如"经理审批"。引擎会将该任务分配给指定的用户或组。
  5. 服务任务:自动执行的任务,例如调用一个 Java 类或 HTTP API。
  6. 网关:用于控制流程的走向,如"排他网关"(类似 if-else)、"并行网关"(同时执行多个分支)。
1.4 工作流程
  1. 建模:使用图形化工具(如 Flowable Modeler, Camunda Modeler)或直接编写 BPMN 2.0 XML 来设计流程。
  2. 部署:将流程定义文件部署到引擎中。
  3. 启动实例:通过 API 启动一个流程实例。
  4. 查询任务:用户登录系统后,通过 API 查询分配给自己的任务。
  5. 处理任务:用户完成任务(如审批通过),引擎会自动推动流程到下一个节点。
  6. 监控:通过 API 或管理后台监控所有流程实例的状态。

二、Flowable 工作流引擎

2.1 Flowable 核心特性
  • BPMN 2.0 标准支持:完整的业务流程建模与执行
  • DMN 1.3 支持:决策规则引擎
  • CMMN 1.1 支持:案例管理
  • 轻量级设计:可嵌入到应用中,也可独立部署
  • REST API:提供完整的 RESTful 接口
  • Spring 集成:与 Spring 框架深度集成
  • 多数据库支持:MySQL、PostgreSQL、Oracle 等
  • 高性能:优化的流程执行引擎
2.2 Flowable 核心引擎组件
  • Flowable Process Engine:核心流程引擎
  • Flowable DMN Engine:决策规则引擎
  • Flowable CMMN Engine:案例管理引擎
  • Flowable Content Engine:内容管理引擎
  • Flowable IDM Engine:身份管理引擎
2.3 Flowable 核心服务
java 复制代码
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService(); // 流程部署
RuntimeService runtimeService = processEngine.getRuntimeService();         // 流程运行
TaskService taskService = processEngine.getTaskService();                 // 任务管理
HistoryService historyService = processEngine.getHistoryService();         // 历史数据
IdentityService identityService = processEngine.getIdentityService();     // 用户管理
2.4 Flowable 核心表结构
  • ACT_RE_*:资源存储(流程定义、图片等)
  • ACT_RU_*:运行时数据(任务、变量等)
  • ACT_HI_*:历史数据(已完成流程实例)
  • ACT_GE_*:通用数据(变量、字节数组等)

三、Flowable 使用示例

3.1 环境配置
  1. 引入依赖

    xml 复制代码
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-engine</artifactId>
        <version>6.7.2</version>
    </dependency>
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring</artifactId>
        <version>6.7.2</version>
    </dependency>
  2. Spring Boot application.yml 配置:

    yml 复制代码
    # application.yml
    flowable:
      database-schema-update: true
      async-executor-activate: false
      history-level: audit
3.2 定义 BPMN 流程

创建一个请假审批流程 leave-request.bpmn20.xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             targetNamespace="http://www.flowable.org/processdef">
    
    <process id="leaveRequest" name="请假审批流程" isExecutable="true">
        
        <!-- 开始事件 -->
        <startEvent id="startEvent" name="开始请假">
            <extensionElements>
                <flowable:formProperty id="leaveType" name="请假类型" 
                    type="enum" required="true">
                    <flowable:value id="sick" name="病假" />
                    <flowable:value id="annual" name="年假" />
                </flowable:formProperty>
                <flowable:formProperty id="days" name="请假天数" 
                    type="long" required="true" />
                <flowable:formProperty id="reason" name="请假事由" 
                    type="string" />
            </extensionElements>
        </startEvent>
        
        <!-- 用户任务 - 员工提交申请 -->
        <userTask id="submitRequest" name="提交请假申请" 
                  flowable:assignee="${applicant}">
            <documentation>员工提交请假申请</documentation>
        </userTask>
        
        <!-- 排他网关 - 路由判断 -->
        <exclusiveGateway id="decisionGateway" name="审批路由" />
        
        <!-- 用户任务 - 经理审批 -->
        <userTask id="managerApprove" name="经理审批" 
                  flowable:candidateGroups="managers">
            <extensionElements>
                <flowable:formProperty id="managerApproved" name="是否批准" 
                    type="enum" required="true">
                    <flowable:value id="true" name="批准" />
                    <flowable:value id="false" name="拒绝" />
                </flowable:formProperty>
                <flowable:formProperty id="managerComment" name="审批意见" 
                    type="string" />
            </extensionElements>
        </userTask>
        
        <!-- 用户任务 - HR备案(仅3天以上需要) -->
        <userTask id="hrRecord" name="HR备案" 
                  flowable:candidateGroups="hr">
            <extensionElements>
                <flowable:formProperty id="hrRecorded" name="是否已备案" 
                    type="enum" required="true">
                    <flowable:value id="true" name="已备案" />
                </flowable:formProperty>
            </extensionElements>
        </userTask>
        
        <!-- 结束事件 -->
        <endEvent id="endEvent" name="流程结束" />
        
        <!-- 顺序流 -->
        <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitRequest" />
        <sequenceFlow id="flow2" sourceRef="submitRequest" targetRef="decisionGateway" />
        
        <!-- 条件顺序流 -->
        <sequenceFlow id="flowToManager" sourceRef="decisionGateway" targetRef="managerApprove">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${days <= 3}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flowToHR" sourceRef="decisionGateway" targetRef="hrRecord">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${days > 3}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flow3" sourceRef="hrRecord" targetRef="managerApprove" />
        
        <!-- 审批结果流向 -->
        <sequenceFlow id="flowApprove" sourceRef="managerApprove" targetRef="endEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${managerApproved == 'true'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flowReject" sourceRef="managerApprove" targetRef="endEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${managerApproved == 'false'}]]>
            </conditionExpression>
        </sequenceFlow>
        
    </process>
</definitions>
3.3 Java 代码实现

流程部署服务:

java 复制代码
@Service
public class ProcessDeploymentService {
    
    @Autowired
    private RepositoryService repositoryService;
    
    /**
     * 部署请假流程
     */
    public Deployment deployLeaveProcess() {
        return repositoryService.createDeployment()
                .addClasspathResource("processes/leave-request.bpmn20.xml")
                .name("请假审批流程")
                .category("HR")
                .deploy();
    }
}

流程启动服务:

java 复制代码
@Service
@Transactional
public class LeaveProcessService {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private TaskService taskService;
    
    @Autowired
    private IdentityService identityService;
    
    /**
     * 启动请假流程
     */
    public ProcessInstance startLeaveProcess(String applicant, 
                                           String leaveType, 
                                           Long days, 
                                           String reason) {
        
        // 设置流程变量
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", applicant);
        variables.put("leaveType", leaveType);
        variables.put("days", days);
        variables.put("reason", reason);
        variables.put("startTime", new Date());
        
        // 启动流程实例
        return runtimeService.startProcessInstanceByKey("leaveRequest", variables);
    }
    
    /**
     * 提交请假申请
     */
    public void submitLeaveRequest(String taskId, String applicant) {
        // 认领任务
        taskService.claim(taskId, applicant);
        
        // 完成任务
        taskService.complete(taskId);
    }
    
    /**
     * 经理审批
     */
    public void managerApprove(String taskId, Boolean approved, String comment) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("managerApproved", approved.toString());
        variables.put("managerComment", comment);
        variables.put("approveTime", new Date());
        
        taskService.complete(taskId, variables);
    }
    
    /**
     * HR备案
     */
    public void hrRecord(String taskId) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("hrRecorded", "true");
        variables.put("recordTime", new Date());
        
        taskService.complete(taskId, variables);
    }
}

任务查询服务:

java 复制代码
@Service
public class TaskQueryService {
    
    @Autowired
    private TaskService taskService;
    
    @Autowired
    private HistoryService historyService;
    
    /**
     * 查询用户待办任务
     */
    public List<Task> getUserTasks(String userId) {
        return taskService.createTaskQuery()
                .taskAssignee(userId)
                .orderByTaskCreateTime().desc()
                .list();
    }
    
    /**
     * 查询组任务
     */
    public List<Task> getGroupTasks(String group) {
        return taskService.createTaskQuery()
                .taskCandidateGroup(group)
                .orderByTaskCreateTime().desc()
                .list();
    }
    
    /**
     * 查询流程历史
     */
    public List<HistoricProcessInstance> getProcessHistory(String applicant) {
        return historyService.createHistoricProcessInstanceQuery()
                .variableValueEquals("applicant", applicant)
                .orderByProcessInstanceStartTime().desc()
                .list();
    }
}
3.4 Spring Boot 控制器
java 复制代码
@RestController
@RequestMapping("/api/leave")
public class LeaveProcessController {
    
    @Autowired
    private LeaveProcessService leaveProcessService;
    
    @Autowired
    private TaskQueryService taskQueryService;
    
    @Autowired
    private ProcessDeploymentService deploymentService;
    
    /**
     * 部署流程
     */
    @PostMapping("/deploy")
    public ResponseEntity<String> deployProcess() {
        Deployment deployment = deploymentService.deployLeaveProcess();
        return ResponseEntity.ok("流程部署成功,ID: " + deployment.getId());
    }
    
    /**
     * 启动请假流程
     */
    @PostMapping("/start")
    public ResponseEntity<Map<String, Object>> startProcess(
            @RequestParam String applicant,
            @RequestParam String leaveType,
            @RequestParam Long days,
            @RequestParam String reason) {
        
        ProcessInstance instance = leaveProcessService
                .startLeaveProcess(applicant, leaveType, days, reason);
        
        Map<String, Object> result = new HashMap<>();
        result.put("processInstanceId", instance.getId());
        result.put("processDefinitionId", instance.getProcessDefinitionId());
        result.put("status", "流程已启动");
        
        return ResponseEntity.ok(result);
    }
    
    /**
     * 查询待办任务
     */
    @GetMapping("/tasks")
    public ResponseEntity<List<Map<String, Object>>> getTasks(
            @RequestParam(required = false) String userId,
            @RequestParam(required = false) String group) {
        
        List<Map<String, Object>> tasks = new ArrayList<>();
        
        if (userId != null) {
            taskQueryService.getUserTasks(userId).forEach(task -> {
                tasks.add(convertTaskToMap(task));
            });
        }
        
        if (group != null) {
            taskQueryService.getGroupTasks(group).forEach(task -> {
                tasks.add(convertTaskToMap(task));
            });
        }
        
        return ResponseEntity.ok(tasks);
    }
    
    /**
     * 完成任务
     */
    @PostMapping("/complete")
    public ResponseEntity<String> completeTask(
            @RequestParam String taskId,
            @RequestParam(required = false) String userId,
            @RequestParam(required = false) Boolean approved,
            @RequestParam(required = false) String comment) {
        
        if (userId != null) {
            // 提交申请
            leaveProcessService.submitLeaveRequest(taskId, userId);
        } else if (approved != null) {
            // 经理审批
            leaveProcessService.managerApprove(taskId, approved, comment);
        } else {
            // HR备案
            leaveProcessService.hrRecord(taskId);
        }
        
        return ResponseEntity.ok("任务完成");
    }
    
    private Map<String, Object> convertTaskToMap(Task task) {
        Map<String, Object> taskMap = new HashMap<>();
        taskMap.put("id", task.getId());
        taskMap.put("name", task.getName());
        taskMap.put("assignee", task.getAssignee());
        taskMap.put("createTime", task.getCreateTime());
        taskMap.put("processInstanceId", task.getProcessInstanceId());
        return taskMap;
    }
}
3.5 配置类
java 复制代码
@Configuration
@EnableTransactionManagement
public class FlowableConfig {
    
    @Bean
    public ProcessEngineConfiguration processEngineConfiguration(DataSource dataSource) {
        SpringProcessEngineConfiguration configuration = 
            new SpringProcessEngineConfiguration();
        
        configuration.setDataSource(dataSource);
        configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        configuration.setAsyncExecutorActivate(false);
        configuration.setHistoryLevel(HistoryLevel.AUDIT);
        
        return configuration;
    }
    
    @Bean
    public ProcessEngineFactoryBean processEngine(ProcessEngineConfiguration configuration) {
        ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean();
        factoryBean.setProcessEngineConfiguration(configuration);
        return factoryBean;
    }
    
    @Bean
    public RepositoryService repositoryService(ProcessEngine processEngine) {
        return processEngine.getRepositoryService();
    }
    
    @Bean
    public RuntimeService runtimeService(ProcessEngine processEngine) {
        return processEngine.getRuntimeService();
    }
    
    @Bean
    public TaskService taskService(ProcessEngine processEngine) {
        return processEngine.getTaskService();
    }
    
    @Bean
    public HistoryService historyService(ProcessEngine processEngine) {
        return processEngine.getHistoryService();
    }
    
    @Bean
    public IdentityService identityService(ProcessEngine processEngine) {
        return processEngine.getIdentityService();
    }
}
3.6 错误处理
java 复制代码
@Service
public class ProcessErrorHandler {
    
    @Autowired
    private ManagementService managementService;
    
    /**
     * 处理作业失败
     */
    public void handleFailedJob(String jobId) {
        managementService.moveTimerToExecutableJob(jobId);
        managementService.executeJob(jobId);
    }
    
    /**
     * 流程异常处理
     */
    public void handleProcessException(String processInstanceId, Exception e) {
        // 记录错误日志
        // 发送通知
        // 执行补偿操作
    }
}

四、流程监控和管理

4.1 使用 Flowable UI

Flowable 提供了基于 Spring Boot 的管理控制台:

xml 复制代码
<!-- 添加 UI 依赖 -->
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-admin</artifactId>
    <version>6.7.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-modeler</artifactId>
    <version>6.7.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-task</artifactId>
    <version>6.7.2</version>
</dependency>
4.2 自定义监控
java 复制代码
@Service
public class ProcessMonitorService {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private HistoryService historyService;
    
    /**
     * 获取流程实例状态
     */
    public Map<String, Object> getProcessInstanceStatus(String processInstanceId) {
        ProcessInstance instance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        
        HistoricProcessInstance historicInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        
        Map<String, Object> status = new HashMap<>();
        if (instance != null) {
            status.put("status", "RUNNING");
            status.put("currentActivities", runtimeService.getActiveActivityIds(processInstanceId));
        } else if (historicInstance != null) {
            status.put("status", historicInstance.getEndTime() != null ? "COMPLETED" : "SUSPENDED");
            status.put("endTime", historicInstance.getEndTime());
        }
        
        return status;
    }
}
相关推荐
刘逸潇20053 小时前
CSS基础语法
前端·css
奋斗的蛋黄4 小时前
网络卡顿运维排查方案:从客户端到服务器的全链路处理
运维·服务器·网络
鼓掌MVP4 小时前
Java框架的发展历程体现了软件工程思想的持续进化
java·spring·架构
吃饺子不吃馅4 小时前
[开源] 从零到一打造在线 PPT 编辑器:React + Zustand + Zundo
前端·svg·图形学
编程爱好者熊浪4 小时前
两次连接池泄露的BUG
java·数据库
lllsure4 小时前
【Spring Cloud】Spring Cloud Config
java·spring·spring cloud
wanhengidc5 小时前
云手机搬砖 尤弥尔传奇自动化操作
运维·服务器·arm开发·安全·智能手机·自动化
鬼火儿5 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
NON-JUDGMENTAL5 小时前
Tomcat 新手避坑指南:环境配置 + 启动问题 + 乱码解决全流程
java·tomcat