Activiti 5.17.0工作流引擎实战:部署、表结构、API与避坑指南
简介activiti-5.17.0 是 Activiti 工作流引擎的官方发布压缩包面向需要构建业务流程管理系统的 Java 程序员、BPM 架构师以及企业应用集成开发者用于解决流程设计、执行与监控中的复杂问题。压缩包约为 69.35MB内含官方可运行 jar 包、框架源码以及使用手册便于进行环境部署、源码阅读与功能二次开发目前已有 202 人学习参考。该版本基于 BPMN 2.0 标准支持完整的流程定义与建模涵盖引擎执行、用户任务、条件分支、并发子流程、表单集成、事件监听与监控报表等核心机制并可通过任务服务、查询接口和 REST API 与现有系统集成。借助随包源码可深入理解持久化设计、事务管理以及基于角色的访问控制实现手册则提供由浅入深的使用指南能够帮助开发者在企业中快速搭建和维护高效、灵活的工作流平台。1. activiti 5.17.0这套老牌工作流引擎为什么还在大量系统里当家手里要维护老系统的朋友对 activiti 5.17.0 这个名字不会陌生。这个版本发布于工作流引擎在国内审批系统里大面积铺开的时期BPMN 2.0 流程描述、任务分配、会签驳回、定时器这些核心能力都已经稳定。很多公司当年的 OA、工单、合同审批流跑的就是这一套。它最大的价值不是新而是经过大量项目验证踩坑记录可查。这篇文章从零拆一遍 activiti 5.17.0 的部署、表结构、流程运行和排错适合刚接手旧项目的人也适合想在稳定版本上做二次开发的团队。2. 把 activiti 5.17.0 跑起来环境选择、war 部署与数据库初始化2.1 版本选型5.x 这条线为什么值得继续用Activiti 6、7 和 Flowable 这些年迭代了很多但 5.17.0 在遗留系统里的占比依然不低。原因很直接5.x 的 API 是RepositoryService、RuntimeService、TaskService这一套业务代码围绕它写了大量封装。升级到 6.x 之后部分类移动了包路径流程引擎的事件机制也有变化光改编译错误就能耗掉一个迭代。5.17.0 对运行环境的要求也比较宽松。JDK 6 和 7 都能跑Tomcat 6/7/8 都兼容数据库方面 MySQL、Oracle、SQL Server、PostgreSQL 都有官方支持。这意味着很多老服务器不用动系统直接扔一个 war 包就能部署。它的定位也清楚能用 BPMN 2.0 把人工审批流、会签、网关这些常规场景跑稳不追求云原生也不强依赖 Spring Boot。选它还有一个现实原因资料好找。论坛里搜“activiti 5.17”能翻出大量当年项目留下的问题帖遇到怪现象基本都能查到解法这是新版本比不了的。2.2 解压后的目录结构war 包在哪里各目录是干什么的拿到activiti-5.17.0.zip解压后目录划分很清楚。wars目录下有两个关键部署包一个是activiti-explorer.war这是自带的 Web 管理控制台集成了流程设计器、流程发布、任务处理、用户管理功能部署完打开浏览器就能用另一个是activiti-rest.war提供 REST API适合把流程能力暴露给外部系统。libs目录放着各模块的 jar比如activiti-engine、activiti-spring、activiti-bpmn-converter。docs目录里有用户指南和 javadocsetup目录有一些示例配置。我一般会先打开docs/userguide/index.html确认当前版本的行为细节再动工程代码。2.3 初始化数据库从建库到引擎自动建表activiti 5.17.0 支持自动建表前提是把databaseSchemaUpdate配置为true。第一次启动引擎时它会在目标库里创建全部流程表。以 MySQL 为例建库要指定字符集否则后面中文流程名容易乱码CREATE DATABASE activiti DEFAULT CHARACTER SET utf8;bean idprocessEngineConfiguration classorg.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration property namejdbcUrl valuejdbc:mysql://localhost:3306/activiti?useUnicodetrueamp;characterEncodingutf8 / property namejdbcDriver valuecom.mysql.jdbc.Driver / property namejdbcUsername valueroot / property namejdbcPassword valueyourpassword / property namedatabaseSchemaUpdate valuetrue / property nameasyncExecutorActivate valuetrue / /bean这里几个参数值得注意。databaseSchemaUpdatetrue表示如果表不存在就自动创建如果表结构有变化会执行升级脚本生产环境建议部署前手动执行对应版本的create脚本把databaseSchemaUpdate改为false避免引擎在运行期改动表结构。asyncExecutorActivatetrue是开启异步执行器定时器任务、异步延续这些动作依赖它。启动应用后数据库里会出现以ACT_开头的一组表。看到这些表基本可以判断部署成功了。表按前缀划分ACT_RE_*存流程定义和模型ACT_RU_*存运行中的流程实例和任务ACT_HI_*存历史数据ACT_ID_*存用户和组ACT_GE_*存通用数据。2.4 登录 Explorer 控制台发起第一个流程war 包部署到 Tomcat 的 webapps 目录后启动 Tomcat访问/activiti-explorer。首次进入需要登录官方自带演示账号登录后左侧菜单能看到“Processes”“Tasks”“Manage”等入口。Explorer 自带几个演示流程随便挑一个点击启动就能在“Tasks”里看到生成的待办任务。这算是最直观的验证方式引擎在跑表在建任务在流转。3. 核心表结构与数据流转RE / RU / HI 三类表的协作机制3.1 一次审批完整走完数据到底在哪些表里移动运行一个 activiti 流程数据不是固定在某一张表里而是在不同前缀的表之间迁移。理解这套流转逻辑排查问题会顺手很多。部署一个 BPMN 文件时ACT_RE_DEPLOYMENT插入一条部署记录ACT_RE_PROCDEF插入流程定义记录BPMN 的二进制内容进ACT_GE_BYTEARRAY。启动一个流程实例时ACT_RU_EXECUTION插入主执行实例ACT_HI_PROCINST同步写入一条历史流程实例。任务创建时ACT_RU_TASK增加一条待办记录ACT_HI_TASKINST增加一条历史任务记录。任务完成时ACT_RU_TASK删除该行但ACT_HI_TASKINST会带上结束时间继续保留。流程实例走完ACT_RU_EXECUTION里对应数据被清理历史数据留在ACT_HI_*表里。所以可以这样理解运行时表是“现在正在发生什么”历史表是“发生了什么、结果如何”。业务上想查当前待办看ACT_RU_TASK。想追溯整个流程的生命周期看ACT_HI_PROCINST和ACT_HI_ACTINST。3.2 关键表字段与典型排查 SQL实际排查时有几条 SQL 几乎每次都要用。当前待办任务关联流程实例SELECT t.ID_, t.NAME_, t.TASK_DEF_KEY_, e.PROC_INST_ID_, e.ACT_ID_ FROM ACT_RU_TASK t JOIN ACT_RU_EXECUTION e ON t.EXECUTION_ID_ e.ID_ WHERE t.ASSIGNEE_ zhangsan;ACT_RU_TASK的ASSIGNEE_字段存当前办理人TASK_DEF_KEY_对应 BPMN 里用户节点id。查流程实例当前停留在哪个节点可以用ACT_RU_EXECUTION的ACT_ID_字段定位。查历史任务和操作人SELECT PROC_INST_ID_, TASK_DEF_KEY_, NAME_, START_TIME_, END_TIME_, DELETE_REASON_ FROM ACT_HI_TASKINST WHERE PROC_INST_ID_ 2501 ORDER BY START_TIME_;DELETE_REASON_字段很关键正常完成是completed被驳回或删除会有对应原因。流程实例整体状态看ACT_HI_PROCINST里面有START_TIME_、END_TIME_和DELETE_REASON_。END_TIME_为空说明流程还在跑。3.3 运行时表垃圾数据清理5.17.0 有一个常见问题流程异常中止或系统崩溃后ACT_RU_EXECUTION里会残留执行实例ACT_RU_TASK里会残留永远没人处理的待办。清理前先备份数据再删除关联的运行数据。DELETE FROM ACT_RU_IDENTITYLINK WHERE PROC_INST_ID_ 2501; DELETE FROM ACT_RU_VARIABLE WHERE PROC_INST_ID_ 2501; DELETE FROM ACT_RU_TASK WHERE PROC_INST_ID_ 2501; DELETE FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ 2501;删除顺序有讲究先删除子表数据再删执行实例表。如果流程实例下有子执行比如并行网关产生的分支ACT_RU_EXECUTION里会存在多条记录父执行记录通过PARENT_ID_关联删除时要把同一PROC_INST_ID_下的数据一次清干净。只删任务不删执行会导致流程实例卡死在下一次操作。4. 把流程用起来BPMN 部署、流程实例与网关行为4.1 BPMN 文件结构一个请假流程怎么定义审批节点activiti 5.17.0 对 BPMN 2.0 的支持已经相当完整画流程本质上是写 XML。看一个最简请假流程?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:activitihttp://activiti.org/bpmn targetNamespacehttp://www.example.com/leave process idleaveProcess name请假流程 isExecutabletrue startEvent idstartEvent name开始 / userTask idmanagerTask name经理审批 activiti:assignee${manager} / exclusiveGateway idgateway1 name是否通过 / userTask idhrTask nameHR 备案 activiti:assigneehruser / endEvent idendEvent name结束 / sequenceFlow idflow1 sourceRefstartEvent targetRefmanagerTask / sequenceFlow idflow2 sourceRefmanagerTask targetRefgateway1 / sequenceFlow idflow3 sourceRefgateway1 targetRefhrTask conditionExpression xsi:typetFormalExpression${approved true}/conditionExpression /sequenceFlow sequenceFlow idflow4 sourceRefgateway1 targetRefendEvent conditionExpression xsi:typetFormalExpression${approved false}/conditionExpression /sequenceFlow sequenceFlow idflow5 sourceRefhrTask targetRefendEvent / /process /definitions几个属性值得说明。id是流程定义 key启动流程时要通过processDefinitionKey引用它。activiti:assignee指定办理人可以写死也可以写${manager}表达式从流程变量取人。exclusiveGateway排他网关根据条件表达式选择一条分支条件写在conditionExpression里表达式的返回结果必须是布尔值。isExecutabletrue表示这是可执行流程缺失这个属性引擎会忽略。4.2 用 Java API 部署与启动流程引擎 API 是这套资源的核心用法。部署流程定义用RepositoryServiceProcessEngine processEngine ProcessEngines.getDefaultProcessEngine(); RepositoryService repositoryService processEngine.getRepositoryService(); Deployment deployment repositoryService.createDeployment() .addClasspathResource(diagrams/leaveProcess.bpmn) .addClasspathResource(diagrams/leaveProcess.png) .name(请假流程部署) .deploy(); System.out.println(Deployment id: deployment.getId());addClasspathResource从 classpath 加载 BPMN 文件和流程图。name只是部署标识不影响流程定义 key。流程定义以 BPMN 里的process id为准同一个 id 重复部署会自动生成新版本ACT_RE_PROCDEF里的VERSION_字段递增。启动流程实例用RuntimeService流程变量跟随启动传入RuntimeService runtimeService processEngine.getRuntimeService(); MapString, Object variables new HashMapString, Object(); variables.put(manager, lisi); variables.put(days, 3); ProcessInstance processInstance runtimeService.startProcessInstanceByKey( leaveProcess, LEAVE-2024-001, variables); System.out.println(Process instance id: processInstance.getId());第二个参数businessKey是业务单据号比如请假单号在ACT_HI_PROCINST的BUSINESS_KEY_字段里能看到方便按业务维度反查流程。variables是流程变量 Map节点表达式和网关条件都从这里面取值。流程启动后经理审批任务落在ACT_RU_TASK表。处理任务用TaskServiceTaskService taskService processEngine.getTaskService(); Task task taskService.createTaskQuery() .processInstanceId(processInstance.getId()) .taskDefinitionKey(managerTask) .singleResult(); MapString, Object taskVariables new HashMapString, Object(); taskVariables.put(approved, true); taskService.complete(task.getId(), taskVariables);complete第二个参数会追加流程变量。这里approved变量会被排他网关读取决定下一步是进入 HR 备案还是直接结束。singleResult()要求查询结果只有一条任务如果有多个同节点任务会抛出异常。4.3 网关行为与多实例会签网关是流程控制流的核心。排他网关匹配第一条条件为 true 的分支。并行网关分裂出多条分支引擎在ACT_RU_EXECUTION里创建多个子执行每个子执行独立往前跑所有分支都到齐后汇聚。这带来一个排查点并行分支未全部完成时流程不会继续在ACT_RU_EXECUTION里能看到多条PARENT_ID_相同的记录。多实例会签在 5.17.0 里通过multiInstanceLoopCharacteristics实现userTask idcountersignTask name会签 activiti:assignee${assignee} multiInstanceLoopCharacteristics isSequentialfalse activiti:collectionassigneeList activiti:elementVariableassignee /multiInstanceLoopCharacteristics /userTaskisSequentialfalse是并行会签每个候选人各生成一条任务所有人处理完才继续。activiti:collection指流程变量里的候选人列表activiti:elementVariable是循环取的单个对象这里通过${assignee}引用赋值给当前节点办理人。会签任务的完成率控制可以配合activiti:completionCondition设置比如完成半数即通过。4.4 服务任务与服务集成人工审批之外自动节点用服务任务实现。activiti:class指向实现JavaDelegate接口的类serviceTask idautoTask name自动处理 activiti:classcom.example.leave.AutoTask /public class AutoTask implements JavaDelegate { Override public void execute(DelegateExecution execution) { Integer days (Integer) execution.getVariable(days); String manager (String) execution.getVariable(manager); System.out.println(Auto task for: manager , days days); execution.setVariable(autoResult, success); } }JavaDelegate的execute方法在到达该节点时调用DelegateExecution可以读写流程变量。也可以使用activiti:delegateExpression${autoTaskBean}从 Spring 容器里取 bean 执行适合已有 Spring 集成的项目。这里有一个常见坑类必须能被引擎所在的应用类加载器加载如果自定义类在业务系统里而引擎跑在独立的 war 包里会报ClassNotFoundException。5. 5.17.0 避坑指南字符集、定时器、类加载与数据膨胀5.1 中文乱码流程名和表单值显示为问号现象在 Explorer 里部署的中文流程名称正常但表单数据保存后显示???。原因MySQL 建库时默认字符集不是 utf8或者 JDBC 连接串缺少characterEncodingutf8导致引擎写入的 UTF-8 中文被按 latin1 处理。解决建库语句加上DEFAULT CHARACTER SET utf8JDBC 连接串追加useUnicodetruecharacterEncodingutf8两步缺一不可。如果已经表建好了调整连接参数后对已有数据做表字符集转换。5.2 定时器任务不触发或触发延迟现象流程里配置了定时器事件过了设定的时间几分钟任务才执行有时干脆不动。原因5.17.0 的定时器依赖异步执行器asyncExecutorActivate没开启时定时器不会自动触发。开启后执行器默认轮询间隔不是毫秒级而是秒级甚至更长测试时会觉得它“慢半拍”。解决确认activiti.cfg.xml里asyncExecutorActivatetrue。部署后检查ACT_RU_JOB表看定时器任务是否已注册、DUEDATE_字段的时间是否正确。测试环境可以把执行器扫描频率调高生产环境保持默认值即可。5.3 服务任务类找不到现象流程走到服务任务节点控制台报ClassNotFoundException指到自定义的实现类。原因服务任务指定的类没有存在于引擎类加载器的可访问范围内。常见于把引擎部署为独立 war自定义类却放在业务项目里。解决确认类路径。如果类和引擎在同一个 war 里检查WEB-INF/classes或WEB-INF/lib。如果类在另一个服务里改用 HTTP 调用或消息队列解耦不要试图跨 war 共享类。delegateExpression方式也依赖 Spring 容器能加载该 bean排查时先单独测试 bean 是否存在。5.4 数据膨胀历史表占用暴涨现象系统运行几个月后ACT_HI_ACTINST、ACT_HI_TASKINST表膨胀严重数据库备份变慢查询变卡。原因5.17.0 默认不清理历史数据。每个活动的进入、退出都会在ACT_HI_ACTINST落一行量一大就出事。解决在业务低峰期通过HistoryService按时间批量删除历史数据。HistoryService historyService processEngine.getHistoryService(); ListHistoricProcessInstance list historyService.createHistoricProcessInstanceQuery() .finished() .list(); for (HistoricProcessInstance hpi : list) { historyService.deleteHistoricProcessInstance(hpi.getId()); historyService.deleteHistoricTaskInstance(hpi.getId()); }单条接口逐条删除效率不高生产环境建议自己写 JDBC 分批删除防止一次性删太多把数据库锁死。更稳妥的方案是在引擎层做历史归档把老数据转移到归档库。5.5 新部署不生效流程总是跑到旧定义现象修改 BPMN 后重新部署新发起的流程实例仍然按旧版本执行。原因流程定义通过 version 区分。新部署会生成新版本的ACT_RE_PROCDEF但代码里如果用startProcessInstanceById传入旧 id或者缓存了旧定义流程就会走旧逻辑。解决统一用processDefinitionKey启动流程让引擎自动选择最新版本。排查时查一下ACT_RE_PROCDEF的VERSION_和SUSPENSION_STATE_字段确认新版本状态是激活。必要时候把旧版本挂起避免误用。6. 自定义 ID 生成器把默认 DbIdGenerator 换掉5.17.0 默认的DbIdGenerator从ACT_GE_PROPERTY表申请 ID 段每次都要更新表的VALUE_字段。并发高时这一行会变成热点多个线程抢锁流程启动的吞吐量明显下降。替换方案是自定义一个基于时间的 ID 生成器代码风格类似雪花算法时间戳左移、机器节点号、序列号三部分拼接保证全局唯一且趋势递增。public class CustomIdGenerator implements IdGenerator { private long workerId 1; private long sequence 0L; private long lastTimestamp -1L; Override public String getNextId() { long timestamp System.currentTimeMillis(); if (timestamp lastTimestamp) { throw new RuntimeException(Clock moved backwards); } if (timestamp lastTimestamp) { sequence (sequence 1) 4095; if (sequence 0) { timestamp tilNextMillis(lastTimestamp); } } else { sequence 0L; } lastTimestamp timestamp; return String.valueOf(((timestamp - 1288834974657L) 22) | (workerId 12) | sequence); } private long tilNextMillis(long lastTimestamp) { long timestamp System.currentTimeMillis(); while (timestamp lastTimestamp) { timestamp System.currentTimeMillis(); } return timestamp; } }接入方式是在activiti.cfg.xml的属性里注册这个生成器bean idprocessEngineConfiguration classorg.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration property nameidGenerator bean classcom.example.activiti.CustomIdGenerator / /property /bean替换后验证很简单启动流程连续请求几百个流程实例观察日志里的 ID 是否符合时间递增规律同时看数据库ACT_GE_PROPERTY表是否还在频繁更新。从那以后我每次接手 activiti 5.17.0 项目都会先问两个问题数据库字符集确认过没有ID 生成器有没有动过。先把这两件事定下来后面排错能省一半时间。希望帮到你。本文还有配套的精品资源点击获取