前言
Flowable 的流程图是 Flowable Modeler 或 Process Editor 中,使用拖拽和属性面板基于 BPMN 2.0 元素(如任务、网关、事件、序列流等)渲染出的业务流程图形界面。
一、将图形导出可查看的作用
① 可视化建模
帮助业务分析师和开发者共同设计复杂流程,免去了理解纯 XML 定义的障碍。
② 流程部署与运行
确保流程结构符合预期,再由 Flowable 引擎解析 XML 并生成执行计划。
③ 增强透明度与协作
可视化流程图让技术、业务和管理层共同站在"同一页面"审视流程逻辑,减少沟通歧义并加快决策速度。
④ 标准化与合规审计
BPMN 强制流程设计者将隐含或未文档化的业务规则以图形化符号和属性形式显式化,有助于流程审计与合规检查。
⑤ 培训与知识沉淀
流程图与文字说明协同构成易于理解的知识库,新成员可通过图示快速上手,减少培训成本并提升团队整体效率。
二、后端:创建请求对象
这里的请求是表明需要查看具体哪一个部署流程定义
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;
}
三、后端:创建服务接口
/**
* 查询图片信息的函数。
* <p>
* 根据传入的查询请求对象,返回对应的图片信息字符串。
*
* @param queryImageOrXmlReq 查询请求对象,包含查询图片所需的相关参数。
* 该对象的具体结构和内容需符合业务逻辑要求。
* @return 返回一个字符串,表示查询到的图片信息。
* 如果查询失败或无结果,返回值的具体含义需根据业务逻辑定义。
*/
String queryImage(QueryImageOrXmlReq queryImageOrXmlReq);
四、后端:实现服务接口
此实现是本文重点部分:
该方法 queryImage(QueryImageOrXmlReq queryImageOrXmlReq) 的主要功能是根据前端传入的流程定义 ID,调用流程引擎(如 Activiti 或 Flowable)持久化服务,获取对应的流程图资源,并将其以 Base64 编码的字符串形式返回给调用方。核心流程如下:
参数校验:保证输入对象和流程定义 ID 有效;
查询流程定义:通过 getById 方法确认流程定义是否存在;
读取流程图资源流:调用 repositoryService.getResourceAsStream 获取流程图文件的字节流;
转换并编码:将字节流读入内存,通过 Base64 编码输出;
异常处理:对空流、IO 异常及其他未知异常分别捕获并封装业务异常。
/**
* 查询流程图的Base64编码字符串。
*
* @param queryImageOrXmlReq 请求对象,包含查询流程图所需的参数。
* - 不能为空。
* - 必须包含有效的流程定义ID(processDefinitionId)。
* @return 返回流程图的Base64编码字符串。
* - 如果流程定义不存在或资源流为空,则抛出异常。
* @throws IllegalArgumentException 如果请求对象为空或流程定义ID无效。
* @throws BusinessException 如果流程定义不存在、资源流为空或发生IO异常。
*/
@Override
public String queryImage(QueryImageOrXmlReq queryImageOrXmlReq) {
// 参数校验:确保请求对象不为空
if (queryImageOrXmlReq == null) {
log.error("获取流程图失败,原因:请求对象不能为空");
throw new IllegalArgumentException("获取流程图失败,原因:请求对象不能为空");
}
String processDefinitionId = queryImageOrXmlReq.getProcessDefinitionId();
// 参数校验:确保流程定义ID不为空或空字符串
if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
log.error("获取流程图失败,原因:流程定义ID不能为空或空字符串");
throw new IllegalArgumentException("获取流程图失败,原因:流程定义ID不能为空或空字符串");
}
// 根据流程定义ID查询流程定义
ActReProcdef actReProcdef = getById(processDefinitionId);
if (actReProcdef == null) {
log.error("获取流程图失败,原因:流程定义不存在,流程定义ID:{}", processDefinitionId);
throw new BusinessException("获取流程图失败,原因:流程定义不存在,流程定义ID:" + processDefinitionId);
}
// 获取流程图资源流
try (InputStream imageStream = repositoryService.getResourceAsStream(actReProcdef.getDeploymentId(),
actReProcdef.getDgrmResourceName())) {
if (imageStream == null) {
log.error("获取流程图失败,原因:资源流为空,流程定义ID:{}", processDefinitionId);
throw new BusinessException("获取流程图失败,原因:资源流为空,流程定义ID:" + processDefinitionId);
}
// 将资源流内容读取为字节数组并进行Base64编码
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = imageStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (IOException e) {
// 捕获IO异常并记录日志
log.error("获取流程图失败,原因:IO异常,流程定义ID:{}", processDefinitionId, e);
throw new BusinessException("获取流程图失败,原因:IO异常", e);
} catch (Exception e) {
// 捕获其他未知异常并记录日志
log.error("获取流程图失败,原因:未知异常,流程定义ID:{}", processDefinitionId, e);
throw new BusinessException("获取流程图失败,原因:未知异常", e);
}
}
五、后端:创建功能接口
提供接口以供前端调用
// 使用Spring Security的注解进行权限控制,确保只有拥有特定权限的用户才能访问此方法
@PreAuthorize("hasAnyAuthority('/api/v1/actReProcdef/queryImage')")
// 定义参数对象的元数据,用于API文档生成
@Parameter(name = "queryImageOrXmlReq", description = "查询流程定义图片请求对象", required = true)
// 定义操作的元数据,用于API文档生成
@Operation(summary = "查询流程定义图片")
// 声明这是一个POST请求的处理方法
@PostMapping("/queryImage")
public Result<String> queryImage(@RequestBody QueryImageOrXmlReq queryImageOrXmlReq) {
try {
// 调用服务层方法执行流程定义操作,传入请求对象并获取操作结果
String image = actReProcdefService.queryImage(queryImageOrXmlReq);
// 返回操作成功的响应结果
return Result.success(image);
} catch (Exception e) {
// 捕获异常,记录详细的错误日志,并返回包含失败原因的响应结果
log.error("查询流程定义图片失败 具体原因为 : {}", e.getMessage());
return Result.error("查询流程定义图片失败,失败原因:" + e.getMessage());
}
}
六、前端:创建请求参数
与后台接口保持一致
// 查询流程图或者流程XML请求对象
export interface QueryImageOrXmlReq {
processDefinitionId: string // 流程定义ID
}
七、前端:封装请求接口
调用后台提供的接口
// 查询流程图
export function queryImage(data: QueryImageOrXmlReq) {
return request.post<any>({
url: '/pm-process/api/v1/actReProcdef/queryImage',
data,
})
}
八、前端:增加功能方法
// 定义响应式数据 showImage 表示是否显示流程定义的图片对话框
const showImage = ref(false)
// 定义响应式数据 imageData 表示流程定义的图片数据
const imageData = ref('')
/**
* 异步函数:用于显示流程定义的图片
* @param data ActReProcdefVO 类型的对象,包含流程定义的相关信息
*/
async function onShowImage(data: ActReProcdefVO) {
try {
// 组装查询参数,包括流程定义 ID
const param: QueryImageOrXmlReq = {
processDefinitionId: data.id,
}
// 调用后端接口获取流程定义的图片数据
const result: any = await queryImage(param)
// 判断查询结果是否成功
if (result.success && result.code === 200) {
// 如果成功,则更新流程定义的图片数据
imageData.value = result.data
// 打开图片对话框
showImage.value = true
}
else {
// 提示操作失败的错误提示信息
ElMessage({
message: `查询失败原因:${result.message}`,
type: 'error',
})
}
}
catch (error) {
// 捕获异常并提取错误信息
let errorMessage = '未知错误'
if (error instanceof Error) {
errorMessage = error.message
}
// 显示操作失败的错误提示信息
ElMessage({
message: `查询失败: ${errorMessage || '未知错误'}`,
type: 'error',
})
}
}
九、前端:增加界面

<el-button v-hasButton="`btn.actReProcdef.queryImage`" type="primary" @click="onShowImage(scope.row)">
查看流程图
</el-button>
此处需注意,直接使用img标签的base64解码,将图片文件的字符串转码成图片

<!-- 流程图展示弹出框 -->
<el-dialog v-model="showImage" title="流程图展示" width="80%">
<div class="image-container">
<img :src="`data:image/jpeg;base64,${imageData}`" alt="流程图">
</div>
</el-dialog>
.image-container {display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.image-container img {
max-width: 100%;
max-height: 100%;
}
十、添加按钮&分配权限
这一步是添加按钮

这一步是把按钮权限给到当前操作员的角色【超级管理员】

十一、结果查询

十二、后记
本篇文章的前后端仓库地址请查询专栏第一篇文章,后续打算把xml和流程图片展示出来
本文的后端分支是 process-7
本文的前端分支是 process-9