外观
流程事件
流程事件是在流程实例运行过程中产生,在流程事件中我们可以配置远程接口调用、远程消息通知、以及一些复杂业务逻辑性处理。
事件类型
事件在类型上分为全局和节点,他们在配置内容上相同,粒度上不同。下面将介绍各个事件的配置
全局事件
全局事件的配置是在流程插件上,执行上支持指定时机。
流程结束事件
流程结束事件是在流程结束时触发,执行上有两种场景:
正常结束:流程实例正常结束,无人工终止。
人工终止:人为原因操作了 人工终止、撤销、超管终止 等触发
开始事件
开始事件位于流程的开始处,流程实例启动触发
用户任务
服务任务
结束事件
流程正常结束触发
事件内容配置
数据模型
传输变量
json
{
// 业务对象
"busData": {},
// 流程实例
"bpmInstance": {
// 实例ID
"id": "",
// 标题
"title": "",
// 定义ID
"defId": "",
// Activiti实例ID
"actInstId": "",
// 父实例Id
"parentId": "",
// 流程KEY
"defKey": "",
// 业务ID
"bizId": "",
// 状态
"status": "",
// 是否支持移动端 1:是 0:否
"supportMobile": 1,
// 创建时间
"createTime": "",
// 完成时间
"endTime": "",
// 创建人ID
"createBy": ""
},
// 流程任务
"bpmTask": {
// 任务ID
"id": "",
// 任务名称
"name": "",
// 节点KEY
"nodeKey": "",
// 标题
"title": "",
// 实例ID
"instId": "",
// activiti任务ID
"actTaskId": "",
// activiti执行ID
"actExecutionId": "",
// 流程定义ID
"defId": "",
// 任务状态 normal:普通 designate:指派 lock:锁定 sign:会签 hang:挂起 turn:转办 agent:代理 reject:驳回 drag:捞单
"status": "",
// 挂起状态 1:是 0:否
"suspensionState": 0,
// 任务执行人说明
"assigneeNames": "",
// 任务紧急程度
"priority": 50,
// 支持移动端 1:是 0:否
"supportMobile": 0,
// 驳回后返回节点
"backNode": "",
// 到期时间
"dueTime": "",
// 任务类型 userTask:用户任务 serviceTask:服务任务 signTask:会签任务 signSource:会签源任务 agentTask:代理任务
"taskType": "",
// 父任务ID
"parentId": "",
// 任务分类编码
"typeCode": ""
},
// 流程意见
"submitOpinion": "",
// 流程按钮
"submitActionName": "",
// 流程按钮名称
"submitActionDesc": "",
// 流程变量
"variables": {},
// 事件类型
"eventType": "",
// 当前用户
"currentUser": {
// 用户ID
"id": "",
// 用户名
"username": "",
// 用户姓名
"fullName": ""
}
}
远程 HTTP 调用
事件执行方式上通过远程HTTP调用
服务端接口定义规范
TIP
如提供接口服务端使用的 Java 语言,可引入 AgileBPM 提供的依赖来接收参数
引入依赖
xml
<dependency>
<groupId>com.dstz</groupId>
<artifactId>ab-wf-client</artifactId>
<version>xxx</version>
</dependency>
接收类
com.dstz.bpm.api.dto.invoke.BpmPluginGlobalInvokeDataDTO
响应类
com.dstz.base.api.vo.ApiResponse
com.dstz.bpm.api.vo.BpmPluginGlobalInvokeVO
请求方式:POST
请求类型:application/json;charset=UTF-8
请求体:事件内容配置-数据模型-传输变量
响应内容
json
{
"isOk": true,
"code": "Success",
"message": "操作成功",
// 此处未同步返回
"data": {
// 是否跳过当前任务
"skipCurrentTask": false,
// 设置流程变量
"newVariables": {},
// 设置流程标题
"resetInstanceSubject": null,
// 服务任务支持多种形式的反馈 throw businessExcetion 为中断操作 mark success 为默认操作,即反馈调用成功,如果marked error 则流程图,意见列表均提示异常
"serviceTaskMark": "success",
// 服务任务时支持通过该参数标记调用结果的备注信息
"serviceTaskMarkMsg": "",
// 业务数据
"resetBusData": {},
// 业务主键为流程实例与业务关联字段,bizKey会替换URL表单的主键标识,所以请合理设置
"bizKey": ""
}
}
接口地址
接口地址是一个 URL,在微服务下服务间调用需要使用lb://服务名/处理器路径
编写。地址中支持变量编写,运行中将对编写的变量做实际值替换。接口地址可支持多个,执行上顺序调用
地址变量
变量语法:{xxx[.xxx]}
{bizId} 流程示例绑定的业务主键
{token} 请求 Token,从请求头 Authoziation 中获取
{bpmDefinition.xxx} 获取流程定义属性,参考类
com.dstz.bpm.core.entity.BpmDefinition
;示例:流程名称:{bpmDefinition.name}、流程 KEY:{bpmDefinition.key}、流程 ID:{bpmDefinition.id}{bpmInstance.xxx} 获取流程实例属性,参考类
com.dstz.bpm.core.entity.BpmInstance
;示例:流程实例 ID:{bpmInstance.id}、关联业务 ID:{bpmInstance.bizId}{bpmProperty.xxx} 获取系统属性定义值,参考菜单
系统 》系统属性
;示例:某系统访问 token:{bpmProperty.xxx_token}{requestCookie.xxx} 从当前请求 Cookie 中获取值;示例:{requestCookie.JSESSIONID}
{requestHeader.xxx} 从当前请求头中获取值;示例:{requestHeader.Authorization}
接口地址示例
http://{bpmProperty.bizApiServer}/flowRpc/{bpmDefinition.key}
异步
- 异步的使用需要搭配
事务消息组件
,请求将在整个事务执行成功后调用,关于事务消息参考事务消息文档。 - 同步调用需要等待结果返回。
传输变量选择
订阅接口需要的数据
指定执行时机
订阅只在指定时机下执行
使用示例
示例中我们将使用 NodeJS 搭建一个服务接口,并配置流程事件调用服务接口
创建 NODEJS 服务接口
shell
# 创建目录
$ mkdir webserver
# 安装express
$ cnpm install express
$ vim index.js
const express = require("express")
const app = express()
app.use(express.json())
app.post('/flow-event/:defKey', function (req, res) {
console.log(['*'.repeat(50), `请求地址:${req.url}`, `请求参数:${JSON.stringify(req.params)}`, `请求体:${JSON.stringify(req.headers)}`, `请求体:${JSON.stringify(req.body)}`, '*'.repeat(50),].join('\n'))
res.send({isOk: true, code: 'Success', 'message': '执行成功'})
})
app.listen(5000, () => {
console.log(`服务已启动,监听端口:5000`)
})
# 运行服务
$ node index.js
配置流程事件
启动流程实例
接口服务端输出日志
事务消息
使用事务消息需要提前启用事务消息组件,有关如何启用事务消息组件请查询事务消息文档。
BpmPluginGlobalInvokeDataDTO 参数依赖于流程模块,编写处理方法前需提前在模块的 pom.xml 中引入依赖
xml
<dependency>
<groupId>com.dstz</groupId>
<artifactId>ab-wf-client</artifactId>
<version>${project.version}</version>
</dependency>
使用示例
- 编写处理方法
com.dstz.AbSpringBootApp
java
@AbMessage(subscribeKey = "flowEvent")
public void flowEvent(com.dstz.bpm.api.dto.invoke.BpmPluginGlobalInvokeDataDTO dataDTO) {
logger.info("流程事件-事务消息(flowEvent)处理:{}", JsonUtils.toJSONString(dataDTO));
}
- 流程事件配置
启动流程
控制台查看日志
2023-03-21 17:46:41.400 INFO 23185 --- [ trxredo-pool-1] com.dstz.AbSpringBootApp : 流程事件-事务消息(flowEvent)处理:{"busData":{"wbb":{"zfc2":"1","zfc1":"1","zfc3":"1","id":"1638114895946207232"}},"bpmInstance":{"id":"1638114895572914176","title":"系统管理员发起的流程事件","defId":"1638064724775600128","actInstId":"1638114896202059776","parentId":"","defKey":"lcsj","bizId":"1638114895946207232","status":"running","supportMobile":0,"suspensionState":0,"typeCode":"bglc","createTime":"2023-03-21 17:46:40","createBy":"1602918114232172634","createOrgId":"1602918114232172544","updater":"系统管理员"},"bpmTask":{"id":"1638114897389047808","name":"用户任务1","nodeKey":"UserTask1","title":"系统管理员发起的流程事件","instId":"1638114895572914176","actTaskId":"1638114897389047808","actExecutionId":"1638114896252391424","defId":"1638064724775600128","status":"normal","assigneeNames":"系统管理员","priority":50,"supportMobile":0,"taskType":"userTask","parentId":"","typeCode":"bglc"},"submitOpinion":"","submitActionName":"create","eventType":"TASK_CREATED","currentUser":{"id":"1602918114232172634","username":"admin","fullName":"系统管理员"}}
ServiceBean
包含业务处理的 Spring Bean,使用语法:beanId.methodName
,入参为com.dstz.bpm.engine.action.model.ActionModel
。
ActionModel 依赖于流程模块,编写处理方法前需提前在模块的 pom.xml 中引入依赖
xml
<dependency>
<groupId>com.dstz</groupId>
<artifactId>ab-spring-boot-wf-starter</artifactId>
<version>${project.version}</version>
</dependency>
使用示例
- 编写处理方法
java
package com.dstz;
import com.dstz.bpm.core.entity.BpmTask;
import com.dstz.bpm.engine.action.model.ActionModel;
import com.dstz.bpm.engine.action.model.TaskActionModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("bpmServiceBean")
public class BpmServiceBean {
private static final Logger logger = LoggerFactory.getLogger(BpmServiceBean.class);
public void execute(ActionModel actionModel) {
// 转换为任务
if (actionModel instanceof TaskActionModel) {
TaskActionModel taskActionModel = (TaskActionModel) actionModel;
BpmTask bpmTask = taskActionModel.getBpmTask();
logger.info("ServiceBean 节点KEY:{}, 节点名称:{}", bpmTask.getNodeKey(), bpmTask.getName());
}
}
}
- 流程事件配置
启动流程
控制台查看日志
2023-03-21 18:39:38.716 INFO 24363 --- [nio-8080-exec-9] com.dstz.BpmServiceBean : ServiceBean 节点KEY:UserTask1, 节点名称:用户任务1
Groovy 脚本
Groovy 脚本,一种对 Java 扩展了语法的脚本语言。可使用纯 Java 编写,也可以使用 Groovy 特性来编写。
Groovy 文档:https://groovy-lang.org
内置变量
bpmInstance
流程实例,对应类
com.dstz.bpm.core.entity.BpmInstance
bpmTask
流程任务(只在是任务节点时有值),对应类
com.dstz.bpm.core.entity.BpmTask
isTask
是否任务,取值:true/false
variableScope
流程变量,对应类
org.activiti.engine.delegate.VariableScope
;示例:获取流程变量variableScope.getVariable("xxx")
submitOpinion
提交意见
actionModel
处理模型,当为任务节点对应类
com.dstz.bpm.engine.action.model.ActionModel
,否则对应com.dstz.bpm.engine.action.model.ActionModel
currentUser
当前用户,对应类
com.dstz.base.common.script.variables.UserGroovyVariable
submitActionName
提交动作 KEY
startUser
申请人,对应类
com.dstz.base.common.script.variables.UserGroovyVariable
示例
- 远程接口调用
java
import com.dstz.base.common.enums.GlobalApiCodes
import com.dstz.base.common.exceptions.BusinessMessage
import com.dstz.base.common.utils.AbRestTemplateUtil
// 获取RestTemplate,微服务下参数传递true
final restTemplate = AbRestTemplateUtil.getRestTemplate(false)
// 发送请求 POST application/json
def apiResponse = restTemplate.postForObject("http://localhost:3000/flow-event", [taskName: bpmTask.getName(), taskKey: bpmTask.getNodeKey()], Map.class)
// 发送请求 POST application/x-www-form-urlencoded
def apiResponse = restTemplate.postForObject(
"http://localhost:3000/flow-event",
new LinkedMultiValueMap().tap {
add("taskKey", bpmTask.getNodeKey())
add("taskName", bpmTask.getName())
},
Map.class
)
// 发送请求 GET 占位符
def apiResponse = restTemplate.getForObject("http://localhost:3000/flow-event?taskKey={0}&taskName={1}", Map.class, bpmTask.getNodeKey(), bpmTask.getName())
// 发送请求 GET 命名占位符
def apiResponse = restTemplate.getForObject("http://localhost:3000/flow-event?taskKey={taskKey}&taskName={taskName}", Map.class, [taskKey: bpmTask.getNodeKey(), taskName: bpmTask.getName()])
// 处理请求返回
if (!apiResponse?.isOk) {
throw new BusinessException(GlobalApiCodes.REMOTE_CALL_ERROR.formatDefaultMessage(apiResponse.message))
}
- 修改流程实例标题
java
bpmInstance.setTitle("xxxxx")
bpmInstance.setHasUpdate(true)
- 修改数据表
java
import com.dstz.base.utils.AbDataSourceUtils
def jdbcTemplate = AbDataSourceUtils.getCurrentDataSource().getJdbcOperations()
jdbcTemplate.update("UPDATE xxx SET col = ? WHERE tak_id_ = ?", ['xxx', bpmTask.getId()])
- 流程变量
java
// 获取流程变量
variableScope.getVariable("xxx")
// 设置流程变量
variableScope.setVariable("orderNo", "xxx")
- 常用信息获取
java
// 是否任务,用在全局插件中判断是否任务节点;在用户任务节点事件中可直接使用 bpmTask, 无需判断
if (isTask) {
// 任务ID
final String taskId = bpmTask.getId()
// 任务名称
final String taskName = bpmTask.getName()
// 任务KEY
final String nodeKey = bpmTask.getNodeKey()
// 任务类型 userTask: 用户任务 serviceTask: 服务任务 signTask: 会签任务 signSource: 会签源任务 agentTask: 代理任务
final String taskType = bpmTask.getTaskType()
}
// 流程实例ID
final String instId = bpmInstance.getId()
// 流程标题
final String title = bpmInstance.getTitle()
// 关联业务主键
final String bizId = bpmInstance.getBizId()
// 流程分类编码
final String typeCode = bpmInstance.getTypeCode()
// 是否支持移动端
final boolean supportMobile = bpmInstance.getSupportMobile() as boolean
// 流程定义KEY
final String defKey = bpmInstance.getDefKey()
// 流程定义ID
final String defId = bpmInstance.getDefId()
// 当前用户ID
final String currentUserId = currentUser.getId()
// 当前用户名
final String currentUsername = currentUser.getUsername()
// 当前用户-组织ID
final String currentOrgId = currentUser.getOrgId()
// 当前用户-组织编码
final String currentOrgCode = currentUser.getOrgCode()
// 当前用户-组织名称
final String currentOrgName = currentUser.getOrgName()
// 发起人-用户ID
final String startUserId = startUser.getId()
// 发起人-用户名
final String startUsername = startUser.getUsername()
// 当前用户-组织ID
final String startUserOrgId = startUser.getOrgId()
// 当前用户-组织编码
final String startUserCode = startUser.getOrgCode()
// 当前用户-组织名称
final String startUserName = startUser.getOrgName()