Flowable7.x学习笔记(十四)查看部署流程Bpmn2.0-xml

前言

Flowable 在其前端 Modeler 中,采用 BPMN 2.0 标准将流程中的任务、网关、事件等元素以 XML 形式存储,并附带图形互换(Diagram Interchange,DI)数据,以保证在前端与后端都能精准重建可视化流程图。

生成的 .bpmn.bpmn20.xml 文件会随业务归档(BAR)一起部署到 Flowable 引擎,并被存储在数据库中。当部署完成后,流程引擎会读取并解析这些 XML 文件,构建内部执行模型,用于后续实例创建与任务调度。本文将采用后端API的方式将XML查询出来在前端界面展示。

一、XML展示的作用

① 可视化编辑

由于 XML 文件包含 DI 信息,可借助 bpmn-js、Flowable Modeler 或其他 BPMN 渲染库,将其加载至前端,实时重现可拖拽的流程图界面,并支持恢复图元位置、节点属性等,方便在线修订。

② 审计与对比

流程定义以文本 XML 形式存储后,可轻松纳入 Git 等版本控制工具,通过 diffblame 等功能直观查看变更记录,并支持快速回退、历史审计与协同评审。

二、XML展示的必要性

① 协作与共享

导出后的 XML 文件可在设计、开发与运维团队之间共享,确保各方在同一流程版本上评审与协作,减少沟通成本与误解。

② 自动化测试与部署

CI/CD 流水线可自动拉取并部署导出的 XML,触发集成测试和端到端验证,保证流程逻辑在不同环境中的一致性与可重复性。

③ 合规与审计

在金融、医疗等高合规行业,需将流程定义以 XML 形式存档,供审计机构或内控团队查验。导出功能可生成可追溯的流程快照,证明流程设计未经篡改。

④ 平台互操作

标准化的 BPMN 2.0 XML 文件能与 Camunda、jBPM 等第三方流程引擎互操作,便于跨平台迁移与系统整合,提升整体架构的灵活性。

三、后端完成查询功能

① 明确http请求参数

此处我们只需要部署的流程的定义ID,具体信息都由ID查询出来,保证数据的确定性

复制代码
package com.ceair.entity.request;

import lombok.Data;

import java.io.Serial;
import java.io.Serializable;

/**
 * @author wangbaohai
 * @ClassName QueryImageOrXmlReq
 * @description: 查询部署流程定义的图片或xml文件请求对象
 * @date 2025年04月22日
 * @version: 1.0.0
 */
@Data
public class QueryImageOrXmlReq implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 流程定义ID
     */
    private String processDefinitionId;

}

② 创建服务接口

复制代码
/**
 * 根据查询条件生成并返回查询的XML字符串
 * 此方法用于将查询条件封装对象转换为XML格式的字符串,以便进行后续的查询操作
 * 它接受一个包含查询条件的请求对象,可以是图像信息或XML格式的查询条件
 *
 * @param queryImageOrXmlReq 包含查询条件的请求对象,可以是图像信息或XML格式的查询条件
 * @return 返回根据查询条件生成的XML字符串
 */
String queryXML(QueryImageOrXmlReq queryImageOrXmlReq);

③ 实现服务接口

此处是实现导出的核心部署,大致的思路是:接收一个包含 流程定义 ID 的请求对象,校验参数无误后,从 Flowable 的 RepositoryService 中读取对应流程定义的 XML 资源,最后将 XML 内容先进行 Base64 编码,再做 URL 安全转码返回给调用方。在整个过程中,通过详尽的日志和多层异常捕获,保证了调用链的可观察性与健壮性。

1)安全与健壮

参数校验 + 多层异常捕获,避免非法调用和运行时崩溃。

2)兼容前端

Base64 + URL 编码后的字符串,可直接在浏览器端 decodeURIComponent(atob(...)) 复原,适配多数 JS 环境。

3)可扩展

若 XML 内容较大,可考虑流式分块返回或引入压缩(如 GZIP)以降低网络带宽;对于高并发场景,可将 XML 缓存到 Redis 等中间件,减少对 Flowable 引擎的频繁访问。

具体代码如下:

复制代码
/**
 * 根据流程定义ID查询流程XML信息
 *
 * @param queryImageOrXmlReq 查询请求对象,包含流程定义ID
 * @return 返回流程XML的Base64编码字符串
 * @throws IllegalArgumentException 如果请求对象或流程定义ID为空
 * @throws BusinessException        如果流程定义不存在或读取资源时发生业务异常
 */
@Override
public String queryXML(QueryImageOrXmlReq queryImageOrXmlReq) {
    // 参数校验:确保请求对象不为空
    if (queryImageOrXmlReq == null) {
        log.error("获取流程XML失败,原因:请求对象不能为空");
        throw new IllegalArgumentException("获取流程XML失败,原因:请求对象不能为空");
    }

    String processDefinitionId = queryImageOrXmlReq.getProcessDefinitionId();

    // 参数校验:确保流程定义ID不为空或空字符串
    if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
        log.error("获取流程XML失败,原因:流程定义ID不能为空或空字符串");
        throw new IllegalArgumentException("获取流程XML失败,原因:流程定义ID不能为空或空字符串");
    }

    // 根据流程定义ID查询流程定义
    ActReProcdef actReProcdef = getById(processDefinitionId);
    if (actReProcdef == null) {
        log.error("获取流程XML失败,原因:流程定义不存在,流程定义ID:{}", processDefinitionId);
        throw new BusinessException("获取流程XML失败,原因:流程定义不存在,流程定义ID:" + processDefinitionId);
    }

    // 获取流程图资源流
    try (InputStream xmlStream = repositoryService.getResourceAsStream(actReProcdef.getDeploymentId(),
            actReProcdef.getResourceName())) {
        if (xmlStream == null) {
            log.error("获取流程XML失败,原因:资源流为空,流程定义ID:{}", processDefinitionId);
            throw new BusinessException("获取流程XML失败,原因:资源流为空,流程定义ID:" + processDefinitionId);
        }

        // 将资源流内容读取为字节数组并进行Base64编码
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = xmlStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        // 使用 UrlEncoder 进行编码
        return URLEncoder.encode(Base64.getEncoder().encodeToString(outputStream.toByteArray()),
                StandardCharsets.UTF_8);
    } catch (IOException e) {
        // 捕获IO异常并记录日志
        log.error("获取流程XML失败,原因:IO异常,流程定义ID:{}", processDefinitionId, e);
        throw new BusinessException("获取流程XML失败,原因:IO异常", e);
    } catch (Exception e) {
        // 捕获其他未知异常并记录日志
        log.error("获取流程XML失败,原因:未知异常,流程定义ID:{}", processDefinitionId, e);
        throw new BusinessException("获取流程XML失败,原因:未知异常", e);
    }
}

④ 创建功能接口

复制代码
// 使用Spring Security的注解进行权限控制,只有拥有特定权限的用户才能访问此方法
@PreAuthorize("hasAnyAuthority('/api/v1/actReProcdef/queryXml')")
// 定义参数对象的元数据,用于API文档生成
@Parameter(name = "queryImageOrXmlReq", description = "查询流程定义图片请求对象", required = true)
// 定义操作的元数据,用于API文档生成
@Operation(summary = "查询流程定义XML")
// 声明这是一个POST类型的HTTP请求处理方法
@PostMapping("/queryXml")
public Result<String> queryXML(@RequestBody QueryImageOrXmlReq queryImageOrXmlReq) {
    try {
        // 调用服务层方法执行流程定义操作,传入请求对象并获取操作结果
        String xml = actReProcdefService.queryXML(queryImageOrXmlReq);

        // 返回操作成功的响应结果
        return Result.success(xml);
    } catch (Exception e) {
        // 捕获异常,记录详细的错误日志,并返回包含失败原因的响应结果
        log.error("查询流程定义XML失败 具体原因为 : {}", e.getMessage());
        return Result.error("查询流程定义XML失败,失败原因:" + e.getMessage());
    }
}

四、前端完成查询功能

① 安装 XML 美化插件

本文采用Vue3的美化插件-pretty-xml-vue3,各位可以选择其他工具,使用方法各工具根据官方文档即可。

安装命令:yarn add pretty-xml-vue3 -D

安装结果:

② 引入样式

在 main.ts 引入样式【import "pretty-xml-vue3/style.css"】,此步骤不可跳过,否则高亮配色不会生效

③ 声明declare

此步骤是解决type校验错误提示

// pretty-xml-vue3

declare module 'pretty-xml-vue3';

④ 创建接口

复制代码
// 查询流程定义的xml
export function queryXml(data: QueryImageOrXmlReq) {
  return request.post<any>({
    url: '/pm-process/api/v1/actReProcdef/queryXml',
    data,
  })
}

⑤ index.vue 引入xml工具

// 引入 xml 美化工具

import { PrettyXml } from 'pretty-xml-vue3'

⑥ 创建查看xml按钮方法

复制代码
/**
 * 异步函数:用于显示流程定义的 XML 信息
 *
 * @param data ActReProcdefVO 类型的对象,包含流程定义的相关信息
 */
async function onShowXml(data: ActReProcdefVO) {
  try {
    // 组装查询参数,包括流程定义 ID
    const param: QueryImageOrXmlReq = {
      processDefinitionId: data.id,
    }

    // 调用后端接口获取流程定义的 XML 数据
    const result: any = await queryXml(param)

    // 判断查询结果是否成功
    if (result.success && result.code === 200) {
      // 第一步:URL 解码,得到 Base64
      const base64Str: string = decodeURIComponent(result.data)
      // 第二步:Base64 解码为二进制字符串
      const binaryStr = atob(base64Str)
      // 第三步:转为 UTF-8 文本
      const bytes = Uint8Array.from(binaryStr, c => c.charCodeAt(0))

      // 获取数据
      xmlData.value = new TextDecoder('utf-8').decode(bytes)

      // 打开 XML 对话框
      showXml.value = true
    }
    else {
      // 提示操作失败的错误提示信息
      ElMessage({
        message: `查询失败原因:${result.message}`,
      })
    }
  }
  catch (error) {
    // 捕获异常并提取错误信息
    let errorMessage = '未知错误'
    if (error instanceof Error) {
      errorMessage = error.message
    }

    // 显示操作失败的错误提示信息
    ElMessage({
      message: `查询失败: ${errorMessage || '未知错误'}`,
      type: 'error',
    })
  }
}

⑦ 创建查询xml按钮

⑧ 创建xml展示区域

此处需要说明的是,这里使用了xml美化工具,需要指定标签 【PrettyXml】

复制代码
  <!-- XML 展示弹出框 -->
  <el-dialog v-model="showXml" title="XML 展示" width="30%">
    <div class="xml-container">
      <PrettyXml :xml="xmlData" :options="{ shortRecord: true }" />
    </div>
  </el-dialog>

.xml-container {

min-height: 300px;

max-height: 600px;

width: 100%;

overflow-x: unset;

overflow-y: scroll;

}

五、分配权限

① 新增按钮

② 分配权限

给当前admin用户的角色【超级管理员】分配按钮权限

六、结果验证

七、后记

本篇文章的前后端仓库地址请查询专栏第一篇文章,后续打算把xml和流程图片展示出来

本文的后端分支是 process-7

本文的前端分支是 process-9

相关推荐
绵绵细雨中的乡音17 分钟前
Linux进程学习【基本认知】
linux·运维·学习
时间之城22 分钟前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
灏瀚星空30 分钟前
用Obsidian四个插件打造小说故事关联管理系统:从模板到图谱的全流程实践
经验分享·笔记·开源
一只可爱的小猴子1 小时前
2022李宏毅老师机器学习课程笔记
人工智能·笔记·机器学习
孞㐑¥1 小时前
C++11介绍
开发语言·c++·经验分享·笔记
使一颗心免于哀伤1 小时前
《重构》笔记摘录 - 9.处理继承关系
笔记
再睡一夏就好1 小时前
Linux常见工具如yum、vim、gcc、gdb的基本使用,以及编译过程和动静态链接的区别
linux·服务器·c语言·c++·笔记
俺的图图呢?2 小时前
Django笔记——CSRF
笔记·django·csrf
我的golang之路果然有问题2 小时前
快速了解redis,个人笔记
数据库·经验分享·redis·笔记·学习·缓存·内存
无心水2 小时前
【Java面试笔记:基础】8.对比Vector、ArrayList、LinkedList有何区别?
java·笔记·面试·vector·arraylist·linkedlist