1. 项目概述:Activiti工作流引擎在企业级应用中的实战集成
在企业管理软件领域,OA、ERP、CRM等系统都离不开审批流程的支撑。传统硬编码的审批逻辑不仅开发效率低下,面对流程变更时更是需要反复修改代码。这正是Activiti这类工作流引擎大显身手的地方——通过可视化流程设计将业务逻辑与流程控制解耦。最近我在一个SpringBoot+Vue的前后端分离项目中完整实现了Activiti工作流集成,从流程建模到审批结束的全生命周期管理,下面就把这套经过实战检验的解决方案分享给大家。
这个项目的核心价值在于:提供了一套开箱即用的工作流中间件,包含在线流程设计器、灵活的任务分配策略、完备的审批操作(同意/驳回/委托/撤回)以及实时的流程监控。特别适合需要快速构建审批系统的开发团队,直接集成到现有业务系统中即可实现复杂的流程管控,而无需从零开始研究Activiti的API体系。我们以请假审批为例,演示了条件分支(30岁上下走不同审批路径)、多级审批、消息通知等典型场景,这些模式可以复用到采购、报销等各种业务流程中。
2. 技术架构与核心组件
2.1 整体技术栈设计
项目采用现在主流的前后端分离架构,技术选型上追求稳定性与开发效率的平衡:
- 后端:Spring Boot 2.7 + Activiti 7 + MyBatis Plus
- 前端:Vue 3 + Element Plus + bpmn-js(流程设计器)
- 数据库:MySQL 8.0(流程数据与业务数据同库)
- 消息队列:RabbitMQ(用于解耦审批消息通知)
这种组合既保证了核心流程引擎的稳定性(Activiti经过多年企业级验证),又利用Spring Boot的自动配置特性简化了集成难度。前端选用bpmn-js这个专业的BPMN 2.0渲染库,使得流程设计器能达到与专业工具媲美的用户体验。
2.2 关键模块划分
系统主要包含四大核心模块:
- 流程建模中心:提供基于BPMN 2.0标准的可视化流程设计器,支持网关、事件等元素拖拽配置
- 流程运行时服务:处理流程实例的启动、任务分配、审批动作执行等运行时逻辑
- 审批任务门户:统一处理待办任务、已办任务、委托任务等审批操作入口
- 监控分析台:实时展示流程图进度、审批耗时统计等监控指标
提示:在实际部署时,建议将Activiti的引擎服务单独打包为JAR,通过Feign接口供业务系统调用。这种微服务化的架构更利于工作流功能的复用。
3. 流程建模实战详解
3.1 在线流程设计器集成
Activiti原生的流程设计器体验较为简陋,我们采用bpmn-js这个专业的前端库实现了更强大的设计器功能。关键集成步骤包括:
- 在Vue项目中安装依赖:
bash复制npm install bpmn-js bpmn-js-properties-panel
- 初始化设计器容器(核心代码片段):
javascript复制import BpmnModeler from 'bpmn-js/lib/Modeler';
export default {
mounted() {
this.modeler = new BpmnModeler({
container: '#bpmn-container',
propertiesPanel: {
parent: '#properties-panel'
},
additionalModules: [ customModule ] // 自定义扩展模块
});
this.loadSampleDiagram();
},
methods: {
async loadSampleDiagram() {
const response = await fetch('/template/leave-process.bpmn');
const xml = await response.text();
await this.modeler.importXML(xml);
}
}
}
- 后端需要提供BPMN文件的保存接口:
java复制@PostMapping("/model/save")
public Result saveModel(@RequestParam String xml,
@RequestParam String modelId) {
repositoryService.saveModel(modelId, xml.getBytes());
return Result.success();
}
3.2 条件分支流程配置
在请假审批示例中,我们设计了按年龄分流的条件分支:
- 在流程图中添加排他网关(Exclusive Gateway)
- 为每个出口连线设置条件表达式:
- 30岁以下路径:
${age <= 30} - 30岁以上路径:
${age > 30}
- 30岁以下路径:
- 条件表达式支持EL语法,可以引用流程变量

注意事项:条件表达式中的变量需要在流程启动时通过
runtimeService.startProcessInstanceWithVariables方法传入,否则会抛出异常。建议对可能为null的变量设置默认值。
4. 流程运行时核心实现
4.1 动态任务分配策略
Activiti最强大的特性之一是支持灵活的任务分配方式。我们通过扩展TaskListener接口实现了多种分配策略:
java复制public class DynamicAssigneeListener implements TaskListener {
@Override
public void notify(DelegateTask task) {
String assigneeType = (String) task.getVariable("assigneeType");
switch (assigneeType) {
case "role":
String roleCode = (String) task.getVariable("roleCode");
List<User> users = userService.findByRole(roleCode);
task.addCandidateUsers(users);
break;
case "deptLeader":
String deptId = (String) task.getVariable("deptId");
User leader = deptService.getLeader(deptId);
task.setAssignee(leader.getId());
break;
// 其他分配策略...
}
}
}
在流程图中对应的XML配置:
xml复制<userTask id="leaderApproval" name="部门领导审批">
<extensionElements>
<activiti:taskListener event="create"
class="com.workflow.listener.DynamicAssigneeListener"/>
</extensionElements>
</userTask>
4.2 审批动作的完整实现
以最常见的"同意/驳回"操作为例,后端接口需要处理以下逻辑:
java复制@PostMapping("/complete")
public Result completeTask(@RequestBody ApproveDTO dto) {
// 1. 校验任务状态
Task task = taskService.createTaskQuery()
.taskId(dto.getTaskId())
.singleResult();
if (task == null) {
throw new BusinessException("任务不存在或已完成");
}
// 2. 处理审批意见
Comment comment = taskService.addComment(
dto.getTaskId(),
task.getProcessInstanceId(),
dto.getComment());
// 3. 设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("approveResult", dto.getResult());
variables.put("operator", SecurityUtils.getCurrentUserId());
// 4. 完成审批动作
if ("reject".equals(dto.getResult())) {
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdTo(task.getTaskDefinitionKey(), dto.getTargetNodeId())
.changeState();
} else {
taskService.complete(dto.getTaskId(), variables);
}
// 5. 发送通知
sendApproveNotification(task, dto.getResult());
return Result.success();
}
关键点说明:
- 驳回操作使用
ChangeActivityStateBuilder实现跳转到历史节点 - 审批意见通过
addComment方法持久化到ACT_HI_COMMENT表 - 流程变量会决定后续分支走向
5. 深度扩展与性能优化
5.1 与业务表单的无缝集成
工作流需要与业务表单深度结合,我们设计了通用绑定方案:
- 表单设计时添加
processDefinitionKey字段关联流程 - 流程启动时自动绑定业务ID:
java复制ProcessInstance instance = runtimeService.startProcessInstanceByKey(
form.getProcessDefinitionKey(),
form.getId().toString(),
variables);
form.setProcessInstanceId(instance.getId());
formMapper.updateById(form);
- 通过历史服务查询业务数据:
java复制HistoricProcessInstance instance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(form.getProcessInstanceId())
.singleResult();
Map<String, Object> processData = runtimeService.getVariables(instance.getId());
5.2 高并发场景下的优化策略
当流程实例数量达到万级时,需要注意以下性能瓶颈:
- 历史数据归档:配置异步执行器定期清理历史数据
properties复制# 启用异步历史清理
activiti.async-history.enabled=true
activiti.async-history.cycle-time=86400 # 每天执行
activiti.async-history.cleanup-age=2592000 # 保留30天数据
-
数据库优化:
- 为ACT_RU_TASK表的PROC_INST_ID_字段添加索引
- 分库分表策略:将ACT_HI_*历史表单独存放
-
缓存策略:
java复制@Cacheable(value = "processDefinition",
key = "#processDefinitionId")
public ProcessDefinition getDefinition(String processDefinitionId) {
return repositoryService.getProcessDefinition(processDefinitionId);
}
6. 常见问题排查指南
6.1 流程无法启动的典型原因
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 报错"no processes deployed with key" | 流程定义未部署或KEY不匹配 | 检查repositoryService.createDeployment().key()设置 |
| 启动时报空指针异常 | 必需的流程变量未传入 | 检查startProcessInstanceWithVariables调用 |
| 网关条件不生效 | 条件表达式语法错误 | 使用runtimeService.getVariable检查实际变量值 |
6.2 任务分配失败的排查步骤
- 检查ACT_RU_IDENTITYLINK表是否有对应任务记录
- 验证TaskListener是否被正确触发(添加日志)
- 查看候选人的用户组设置(ACT_ID_MEMBERSHIP)
- 检查任务候选人查询SQL:
sql复制SELECT * FROM ACT_RU_IDENTITYLINK
WHERE TASK_ID_ = '任务ID' AND TYPE_ = 'candidate'
6.3 历史数据查询优化建议
对于复杂的历史查询,建议直接使用原生SQL代替Activiti的API:
java复制String sql = "SELECT hi.* FROM ACT_HI_TASKINST hi " +
"JOIN ACT_HI_PROCINST pi ON hi.PROC_INST_ID_ = pi.ID_ " +
"WHERE pi.BUSINESS_KEY_ = :businessKey";
List<HistoricTaskInstance> tasks = historyService.createNativeHistoricTaskInstanceQuery()
.sql(sql)
.parameter("businessKey", "业务ID")
.list();
7. 消息通知与系统集成
7.1 多通道消息通知实现
通过策略模式封装不同通知方式:
java复制public interface NotifyHandler {
void send(Message message);
}
@Service
public class EmailNotifyHandler implements NotifyHandler {
@Override
public void send(Message message) {
// 实现邮件发送逻辑
}
}
@Service
@ConditionalOnProperty(name = "notify.dingtalk.enabled", havingValue = "true")
public class DingTalkNotifyHandler implements NotifyHandler {
@Override
public void send(Message message) {
// 调用钉钉机器人API
}
}
在审批完成时自动触发:
java复制private void sendApproveNotification(Task task, String result) {
Message message = new Message();
message.setType(taskService.getVariable(task.getId(), "notifyType"));
message.setContent("您的审批请求已" + ("agree".equals(result) ? "通过" : "驳回"));
notifyHandlerFactory.getHandler(message.getType())
.send(message);
}
7.2 与企业微信的深度集成
除了简单的消息推送,还可以实现:
- 审批动作直接在企业微信完成
- 通过企业微信获取审批人信息
- 同步组织架构到ACT_ID_*表
关键集成代码:
java复制@GetMapping("/wxwork/callback")
public String wxworkCallback(
@RequestParam("code") String code,
@RequestParam("state") String state) {
// 1. 通过code获取用户身份
WxWorkUser user = wxWorkService.getUserInfo(code);
// 2. 查询用户待办任务
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateOrAssigned(user.getUserId())
.list();
// 3. 返回任务列表给前端
return "redirect:/task/list?userId=" + user.getUserId();
}
8. 部署与运维实践
8.1 生产环境部署要点
- 数据库配置:
yaml复制spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
maximum-pool-size: 20
idle-timeout: 30000
activiti:
database-schema-update: false # 生产环境必须关闭自动更新
- 线程池调优:
java复制@Bean
public SpringAsyncExecutor springAsyncExecutor() {
SpringAsyncExecutor executor = new SpringAsyncExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueSize(100);
return executor;
}
8.2 监控指标暴露
通过Actuator暴露关键指标:
java复制@Endpoint(id = "activiti")
public class ActivitiMetricsEndpoint {
@ReadOperation
public Map<String, Object> metrics() {
Map<String, Object> metrics = new HashMap<>();
metrics.put("runningInstances",
runtimeService.createProcessInstanceQuery().count());
metrics.put("activeTasks",
taskService.createTaskQuery().active().count());
return metrics;
}
}
配置Prometheus监控:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,activiti
metrics:
export:
prometheus:
enabled: true
9. 项目二次开发建议
对于需要深度定制的场景,可以考虑以下扩展方向:
- 会签功能增强:
java复制// 在流程定义中配置多实例
<userTask id="multiReview" name="多部门会签">
<multiInstanceLoopCharacteristics
isSequential="false"
activiti:collection="${deptList}"
activiti:elementVariable="dept">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
- 移动端适配方案:
- 封装轻量级审批API
- 采用WebSocket实现实时通知
- 简化流程图渲染(只显示当前节点)
- 流程版本迁移工具:
java复制public void migrateInstance(String processInstanceId, String targetDefinitionId) {
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
repositoryService.createChangeDeploymentBuilder()
.moveProcessInstanceTo(processInstanceId, targetDefinitionId)
.changeDeployment();
}
这套工作流集成方案已经在多个生产环境稳定运行,处理过包括采购审批、费用报销、项目立项在内的十余种业务流程。最大的优势在于将Activiti的复杂性封装在了可复用的服务层,业务开发人员只需关注表单设计和节点配置,无需深入理解BPMN规范底层实现。