简介:一套将 jeecg-boot 3.0、Activiti 5.22 与官方流程画布整合在一起的开发方案,面向需要快速搭建企业级应用、并实现业务流程自动化的 Spring Boot 开发者,既可帮助初学者理解三大组件的集成思路,也能为已有项目从 2.4.6 升级到 3.0 提供参考。压缩包共 2000 个文件、约 22.5MB,其中 625 个 Java 文件承载后端核心逻辑,349 个 Vue 与 193 个 JS 文件构成前端交互界面,168 个 bcmap 为字体映射资源,另有 XML 流程定义、SQL 增量脚本、Dockerfile、yml 配置及 README 等文件,覆盖开发、部署与文档说明等多个环节。目前已有 278 人学习。借助资源包可系统了解 jeecg-boot 代码生成、Activiti 流程设计、官方画布可视化建模如何衔接,并借助完整代码、升级脚本与配置快速搭建可运行原型;同时清晰的目录结构和多样文件类型便于按需查阅,升级脚本也能帮助旧版本用户平滑迁移,减少重复搭建成本。 几年前接一个内部管理系统时,甲方列了一堆流程审批需求:多级会签、撤回、改签、环节超时提醒。我扫了一遍 JeecgBoot 3.0 自带的流程能力,发现应付简单审批可以,碰到这种复杂度就吃力了。于是当时的方案很直接——把 Activiti 5.22 塞进 JeecgBoot,并且直接用 Activiti 官方的流程画布(Modeler)做在线流程设计。这套组合从搭环境到正式跑通,前后花了两周左右,中间踩的坑不少,但最后效果是真稳。这篇就是把整个集成过程、版本冲突处理、画布适配、数据库兼容这些细节完整记录下来,给正在折腾 JeecgBoot + Activiti 的开发者一个参考。
如果你是刚开始了解 JeecgBoot,或者准备在项目里接工作流引擎但不知道选哪个版本,又或者已经导入了 activiti 依赖但启动报错、画布打不开,这篇文章都能给你省掉不少排查时间。下面我按实际集成顺序讲,从选型原因到最终上线,一条线捋到底。
1. 为什么是“jeecg-boot 3.0 + activiti 5.22”这套组合
1.1 本来是冲着JeecgBoot的快速开发去的
JeecgBoot 3.0 吸引我的地方在于低代码能力:代码生成器一键生成前后端页面,表单、列表、菜单这些基础开发量直接砍掉大半。对于企业内部管理系统来说,这是很实用的一套底座。但管理系统的核心业务里,审批流几乎是躲不开的模块。采购单要走审批,请假要审批,合同要审批,而且每家公司的流程都不太一样,甲方今天说三步审批,明天就可能改成五步,还要求流程中途能改签、能撤回。这种需求已经超出了 JeecgBoot 内置流程插件的范畴,必须引入专业的工作流引擎。
在 Java 生态里,工作流引擎无非就是 Activiti、Flowable、Camunda 这几个主流选择。考虑到团队对 Activiti API 最熟,网上资料最多,最终定了 Activiti。
1.2 Activiti 5.22并没有网上说的那么“不能用”
很多人在选型时会纠结:Activiti 都出到 7 了,为什么还要用 5.22?这里有个现实问题——项目的基础环境是 JDK 8 + Spring Boot 2.x + MyBatis-Plus,这套组合里 Activiti 5.22 反而是最稳定的选择。
先说 5.22 的定位。5.22.0 是 Activiti 5 系列的最终版本,5 系列这么多年的 bug 修复都沉淀在这个版本里,社区的踩坑贴子也最多,遇到问题基本能搜到解决方案。而且 5.22 的 API 设计非常直观,RepositoryService、RuntimeService、TaskService 几个核心 Service 一学就会,项目成员的接手成本很低。
Activiti 7 虽然新,但它把身份管理、安全框架都重写了,和 Spring Boot 2.x 集成时需要引入额外的 Spring Security 配置,对于一个只想在后台管理系统里加审批流的场景来说,改动面太大。Flowable 是从 Activiti 5 分叉出去的,功能确实强,但 API 有细微变动,团队还得重新熟悉一遍。权衡下来,5.22 是最务实的选项。
至于官方画布,这个选择就更明确了。自己从头写一个流程设计器不现实,基于 bpmn-js 二次开发工作量也不小。Activiti 官方自带的 Modeler 是一个基于 AngularJS 的在线拖拽画布,能画开始事件、用户任务、排他网关、结束事件这些基础元素,保存成官方模型 JSON,发布时再解析成 BPMN 文件。直接把官方这套搬进来用,省时省力。
2. 依赖整合与数据源配置:先把引擎跑起来
2.1 处理Maven依赖冲突(mybatis/jackson/spring三座大山)
第一关就是 Maven 依赖。Activiti 5.22 是 2017 年前后的版本,它内部依赖的 Spring、MyBatis、Jackson 版本都偏老,直接往 JeecgBoot 3.0 的 pom.xml 里一放,启动大概率会报 jar 包冲突。最常见的冲突有三类:
- MyBatis:Activiti 引擎内部自己也用了 MyBatis 做持久层,这一点很多人不知道。它自带的 mybatis 版本如果和 JeecgBoot 里的 MyBatis-Plus 依赖的 mybatis 核心包版本不一致,轻则启动告警,重则
NoClassDefFoundError。 - Spring:Activiti 5.22 的 activiti-spring 模块依赖的是 Spring 4 时代的包,而 Spring Boot 2.x 用的是 Spring 5。两边如果没处理干净,会出现
BeanCreationException。 - Jackson:官方画布的前后端交互走的是 JSON,Activiti 内部也绑定了 Jackson 版本,和 Boot 自带的 Jackson 版本不一致时,模型解析会出怪问题。
我的做法是引入 activiti-spring 时把它的传递依赖全部排除掉,再让项目统一使用 JeecgBoot 锁定的版本:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>5.22.0</version> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency>这里要注意,排除掉 Activiti 自带的 mybatis 后,需要保证项目里存在一个它能识别的 MyBatis 版本。我项目里没有额外加依赖,直接用了 MyBatis-Plus 传递进来的 MyBatis 核心包,实测没问题。
2.2 引擎配置与自动建表
依赖搞定后就是创建 ProcessEngine。Activiti 5.22 在 Spring 环境下的标准做法是用ProcessEngineFactoryBean配合SpringProcessEngineConfiguration。在 JeecgBoot 里写一个配置类:
@Configuration public class ActivitiConfig { @Bean public ProcessEngineFactoryBean processEngineFactoryBean(DataSource dataSource) { ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean(); SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setDatabaseSchemaUpdate("true"); config.setJobExecutorActivate(true); factoryBean.setProcessEngineConfiguration(config); return factoryBean; } @Bean public RepositoryService repositoryService(ProcessEngine processEngine) { return processEngine.getRepositoryService(); } // RuntimeService、TaskService、HistoryService 同理 }几个关键参数要解释一下。databaseSchemaUpdate=true表示启动时自动建表或更新表结构,第一次集成时建议开启,跑起来后到数据库里确认 ACT_ 开头的表都生成了,再把参数改成false,避免生产环境误操作。jobExecutorActivate=true是激活定时任务执行器,如果流程里没有定时事件,可以先关掉,减少不必要的后台线程。
这里有个非常容易踩的坑:传入的 DataSource 必须是业务库同一个数据源。如果 JeecgBoot 配置了多数据源,或者 Activiti 配置类里自己 new 了一个数据源,那流程表就会建到别的库去,后面查数据的时候一脸懵。我用的是 JeecgBoot 默认数据源,直接把 DataSource 注入进来就好。
2.3 事务处理细节
Activiti 5.22 的事务机制是独立的,它内部用 CommandContext 管理事务。如果业务代码里加了@Transactional,然后又调用了 Activiti 的 Service 方法,两者并不是天然在同一个事务里。简单场景下,比如启动流程、完成任务,不需要强行把它们做到一个事务里,流程操作独立提交反而更安全。
如果业务表单数据和流程实例必须保持强一致,我建议在业务事务提交后,通过TransactionSynchronizationManager.registerSynchronization再触发流程操作。虽然多了一步,但能避免“业务数据提交了,流程没启动成功”或者反过来“流程启动了,业务数据回滚了”这种尴尬情况。
3. 官方画布搬进JeecgBoot:前后端的适配过程
3.1 从Explorer包中拆出Modeler前端资源
Activiti 官方画布并没有作为独立插件发布,它藏在 Activiti Explorer 的 Web 应用里。你需要下载activiti-webapp-explorer-5.22.0.war,解压后把里面的editor-app和diagram-viewer两个目录拷贝出来,放到 JeecgBoot 的src/main/resources/static/activiti/目录下。
editor-app是画布的核心前端资源,里面是 AngularJS 写的单页应用。diagram-viewer是流程图预览组件。注意把这些静态资源放到 static 目录后,访问路径就是/activiti/editor-app/editor.html,后续挂 iframe 要用这个路径。
拷贝完后要改一个关键文件:editor-app/app-cfg.js。这个文件里有 AngularJS 的 context 路径配置,默认指向的是 Activiti Explorer 的服务端地址。不改的话,前端会去请求一个不存在的接口,画布打开是空白。改成你的项目根路径即可。
3.2 补一套模型读写接口
官方 Modeler 保存画布时,调用的核心接口是“读取模型 JSON”和“保存模型 JSON”。Activiti 官方把这套接口写在ModelEditorJsonRestResource里。最简单的办法是把这些 REST 接口的代码抄到自己的 Controller 里,只保留和模型相关的部分,不依赖完整的 activiti-rest 模块。
核心逻辑并不复杂,主要围绕RepositoryService:
@RestController @RequestMapping("/activitiModel") public class ActivitiModelController { @Autowired private RepositoryService repositoryService; @GetMapping("/{modelId}/json") public AjaxResult getModelJson(@PathVariable String modelId) { Model model = repositoryService.createModelQuery().modelId(modelId).singleResult(); byte[] editorSource = repositoryService.getModelEditorSource(modelId); // 返回给前端画布使用的 JSON return AjaxResult.success("ok", JSON.parseObject(new String(editorSource, StandardCharsets.UTF_8))); } @PostMapping("/{modelId}/save") public AjaxResult saveModel(@PathVariable String modelId, @RequestBody JsonNode body) { repositoryService.addModelEditorSource(modelId, body.toString().getBytes(StandardCharsets.UTF_8)); return AjaxResult.success(); } }新建模型的接口也一样,用repositoryService.newModel()创建 Model 对象,设置 key、name、metaInfo,然后调用saveModel保存。这样官方画布的前后端链路就通了。
3.3 Vue3页面用iframe挂载画布并放行登录拦截
JeecgBoot 3.0 的前端是 Vue3,官方画布是 AngularJS,两者技术栈完全不同,硬往一个页面里揉很容易互相干扰。我用的是最省事的方案:在 JeecgBoot 里新开一个路由,页面内容就是一个 iframe,指向/activiti/editor-app/editor.html?id=xxx。
<template> <div style="height: calc(100vh - 50px)"> <iframe :src="modelerUrl" style="width: 100%; height: 100%; border: none" /> </div> </template>iframe 隔离性最好,AngularJS 的全局变量和 Vue3 的响应式系统互不干扰,这是我最推荐的做法。
但这里有个绕不开的问题:JeecgBoot 默认有登录拦截,无论是 Shiro 还是其他权限框架,请求/activiti/**和/activitiModel/**时都会被拦下来。我的处理方式是在权限配置里放行这两类资源,画布加载时通过 URL 参数携带 token,后端写一个简单的过滤器校验 token 合法性。这样既不影响整体登录安全,画布又能正常访问。
还有一个小坑是stencilset.json的路径。这个文件定义了画布里有哪些组件(开始事件、用户任务、网关等),官方 Modeler 初始化时会去请求/service/editor/stencilset。如果这个请求 404 了,画布打开后左侧组件区是空的。需要把官方StencilsetRestResource的逻辑也复制到自己的 Controller 里,或者直接把 stencilset 静态资源放到可访问的路径下。
4. 流程定义、部署与业务表单打通
4.1 从画布保存到流程部署
画布保存的是模型 JSON,不是真正的 BPMN 文件,要让 Activiti 引擎跑起来,还要把模型 JSON 转成 BPMN 并创建部署。Activiti 5.22 提供了两个转换器:BpmnJsonConverter和BpmnXMLConverter。转换代码如下:
ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(modelEditorSource); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode); byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel); Deployment deployment = repositoryService.createDeployment() .name(model.getName()) .addString(model.getKey() + ".bpmn20.xml", new String(bpmnBytes, StandardCharsets.UTF_8)) .deploy();部署成功后,会在ACT_RE_PROCDEF表里生成一条流程定义记录,key 对应模型 key,版本号默认从 1 开始。重复部署同 key 的模型,版本号会递增,这在流程版本管理上是合理的,但要注意旧版本流程实例仍在运行,不要贸然清理。
我的做法是在模型列表页面增加一个“部署”按钮,点击后执行转换和部署逻辑,部署成功后再把 deploymentId 回写到模型表的 metaInfo 里,方便后面追溯。
4.2 启动流程实例与业务数据关联
部署完成不代表流程能直接用,你还需要从业务页面发起流程。启动流程实例最常用的方法是按流程 key 启动:
Map<String, Object> variables = new HashMap<>(); variables.put("applyUserId", currentUserId); variables.put("days", 3); variables.put("reason", "出差申请"); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( processDefinitionKey, businessKey, variables );第三个参数businessKey非常关键。我习惯把业务表单的主键传进去,比如采购单 id、请假单 id,这样通过runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey)就能反查流程实例,审批记录和业务数据也能一一对应。
画布上每个用户任务的Assignee、Candidate Users这些属性,如果在画布上写死字符串,那就所有流程都一个办理人,显然不现实。我的做法是启动流程时把当前登录用户 id 放进 variables,然后在画布的用户任务分配表达式里写${applyUserId}之类的变量引用。这样每次发起流程时,第一个环节的办理人就是申请人自己,后续节点再根据业务逻辑动态指定。
4.3 待办查询与审批完成
待办查询对接到 JeecgBoot 的后台管理页面后,实现起来很直接:
List<Task> todoTasks = taskService.createTaskQuery() .taskAssignee(currentUserId) .orderByTaskCreateTime().desc() .list();审批通过就是完成任务并传入流程变量:
Map<String, Object> taskVariables = new HashMap<>(); taskVariables.put("approved", true); taskService.complete(taskId, taskVariables);审批历史则从historyService.createHistoricProcessInstanceQuery()和createHistoricActivityInstanceQuery()里查。到这里,一条完整的审批链路就走通了。
5. 数据库版本兼容与Idea插件离线安装这些周边问题
5.1 Activiti 5.22在不同数据库版本下的适配
Activiti 5.22 支持的数据库不少,但我实际用下来遇到最多问题的是 MySQL 版本差异。项目里两个环境,一个 MySQL 5.7,一个 MySQL 8.0,这里有个大坑必须说:MySQL 8.x 默认的密码认证插件是caching_sha2_password,如果项目还在用老版本的 mysql-connector-java,启动时直接报Unable to load authentication plugin 'caching_sha2_password'。
解决办法是把 mysql-connector-java 升级到 8.0.x,并在 JDBC URL 里补上几个参数:
jdbc:mysql://localhost:3306/jeecg_boot?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=trueActiviti 5.22 自动生成的表结构在 MySQL 5.7 和 8.0 下都能正常使用,主要就是驱动和连接参数的问题。表引擎一定得是 InnoDB,MyISAM 不支持事务,如果发现历史表是 MyISAM 建的,用下面这句批量改回来:
ALTER TABLE act_hi_procinst ENGINE = InnoDB;另外,ACT_GE_PROPERTY表里存着 schema 版本号,启动时引擎会检查它和自身版本是否匹配。如果从老版本 Activiti 升级上来,版本号对不上会直接抛异常,这个后面排查章节再细讲。
5.2 Idea的Activiti插件离线安装经历与建议
一开始我也想着在 Idea 里装个 Activiti 插件本地画 .bpmn 文件,但折腾了一圈后放弃了。主要原因有两个:一是 actiBPM 这个插件基本停更了,在最近几个 Idea 版本上要么搜不到,要么装完界面渲染异常;二是就算装上,离线安装包也不好找,官方插件仓库目前把旧版隐藏了不少。
如果确实处于内网环境只能离线安装,标准操作是:在 JetBrains 插件商店页面手动下载对应版本的 zip 包,然后打开 Idea 的 Settings -> Plugins -> 齿轮图标 -> Install Plugin from Disk,选择下载好的 zip,重启后生效。但据我实测,Idea 2021 之后的版本对 actiBPM 的兼容性很不乐观,经常出现插件列表里能看到但画布工具栏空白的情况。
所以我后来彻底放弃了本地插件画流程,统一使用官方画布在线设计。官方画布能直接保存和部署,少一次文件传输环节,对于团队协作也更友好。如果你实在需要在本地画 BPMN,我更推荐 VS Code 的 bpmn-io 扩展,轻量且渲染可靠,画完导出.bpmn20.xml再导入 JeecgBoot 即可。
6. 集成后最容易踩的坑:问题排查看图说话
6.1 高发报错对照表
集成过程中我遇到过不少报错,整理成一张表,方便你对照排查:
| 现象 | 根本原因 | 解决办法 |
|---|---|---|
启动失败NoClassDefFoundError: org/mybatis/... | Activiti 内置 MyBatis 与项目版本冲突 | 排除 Activiti 传递的 mybatis,统一用项目版本 |
BeanCreationException: ProcessEngine初始化失败 | 数据源连接参数不对或驱动过旧 | 检查 url、driver、MySQL 驱动版本 |
| 画布打开后左侧组件区空白 | stencilset.json请求 404 | 补上官方 StencilsetRestResource 或放行静态资源 |
| 画布保存接口 401 | 登录拦截器把 REST 请求拦了 | 放行/activitiModel/**,并校验 token |
| 启动流程后待办查不到 | 任务 Assignee 与当前查询用户不一致 | 确认流程变量和用户任务表达式 |
Schema validation failed | 数据库 schema 版本与引擎版本不匹配 | 检查 ACT_GE_PROPERTY 里的版本号 |
| 模型 JSON 转 BPMN 时报节点解析异常 | 画布里有官方 JSON 转换器不识别的扩展节点 | 回画布删掉多余元素,重新发布 |
6.2 一次完整的排错案例:ACT_表“消失”了
这里分享一个我印象最深的案例。配置全部写好后启动项目,日志里没有报错,ProcessEngine 也创建成功了,但打开数据库一查,一张 ACT_ 的表都没有。我愣了很久,还以为是databaseSchemaUpdate=true没生效。
后来把日志级别调到 DEBUG 才发现,引擎执行建表语句用的连接指向的是另一个库——因为我项目里配了动态数据源,Activiti 注入到的 DataSource 是动态数据源的默认路由,但实际运行时上下文里选中的默认库和业务库不是同一个。MyBatis-Plus 的多数据源插件在启动阶段拿到的是主数据源,而 Act 引擎用的连接被路由到了别的库。
解决方式也很简单:给 Activiti 单独定义一个数据源 Bean,明确指向业务库,然后在配置类里用@Qualifier指定这个数据源。
这类问题不看日志很难发现,强烈建议在集成阶段把org.activiti的日志级别调到 DEBUG,启动时能看到完整的建表 SQL 和引擎初始化的每一步。
6.3 把日志级别打开,能省一大半时间
最后这点算是我反复踩坑后的心得。Activiti 5.22 的日志系统用的是 slf4j,可以直接通过 application.yml 配置日志级别:
logging: level: org.activiti: debug org.apache.ibatis: debug开启后,每次启动都能看到引擎执行了哪些 SQL、是否执行了 schema 检查、部署了哪些流程定义。遇到问题时,不要一上来就怀疑代码逻辑,先看日志里引擎是否正常初始化,再看 SQL 是否执行到预期位置,大多数问题都能快速定位到是哪一层出的问题。
7. 关于这套方案,我最后想说的几点经验
这套“jeecg-boot 3.0 + activiti 5.22 + 官方画布”的组合,我这边项目已经跑了半年多,线上流程定义有四十多个,引擎一直很稳定,没出现过流程数据错乱或任务丢失的情况。我个人体会是,技术选型不一定要追新,稳定和团队熟悉度更重要。
如果你也在集成过程中卡住,按这篇文章的顺序捋一遍基本能解决大部分问题——依赖冲突、数据源指向、画布静态资源、登录拦截,这四个环节是 90% 故障的根源。再补一句特别实用的建议:pom.xml 里的 activiti 版本千万不要用 latest 这种动态版本号,直接锁死 5.22.0,不然哪天依赖解析到不同小版本,线上跑着跑着就可能出现诡异行为,到时候排查成本远比今天多写一个版本号高得多。
本文还有配套的精品资源,点击获取