1. 项目背景与核心价值
工作流引擎是企业级应用开发中不可或缺的基础设施。Activiti作为Apache旗下的开源工作流引擎,在业务流程自动化领域已经深耕十余年。最新发布的Activiti 7版本与SpringBoot深度整合,让开发者能够以最低成本将复杂的审批流、业务流集成到现代Java应用中。
我在最近三个企业级项目中都采用了Activiti 7 + SpringBoot的技术组合。相比传统的工作流实现方式,这种组合最明显的优势在于:
- 配置化程度高:80%的流程逻辑可以通过BPMN 2.0标准流程图定义
- 开发效率提升:与Spring生态无缝集成,避免大量样板代码
- 运维成本低:自带管理控制台,支持运行时流程监控和干预
2. 环境准备与基础配置
2.1 依赖引入关键点
在pom.xml中需要特别注意依赖版本匹配问题。以下是经过生产验证的稳定版本组合:
xml复制<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.6.3</version>
</dependency>
重要提示:不要直接使用最新版本!Activiti 7.x与SpringBoot 2.6.x存在已知的自动配置冲突,建议锁定上述版本组合。
2.2 数据库配置陷阱
Activiti默认会在启动时自动创建23张表。在实际项目中,我强烈建议关闭自动建表功能:
yaml复制spring:
activiti:
database-schema-update: false
db-history-used: true
这样做的原因是:
- 生产环境需要DBA审核SQL脚本
- 自动生成的表可能不符合企业命名规范
- 部分字段长度需要根据业务调整
3. 流程设计与开发实战
3.1 BPMN设计规范
使用Eclipse的BPMN2.0插件设计流程时,必须遵循以下规范:
- 每个UserTask必须设置assignee或candidateUsers
- 网关(Gateway)必须明确命名
- 所有元素ID采用"流程名_步骤名"格式
xml复制<userTask id="leave_approval" name="请假审批"
activiti:assignee="${approver}"/>
3.2 服务层开发模式
推荐采用命令模式封装流程操作:
java复制public class CompleteTaskCmd implements Command<Void> {
private String taskId;
private Map<String, Object> variables;
@Override
public Void execute(CommandContext commandContext) {
TaskService taskService = commandContext.getTaskService();
taskService.complete(taskId, variables);
return null;
}
}
这种模式的优势:
- 统一异常处理
- 支持事务管理
- 便于扩展审计日志
4. 高级特性实现
4.1 动态路由实现
在复杂审批场景中,我总结出三种动态路由方案:
- 表达式路由(适合简单条件)
xml复制<sequenceFlow id="flow1" sourceRef="decision" targetRef="approval"
conditionExpression="${days > 3}"/>
- 委托类路由(中等复杂度)
java复制public class DynamicRouter implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
int days = (int) execution.getVariable("days");
execution.setVariable("nextStep", days > 5 ? "ceoApprove" : "hrApprove");
}
}
- 外部服务路由(最高灵活性)
java复制@Service
public class RouteService {
@Autowired
private RestTemplate restTemplate;
public String determineNextStep(String processInstanceId) {
// 调用外部决策引擎
return restTemplate.getForObject(...);
}
}
4.2 历史数据优化
Activiti的历史表会快速膨胀,必须实施以下优化策略:
- 分级存储配置
yaml复制spring:
activiti:
history-level: audit # 生产环境推荐级别
- 自定义历史处理器
java复制public class CustomHistoryListener implements ActivitiEventListener {
@Override
public void onEvent(ActivitiEvent event) {
if(event.getType() == ActivitiEventType.TASK_COMPLETED) {
// 只记录关键字段到业务库
myRepository.save(extractKeyData(event));
}
}
}
5. 生产环境注意事项
5.1 性能调优参数
以下参数经过百万级流程实例验证:
yaml复制spring:
activiti:
async-executor-activate: true
async-executor-thread-pool-size: 20
lock-wait-time: 300000
transaction:
timeout: 120
5.2 监控方案
推荐采用以下监控指标:
- 流程实例平均耗时
- 任务积压数量
- 异步作业失败率
SpringBoot Actuator集成示例:
java复制@Endpoint(id="activiti")
public class ActivitiMetricsEndpoint {
@ReadOperation
public Map<String, Object> metrics() {
return Map.of(
"runningInstances", runtimeService.createProcessInstanceQuery().count(),
"failedJobs", managementService.createDeadLetterJobQuery().count()
);
}
}
6. 踩坑实录
6.1 事务死锁问题
在高并发场景下,我们曾遇到事务死锁。解决方案是:
- 为所有ServiceTask添加异步属性
xml复制<serviceTask id="service1" activiti:async="true"/>
- 配置重试策略
yaml复制spring:
activiti:
async-executor-retry-wait-time: 5000
async-executor-max-retries: 3
6.2 变量序列化陷阱
当流程变量包含自定义对象时,必须实现Serializable接口并定义serialVersionUID。更稳妥的做法是:
java复制public class ProcessVariables {
private static final long serialVersionUID = 1L;
private String jsonData; // 复杂对象转为JSON存储
// 避免直接存储JPA实体
@Transient
private transient UserEntity user;
}
7. 扩展开发建议
7.1 表单集成方案
推荐两种表单实现方式:
- 动态表单(适合快速开发)
javascript复制// 前端根据meta渲染
const formMeta = {
"approvalComment": {
"type": "textarea",
"required": true
}
}
- 嵌入式表单(适合复杂场景)
html复制<iframe src="/custom-form/${taskId}"
style="border: none; width: 100%; height: 500px;">
</iframe>
7.2 消息通知策略
采用事件监听模式实现分级通知:
java复制@Component
public class NotificationListener {
@EventListener
public void handleTaskEvent(ActivitiEvent event) {
if(event.getType() == ActivitiEventType.TASK_CREATED) {
Notification notification = Notification.builder()
.type("TASK_ASSIGNED")
.targetUser(((TaskEntity)event.getEntity()).getAssignee())
.content("您有新待办任务")
.build();
notificationService.push(notification);
}
}
}
在实际项目中,我发现工作流开发最难的不是技术实现,而是业务流程的抽象能力。建议在开发前先绘制完整的泳道图,与业务方确认每个环节的异常处理路径。对于会签、加签等中国特色需求,Activiti需要通过扩展ActivityBehavior来实现,这部分内容值得单独展开讨论。