导入导出与持久化:流程图的保存与加载

流程图的价值在于能够被保存、加载、版本管理。本文讲解如何安全地导入导出BPMN XML、处理错误、与后端系统集成,以及流程的持久化策略。

导入流程(importXML)

基本用法

1
2
3
4
5
6
7
8
9
const bpmnXml = `<?xml version="1.0" encoding="UTF-8"?>...`;

modeler.importXML(bpmnXml)
.then(() => {
console.log('✓ 流程加载成功');
})
.catch(err => {
console.error('✗ 加载失败:', err.message);
});

常见错误及处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
async function safeImportXML(xml) {
try {
// 1. 基础格式检查
if (!xml || typeof xml !== 'string') {
throw new Error('XML必须是字符串类型');
}

if (!xml.includes('<bpmn:definitions')) {
throw new Error('无效的BPMN XML格式');
}

// 2. 导入流程
await modeler.importXML(xml);

// 3. 自动适配
modeler.get('canvas').zoom('fit-viewport');

return { success: true };
} catch (err) {
// 处理不同类型的错误
let errorMsg = '未知错误';

if (err.message.includes('unexpected')) {
errorMsg = 'XML结构不正确,可能包含非法元素';
} else if (err.message.includes('parse')) {
errorMsg = 'XML解析失败,请检查格式';
}

console.error('导入错误:', errorMsg);
return {
success: false,
error: errorMsg,
details: err.message
};
}
}

// 使用
const result = await safeImportXML(bpmnXml);
if (!result.success) {
showErrorNotification(result.error);
}

导出流程(saveXML)

导出为XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
async function exportAsXML() {
try {
const result = await modeler.saveXML({ format: true });
const xmlString = result.xml;

return {
success: true,
xml: xmlString,
timestamp: new Date().toISOString()
};
} catch (err) {
console.error('导出失败:', err);
return { success: false, error: err.message };
}
}

// 下载为文件
async function downloadBPMN() {
const result = await exportAsXML();
if (!result.success) return;

const filename = `process_${result.timestamp}.bpmn`;
const link = document.createElement('a');
link.href = 'data:application/xml,' + encodeURIComponent(result.xml);
link.download = filename;
link.click();
}

导出为SVG(矢量图)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function exportAsSVG() {
try {
const result = await modeler.saveSVG();
const svgString = result.svg;

return {
success: true,
svg: svgString
};
} catch (err) {
console.error('SVG导出失败:', err);
return { success: false, error: err.message };
}
}

// 在网页中嵌入SVG
async function embedSVGPreview() {
const result = await exportAsSVG();
if (result.success) {
document.getElementById('preview').innerHTML = result.svg;
}
}

与后端集成:持久化策略

策略1:保存到数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
async function saveToDatabase(processName) {
try {
// 1. 导出XML
const { xml } = await modeler.saveXML({ format: true });

// 2. 导出SVG(用于预览)
const { svg } = await modeler.saveSVG();

// 3. 发送到服务器
const response = await fetch('/api/processes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: processName,
bpmn: xml,
preview: svg,
createdAt: new Date().toISOString(),
version: 1
})
});

if (!response.ok) {
throw new Error(`服务器错误: ${response.status}`);
}

const data = await response.json();
console.log('✓ 流程已保存, ID:', data.id);
return { success: true, processId: data.id };
} catch (err) {
console.error('✗ 保存失败:', err.message);
return { success: false, error: err.message };
}
}

// 从数据库加载流程
async function loadFromDatabase(processId) {
try {
const response = await fetch(`/api/processes/${processId}`);
if (!response.ok) throw new Error('流程不存在');

const { bpmn } = await response.json();

// 导入到编辑器
await modeler.importXML(bpmn);
modeler.get('canvas').zoom('fit-viewport');

console.log('✓ 流程已加载');
return { success: true };
} catch (err) {
console.error('✗ 加载失败:', err);
return { success: false, error: err.message };
}
}

策略2:自动保存(草稿)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 监听编辑动作,定时保存
let autoSaveTimer = null;
const AUTO_SAVE_INTERVAL = 30000; // 30秒

modeler.get('commandStack').on('post:execute', () => {
// 清除上一个定时器
clearTimeout(autoSaveTimer);

// 设置新的定时器(防抖)
autoSaveTimer = setTimeout(async () => {
const { xml } = await modeler.saveXML();

// 保存到本地存储(浏览器)
localStorage.setItem('draft_process', xml);

// 或发送到服务器
await fetch('/api/processes/draft', {
method: 'POST',
body: JSON.stringify({ bpmn: xml })
});

console.log('✓ 草稿已自动保存');
}, 2000); // 2秒内无操作后保存
});

// 恢复草稿
function loadDraft() {
const draft = localStorage.getItem('draft_process');
if (draft) {
modeler.importXML(draft);
console.log('✓ 已恢复草稿');
}
}

策略3:版本管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 每次保存时记录版本
let versionHistory = [];

async function saveWithVersion(processName, description) {
const { xml } = await modeler.saveXML();

const version = {
id: `v_${Date.now()}`,
name: processName,
description: description,
bpmn: xml,
timestamp: new Date().toISOString(),
versionNumber: versionHistory.length + 1
};

versionHistory.push(version);

// 保存到服务器
await fetch('/api/processes/versions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(version)
});

console.log(`✓ 已保存为版本 ${version.versionNumber}`);
return version;
}

// 查看版本历史
function showVersionHistory() {
versionHistory.forEach((v, idx) => {
console.log(`${idx + 1}. ${v.description} - ${v.timestamp}`);
});
}

// 回到某个版本
async function revertToVersion(versionId) {
const version = versionHistory.find(v => v.id === versionId);
if (version) {
await modeler.importXML(version.bpmn);
console.log('✓ 已回到版本:', versionId);
}
}

校验与清理

流程有效性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function validateProcess() {
const elementRegistry = modeler.get('elementRegistry');
const allElements = elementRegistry.getAll();

const issues = [];

// 检查:是否有启动事件
const hasStart = allElements.some(el => el.type === 'bpmn:StartEvent');
if (!hasStart) issues.push('缺少启动事件');

// 检查:是否有结束事件
const hasEnd = allElements.some(el => el.type === 'bpmn:EndEvent');
if (!hasEnd) issues.push('缺少结束事件');

// 检查:是否有孤立节点(无连接)
allElements.forEach(el => {
if (el.type === 'bpmn:Task' && el.incoming.length === 0 && el.outgoing.length === 0) {
issues.push(`任务 "${el.businessObject.name}" 未连接`);
}
});

return {
valid: issues.length === 0,
issues: issues
};
}

// 保存前验证
async function safeSave() {
const validation = validateProcess();

if (!validation.valid) {
showWarning('流程存在问题:\n' + validation.issues.join('\n'));
return false;
}

// 通过验证,执行保存
await saveToDatabase('my-process');
return true;
}

文件上传与解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 用户上传BPMN文件
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;

try {
// 读取文件内容
const text = await file.text();

// 导入到编辑器
await modeler.importXML(text);
modeler.get('canvas').zoom('fit-viewport');

console.log('✓ 文件已加载');
} catch (err) {
console.error('✗ 文件读取失败:', err);
}
});

小结

掌握导入导出:

  • ✓ 正确处理 importXML 的错误
  • ✓ 使用 saveXML 与 saveSVG 导出
  • ✓ 与后端集成(保存、加载、版本管理)
  • ✓ 自动保存与草稿恢复
  • ✓ 流程有效性校验

便能构建专业的流程管理与存储系统

参考资料