1. 项目整体设计与思路拆解
1.1 activiti-app到底是什么
很多首次接触工作流引擎的朋友,很容易被Activiti 6那一堆组件搞懵:activiti-engine是核心引擎,activiti-spring把引擎跟Spring拆在一起,activiti-modeler负责图形化建模,activiti-rest暴露REST接口,activiti-app又是哪一尊?如果你下载过Activiti 6的发布包,解压之后会看到里面有activiti-app.war、activiti-app-explorer.war之类的好几个部署文件,其中activiti-app就是官方精心包装的一个完整可运行Demo项目。
它本质上是官方把引擎、REST接口、建模器、管理控制台、用户权限管理、表单引擎全部整合进一个Web应用里,打包成可以直接丢进Tomcat的War包。你可以把它理解成“Activiti引擎的官方驾驶舱”——不需要写一行Java代码、不搭Spring Boot工程、不手动建库脚本,只要把War包跑起来,就能在浏览器里看到一套完整的工作流系统界面。
这套Demo至少能回答三个问题:Activiti 6到底有什么用、BPMN流程图长什么样、流程从建模到发起到审批的完整链路是怎么串起来的。我当年也是从它上手的,直接省掉了从零搭环境的时间成本。
1.2 为什么值得“玩儿转”它
有些人觉得Demo只是玩具,随便点两下就行了。我的观点相反:activiti-app是理解Activiti 6整体架构的最佳入口。
原因很简单:它包含了完整的流程生命周期。你可以在里面用画布拖拽画出请假审批流程,配置表单字段,然后部署这个流程定义,再发起一个新流程实例,让不同用户登录办理任务,最后还能通过流程历史查看所有轨迹。这一整套操作,恰好把一个工作流引擎的六大核心能力——流程建模、流程引擎、身份管理、表单管理、任务管理、流程监控——全部覆盖了。
而且它是官方维护的,意味着代码质量和设计思路都代表了Activiti团队的推荐姿势。你在Demo里看到的这个按钮对应哪个REST接口、点击之后动了哪张表、前端请求返回了什么JSON,这些都可以通过浏览器开发者工具看得清清楚楚。对后续做二次开发的人来说,这个Demo就是一本“活文档”。
1.3 模块划分与整体架构
activiti-app的界面里面其实藏了两套风格完全不同的前端模块:一套是用于流程设计的管理端,看起来像老旧的后台管理系统;另一套是任务办理端,外观更现代一些,功能也简化了很多。这两套模块本质上是同一套引擎,但面向不同角色。
管理端提供了流程定义的新建、编辑、部署、挂起、激活,以及用户和用户组的管理、表单的定义、数据模型的管理,甚至还有一套简单的定时任务和报表功能。任务办理端则是给业务用户用的,登录后能看到自己待办的任务、已办的任务、可发起的流程,以及流程实例的执行状态。
从技术架构上看,整个应用依赖于Activiti 6引擎,数据存储默认可以是内置的H2数据库,也可以换成MySQL、PostgreSQL等主流关系型数据库。后端通过Spring MVC暴露了一组REST API,前端用AngularJS调用这些API完成操作。也就是说,Modeler画布上的每一次拖拽,最终都是通过REST接口把BPMN XML保存到了数据库里。
2. 环境准备与部署实操
2.1 运行环境要求
我习惯把这一步叫做“三步检查法”,先过一遍再动手,省得后面折腾半天发现是环境问题。
第一步是JDK。Activiti 6官方要求JDK 7及以上,但实测用JDK 8最稳,因为开发调试时很多工具链默认就是JDK 8。JDK 11也是可以跑的,但有些老版本的依赖可能在运行时对模块化有意见,既然官方Demo本身就是老技术栈,完全没必要给自己找麻烦。安装JDK之后记得配好JAVA_HOME环境变量,并确保Tomcat能读到它。
第二步是Web容器。Tomcat 8.5版本是我用得最多的组合,Tomcat 9也能跑,但要注意Servlet规范版本差异。Tomcat 7或者更老的版本不建议用,部分前端的依赖和Servlet API不匹配会直接启动报错。如果你服务器上已经装了别的应用占用了8080端口,要么换端口,要么跟现有应用共存时注意Context路径别冲突。
第三步是数据库。这里有两种选择:只想快速体验,直接用自带的H2内存库;想深入研究或者做二次开发,建议配置成MySQL。H2的好处是零配置,坏处是重启数据丢失,而且你无法跑到外面用Navicat之类的工具查看表数据。我的建议是至少第一次先用H2把系统跑通,第二次再切MySQL,这样排查问题的时候至少知道问题不是出在数据库连接上。
2.2 两种部署方式对比
War包部署是官方文档的经典姿势。拿到activiti-app.war之后,直接拷贝到Tomcat的webapps目录下,启动Tomcat,War包会自动解压并初始化数据库。
操作步骤非常简单:
- 确认
JAVA_HOME已配置,Tomcat能正常启动。 - 把
activiti-app.war复制到tomcat/webapps/目录。 - 如果有旧版本残留,先删掉解压出来的
activiti-app目录,避免旧配置和新代码混在一起。 - 启动Tomcat,等待日志中出现启动完成信息。
- 浏览器访问
http://localhost:8080/activiti-app/。
这里有个细节要注意:War包第一次启动时会自动执行数据库建表脚本,如果数据库连接配错,或者权限不足,启动过程会报出一大堆红色异常。不过即便报错,Tomcat进程一般不会直接退出,而是处于“引擎初始化失败但应用能访问”的半瘫状态。判断是否成功的最可靠方法,是看浏览器能不能打开登录页,以及日志中有没有出现Initialising Activiti Engine之类的字样。
另一种方式是直接下载官方发布的Tomcat集成包。有些发布版本直接附带了一个配置好的Tomcat,解压后运行启动脚本就能用。这种方式更适合纯体验,不需要提前装Tomcat,但缺点是你得了解这个内置Tomcat的具体版本和目录结构,后期要换配置时容易找不到东西。
我个人的建议是走War包部署路线。原因很务实:工作流引擎要上线,最终还是得部署在业务系统的容器里,提前熟悉War包的部署特征,后面的迁移和扩容都比较顺手。
2.3 切换MySQL数据库的完整配置
如果想把数据落到MySQL,在部署前就要准备好数据库和账号。以下是基于常见实践的补充配置方法。
在MySQL里创建库和账号:
create database activiti default character set utf8 collate utf8_bin; create user 'activiti'@'%' identified by 'your_password'; grant all privileges on activiti.* to 'activiti'@'%'; flush privileges;然后找到activiti-app/WEB-INF/classes目录下的db.properties文件,修改连接信息:
db=mysql jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=utf8 jdbc.username=activiti jdbc.password=your_password同时把WEB-INF/lib/目录下自带的H2数据库驱动清理掉,避免驱动冲突。Activiti 6发行包自带MySQL驱动,不需要额外导入。
这里最容易被忽略的一点是时区和编码问题。首次建表时如果编码不是utf8,后续往表里存中文流程名称、用户名称时会直接变成问号。所以建库时指定character set utf8不是可选项,而是必选项。另外MySQL 8的连接驱动要求serverTimezone=Asia/Shanghai这种时区参数,否则JDBC连接可能直接报错,具体参数取决于你的驱动版本。
2.4 端口与内存调优
Tomcat默认内存参数-Xms和-Xmx对Activiti这种带建模器和前端框架的应用来说有点局促。activiti-app启动后内存占用轻则几百兆,重则上G,如果-Xmx设成256M,大概率在打开建模器的时候卡到怀疑人生。
建议在Tomcat的bin/catalina.sh(Windows下是catalina.bat)里加上:
JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"这里MetaspaceSize主要是为了控制类加载器加载大量前端框架类时的元空间占用。如果你的机器内存足够,-Xmx可以放到2G。另外,如果8080端口有别的服务占用了,改Tomcat的server.xml中Connector的port属性为8081,访问路径也跟着改成http://localhost:8081/activiti-app/。
3. 核心功能实操全流程
3.1 登录与用户体系
启动完成后,浏览器打开登录页,默认账号和密码是admin/admin。这是一个内置的管理员账号,拥有所有权限,包括流程定义、用户管理、数据模型等。
登录进去后先不要急着画流程图,我建议先去管理端的用户列表看一眼带着新机制的身份体系。Activiti 6做了用户和组的概念区分:用户是具体的人,组是用户的集合,比如“人事组”“审批组”。流程中每个环节的审批人,可以通过候选人组的方式关联到某个组,运行时引擎会自动把任务分配给组内所有成员,成员中任何一个领取了任务,其他成员就看不到这个待办了。
这里有个小坑:官方Demo里admin用户属于默认的管理员组,但不是所有流程定义都能由管理员直接参与审批。要测试多人审批场景,最好手动创建几个普通用户,再建一个组,把用户加进组里,流程定义节点上配置候选人组用那个组的ID。
3.2 创建并绘制第一个流程定义
进入“流程定义”页面,点击新建流程,输入名称和标识Key。这个标识Key很重要,后续发起流程时要用到,相当于流程的唯一代码。注意Key在整个应用中要唯一,建议用业务相关的英文缩写。
接下来进入建模器。这是activiti-app最核心的面板之一,左侧是工具箱,右侧是属性面板,中间是画布。第一次用的时候可能有点懵,我先说几个关键点:
- 开始事件:一个流程必须有且只有一个开始事件,拖一个圆圈放到画布上。
- 用户任务:拖一个“用户任务”框放到开始事件右边,表示需要人处理的一环。
- 排他网关:用菱形表示,用于分支判断。比如请假超过3天走总监审批,3天以内部门经理直接审批。
- 结束事件:流程终点,也是圆圈,但样式是粗边框。
连线的方法是从一个节点边缘的箭头拖到下一个节点。双击连线可以给连线取名,比如“通过”“驳回”。
在用户任务的属性面板里,有“分配候选人”和“分配候选人组”两个关键属性。填上某个用户ID或组ID,这个任务在运行时就会进入对应人的待办列表。如果不填,任务会变成无人认领状态,只能由管理员操作。
属性面板里还有一个“表单”配置,可以为这个用户任务绑定一个表单。这样审批人在办理任务的时候,能看到并填写这个环节需要的表单字段。
3.3 配置表单与部署流程
流程图画完之后,不要急着部署,先配置表单。activiti-app支持在建模器里直接创建表单定义,也可以为每个环节单独配置外键表单。
具体做法:在用户任务的属性面板中找到“表单”字段,选择“新建表单”,然后在弹出的表单设计器里拖入文本框、日期框、下拉框等控件。每个控件都要设置字段ID和显示名称。字段ID建议用英文小写加下划线,因为后续在进行子流程传参或API对接时,这个字段ID就是数据的Key。
保存流程定义之后,回到“流程定义”列表页,看到刚建好的流程处于“已编辑”状态,点击“部署”按钮。部署完成后状态变成“已部署”,同时系统会自动生成一个版本的流程定义。如果你后面又修改了流程并重新部署,就会生成一个新版本,但已经发起的流程实例仍然走旧版本,这设计叫“版本隔离”,也是工作中经常需要解释的概念。
部署的实质是把BPMN XML和表单定义一起打包发布到数据库中,引擎解析并缓存起来。一旦部署成功,就可以发起流程了。
3.4 发起流程与任务审批
切到任务办理端的“开始流程”页面,列表中能看到所有已部署的流程。点击一个流程的“发起”按钮,会弹出一个表单页,这个表单就是你在建模器里为开始事件绑定的那个表单。填写完字段后提交,流程实例就创建成功了。
回到“待办任务”页面,能看到流程流转到当前节点后产生的任务。如果流程第一个节点配置了候选人组为“人事组”,那么组内用户A登录后能看到这个待办,领取后任务就到A名下,其他组员就看不到了。如果配置的是“候选人”为具体用户B,那这个任务只有B登录才能看到,其他用户即便登录也不会出现。
在审批页面,可以填写审批意见,点击“完成”按钮,任务就结束,流程自动流转到下一个节点。这里的“完成”对应引擎API中的taskService.complete(),所有流程跳转都是通过完成当前任务来驱动的。如果流程中有排他网关,引擎会根据表单字段的值自动计算走向,完全不需要额外写代码。
3.5 管理后台的监控与操作
管理端的流程实例页面可以看到所有运行中的流程实例,每一笔的启动时间、启动人、当前环节都能看到。选中某个实例,可以做几个在业务系统里非常有用的操作:终止流程、挂起流程、修改流程变量。
挂起和激活是Activiti一个很实用的特性。挂起的流程实例无法继续流转,直到重新激活。这在做系统维护、暂停业务时非常有用。
流程变量也没必要回避,直接在界面上调整即可。比如某个审批环节因为特殊原因想改一下流程变量值,选中实例后修改对应变量,后续网关判断就会采用新值。这在调试流程分支时能省很多事。
4. 技术机制解析
4.1 BPMN 2.0在Demo中的直观映射
BPMN 2.0是整个Activiti的通用语言,activiti-app的建模器保存的就是标准BPMN XML。如果你在建模器中画了一个“开始事件”,一个“用户任务”,一个“结束事件”,部署到数据库里的XML片段大概是这样的:
<process id="myProcess" name="我的流程" isExecutable="true"> <startEvent id="startEvent1" name="开始" activiti:formKey="startForm" /> <userTask id="sid-XXXX" name="审批节点" activiti:candidateGroups="managerGroup" /> <endEvent id="endEvent1" name="结束" /> <sequenceFlow id="flow1" sourceRef="startEvent1" targetRef="sid-XXXX" /> <sequenceFlow id="flow2" sourceRef="sid-XXXX" targetRef="endEvent1" /> </process>为什么说这个值得了解?因为后续你一旦脱离Demo,进入项目开发,所有的流程定义都是通过写BPMN XML或者用IDEA插件生成XML,然后再调用API部署的。如果你在可视化界面里已经理解了节点与XML的对应关系,后面看到XML就完全不慌。
流程实例和流程定义的区别也在这里顺势梳理清楚。流程定义是“模板”,就像一条地铁线路图;流程实例是“实际运行的一班车”,每发起一次,就会创建一个实例,记录当前到了哪个站、车上都有谁、带了什么行李(流程变量)。在Demo里,部署一次生成定义、每一次发起生成实例,这个对应关系非常直观。
4.2 Activiti数据库表分类
切到MySQL模式之后,数据库里会自动生成几十张表,表名前缀很有规律,全部以ACT_开头。我把它们按用途整理成了几大类,这个分类对排查问题特别有帮助。
| 前缀/分类 | 核心表 | 作用 |
|---|---|---|
| ACT_RE_ | ACT_RE_PROCDEF | 流程定义、流程模型等静态信息 |
| ACT_RU_ | ACT_RU_TASK、ACT_RU_EXECUTION | 运行时任务、执行实例等动态数据 |
| ACT_HI_ | ACT_HI_PROCINST、ACT_HI_TASKINST | 历史流程实例、历史任务等归档数据 |
| ACT_ID_ | ACT_ID_USER、ACT_ID_GROUP | 用户和用户组身份数据 |
| ACT_GE_ | ACT_GE_BYTEARRAY | 二进制数据,比如BPMN XML和流程图资源 |
你可以在运行一个流程实例的过程中,实时查询ACT_RU_TASK表,会看到当前待办任务对应的执行实例ID和任务定义Key。等流程走完了,这条运行时的记录不会直接删除,而是会被“归档”到历史表里,运行时表查不到。理解这种“运行时+历史”双轨模式,对后续做业务数据统计和流程追踪很有帮助。
我实测中发现,很多同学在做了几次发起和审批操作后,打开数据库看ACT_HI_ACTINST表,能查到每一步活动节点的开始时间和结束时间,这对做流程效率分析非常有用。比如可以算出某个审批节点平均要等几天,系统瓶颈在哪里一目了然。
4.3 设计器与引擎的分工
activiti-app把“设计时”和“运行时”分得很清楚。Modeler部分是纯前端功能,负责把图形化的流程图转化成BPMN XML,并把XML通过REST保存到数据库的ACT_RE_*表中。引擎部分是纯后端功能,负责读BPMN XML、解析成流程定义缓存、执行实例流转。
这也是为什么官方Demo里,只要部署了流程定义,即使Modeler对应的数据库表被清了,引擎也不会受影响——因为它读取的是已经部署且解析完毕的流程缓存。理解这个分工,你就知道:在线修改流程图之后,如果忘记部署,修改是不会生效的;而部署行为本质上切换了版本。前面提到“已编辑”和“已部署”两种状态,就是设计时和运行时的分界点。
5. 常见问题与避坑清单
5.1 启动失败与数据库连接异常
这是遇到最多的问题。现象是Tomcat启动日志里出现了大量Exception,activiti-app页面打不开。排查步骤我建议按顺序来。
先看db.properties里的连接配置是否正确,账号密码、库名、IP端口任何一个错了,建表脚本都跑不起来。再看MySQL驱动版本是否和数据库版本匹配,MySQL 8一定要用mysql-connector-java对应的新版本,老驱动会报Public Key Retrieval is not allowed。最后,如果是修改配置之后重启,记得先把Tomcat的缓存目录(work、temp)清一遍,有时候旧class文件或者旧配置没彻底清除会导致问题神秘复现。
5.2 中文乱码问题
流程名称、用户姓名、表单label在页面上显示成问号,十有八九是数据库连接串没有指定编码。在MySQL配置中,useUnicode=true&characterEncoding=utf8这两个参数必须加上。另外创建库时如果没指定utf8,即使连接串对了,建表时默认排序规则也可能会用latin1,所以务重建库时带上character set utf8。
还有一个常见误伤源:Tomcat的URIEncoding。如果流程定义的Key或者用户名里包含中文(虽然不建议),需要确认Tomcat的server.xml中Connector设置了URIEncoding="UTF-8"。不过更推荐方案是数据库编码和连接串都正确,这能杜绝绝大多数乱码。
5.3 流程部署成功但无法发起
部署成功后,在任务办理端的“开始流程”列表里看不到这个新部署的流程。这种情况通常是因为流程定义中没有配置好“开始事件”的表单,或者流程定义本身挂起。
排查时先到管理端检查流程定义状态是否为“已部署”。如果处于挂起状态,点击激活即可。如果状态正常但发起列表里还是没有,检查流程定义是否绑定了表单。在某些版本里,没有绑定表单的流程定义,默认不会出现在发起列表里。
再补充一个容易被忽略的:如果流程的某个节点没有配置处理人或者候选人组,流程发起后,流转到这个节点时引擎不会自动分配人员,任务会跑到“待领任务”里,页面却显示没有待办。这时候管理端能看到任务处于Unassigned状态,把它指派给具体用户就能继续前进了。
5.4 建模器打不开或保存失败
建模器页面加载特别慢,或者拖拽节点时页面卡顿。这大概率是浏览器兼容性或者机器内存问题。建议用较新的Chrome或者Edge浏览器访问,同时确保Tomcat的内存参数不至于太小。建模器保存失败时,先检查部署的流程定义是否处于“已部署”状态——Activiti的建模器并不支持在线修改已部署的流程定义,如果流程已经部署,从列表里进去只能查看,不能编辑,这时候需要新建版本或者先删除旧版本(谨慎操作)。
5.5 任务列表与历史数据不一致
这种情况多见于直接操作数据库导致的。有人为了测试,手动往ACT_RU_TASK表插入了一条任务记录,结果页面能查到但无法办理,因为缺少执行实例等关联数据。Activiti是一个强一致性的状态机,数据完整性必须通过API来保证,直接改库属于高风险操作。如果真的需要模拟数据,建议通过REST API创建流程实例,而不是手工写表。
6. 二次开发思路与扩展方向
6.1 从Demo到业务集成的路径
activiti-app虽然好用,但毕竟不是专门面向业务系统的,它更适合作为“流程平台原型”。真正落地的时候,一般有两种集成姿势:一是放弃前端,只依赖引擎的REST API,把流程相关能力嵌入到自己的后台系统中;二是保留activiti-app作为后台管理端,业务系统前端通过API调用发起流程、办理任务。
无论选哪种,Demo已经提供了很好的参考。你可以用浏览器开发者工具观察每个操作的HTTP请求,看看每个接口的参数结构和返回格式,这些接口背后的调用逻辑,就是二次开发的最佳样例。
6.2 常用API速览
如果是第一次做集成,我建议先熟悉几个核心API的对应关系。
以流程模型发布为例,需要先准备好BPMN XML,然后调用引擎的repositoryService完成部署:
InputStream inputStream = new FileInputStream("leave.bpmn20.xml"); repositoryService.createDeployment() .addInputStream("leave.bpmn20.xml", inputStream) .name("请假流程") .deploy();启动流程实例对应的方式是:
Map<String, Object> variables = new HashMap<>(); variables.put("days", 3); runtimeService.startProcessInstanceByKey("leave", variables);办理任务需要先获取任务ID,再调用taskService完成任务:
List<Task> tasks = taskService.createTaskQuery() .taskCandidateGroup("managerGroup") .list(); for (Task task : tasks) { Map<String, Object> taskVariables = taskService.getVariables(task.getId()); taskService.complete(task.getId(), taskVariables); }这些代码在Demo中都有对应的可视化操作,你完全可以在界面上先用一遍,再对照API理解参数含义,比直接看代码文档容易得多。
6.3 流程变量驱动的业务能力
在Demo里操作表单时,每个表单字段最终都会对应为一个流程变量。换句话说,BPMN流程本身不关心你的业务字段叫什么,只关心变量名。这个设计让流程引擎和业务系统之间的耦合度降到了最低。
我建议在项目初期就定好流程变量命名规范。常见的做法是全部小写加下划线,比如apply_user、leave_days、manager_approval。网关表达式里引用变量时,命名一致性直接决定维护效率。另外,在流程编排上,涉及多人审批时,优先用“候选人组+领取”模式,而不是把任务直接指派给某个人,这样能在不改变流程定义的情况下,通过调整组成员来适应人员变动。
7. 写在最后的一点经验
跑通activiti-app只是起点,真正有价值的是把“流程思维”带到业务中去。我见过不少项目,虽然用上了工作流引擎,但把流程写成了硬编码的if-else,导致后续需求一变动就要改代码。反过来,如果在流程设计阶段就花时间把节点、表单、候选人组、网关理清楚,后面的维护成本会低很多。
linbin524 提到的很多Activiti 6实操经验,我自己也深有同感:Demo不是用来跑通就扔掉的,它是理解引擎的一次绝佳实景教学。你可以在里面反复做实验,比如故意配置一个没有处理人的节点,观察引擎行为;或者设置一个排他网关,看看条件不满足时是怎么走默认流的。这些操作在真实项目里不太敢随意尝试,在Demo环境里却可以随便折腾。
我个人建议把activiti-app长期保留在一个本地环境里,每次要用到某个流程引擎特性时,先去Demo里验证思路,再落地到项目代码中。这样做的好处是思路清晰,踩坑也少。官方Demo也许看起来不够酷炫,但对学习工作流的人来说,它就是最好的起点。