前言
到本篇为止,我们已经完成了流程定义以及其 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 对象,用于配置流程引擎的相关属性。* 该参数不能为空,且应为已初始化的配置对象。** 此方法通过设置活动字体、标签字体和注解字体为“宋体”,确保生成的流程图中中文能够正确显示。*/@Overridepublic 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 {@Serialprivate 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 = 1pageSize.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