1. 为什么需要工作流引擎
第一次接触工作流引擎是在2015年,当时我负责一个OA系统的开发。客户要求实现复杂的请假审批流程:普通员工请假需要部门经理审批,超过3天还需要总监审批,而部门经理请假则直接由总监审批。如果用传统的if-else硬编码实现,代码会变得极其臃肿且难以维护。这时我发现了Activiti这个开源工作流引擎,它完美解决了我的痛点。
工作流引擎本质上是一个业务流程的调度中心。就像交通信号灯控制系统,它不关心具体车辆如何行驶,只负责在正确的时间点亮正确的信号灯。Activiti作为轻量级的工作流引擎,特别适合需要业务流程管理的企业应用场景。
提示:当你的业务中出现"如果...那么需要...审批"这类逻辑时,就是考虑引入工作流引擎的最佳时机。
2. Activiti核心架构解析
2.1 引擎内核设计
Activiti的核心就像汽车的发动机,由几个精密配合的组件构成:
- ProcessEngine:总控制台,通过它获取所有服务接口
- RepositoryService:管理流程定义(相当于图纸仓库)
- RuntimeService:处理运行中的流程实例(实际行驶的车辆)
- TaskService:管理待办任务(需要人工处理的检查点)
- HistoryService:记录历史数据(行车记录仪)
这种模块化设计使得每个组件各司其职。我在实际项目中经常这样初始化引擎:
java复制ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useSSL=false")
.setJdbcUsername("root")
.setJdbcPassword("password")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
.buildProcessEngine();
2.2 数据库表结构
Activiti使用23张表来支撑工作流运行,就像汽车的各个部件:
- ACT_RE_*:存储流程定义和资源(RE表示Repository)
- ACT_RU_*:运行时数据(RU表示Runtime),这些表最需要关注性能优化
- ACT_HI_*:历史数据(HI表示History),随着业务增长需要定期归档
- ACT_GE_*:通用数据(GE表示General)
我在性能调优时发现,ACT_RU_TASK和ACT_RU_EXECUTION是最频繁查询的表,建议为这两个表建立合适的索引。
3. 开发第一个工作流应用
3.1 流程设计器使用
Activiti Designer是Eclipse插件,就像流程的绘图板。设计请假流程时:
- 定义开始事件(请假申请)
- 添加用户任务(部门审批)
- 设置排他网关(判断天数)
- 连接后续审批节点
关键是要设置每个节点的Assignee(处理人),可以使用固定用户ID,更推荐使用表达式如${deptLeader},这样可以在运行时动态确定审批人。
3.2 部署与启动流程
部署流程就像把设计图存入档案室:
java复制RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/leave.bpmn20.xml")
.deploy();
启动流程实例则是根据图纸开始施工:
java复制RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<>();
variables.put("employee", "张三");
variables.put("days", 5);
ProcessInstance instance = runtimeService.startProcessInstanceByKey("leaveProcess", variables);
3.3 任务处理实战
审批人查询待办任务就像查看自己的待办清单:
java复制TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("李四")
.list();
完成任务时需要传递审批结果:
java复制taskService.complete(task.getId(), Collections.singletonMap("approved", true));
4. 高级特性与性能优化
4.1 异步执行与事务
Activiti的异步执行器(AsyncExecutor)就像后台工作的助手线程。对于耗时操作(如调用外部系统),建议设置为异步:
xml复制<serviceTask id="externalCall" activiti:async="true" activiti:class="com.example.ExternalServiceDelegate"/>
注意:异步任务需要配置activiti.cfg.xml中的asyncExecutorEnabled为true,并注意事务边界问题。
4.2 多实例与会签
部门全员投票的场景可以使用多实例:
xml复制<userTask id="teamVote" activiti:assignee="${member}">
<multiInstanceLoopCharacteristics
activiti:collection="${teamMembers}"
activiti:elementVariable="member"
isSequential="false">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
这个配置表示当60%的成员完成投票时,任务即视为完成。
4.3 性能调优经验
在高并发场景下,我总结出这些优化手段:
- 调整异步执行器线程池大小:
properties复制activiti.async.executor.thread.pool.size=10
- 启用执行器线程池队列:
properties复制activiti.async.executor.queue.size=100
- 历史日志级别设置为none(生产环境建议audit):
properties复制activiti.history.level=none
- 定期清理历史数据(特别是ACT_HI_TASKINST和ACT_HI_PROCINST)
5. 常见问题排查指南
5.1 流程定义版本冲突
当重复部署同名流程时,Activiti会自动生成新版本。但运行时如果未明确指定版本,默认会使用最新版本。这可能导致意外行为。解决方法:
java复制ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveProcess",
"businessKey123",
Collections.singletonMap("version", 2)
);
5.2 事务回滚处理
在Spring集成环境中,如果业务方法抛出异常导致事务回滚,但流程状态已更新,会造成不一致。解决方案是配置事务传播:
java复制@Transactional(propagation = Propagation.REQUIRES_NEW)
public void completeTask(String taskId) {
// 任务处理逻辑
}
5.3 内存泄漏预防
长时间运行的流程可能积累大量运行时数据。建议:
- 监控ACT_RU_*表的增长
- 对已完成流程及时调用runtimeService.deleteProcessInstance
- 设置合理的流程超时时间
6. 实际项目中的最佳实践
6.1 动态指派处理人
比起固定assignee,更灵活的方案是使用监听器:
java复制public class DynamicAssigneeListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
if ("create".equals(eventName)) {
String dept = (String) delegateTask.getVariable("department");
String leader = orgService.findLeaderByDept(dept);
delegateTask.setAssignee(leader);
}
}
}
6.2 流程版本升级策略
生产环境流程变更需要谨慎:
- 保持向后兼容的变量定义
- 使用versionTag标记重大变更
- 对新流程实例使用新版本
- 对运行中的旧实例提供迁移路径
6.3 监控与报警实现
我们团队开发的监控方案包括:
- 定时扫描长时间运行的任务
sql复制SELECT * FROM ACT_RU_TASK
WHERE CREATE_TIME_ < DATE_SUB(NOW(), INTERVAL 2 DAY)
- 使用Prometheus收集指标
- 对关键节点配置邮件提醒
7. 与其他技术的整合
7.1 Spring Boot集成
现代项目推荐使用activiti-spring-boot-starter:
properties复制# application.properties
spring.activiti.database-schema-update=true
spring.activiti.async-executor-activate=true
自动配置的ProcessEngine可以直接注入使用:
java复制@Autowired
private RuntimeService runtimeService;
7.2 微服务架构下的考量
在微服务环境中:
- 每个服务维护自己的工作流引擎实例
- 跨服务流程使用消息队列协调
- 考虑使用Activiti Cloud的Docker镜像
7.3 前端整合方案
推荐两种前端集成方式:
- 纯前端方案:使用bpmn-js渲染流程图
javascript复制const viewer = new BpmnJS({ container: '#canvas' });
viewer.importXML(xml).then(() => { viewer.get('canvas').zoom('fit-viewport') });
- 混合方案:后端通过REST API提供流程状态,前端组合展示
8. 测试策略与质量保障
8.1 单元测试框架
Activiti提供专门的测试支持:
java复制@RunWith(ActivitiRule.class)
public class ProcessTest {
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment(resources = {"processes/leave.bpmn20.xml"})
public void testApprovalFlow() {
// 测试代码
}
}
8.2 性能测试要点
使用JMeter测试时应关注:
- 流程启动吞吐量
- 任务查询响应时间
- 并行网关的并发处理能力
- 数据库连接池使用情况
8.3 生产环境检查清单
上线前必须验证:
- 数据库备份机制是否就绪
- 是否有监控仪表盘
- 是否配置了适当的告警阈值
- 文档是否包含回滚步骤
