前言
到本篇为止,我们已经完成了流程定义以及其 BPMN XML 本身的查询和新增功能,那我们有有了XML之后就可以开始着手研究实现 Flowable7对流程的各种操作了,比如部署,挂起,发起等等。
首先第一步,我们本篇文章先来探讨一下 BPMN XML 的部署(deploy)的知识点以及基于当前框架的实践操作。
一、部署(deploy)是什么?
部署(deploy)是 Flowable7 中将流程定义、表单、决策规则等资源打包、解析、校验并持久化到引擎数据库的关键环节。
二、部署(deploy)发挥的作用
① 资源解析与合法性校验
Flowable 在部署阶段将 BPMN XML 转为内存模型(BpmnModel
),并对流程结构(如 StartEvent、连线正确性等)进行基础校验,以避免运行时异常。
② 持久化部署数据
部署操作会写入多张数据库表:
ACT_RE_DEPLOYMENT:部署元信息;
ACT_GE_BYTEARRAY:流程定义、图像等二进制资源;
ACT_RE_PROCDEF:流程定义记录,包含 key、version、deploymentId 等字段。
③ 流程定义注册
只有部署后的流程定义才能被 RuntimeService.startProcessInstanceByKey()
识别并实例化,部署是流程从“设计”到“执行”的必要桥梁。
④ 版本管理
每次部署同一流程 key 时,Flowable 会在 ACT_RE_PROCDEF.version
上递增版本号,旧版可继续服务已运行实例,新版可用于新实例,实现灰度发布和快速回滚。
⑤ 集群高可用
集群模式下,各节点共享同一数据库的部署信息,确保任意节点都能加载相同流程定义,实现负载均衡与故障切换。
⑥ 审计与追踪
部署记录与资源持久化后,可通过 API 或管理界面查询历史部署、导出流程图等,对合规审计和故障排查至关重要。
三、执行部署(deploy)的必要性
① 流程实例启动前提
未部署的流程定义对引擎“不可见”,无法创建实例,也无法执行任何任务或事件。
② 提前发现设计问题
部署时的解析和校验能在 CI/CD 流程中及早捕获模型缺陷,降低生产环境风险。
③ 支持多版本并存
通过部署版控,可平滑升级流程、验证新版行为,并在出现问题时迅速回滚到旧版,提高系统可靠性。
④ 资源统一管理
将流程、表单与决策规则集中存储并分类(如设置 name
、category
、tenantId
),便于检索与运维管理。
⑤ 程序化与自动化
无论是通过 Java API、Spring Boot 自动部署还是 REST 接口,部署都可集成至 DevOps 管道,确保每次版本发布都能稳定、可控。
四、后端:完成部署功能
① 创建一个常量类
package com.ceair.constant;
/**
* @author wangbaohai
* @ClassName Flowable7Constants
* @description: Flowable7 常量类
* @date 2025年04月18日
* @version: 1.0.0
*/
public class Flowable7Constants {
/**
* 流程 xml 文件后缀
*/
public static final String BPMN_XML_SUFFIX = ".bpmn20.xml";
}
② 创建一个流程状态枚举类
package com.ceair.enums;
import lombok.Getter;
/**
* @author wangbaohai
* @ClassName ProcessStatus
* @description: 流程状态
* @date 2025年04月18日
* @version: 1.0.0
*/
@Getter
public enum ProcessStatus {
/**
* 0:草稿
*/
DRAFT(0, "草稿"),
/**
* 0:发布
*/
PUBLISH(1, "发布"),
/**
* 0:草稿
*/
DEACTIVATE(2, "停用");
/**
* 状态码
*/
private final Integer code;
/**
* 状态描述
*/
private final String value;
ProcessStatus(Integer code, String value) {
this.code = code;
this.value = value;
}
}
③ 创建一个流程引擎配置类
主要是为了让Flowable支持中文,具体什么字体可以自由指定,我使用【宋体】
package com.ceair.config;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
/**
* @author wangbaohai
* @ClassName FlowableConfig
* @description: Flowable配置类
* @date 2025年04月18日
* @version: 1.0.0
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
/**
* 配置流程引擎的字体设置,以解决流程图中中文显示乱码的问题。
*
* @param engineConfiguration SpringProcessEngineConfiguration 对象,用于配置流程引擎的相关属性。
* 该参数不能为空,且应为已初始化的配置对象。
*
* 此方法通过设置活动字体、标签字体和注解字体为“宋体”,确保生成的流程图中中文能够正确显示。
*/
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
// 设置流程图中活动节点的字体为宋体,避免中文乱码问题
engineConfiguration.setActivityFontName("宋体");
// 设置流程图中标签的字体为宋体,确保标签中的中文正常显示
engineConfiguration.setLabelFontName("宋体");
// 设置流程图中注解的字体为宋体,防止注解内容出现中文乱码
engineConfiguration.setAnnotationFontName("宋体");
}
}
④ 创建一个请求类
package com.ceair.entity.request;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author wangbaohai
* @ClassName DeployProcessDefinitionXmlReq
* @description: 部署流程定义XML请求对象
* @date 2025年04月18日
* @version: 1.0.0
*/
@Data
public class DeployProcessDefinitionXmlReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程唯一标识(业务中使用的 key)
*/
private String processKey;
/**
* 流程版本号
*/
private Integer processVersion;
}
⑤ 创建一个部署服务
/**
* 部署BPMN XML文件的函数。
*
* 该函数接收一个包含流程定义XML部署请求的对象,并返回部署结果。
*
* @param deployProcessDefinitionXmlReq 包含流程定义XML部署请求的参数对象。
* 该对象通常包含BPMN XML文件的内容、部署的相关配置信息等。
*
* @return Boolean 返回部署结果。
* - 如果部署成功,返回true;
* - 如果部署失败,返回false。
*/
Boolean deployBpmnXml(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq);
⑥ 创建服务的实现
private final RepositoryService repositoryService;
/**
* 部署 BPMN XML 流程定义。
*
* @param deployProcessDefinitionXmlReq 包含部署流程定义所需信息的请求对象。
* 必须包含有效的流程定义标识和相关信息。
* @return 返回布尔值,表示部署是否成功。如果方法执行完成且未抛出异常,则返回 true。
* @throws BusinessException 如果在部署过程中发生业务异常,则抛出此异常。
* 异常信息会记录日志并重新抛出。
* @throws Exception 如果在部署过程中发生未知异常,则包装为 BusinessException 抛出。
*/
@Override
public Boolean deployBpmnXml(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
try {
// 校验请求参数的合法性,确保输入数据符合业务要求
validateRequest(deployProcessDefinitionXmlReq);
// 查询流程定义信息,获取与请求参数匹配的流程定义对象
ProcessDefinition processDefinition = fetchProcessDefinition(deployProcessDefinitionXmlReq);
// 获取流程定义对应的 BPMN XML 数据,用于后续部署操作
String bpmnXml = fetchBpmnXml(processDefinition);
// 使用 Flowable 的 RepositoryService 部署流程定义
repositoryService.createDeployment()
.addString(processDefinition.getProcessKey() + Flowable7Constants.BPMN_XML_SUFFIX, bpmnXml)
.name(processDefinition.getProcessName())
.key(processDefinition.getProcessKey())
.deploy();
// 将流程定义的状态变更为 1:发布
processDefinition.setProcessStatus(ProcessStatus.PUBLISH.getCode());
updateById(processDefinition);
} catch (BusinessException e) {
// 记录业务异常日志,并重新抛出异常以便调用方处理
log.error("部署流程时出现业务异常,具体异常原因: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
// 记录未知异常日志,并将异常包装为 BusinessException 抛出
log.error("部署流程时出现未知异常,具体异常原因: {}", e.getMessage(), e);
throw new BusinessException("部署流程时出现未知异常", e);
}
return true;
}
// 参数校验方法
private void validateRequest(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
if (deployProcessDefinitionXmlReq == null
|| StringUtils.isBlank(deployProcessDefinitionXmlReq.getProcessKey())
|| deployProcessDefinitionXmlReq.getProcessVersion() == null) {
log.error("部署流程时参数校验失败,请重新选择流程定义");
throw new BusinessException("部署流程时参数校验失败,请重新选择流程定义");
}
}
// 查询流程定义信息方法
private ProcessDefinition fetchProcessDefinition(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
ProcessDefinition processDefinition = lambdaQuery()
.eq(ProcessDefinition::getProcessKey, deployProcessDefinitionXmlReq.getProcessKey())
.eq(ProcessDefinition::getProcessVersion, deployProcessDefinitionXmlReq.getProcessVersion())
.one();
if (processDefinition == null) {
log.error("部署流程时流程定义已不存在");
throw new BusinessException("部署流程时流程定义已不存在");
}
return processDefinition;
}
// 获取 BPMN XML 数据方法
private String fetchBpmnXml(ProcessDefinition processDefinition) {
Optional<BpmnDefinitions> bpmnDefinitions = bpmnDefinitionRepository.findById(processDefinition.getXmlMongoId());
return bpmnDefinitions.map(def -> {
String bpmnXml = def.getBpmnXml();
if (StringUtils.isBlank(bpmnXml)) {
log.error("部署流程时流程定义的 BPMN XML 数据为空");
throw new BusinessException("部署流程时流程定义的 BPMN XML 数据为空");
}
return bpmnXml;
}).orElseThrow(() -> {
log.error("部署流程时流程定义的 BPMN XML 数据为空");
return new BusinessException("部署流程时流程定义的 BPMN XML 数据为空");
});
}
⑦ 创建接口
/**
* 部署流程定义XML的接口方法。
*
* 该方法通过接收一个部署流程定义XML的请求对象,调用服务层方法完成流程定义的部署。
* 如果部署过程中发生异常,会根据异常类型返回具体的错误信息。
*
* @param deployProcessDefinitionXmlReq 部署流程定义XML请求对象,包含部署所需的必要信息(必填)。
* @return 返回一个Result对象,包含布尔值表示部署是否成功。
* - 如果成功,返回Result.success(true)。
* - 如果失败,返回Result.error(),并附带具体的错误信息。
*/
@PreAuthorize("hasAnyAuthority('/api/v1/definition/deployProcessDefinitionXml')")
@Parameter(name="deployProcessDefinitionXmlReq", description = "部署流程定义XML请求对象", required = true)
@Operation(summary = "部署流程定义XML")
@PostMapping("/deployProcessDefinitionXml")
public Result<Boolean> deployProcessDefinitionXml(@RequestBody DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
try {
// 调用服务层方法部署流程定义XML,并返回成功结果
return Result.success(processDefinitionService.deployBpmnXml(deployProcessDefinitionXmlReq));
} catch (BusinessException e) {
// 捕获业务逻辑异常,返回具体的错误信息
return Result.error("部署流程定义失败,发生已知业务异常,具体原因:" + e);
} catch (Exception e) {
// 捕获其他未知异常,返回通用错误信息
return Result.error("部署流程定义失败,发生未知业务异常,具体原因:" + e);
}
}
五、前端:完成部署界面
① 创建请求对象
// 发布流程 xml 请求对象
export interface PublishProcessDefinitionXmlReq {
processKey?: string // 流程唯一标识(业务中使用的 key)
processVersion?: number
}
② 创建请求接口
// 发布流程定义 xml
export function publishProcessDefinition(data: PublishProcessDefinitionXmlReq) {
return request.post<any>({
url: '/pm-process/api/v1/definition/deployProcessDefinitionXml',
data,
})
}
③ 引入请求接口
④ 创建按钮方法
/**
* 发布流程定义的异步函数。
*
* @param {ProcessDefinitionVO} row - 包含流程定义信息的对象,需提供 processKey 和 processVersion 属性。
* @returns {Promise<void>} - 无返回值,函数主要通过调用接口和更新状态来完成操作。
*/
async function onPublishProcessDefinition(row: ProcessDefinitionVO) {
try {
// 调用发布流程定义的接口,并传递流程键和版本信息
const result: any = await publishProcessDefinition({
processKey: row.processKey,
processVersion: row.processVersion,
})
// 根据接口返回的结果判断发布是否成功,并执行相应操作
if (result.success && result.code === 200) {
// 如果发布成功,提示用户操作成功
ElMessage({
message: '发布成功',
type: 'success',
})
// 初始化分页参数为默认值:当前页为第一页,每页显示10条数据
currentPage.value = 1
pageSize.value = 10
// 清空流程名称的输入框内容
processName.value = ''
// 重新获取流程定义数据以刷新页面
getProcessDefinitionPageData()
}
else {
// 如果发布失败,提示用户具体的错误信息
ElMessage({
message: result.message,
})
}
}
catch (error) {
// 捕获异常并提取错误信息,确保提供有意义的错误提示
let errorMessage = '未知错误'
if (error instanceof Error) {
errorMessage = error.message
}
// 显示异常错误消息,提示用户发布失败的具体原因
ElMessage({
message: `发布失败: ${errorMessage || '未知错误'}`,
type: 'error',
})
}
}
⑤ 操作列增加按钮
<!-- 发布流程功能按钮 -->
<el-button type="primary" @click="onPublishProcessDefinition(scope.row)">
发布
</el-button>
六、添加权限
① 增加按钮
② 绑定角色权限
七、XML开启允许发布
填写基础信息并且按照图中勾选即可
八、执行发布操作以及验证
验证 【ACT_RE_DEPLOYMENT】表中的NAME和KEY字段都和我们自己的流程定义的对应
验证 【ACT_GE_BYTEARRAY】
验证【ACT_RE_PROCDEF】
后记
本篇文章的前后端仓库地址请查询专栏第一篇文章
本文的后端分支是 process-4
本文的前端分支是 process-6