Jeecg-Boot免费版集成Activiti 6工作流实战指南
在开源低代码平台Jeecg-Boot的实际应用中,工作流引擎的缺失常常成为项目落地的瓶颈。本文将带你从零开始,通过源码级改造将Activiti 6深度集成到Jeecg-Boot免费版中,特别针对单租户场景下的多租户适配、前后端联调等核心痛点提供解决方案。
1. 环境准备与基础配置
1.1 依赖管理
首先在pom.xml中添加Activiti核心依赖,注意版本兼容性:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-modeler</artifactId> <version>6.0.0</version> </dependency>注意:Jeecg-Boot默认使用MyBatis-Plus 3.4.0,与Activiti 6.0.0存在SQL语法兼容问题,建议添加如下配置解决:
mybatis-plus.global-config.db-config.column-format=``1.2 数据库初始化
执行以下SQL创建Activiti基础表结构:
-- 使用Jeecg-Boot的DataSource自动建表 spring.activiti.database-schema-update=true spring.activiti.history-level=audit常见问题排查表:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 表前缀不生效 | MyBatis-Plus拦截器冲突 | 禁用MP的SQL解析器 |
| 历史表未创建 | history-level配置错误 | 设置为full或audit |
| 流程定义加载失败 | 字符集不匹配 | 检查MySQL的utf8mb4配置 |
2. 多租户模式适配改造
2.1 租户ID自动注入
创建自定义的ActivityTenantIdHandler实现TenantIdHandler接口:
public class ActivityTenantIdHandler implements TenantIdHandler { @Override public String getCurrentTenantId() { // 从Jeecg-Boot的Token中提取租户ID return TokenUtils.getTenantId(); } }在配置类中注册处理器:
@Bean public SpringProcessEngineConfiguration processEngineConfiguration( DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setTenantIdHandler(new ActivityTenantIdHandler()); // 其他配置... return config; }2.2 流程实例隔离方案
针对单租户需求,需要重写以下关键类:
CustomUserEntityManager:对接Jeecg用户体系CustomGroupEntityManager:处理部门映射CustomRuntimeService:封装流程启动逻辑
关键改造点代码示例:
public class CustomRuntimeService extends RuntimeServiceImpl { @Override public ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables) { variables.put("tenantId", TenantContext.getTenant()); return super.startProcessInstanceByKey( processDefinitionKey + "_" + TenantContext.getTenant(), businessKey, variables ); } }3. 前后端深度集成
3.1 前端页面嵌入方案
在src/views/modules下新建流程管理模块:
<template> <div class="jeecg-container"> <a-tabs> <a-tab-pane key="1" tab="流程设计"> <iframe :src="modelerUrl" style="width:100%;height:calc(100vh - 180px)" /> </a-tab-pane> </a-tabs> </div> </template> <script> export default { data() { return { modelerUrl: `${window._CONFIG['domianURL']}/activiti/modeler.html?token=${this.$store.getters.token}` } } } </script>提示:需要配置Nginx解决跨域问题:
location /activiti { proxy_pass http://localhost:8080/activiti; add_header 'Access-Control-Allow-Origin' $http_origin; add_header 'Access-Control-Allow-Credentials' 'true'; }3.2 接口安全改造
在SecurityConfig中添加白名单:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers( "/activiti/**", "/modeler.html", "/editor-app/**" ).permitAll() // 其他配置... }Token传递拦截器示例:
public class ActivitiTokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getParameter("token"); if(StringUtils.isNotBlank(token)){ TokenUtils.setToken(token); } return true; } }4. 业务场景实战案例
4.1 请假流程完整实现
流程定义设计:
- 使用BPMN设计包含【提交】【部门审批】【HR备案】三个节点
- 设置表单属性:
<activiti:formProperty id="leaveDays" name="请假天数" type="long" required="true"/>
业务表关联:
@TableName("oa_leave") public class LeaveApply extends BaseEntity { @TableField("process_instance_id") private String processInstanceId; // 其他字段... }流程启动控制器:
@PostMapping("/start") public Result<?> startProcess(@RequestBody LeaveApply leave) { // 业务数据入库 leaveService.save(leave); // 启动流程 runtimeService.startProcessInstanceByKey( "leave_process", leave.getId().toString(), Map.of("applyUser", getCurrentUsername(), "days", leave.getDays()) ); return Result.ok(); }
4.2 审批节点自动分配
实现动态任务分配策略:
public class LeaveTaskAssignee implements TaskAssigneeCalculator { @Override public String calculateAssignee(DelegateTask task) { String eventName = task.getEventName(); if ("create".equals(eventName)) { // 部门审批环节自动分配 if ("dept_approve".equals(task.getTaskDefinitionKey())) { return userService.getDeptManager( TaskUtils.getVariable(task, "applyUser") ); } } return null; } }在流程定义中引用:
<userTask id="dept_approve" name="部门审批"> <extensionElements> <activiti:taskListener event="create" class="com.jeecg.activiti.LeaveTaskAssignee"/> </extensionElements> </userTask>5. 性能优化与生产建议
5.1 数据库连接池配置
推荐使用HikariCP并调整以下参数:
spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=30000 spring.activiti.async-executor-activate=true5.2 历史数据归档策略
创建定时任务清理历史数据:
@Scheduled(cron = "0 0 2 * * ?") public void archiveHistoricData() { historyService.createHistoricProcessInstanceQuery() .finished() .list() .forEach(instance -> { // 归档到MongoDB mongoTemplate.save(instance, "historic_process"); // 删除关系型数据库记录 historyService.deleteHistoricProcessInstance(instance.getId()); }); }5.3 缓存优化方案
自定义流程定义缓存:
@Bean public ProcessDefinitionCache processDefinitionCache() { return new CustomProcessDefinitionCache() { @Override protected ProcessDefinitionEntity getProcessDefinition(String processDefinitionId) { // 结合Redis实现二级缓存 String redisKey = "activiti:pd:" + processDefinitionId; ProcessDefinitionEntity pd = redisTemplate.opsForValue().get(redisKey); if(pd == null) { pd = super.getProcessDefinition(processDefinitionId); redisTemplate.opsForValue().set( redisKey, pd, 1, TimeUnit.HOURS ); } return pd; } }; }