一 介绍
1.1 Activiti 介绍
Activiti 是由 jBPM (BPM,Business Process Management 即业务流程管理) 的创建者 Tom Baeyens 离开 JBoss 之后建立的项目,构建在开发 jBPM 版本 1 到 4 时积累的多年经验的基础之上,旨在创建下一代的 BPM 解 决方案。
Activiti 作为一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调 度。 Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于Java的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。
Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形 式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。 Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快 速、稳定的BPMN 2.0流程引擎。Activiti是在ApacheV2许可下发布的,可以运行在任何类型的Java程序中,例如服 务器、集群、云服务等。
Activiti可以完美地与Spring集成。同时,基于简约思想的设计使Activiti非常轻量级。 官网:
1.2 Activiti 开发流程
- 画流程定义模型: 遵守BPMN的流程规范,使用BPMN的流程定义工具,通过 流程符号 把整个业务流程定义出来,可以将流程 定义文件字节流保存到模型数据表中(Model)。
- 部署流程定义: 加载画好的流程定义文件,将它转换成流程定义数据(ProcessDefinition),保存到流程定义数据表中。
- 启动流程(提交流程申请): 生成流程实例数据(ProcessInstance),生成第1节点任务数据(Task)。
- 处理人审批流程节点任务: 完成任务审批,生成审批结果,生成下一节点任务数据。
1.3 BPMN 2.0 规范是什么
业务流程模型注解(Business Process Modeling Notation - BPMN)是业务流程模型的一种标准图形注解。这个 标准是由对象管理组(Object Management Group - OMG)维护的。
标准的早期版本(1.2版以及之前)仅仅限制在模型上, 目标是在所有的利益相关者之间形成通用的理解, 在文 档,讨论和实现业务流程之上。 BPMN标准证明了它自己,现在市场上许多建模工具都使用了BPMN标准中的元素 和结构。
BPMN规范的2.0版本,当前已经处于最终阶段了, 允许添加精确的技术细节在BPMN的图形和元素中, 同时制定 BPMN元素的执行语法。 通过使用XML语言来指定业务流程的可执行语法, BPMN规范已经演变为业务流程的语 言, 可以执行在任何兼容BPMN2的流程引擎中, 同时依然可以使用强大的图形注解。
目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。BPMN 2.0是使用一些符号来明确 业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。
1.4 BPMN 2.0 基本流程符号
1.4.1 事件 Event
开始 中间事件 结束
1.4.2 活动
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;
1.4.3 网关 Gateway
网关用来处理决策:
- 排他网关 (x)
只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继 续执行当前网关的输出流; 如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引 擎会抛出异常。 排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
- 并行网关 (+)
所有路径会被同时选择 分支: 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。 汇聚 :所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
- 包含网关 (o)
可以同时执行多条线路,也可以在网关上设置条件分支:计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行 汇聚:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
- 事件网关 (o+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关 后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
1.5 Activiti API 服务接口
Activiti 流程引擎包含了25张表,而且表之间的关系也比较复杂,比如包含各种外键约束。
按照传统的方式,有了数据库表后,就应该为每张表创建 Entity 实体类,然后为其创建对应的 DAO 接口,然 后再创建对应的 Service来实现对表数据的增删改查;
但是按照传统方式的,就会有一个很严峻的问题,表的数量太多,并且关系复杂,还要兼顾流程引擎的处理方 式,自己去搞一套,几乎不可能;
其实不需要我们去创建Entity 、 DAO、Service、Controller,因为Activiti已经把这些东西给搞好了,只需要 调用即可。
Process Engine API 和服务
引擎 API 是与 Activiti 交互的最常见方式。
可以从ProcessEngine中获取包含工作流/ BPM方法的各种服务。
ProcessEngine和服务对象是线程安全的。因此,可以为整个服务器保留对其中之一的引用。
Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用对应Service接口可以操作对应 的数据表。
1.5.1 核心Service接口及其获取
// 会在首次调用时初始化并构建一个流程引擎,此后始终返回相同的流程引擎。 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 引擎管理类 ManagementService managementService = processEngine.getManagementService(); // 动态修改流程管理类 DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService(); // 流程运行管理类 RuntimeService runtimeService = processEngine.getRuntimeService(); // 流程仓库管理类 RepositoryService repositoryService = processEngine.getRepositoryService(); // 任务管理类 TaskService taskService = processEngine.getTaskService(); // 历史管理类 HistoryService historyService = processEngine.getHistoryService();
二 表
2.1 建表
前提:在数据库中建库 并和配置文件中的连接信息保持一致。
在test中执行如下语句,即可建表成功:
@Autowired ProcessEngine processEngine; @Test public void getProcessEngine() { System.out.println("processEngine: " + processEngine); }
建好表后,执行两条语句,使其完整:
ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN VERSION_ VARCHAR(255); ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN PROJECT_RELEASE_VERSION_ VARCHAR(255);
2.2 表介绍
Acitiviti数据库中表的命名都是以 ACT_ 开头的。第二部分是一个两个字符用例表的标识。此用例大体与服务API是 匹配的。
ACT_GE_* : GE 表示 general 。通用数据,各种情况都使用的数据 ,如存放资源文件(图片,规则等)。
ACT_HI_xxx : HI 表示history。就是这些表包含着历史的相关数据,如结束的流程实例(变量,任务等)。
ACT_RE_xxx : RE 表示repository。带此前缀的表包含的是静态信息,如,流程定义,流程的资源(图片,规则 等)。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直 很小速度很快。
ACT_RU_xxx : RU 表示 runtime。这是运行时的表存储着流程变量,用户任务,变量,职责(job)等运行时的 数据。Activiti只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的 表小且快。
ACT_EVT_* :EVT表示EVENT,流程引擎的通用事件日志记录表,方便管理员跟踪处理。
表分类 | 表名 | 说明 |
通用数据 | ||
act_ge_bytearray | 二进制数据表(流程图) | |
act_ge_property | 属性数据表,存储整个流程引擎级别的数据,初始化表结构时,会 插入版本号信息等 |
历史数据 | ||
act_hi_actinst | 历史节点表 | |
act_hi_attachment | 历史节点表 | |
act_hi_comment | 历史节点表 | |
act_hi_detail | 历史详情表,提供历史变量的查询 |
act_hi_identitylink | 历史流程人员表,主要存储任务节点与参与者的相关信息 | |
act_hi_procinst | 历史流程实例表 | |
act_hi_taskinst | 历史任务实例表 | |
act_hi_varinst | 历史变量表 |
流程定义表 | ||
act_re_deployment | 部署信息表 | |
act_re_model | 流程设计模型表 | |
act_re_procdef | 流程设计模型表 |
流程运行表 | ||
act_ru_deadletter_job | 作业死亡信息表,如果作业失败超过重试次数,则写入到此表 | |
act_ru_event_subscr | throwEvent、catchEvent时间监听信息表 | |
act_ru_execution | 运行时流程执行实例表 |
aact_ru_identitylink | 运行时流程人员表,主要存储任务节点与参与者的相关信息 | |
act_ru_integration | 运行时积分表 | |
act_ru_job | 定时异步任务数据表 |
act_ru_suspended_job |
运行时作业暂停表, 比如流程中有一个定时任务,如果把这个任 务停止工作了,这个任务写入到此表中 | |
act_ru_task | 运行时任务节点表 | |
act_ru_timer_job | 运行时定时器作业表 | |
act_ru_variable | 运行时流程变量数据表 |
其他表 | ||
act_procdef_info | 流程定义的动态变更信息 | |
act_evt_log | 流程引擎的通用事件日志记录表 |
三 关于model
3.1 model画图
画图前,先指定如下三处
必须有个开始 和 结束 :
加入任务:
注意:双击节点可以指定节点名称
指定任务办理人:
然后到下一步(此时是以写死的方式来指定办理人的)
完成整个流程绘图后,点击左上角保存
点击保存后出现如下框,填写完 点保存就好
动态分配办理人
方式1:指定变量
此时是指定了一个表达式的样子来指定的动态办理人,为该表达式赋值的时机是流程到达该节点之前,如果该节点之前是 开始 ,那么就在流程启动的时候指定,如果该节点之前是 任务 ,那么就在完成上一步任务的同时指定下一步办理人。
如何加流程参数在本文档的第九节。
方式2:指定对象属性名
在启动流程或者完成上一步任务办理的同时,指定一个user对象进去就好了。
如何加流程参数在本文档的第九节。
方式3:方法表达式
注意:userService是Spring容器中的一个bean实例,对应调用getUsername()方法。
如何加流程参数在本文档的第九节。
3.2 操作model
3.2.1 模型查询对象
@Autowired RepositoryService repositoryService; /** * 查询所有流程定义模型 */ @Test public void modelList() { ModelQuery query = repositoryService.createModelQuery(); List<Model> list = query.orderByCreateTime() .desc() .list(); for (Model model : list) { System.out.print("模型id:" + model.getId()); System.out.print(", 模型名称:" + model.getName()); System.out.print(",模型描述: " + model.getMetaInfo()); System.out.print(",模型标识key:" + model.getKey()); System.out.println(",模型版本号:" + model.getVersion()); } }
3.2.2 删除模型对象
@Autowired RepositoryService repositoryService; /** * 删除流程定义模型 * 会影响到的表:ACT_RE_MODE 和 ACT_GE_BYTEARRAY */ @Test public void deleteModel() { // modelId 为表ACT_RE_MODE 中的ID字段 String modelId = "0a499790-7c1d-11f6-9001-2c337a6d7e1d"; repositoryService.deleteModel(modelId); System.out.println("删除成功"); }
3.2.3 导出流程定义模型资源的zip压缩包
@Autowired RepositoryService repositoryService; @Test public void exportZip() throws IOException { // 1. 查询模型基本信息 (modelId 为表ACT_RE_MODE 中的ID字段) String modelId = "da0fbf5c-7c1d-11f6-949f-2c337a6d7e1d"; Model model = repositoryService.getModel(modelId); if(model != null) { // 2. 查询流程定义模型的json字节码 byte[] bpmnJsonBytes = repositoryService.getModelEditorSource(modelId); // 2.1 将json字节码转换为xml字节码 byte[] xmlBytes = bpmnJsonXmlBytes(bpmnJsonBytes); if(xmlBytes == null) { System.out.println("模型数据为空-请先设计流程定义模型,再导出"); }else { // 压缩包文件名 String zipName = model.getName() + "." + model.getKey() + ".zip"; File file = new File("D:/" + zipName); FileOutputStream outputStream = new FileOutputStream(file); // 实例化zip输出流 ZipOutputStream zipos = new ZipOutputStream(outputStream); // 将xml添加到压缩包中(指定xml文件名:请假流程.bpmn20.xml ) zipos.putNextEntry(new ZipEntry(model.getName() + ".bpmn20.xml")); zipos.write(xmlBytes); // 3. 查询流程定义模型的图片字节码 byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId); if(pngBytes != null) { // 图片文件名(请假流程.leaveProcess.png) zipos.putNextEntry(new ZipEntry(model.getName() + "." + model.getKey() + ".png")); zipos.write(pngBytes); } // 4. 以压缩包的方式导出流程定义模型文件 zipos.closeEntry(); zipos.close(); System.out.println("导出流程定义模型zip成功"); } }else { System.out.println("模型不存在"); } } private byte[] bpmnJsonXmlBytes(byte[] jsonBytes) throws IOException { if(jsonBytes == null) { return null; } // 1. json字节码转成 BpmnModel 对象 JsonNode jsonNode = objectMapper.readTree(jsonBytes); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode); if(bpmnModel.getProcesses().size() == 0) { return null; } // 2. BpmnModel 对象转为xml字节码 byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel); return xmlBytes; }
3.2.4 导出流程定义模型资源的.xml文件
@Test public void exportXml() throws IOException { // 1. 查询模型基本信息 String modelId = "da0fbf5c-7c1d-11f6-949f-2c337a6d7e1d"; // 查询json字节码 byte[] bytes = repositoryService.getModelEditorSource(modelId); String filename = null; ByteArrayInputStream in = null; if(bytes != null) { // 1. json字节码转成 BpmnModel 对象 JsonNode jsonNode = objectMapper.readTree(bytes); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode); if(bpmnModel.getProcesses().size() > 0) { // 2. BpmnModel 对象转为xml字节码 byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel); in = new ByteArrayInputStream(xmlBytes); filename = StringUtils.isBlank(bpmnModel.getMainProcess().getName()) ? bpmnModel.getMainProcess().getId() : bpmnModel.getMainProcess().getName(); } } if(filename == null) { filename = "模型数据为空,请先设计流程,再导出"; in = new ByteArrayInputStream(filename.getBytes("utf-8")); } // 文件输出流 FileOutputStream out = new FileOutputStream(new File("D:/" + filename + ".bpmn20.xml")); IOUtils.copy(in, out); out.close(); in.close(); System.out.println("导出xml成功"); }
3.2.5 通过流程定义模型数据部署流程定义
/** * 会影响到的表: * ACT_RE_PROCDEF * ACT_RE_DEPLOYMENT * ACT_GE_BYTEARRAY * ACT_RE_MODEL 更新流程部署id,将模型与部署的流程定义绑定 * @throws Exception */ @Test public void deploy() throws Exception { // 1. 查询流程定义模型json字节码 String modelId = "b8efa921-da43-11eb-8aa7-2c337a6d7e1d"; byte[] jsonBytes = repositoryService.getModelEditorSource(modelId); if(jsonBytes == null) { System.out.println("模型数据为空,请先设计流程定义模型,再进行部署"); return; } // 将json字节码转为 xml 字节码,因为bpmn2.0规范中关于流程模型的描述是xml格式的,而activiti遵守了这个规范 byte[] xmlBytes = bpmnJsonXmlBytes(jsonBytes); if(xmlBytes == null) { System.out.println("数据模型不符合要求,请至少设计一条主线流程"); return; } // 2. 查询流程定义模型的图片 byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId); // 查询模型的基本信息 Model model = repositoryService.getModel(modelId); // xml资源的名称 ,对应act_ge_bytearray表中的name_字段 String processName = model.getName() + ".bpmn20.xml"; // 图片资源名称,对应act_ge_bytearray表中的name_字段 String pngName = model.getName() + "." + model.getKey() + ".png"; // 3. 调用部署相关的api方法进行部署流程定义 Deployment deployment = repositoryService.createDeployment() .name(model.getName()) // 部署名称 .addString(processName, new String(xmlBytes, "UTF-8")) // bpmn20.xml资源 .addBytes(pngName, pngBytes) // png资源 .deploy(); // 更新 部署id 到流程定义模型数据表中 model.setDeploymentId(deployment.getId()); repositoryService.saveModel(model); System.out.println("部署成功"); }
四 操作部署信息
4.1 部署
4.1.1通过zip压缩包进行部署流程定义
/** * 通过zip压缩包进行部署流程定义 */ @Test public void deployByZip() throws Exception { File file = new File("D:/请假流程模型xx.leaveProcess.zip"); // 创建输入流 FileInputStream inputStream = new FileInputStream(file); // 读取zip资源压缩包,转成输入流 ZipInputStream zipInputStream = new ZipInputStream(inputStream); Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .name("请假申请流程222-压缩包") .deploy(); // 4. 输出部署结果 System.out.println("部署ID: " + deployment.getId()); System.out.println("部署名称:" + deployment.getName()); }
4.1.2 通过.xml文件部署
@Test public void deployByFile() throws FileNotFoundException { File file = new File("D:/请假申请流程.bpmn20.xml"); // 文件输入流 FileInputStream inputStream = new FileInputStream(file); // 资源名称 String filename = file.getName(); // 调用相关api方法进行部署 Deployment deployment = repositoryService.createDeployment() .name("请假申请流程") .addInputStream(filename, inputStream) .deploy(); // 输出部署结果 System.out.println("部署ID: " + deployment.getId()); System.out.println("部署名称:" + deployment.getName()); }
4.2 删除部署流程定义
/** * 根据部署ID来删除部署的流程定义数据 * 影响到的表: * ACT_GE_BYTEARRAY * ACT_RE_DEPLOYMENT * ACT_RE_PROCDEF * ACT_RU_EVENT_SUBSCR * ACT_RU_IDENTITYLINK */ @Test public void delete() { try { String deploymentId = "f88ccd79-db17-11eb-a1e0-2c337a6d7e1d"; // 默认不是级联删除操作,如果有正在执行的流程实例,则删除时会抛出异常,并且不会删除历史表数据 //repositoryService.deleteDeployment(deploymentId); // 如果为true则是级联删除操作(小心使用~) // 如果流程定义启动了对应的流程实例,也可以进行删除,并且会删除历史数据 //repositoryService.deleteDeployment(deploymentId, true); System.out.println("删除流程定义数据成功"); }catch (Exception e) { e.printStackTrace(); if(e.getMessage().indexOf("a foreign key constraint fails") > 0) { System.out.println("有正在执行的流程实例,不允许删除"); }else { System.out.println("删除失败,原因: " + e.getMessage()); } } }
4.3 启动流程
@Autowired RuntimeService runtimeService; @Test public void startProcessInstance() { // 启动流程实例(流程定义key processDefinitionKey) // 通过流程定义key启动的流程实例 ,找的是最新版本的流程定义数据 // 根据key部署 这个案例里"leaveProcess" 为部署的key ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess"); System.out.println("流程定义id: " + processInstance.getProcessDefinitionId()); System.out.println("流程实例id: " + processInstance.getId()); }