ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot集成Activiti 7工作流引擎:从BPMN部署到审批流实战

SpringBoot集成Activiti 7工作流引擎:从BPMN部署到审批流实战

SpringBoot 集成 Activiti 7 这个事,我最近在一个审批流程项目里完整踩了一遍。项目不大,但偏偏选了 Activiti 7 这个"半新不旧"的版本,网上一搜全是 5.x 和 6.x 的老文章,7 的坑只能自己一点点填。这篇文章就把我从选型到落地的全过程写清楚,包括版本兼容、自动建表、BPMN 部署、任务流转,还有那几个搜索引擎翻半天也翻不到答案的坑。适合正在做审批流、工单流、合同流转,又刚好想在 SpringBoot 里集成工作流引擎的开发者参考。

1. 项目背景与选型:为什么是 Activiti 7

1.1 审批流到底要解决什么问题

很多业务系统做到一定阶段,就会冒出"审批"的需求:请假要走流程、报销要走流程、合同要多人会签、工单要按角色流转。最开始大家可能用状态机硬写,一个字段改来改去,分支一多代码就炸了。比如请假时长不同要不同的审批人,金额超过一定限制要加签,这些逻辑如果用 if else 堆在 Service 里,后续加一个审批节点就要动一坨代码,测试压力巨大。

工作流引擎解决的就是这个问题:把流程定义从代码里抽出来,放到 BPMN 文件里,由引擎统一管理流程实例的推进、任务的分配、历史的记录。业务代码只关心"发起流程""查询待办""审批通过/驳回"这几个动作,至于流程怎么走、走到哪一步、能不能走分支,全部交给引擎。这么一来,流程变更基本不需要改 Java 代码,改完 BPMN 重新部署就行,对开发和业务来说都是实打实的降负担。

1.2 几个主流引擎的对比

选型的时候我主要看了三个:Activiti、Flowable、Camunda。这三个同源,代码里很多 API 长得几乎一模一样,但定位上已经分了家。

Flowable 是从 Activiti 5 分支出来的,社区活跃,文档也比较全,但它的版本升级节奏快,SpringBoot 2 和 SpringBoot 3 的兼容性要小心确认。Camunda 在流程可视化、运维监控上做得最重,平台化能力强,适合大团队大平台,但对一个中小型项目来说有点"杀鸡用牛刀",部署产物也偏重。Activiti 7 是 Activiti 团队在 6 之后重新梳理的项目结构,把很多模块拆分重构了,虽然资料少,但如果你原本就是 Activiti 系的思路,7 的 API 延续性还算友好。

综合下来,我这个项目不需要 Camunda 那么重的 Cockpit 监控界面,Flowable 虽然稳但迁移成本没优势,最终选了 Activiti 7。这里也建议读者别盲目抄作业,要根据团队熟悉度和项目复杂度来定。

1.3 Activiti 7 的"变"与"不变"

既然是 7,就要先搞清楚它跟老版本差在哪。最直观的变化是包名和模块结构:老的 Activiti 5/6 里很多内部实现被重新组织,7 的引擎更强调"可嵌入、可配置"。你在代码里常打交道的还是那几个 Service:RepositoryService 管流程定义,RuntimeService 管流程实例,TaskService 管任务,HistoryService 管历史。这一点跟 5/6 是一脉相承的,老经验大部分能复用。

但有一个很关键的变化:7 对 IdentityService 的定位弱化了。老版本里有一张 ACT_ID_USER 之类的用户表,可以直接往里面塞用户做人员管理。到了 7,官方更希望你把用户体系留在业务系统里,流程引擎只认用户 ID 字符串,不负责认证和权限。这一点从一开始就要想清楚,不要指望工作流引擎帮你管登录用户。

2. 基础环境与依赖集成

2.1 版本兼容性是第一道坎

Activiti 7 对 SpringBoot 版本有明确要求。我这里用的是 SpringBoot 2.7.x 配 Activiti 7.1.0.M6,这个组合实测比较稳。如果你拿 SpringBoot 3 去配 Activiti 7,启动大概率会报各种 Javax 到 Jakarta 的迁移错误,因为 Activiti 7 内部还在用 javax 命名空间,适配 SpringBoot 3 是后面版本才做的事。所以版本选择上不要贪新,先保证能跑起来。

组合方案结论
SpringBoot 2.3.x + Activiti 7.1.0.M6兼容,可运行
SpringBoot 2.7.x + Activiti 7.1.0.M6我实测的组合,稳定
SpringBoot 2.7.x + Activiti 7.0.0.GA兼容,但 7.1 修复了一些问题
SpringBoot 3.x + Activiti 7.x不推荐,启动容易踩坑

这里多说一句,Maven 坐标要注意:7 的 starter 还是activiti-spring-boot-starter,但版本要写 7.1.0.M6 或 7.0.0.GA。别看到"M"就以为是测试版不能上生产,Activiti 官方对 7 的里程碑版本维护得还可以,很多项目实际就是跑在 M 版本上的。

2.2 引入依赖与起步配置

如果你用的是 Maven,直接在 pom 里加这一段:

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency>

然后配置文件里加这几项,我是放在 application.yml 里的:

spring: datasource: url: jdbc:mysql://localhost:3306/workflow?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver activiti: database-schema-update: true history-level: full db-history-used: true check-process-definitions: true

database-schema-update: true的意思是让引擎启动时自动检查并创建 Activiti 自己的表,这对第一次跑通非常关键。history-level: full则是把历史数据完整记录,包括流程变量、任务操作记录,虽然占空间多一点,但后续排查流程问题全靠它。check-process-definitions: true配合后面的classpath:/processes/目录,SpringBoot 启动时会自动部署 BPMN 文件。

注意一点:数据库要预先建好,Activiti 不会帮你创建 database,只会创建表。我用的是 MySQL 8.0,如果你的数据库版本太老,比如 MySQL 5.5,可能会有兼容问题,建议至少 5.7 以上。

2.3 数据源与 Activiti 表的初始化策略

Activiti 7 默认会使用项目里的主数据源。如果你的 SpringBoot 项目只有一个数据源,直接把spring.datasource配好就行,引擎启动后会自动建表。它建的表大致分这几类:通用表以 ACT_GE_ 开头,流程定义和部署相关以 ACT_RE_ 开头,运行时数据以 ACT_RU_ 开头,历史数据以 ACT_HI_ 开头,用户和组相关以 ACT_ID_ 开头。

我刚跑起来的时候,第一次启动日志里会看到一大堆建表语句,这是正常的。第二次启动就不会重复建了,因为 ACT_GE_PROPERTY 表里存了数据库版本信息。

如果你是多数据源项目,就要小心了。Activiti 的自动配置会尝试找到@Primary的数据源来做引擎数据源,如果业务系统有多个库,最好给流程引擎单独配一个专用库,不要让流程表和业务表混在同一个库里。混在同一个库的问题在于:流程表里很多运行时数据需要频繁读写,一旦业务量大,会拖累业务查询性能,而且后续迁移、备份都得连带着处理一堆流程表,维护成本高。

3. 流程定义与部署

3.1 第一个 BPMN 文件:请假流程

Activiti 的流程定义是标准的 BPMN 2.0 文件,后缀通常是.bpmn或者.bpmn20.xml。我第一个拿来做验证的是最简单的请假流程:发起 → 主管审批 → 结束。这个文件放在src/main/resources/processes/目录下。

<?xml version="1.0" encoding="UTF-8"?> <definitions id="definitions" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="http://workflow.example.com/process"> <process id="leaveProcess" name="请假流程" isExecutable="true"> <startEvent id="startEvent" name="开始"/> <userTask id="leaderApprove" name="主管审批" activiti:assignee="${applyUser}"/> <endEvent id="endEvent" name="结束"/> <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="leaderApprove"/> <sequenceFlow id="flow2" sourceRef="leaderApprove" targetRef="endEvent"/> </process> </definitions>

这里我解释几个关键点。process标签的id是流程 key,启动流程时要用到,比如runtimeService.startProcessInstanceByKey("leaveProcess")就是根据这个 id 找到对应流程定义。userTask代表人工审批节点,activiti:assignee="${applyUser}"表示这个任务分配给谁,${applyUser}是一个表达式,启动流程时通过流程变量传进来。也就是说,谁发起请假,主管审批任务就派给谁的直接主管。

刚开始写 BPMN 文件,最容易犯的错是忘记加xmlns:activiti这个命名空间。不加的话,activiti:assignee会解析失败,部署的时候直接报错。另一个坑是isExecutable="true"不能漏,漏了启动流程时引擎会认为它不是可执行流程,抛异常。

3.2 部署方式与验证

Activiti 7 在 SpringBoot 里部署流程定义有两种常见方式。第一种就是我最开始用的自动部署:把 BPMN 文件丢到classpath:/processes/下,启动时自动注册。这个方案适合开发阶段快速验证,但有个隐患:如果同一个流程 key 你改完 BPMN 又重启应用,引擎会生成新的流程定义版本,旧版本并不会被删掉。版本管理是 Activiti 的设计,不算是 bug,但你要明白"新流程从新版本开始,旧流程实例还在旧版本上跑"这个规则。

第二种是手动部署,适合做流程管理后台功能,比如页面上传 BPMN 文件然后调用部署接口:

@Autowired private RepositoryService repositoryService; public void deployProcess(String bpmnPath) { Deployment deployment = repositoryService.createDeployment() .addClasspathResource(bpmnPath) .name("请假流程") .deploy(); System.out.println("部署 ID:" + deployment.getId()); }

RepositoryService是流程定义和部署的管理入口。通过createDeployment()可以加载 classpath 里的 BPMN 文件,也可以换成一个 InputStream 来加载上传的 XML 内容,做在线部署解锁后台上传功能很方便。

验证部署是否成功,可以从数据库看 ACT_RE_PROCDEF 表,里面的 KEY_ 列就是流程 key,VERSION_ 列是版本号。也可以用代码验证:

@Autowired private RepositoryService repositoryService; public long countProcessDefinitions() { return repositoryService.createProcessDefinitionQuery() .processDefinitionKey("leaveProcess") .count(); }

返回结果大于 0,就说明部署成功了。

3.3 流程定义的管理与版本

版本这块多说一点。我第一次部署 leaveProcess 是 V1,改了一次 BPMN 再次部署就变成 V2 了。ACT_RE_PROCDEF 表里两个版本都会在,但默认启动流程用的都是最新版本。如果你在流程管理后台想指定版本部署,可以在 query 里加上.processDefinitionVersion(1),然后把 deploymentId 找出来。

这里有一个很隐蔽的坑:如果业务系统里已经有几十个正在跑的旧流程实例,你更新了流程定义后,老实例还是按照部署时的旧版本 XML 继续跑,直到结束。所以流程变更要么接受这种"新流程走新规则"的模式,要么就要处理老实例的迁移,比如人工干预或者流程跳转。这个在设计阶段要提前和业务方讲清楚,否则上线后改一次流程,被问"为什么老的单子还是旧流程",就解释不清楚了。

4. 运行时流程操作:从启动到审批

4.1 发起流程实例与业务绑定

流程定义部署好了,接下来就是业务侧发起流程。这里有一个核心概念要理清:流程定义像是模板,流程实例才是具体的一单审批。每次发起请假,引擎会创建一个新的流程实例,相当于把模板实例化一份。

发起流程的代码很简单,但有几个细节值得注意:

@Autowired private RuntimeService runtimeService; public String startLeaveProcess(String applyUserId, String businessKey, Double days) { Map<String, Object> variables = new HashMap<>(); variables.put("applyUser", applyUserId); variables.put("days", days); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "leaveProcess", businessKey, variables ); return processInstance.getId(); }

startProcessInstanceByKey有三个参数:流程 key、业务 key、流程变量。业务 key 非常建议传上去,它是把流程实例和业务数据关联起来的关键。比如你的请假表单在业务表里主键是 BIZ-2024-001,传到 businessKey 里之后,随时可以通过runtimeService.createProcessInstanceQuery().processInstanceBusinessKey("BIZ-2024-001").singleResult()反查流程实例,这在做流程跟踪页面时非常有用。

流程变量是流程引擎流转时的"上下文数据"。BPMN 里的条件表达式、任务分配表达式、网关判断,用的都是这些变量。传变量的时候要注意类型匹配,金额判断就要传数字,别传字符串 "5000",否则比较大小会出事。

4.2 待办查询与任务审批

流程实例启动后,引擎会自动推进到第一个 userTask,也就是上面例子里的 leaderApprove。这个任务会出现在待办表里,对应的数据库表是 ACT_RU_TASK。查询待办一般用 TaskService:

@Autowired private TaskService taskService; public List<Task> queryTodoTasks(String assignee) { return taskService.createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime() .desc() .list(); }

这里taskAssignee(assignee)是查询分配给指定人的任务。如果你做的是组任务,比如有多个候选人都可以处理,那么查询方式会变成taskCandidateUser,这里先不展开,后面扩展部分再说。

审批动作其实就是完成任务:

public void completeTask(String taskId, boolean approved, String comment) { Map<String, Object> variables = new HashMap<>(); variables.put("approved", approved); variables.put("comment", comment); taskService.complete(taskId, variables); }

complete方法是引擎推进流程的核心。调用之后,当前任务结束,引擎会根据 BPMN 的 sequenceFlow 找到下一个节点。如果是 userTask,就会生成新的待办;如果是 endEvent,流程实例就结束,运行时表里的数据会被清理,相关信息进入历史表。

我在实际项目中踩过一个权限的坑:单纯用taskService.complete(taskId, variables)是不校验操作人身份的,拿到 taskId 就能完成任务。所以业务层必须先查一次任务归属,确认当前登录用户确实是 assignee,再调 complete,否则就是越权审批。这种校验代码一定要写在 service 层,别指望框架帮你做。

4.3 排他网关与条件判断

有了基础审批以后,很快就会有分支需求。比如请假天数超过 3 天需要部门总监审批,3 天以内主管审批就够了。BPMN 里实现这个分支最常用的就是排他网关(Exclusive Gateway)。

<exclusiveGateway id="approvalGateway" name="审批层级判断"/> <sequenceFlow id="flowBranchHigh" sourceRef="approvalGateway" targetRef="directorApprove"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${days > 3}]]> </conditionExpression> </sequenceFlow> <sequenceFlow id="flowBranchLow" sourceRef="approvalGateway" targetRef="endEvent"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${days <= 3}]]> </conditionExpression> </sequenceFlow>

排他网关是"多选一"的逻辑,引擎从上到下检查每一条出线的条件表达式,遇到第一个为 true 的就走那条线。如果所有条件都不满足,默认走没有条件的普通连线;如果也没有普通连线,流程就会抛出异常。

写条件表达式的时候,最容易翻车的是变量没传或者变量名拼错。比如启动流程时变量叫day,BPMN 里判断days,那表达式结果是 false,流程整个卡住不动,还不给你明显报错,只能去 ACT_RU_VARIABLE 表里一个一个对变量名。我建议在流程变量命名上定一个前缀规范,比如所有业务参数统一叫form_开头,至少能避免一半的拼写错误。

4.4 流程历史与状态追踪

流程跑起来之后,业务方一定会在某个列表页想看到"这个单子现在到哪一步了""谁批的""批没批"。Activiti 的 HistoryService 就是干这个的。开启了history-level: full之后,所有环节数据会落到历史表里。

@Autowired private HistoryService historyService; public List<HistoricTaskInstance> getProcessHistory(String processInstanceId) { return historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricTaskInstanceEndTime() .asc() .list(); }

这个查询结果里包含每个节点的名称、办理人、开始时间、结束时间。如果任务还没处理,endTime为空,说明流程当前停在这个节点上。把列表渲染成时间线,就是一个非常直观的审批进度图。

另一个常用查询是判断流程是否还在运行:

public boolean isProcessRunning(String processInstanceId) { ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); return processInstance != null; }

流程实例结束后,ACT_RU_* 表里对应的记录会消失,但 ACT_HI_* 表里会永久保留。所以判断流程是否结束不能查 RuntimeService,要直接看历史表的 deleteTime。

5. 常见问题与排查实录

5.1 ACT 表没有自动创建

这是我第一次启动遇到的头号问题。配置都写好了,项目也起来了,然后一调用流程接口就报Table 'act_ge_property' doesn't exist。原因通常有三个:第一,spring.activiti.database-schema-update没设成 true 或者被其他配置覆盖了;第二,数据库连接指向的库不对,你看错了库;第三,数据库账号权限不够,建表语句被拒绝了。

排查思路很简单:看启动日志。正常的情况下,启动日志里能看到多张 ACT_ 前缀的表格创建记录。如果一条都没有,优先检查配置项是不是生效了。这里有个小技巧,可以在启动类临时打印一下配置值确认:

@Value("${spring.activiti.database-schema-update}") private String schemaUpdate; @PostConstruct public void printConfig() { System.out.println("schemaUpdate = " + schemaUpdate); }

还有一类情况是 MySQL 配置了lower_case_table_names=1,所有表名都会变成小写,这在 Linux 环境下比较多见。Activiti 本身对大小写不敏感,但如果你有报表系统直接查 ACT_ 大写表名,可能就会查不到。这种问题通常不是引擎的锅,是周边系统对接时的表名大小写假设出了问题。

5.2 流程文件没被部署

BPMN 文件放在resources/processes/下,启动时完全没反应,流程定义数量一直为 0。这个问题的根源在于spring.activiti.check-process-definitions默认行为和你实际的 classpath 结构不一致。

排查分三步:先确认 BPMN 文件是否真的在编译后的 classes 目录里。有时候 IDE 的 build 配置把 resources 目录漏掉了,文件根本没进 classpath。再确认文件名后缀,Activiti 默认扫描的是.bpmn和.bpmn20.xml,如果你命名为.xml,它默认不识别,除非你额外配置process-definition-location-prefix和process-definition-location-suffixes。最后确认 BPMN 文件本身是否合法,可以用repositoryService.createDeployment().addClasspathResource("processes/leaveProcess.bpmn").deploy()手动部署一次,如果手动能成功自动没反应,就说明是扫描路径问题。

我这里也建议,不要在自动部署里依赖"顺手看一下"这种心态,每次开发新流程,都要写一个测试用例去断言repositoryService.createProcessDefinitionQuery().count()有值,把这个验证自动化,比盯着日志靠谱得多。

5.3 与 MyBatis Plus 的冲突处理

国内项目用 MyBatis Plus 的比例非常高,而 Activiti 7 内部又自带了 MyBatis。如果项目里已经引入了mybatis-plus-boot-starter,再引入 Activiti 的 starter,启动时可能会出现奇怪的 mapper 扫描冲突、SqlSessionFactory 被覆盖等问题。

我遇到的典型报错是某个org.apache.ibatis.binding.BindingException: Invalid bound statement。排查之后发现是 Activiti 内部的 mapper 被打包进了同一个 SqlSessionFactory,但配置上没被正确识别。

处理方式有两种,看你的项目情况。如果业务系统没有复杂到必须 MyBatis Plus,可以考虑不加 MyBatis Plus,直接用JdbcTemplate或者只保留 Activiti 自带的 MyBatis。但大多数项目不可能为了工作流拆掉业务持久层,所以更推荐第二种:把 Activiti 的数据源和 MyBatis 配置隔离。Activiti 的 starter 对 MyBatis 的依赖是间接的,如果确实冲突,可以在依赖里排除掉 Activiti 自带的mybatis传递依赖,再用排除后的方式把引擎数据源独立出来。不过这种方式要小心,Activiti 引擎本身运行依赖 MyBatis,排除后容易引发引擎内部找不到 mapper 的问题。

如果你用的是 MyBatis Plus,并且项目里有多数据源,最简单的方案是引入 MyBatis Plus 官方提供的多数据源组件,给 Activiti 的路由单独指定数据源,同时明确 MyBatis Plus 的@MapperScan扫描范围,不要让引擎的 mapper 混进去。这样两个 MyBatis 就各管各的库,互不干扰。

5.4 多数据源场景下引擎连错库

前面提过多数据源的问题,这里单独展开。常见的多数据源实现有两种:一种是 Spring 原生DataSource加@Primary,第二种是动态数据源框架。无论哪种,Activiti 默认都会尝试拿容器里的主数据源来创建引擎数据源。如果你的主数据源是业务库,而业务流程库是另一个,启动时 Activiti 直接把流程表建到业务库里了,后续流程数据全混在业务库里,这就是连错库了。

解决思路是给流程引擎单独设数据源。 Activiti 7 里可以通过自定义ProcessEngineConfiguration实现,用代码创建一个独立的配置对象,指定数据源和表策略:

@Bean public ProcessEngineConfiguration processEngineConfiguration(DataSource activitiDataSource) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(activitiDataSource); config.setDatabaseSchemaUpdate(ActivitiProperties.DATABASE_SCHEMA_UPDATE_TRUE); config.setHistoryLevel(HistoryLevel.FULL.getLevel()); config.setProcessEngineName("workflowEngine"); return config; } @Bean public ProcessEngine processEngine(ProcessEngineConfiguration processEngineConfiguration) { return processEngineConfiguration.buildProcessEngine(); }

需要注意,一旦手动创建了ProcessEngineConfiguration,你就接管了 Activiti 的初始化逻辑,SpringBoot 自动配置里的很多属性可能不再生效。你需要把database-schema-update、history-level这些参数都在 bean 里手动指定,否则默认值可能不是你想要的。我建议这种方案只用于确实存在多数据源的项目,单数据源项目就让 starter 自动配置,千万别为了展示技术复杂度去手动接管引擎初始化。

5.5 变量类型与 JSON 序列化问题

流程变量传输时,数类型和序列化值得留意。默认情况下,Activiti 会把 Java 对象序列化后存到数据库的字节数组字段里。如果你在流程变量里塞了一个自定义对象,而该对象没有实现Serializable,启动流程时就会报序列化异常。

更稳妥的做法是尽量用基本类型和 String、List、Map 这些能天然 JSON 化的结构。如果你一定要塞对象,建议用 JSON 字符串传。比如把整个表单数据转成JSONObject.toString(),存成一个 String 变量,使用的时候再反序列化。这样流程表里存的东西人类可读,排查问题也不用去解析二进制流。

我在项目里就吃过这个亏。最开始图省事,直接把一个表单对象塞进去了,升级代码后对象里的字段变了,导致老的历史流程实例在查询时反序列化失败,直接抛异常。后来把所有变量改成基本类型加 JSON 字符串,这个问题再没出现过。

6. 实操心得与后续扩展

6.1 几个常规文档不会写的经验

跑通整个链路之后,我总结了几条比较实用的经验,不一定写进官方文档里,但实际项目里非常重要。

第一,流程引擎初始化只能跑一次。如果你在一个 JVM 里同时启动多个 Spring 上下文,比如测试环境同时起了多个配置文件,就可能出现多个 ProcessEngine,而它们各自可能维护不同的表数据。这个不是大毛病,但容易造成"为什么我改了数据库,查询还是旧数据"的困惑。

第二,业务表和流程表不要强耦合。Activiti 的表结构有自己的生命周期管理,你不应该去直接修改 ACT_ 前缀的表结构,更不要往里面加业务字段。业务数据留在自己的库表里,通过 businessKey 关联,这样才能保证引擎升级时你的业务代码不受影响。

第三,待办任务查询要控制范围。如果你做的是集团级系统,所有人待办直接taskService.createTaskQuery().list(),数据量上来后性能会很难看。一定要配合候选人、候选组、业务分类来查询,不要让用户侧接口全表扫描。真到了大数据量,可以考虑定时把运行时任务数据同步到 Elasticsearch,但这个就属于大型架构方案了,一般项目用不上。

6.2 下一步可以扩展的方向

如果你的项目跑通了基础的部署、启动、审批三个环节,后面的路基本就顺畅了。比较值得扩展的方向有:会签和或签,也就是多实例任务,用 BPMN 的multiInstanceLoopCharacteristics节点实现多人审批;子流程,把公共的审批逻辑抽出来复用;驳回和转办,这些可能需要自己维护节点的跳转逻辑,Activiti 7 提供了runtimeService.createChangeActivityStateBuilder()接口,可以实现流程实例的任意节点跳转。

我个人做下来最深的体会是,工作流引擎的学习曲线其实不在 API 上,而在 BPMN 建模思维上。API 就那几个 Service,翻一遍文档就能写,但怎么把一个业务场景抽象成节点和连线,怎么用网关表达复杂的流转条件,这才是真正需要花时间琢磨的地方。建议你先拿一个小流程练手,跑通了再往复杂里加,别一上来就想做个万能审批平台,那样大概率会被复杂的 BPMN 文件困住。

如果你现在做的就是 SpringBoot 集成 Activiti 7 的项目,我的建议是:第一版把"部署、启动、审批、历史查询"这四件事做得足够扎实,就足够应付大多数审批场景了。后面再加多人会签、驳回、跳转这些高级能力的时候,你会发现基础打好了一切都好说。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进