Flowable7.x学习笔记(九)部署 BPMN XML 流程

发布于:2025-04-22 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言

        到本篇为止,我们已经完成了流程定义以及其 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 流程中及早捕获模型缺陷,降低生产环境风险​。

③ 支持多版本并存

        通过部署版控,可平滑升级流程、验证新版行为,并在出现问题时迅速回滚到旧版,提高系统可靠性​。

④ 资源统一管理

        将流程、表单与决策规则集中存储并分类(如设置 namecategorytenantId),便于检索与运维管理​。

⑤ 程序化与自动化

        无论是通过 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


网站公告

今日签到

点亮在社区的每一天
去签到