项目概述
基于 diagram.js 的 BPMN 流程设计器,通过依赖注入(DI)实现模块化扩展,自定义模块扩展与SVG图形渲染。后端工作流引擎自定义统一任务调度函数,实现异构模型统一调用。
核心技术架构
1. diagram.js 架构基础
核心模块组成
- Canvas: 画布管理,负责SVG容器的创建和管理
- ElementRegistry: 元素注册表,管理所有图形元素的生命周期
- GraphicsFactory: 图形工厂,负责创建和渲染图形元素
- EventBus: 事件总线,实现模块间的解耦通信
- Renderer: 渲染器,负责具体的图形绘制逻辑
渲染流程
用户操作 → EventBus → ElementRegistry → GraphicsFactory → Renderer → SVG渲染
2. 依赖注入(DI)机制实现
模块注册机制
// ProcessDesigner.vue 中的依赖注入配置
const additionalModules = [
CustomModule, // 自定义模块
TranslateModule, // 翻译模块
tokenSimulation, // 流程模拟模块
flowableModdleExtension // Flowable扩展
];
// CustomModeler 初始化
this.bpmnModeler = new CustomModeler({
container: this.$refs["bpmn-canvas"],
additionalModules: this.additionalModules,
moddleExtensions: this.moddleExtensions
});
自定义模块结构
// customModule/index.js - 模块定义
export default {
__init__: ['customPalette', 'customRenderer', 'customContextPad'],
customPalette: ['type', CustomPalette],
customRenderer: ['type', CustomRenderer],
customContextPad: ['type', CustomContextPad]
}
3. 模块化扩展实现
3.1 自定义调色板(CustomPalette)
功能: 扩展工具面板,添加灾害模型节点
// 依赖注入声明
CustomPalette.$inject = [
'bpmnFactory', 'create', 'elementFactory', 'palette', 'translate'
];
// 创建自定义节点
function createAction(type, group, className, title, options) {
const shape = elementFactory.createShape(assign({ type: type }))
shape.businessObject.customType = options.customType;
shape.businessObject.name = options.name;
create.start(event, shape);
}
// 注册灾害模型节点
return {
"create.flood-task": createAction("bpmn:CallActivity", "activity",
"custom-icon-flood-task", "Create Flood Task",
{customType:"flood", name:"调用内涝模型"}),
// ... 其他灾害模型
}
3.2 自定义上下文菜单(CustomContextPad)
功能: 右键菜单扩展,快速创建相关节点
// 动态生成上下文菜单项
const taskTypes = [
{ customType: 'flood', icon: 'custom-icon-flood-task', title: '创建内涝节点' },
{ customType: 'fire', icon: 'custom-icon-fire-task', title: '创建火灾节点' },
// ...
];
taskTypes.forEach(({ customType, icon, title }) => {
entries[`append.${icon}`] = {
group: 'activity',
className: `icon-custom ${icon}`,
title: translate(title),
action: {
click: (event) => createListener(event, customType)
}
};
});
4. SVG自定义图形渲染
4.1 自定义渲染器(CustomRenderer)
核心: 继承BaseRenderer,实现自定义图形绘制
export default class CustomRenderer extends BaseRenderer {
constructor(eventBus, bpmnRenderer) {
super(eventBus, HIGH_PRIORITY) // 设置高优先级
this.bpmnRenderer = bpmnRenderer
}
canRender(element) {
return !element.labelTarget // 忽略标签元素
}
drawShape(parentNode, element) {
const type = element.type
if (customElements.includes(type)) {
// 解析自定义类型
const regex = /调用(.*?)模型/;
const res = element.businessObject.name.match(regex);
const name = res && res[1];
// 映射到图标类型
let customType = this.mapNameToType(name);
if (customType) {
const { url, attr } = customConfig[customType];
// 创建SVG图像元素
const customIcon = svgCreate('image', {
...attr,
href: url
});
// 设置元素尺寸
element['width'] = attr.width;
element['height'] = attr.height;
// 渲染到父节点
svgAppend(parentNode, customIcon);
// 渲染文本标签
this.renderLabel(parentNode, element, attr);
return customIcon;
}
}
// 回退到默认渲染
return this.bpmnRenderer.drawShape(parentNode, element);
}
}
4.2 图标资源管理
动态加载: 使用webpack的require.context动态导入图标
// 动态导入所有PNG图标
const iconContext = require.context('../icons', false, /\.png$/);
const customConfig = {};
iconContext.keys().forEach((key) => {
const iconName = key.replace(/^\.\/(.*)\.png$/, '$1');
customConfig[iconName] = {
url: iconContext(key),
attr: { x: 0, y: 0, width: 68, height: 68 }
};
});
5. 技术特点与优势
5.1 模块化设计
- 松耦合: 通过依赖注入实现模块间解耦
- 可扩展: 新增功能只需添加新模块,无需修改核心代码
- 可维护: 每个模块职责单一,便于维护和测试
5.2 高优先级渲染
const HIGH_PRIORITY = 1500; // 确保自定义渲染器优先执行
5.3 事件驱动架构
// 监听元素变化事件
EventBus.on("commandStack.changed", async event => {
// 实时更新XML
let { xml } = await this.bpmnModeler.saveXML({ format: true });
this.$emit("input", xml);
});
6. 后端工作流引擎集成
统一任务调度函数
- 异构模型统一调用: 通过customType字段区分不同灾害模型
- 标准化接口: 所有模型节点统一使用CallActivity类型
- 参数传递: 通过businessObject传递模型特定参数
// 节点创建时设置自定义属性
shape.businessObject.customType = options.customType;
shape.businessObject.name = options.name;
7. 面试要点总结
技术深度
- diagram.js架构理解: 掌握Canvas、ElementRegistry、GraphicsFactory等核心模块
- 依赖注入机制: 理解DI容器的工作原理和模块注册机制
- SVG渲染原理: 掌握tiny-svg库的使用和自定义图形绘制
- 事件驱动模式: 理解EventBus的作用和事件监听机制
实现难点
- 渲染优先级控制: 通过HIGH_PRIORITY确保自定义渲染器优先执行
- 元素类型映射: 通过正则表达式解析节点名称,映射到对应图标
- 动态资源加载: 使用webpack的require.context实现图标的动态导入
- 模块间通信: 通过依赖注入和事件总线实现模块解耦
项目价值
- 业务适配: 针对灾害应急场景定制化的流程设计器
- 技术创新: 基于开源框架的深度定制和扩展
- 架构设计: 良好的模块化设计,便于后续功能扩展
- 用户体验: 直观的图形化界面,降低流程设计门槛
8. 技术栈总结
- 前端框架: Vue.js + Element UI
- 流程引擎: bpmn-js + diagram.js
- 图形渲染: SVG + tiny-svg
- 模块管理: 依赖注入 + ES6模块
- 构建工具: Webpack (require.context)
- 后端集成: Flowable工作流引擎
这个项目展示了对前端架构设计、模块化开发、图形渲染等多个技术领域的深入理解和实践能力。
BPMN.js 自定义扩展模块与SVG自定义图形渲染实现原理
1. BPMN.js 架构概述
1.1 核心架构
bpmn-js
├── Viewer (查看器)
├── Modeler (建模器)
├── NavigatedViewer (带导航的查看器)
└── 核心模块系统
├── diagram-js (图形引擎)
├── bpmn-moddle (BPMN元模型)
└── 各种扩展模块
1.2 依赖注入系统
- 基于 didi 依赖注入框架
- 模块化设计,每个功能都是独立的模块
- 通过模块注册和依赖解析实现松耦合
2. 自定义扩展模块实现
2.1 模块结构
// 自定义模块示例
export default {
__init__: ['customRenderer', 'customPaletteProvider'],
customRenderer: ['type', CustomRenderer],
customPaletteProvider: ['type', CustomPaletteProvider]
};
2.2 模块注册机制
// 在 Modeler 中注册自定义模块
import BpmnModeler from 'bpmn-js/lib/Modeler';
import customModule from './custom-module';
const modeler = new BpmnModeler({
container: '#canvas',
additionalModules: [
customModule
]
});
2.3 核心扩展点
A. Renderer (渲染器)
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
export default class CustomRenderer extends BaseRenderer {
constructor(eventBus, styles) {
super(eventBus, 1500); // 优先级
this.styles = styles;
}
canRender(element) {
// 判断是否可以渲染该元素
return element.type === 'custom:Task';
}
drawShape(parentNode, element) {
// 绘制自定义形状
const shape = this.drawCustomShape(parentNode, element);
return shape;
}
drawConnection(parentNode, element) {
// 绘制自定义连接线
return this.drawCustomConnection(parentNode, element);
}
}
B. PaletteProvider (工具面板提供者)
export default class CustomPaletteProvider {
constructor(palette, create, elementFactory, spaceTool, lassoTool) {
this.palette = palette;
this.create = create;
this.elementFactory = elementFactory;
}
getPaletteEntries() {
return {
'custom-task': {
group: 'custom',
className: 'custom-task-icon',
title: '自定义任务',
action: {
dragstart: this.createCustomTask.bind(this),
click: this.createCustomTask.bind(this)
}
}
};
}
createCustomTask(event) {
const shape = this.elementFactory.createShape({
type: 'custom:Task'
});
this.create.start(event, shape);
}
}
C. ContextPadProvider (上下文菜单提供者)
export default class CustomContextPadProvider {
constructor(contextPad, modeling, elementFactory) {
this.contextPad = contextPad;
this.modeling = modeling;
this.elementFactory = elementFactory;
}
getContextPadEntries(element) {
if (element.type !== 'custom:Task') {
return {};
}
return {
'custom-action': {
group: 'edit',
className: 'custom-action-icon',
title: '自定义操作',
action: {
click: this.performCustomAction.bind(this, element)
}
}
};
}
}
3. SVG自定义图形渲染实现
3.1 SVG渲染原理
// 自定义形状渲染
drawCustomShape(parentNode, element) {
const { width, height } = element;
// 创建SVG组
const group = this.svgCreate('g');
// 绘制主体形状
const rect = this.svgCreate('rect', {
x: 0,
y: 0,
width: width,
height: height,
rx: 10,
ry: 10,
fill: '#e1f5fe',
stroke: '#01579b',
'stroke-width': 2
});
// 添加图标
const icon = this.svgCreate('path', {
d: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',
fill: '#01579b',
transform: `translate(${width/2 - 12}, ${height/2 - 12})`
});
// 添加文本
const text = this.svgCreate('text', {
x: width / 2,
y: height - 10,
'text-anchor': 'middle',
'font-size': '12px',
fill: '#01579b'
});
text.textContent = element.businessObject.name || 'Custom Task';
// 组装元素
group.appendChild(rect);
group.appendChild(icon);
group.appendChild(text);
// 添加到父节点
parentNode.appendChild(group);
return group;
}
3.2 SVG工具方法
// SVG创建工具方法
svgCreate(tagName, attrs = {}) {
const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
Object.keys(attrs).forEach(key => {
element.setAttribute(key, attrs[key]);
});
return element;
}
// 路径绘制工具
createPathData(points) {
return points.map((point, index) => {
const command = index === 0 ? 'M' : 'L';
return `${command} ${point.x} ${point.y}`;
}).join(' ');
}
3.3 连接线自定义渲染
drawCustomConnection(parentNode, element) {
const waypoints = element.waypoints;
// 创建路径数据
const pathData = this.createPathData(waypoints);
// 创建路径元素
const path = this.svgCreate('path', {
d: pathData,
fill: 'none',
stroke: '#ff5722',
'stroke-width': 3,
'stroke-dasharray': '5,5',
'marker-end': 'url(#custom-arrow)'
});
parentNode.appendChild(path);
return path;
}
4. 元模型扩展
4.1 自定义元素定义
// moddle扩展定义
const customModdleExtension = {
name: 'custom',
uri: 'http://custom.example.com/schema/bpmn',
prefix: 'custom',
xml: {
tagAlias: 'lowerCase'
},
types: [
{
name: 'CustomTask',
extends: ['bpmn:Task'],
properties: [
{
name: 'customProperty',
type: 'String'
},
{
name: 'priority',
type: 'Integer'
}
]
}
]
};
// 在Modeler中使用
const modeler = new BpmnModeler({
moddleExtensions: {
custom: customModdleExtension
}
});
4.2 属性面板扩展
// 自定义属性提供者
export default class CustomPropertiesProvider {
constructor(propertiesPanel, injector) {
this.propertiesPanel = propertiesPanel;
this.injector = injector;
}
getGroups(element) {
if (element.type !== 'custom:Task') {
return [];
}
return [
{
id: 'custom-properties',
label: '自定义属性',
entries: [
{
id: 'custom-property',
label: '自定义属性',
modelProperty: 'customProperty',
widget: 'textField'
},
{
id: 'priority',
label: '优先级',
modelProperty: 'priority',
widget: 'textField'
}
]
}
];
}
}
5. 事件系统与交互
5.1 事件监听
// 自定义行为处理器
export default class CustomBehavior {
constructor(eventBus, modeling) {
this.eventBus = eventBus;
this.modeling = modeling;
// 监听元素创建事件
eventBus.on('shape.added', (event) => {
const { element } = event;
if (element.type === 'custom:Task') {
this.handleCustomTaskAdded(element);
}
});
// 监听元素选择事件
eventBus.on('selection.changed', (event) => {
this.handleSelectionChanged(event.newSelection);
});
}
handleCustomTaskAdded(element) {
// 自定义任务添加后的处理逻辑
console.log('Custom task added:', element);
}
}
5.2 命令扩展
// 自定义命令处理器
export default class CustomCommandHandler {
constructor(modeling) {
this.modeling = modeling;
}
preExecute(context) {
// 命令执行前的处理
}
execute(context) {
// 执行自定义命令
const { element, newProperties } = context;
return this.modeling.updateProperties(element, newProperties);
}
revert(context) {
// 撤销命令
const { element, oldProperties } = context;
return this.modeling.updateProperties(element, oldProperties);
}
}
6. 完整示例:自定义审批节点
// 自定义审批节点模块
import CustomRenderer from './CustomRenderer';
import CustomPaletteProvider from './CustomPaletteProvider';
import CustomContextPadProvider from './CustomContextPadProvider';
import CustomBehavior from './CustomBehavior';
export default {
__init__: [
'customRenderer',
'customPaletteProvider',
'customContextPadProvider',
'customBehavior'
],
customRenderer: ['type', CustomRenderer],
customPaletteProvider: ['type', CustomPaletteProvider],
customContextPadProvider: ['type', CustomContextPadProvider],
customBehavior: ['type', CustomBehavior]
};
// 审批节点渲染器
class ApprovalRenderer extends BaseRenderer {
drawShape(parentNode, element) {
if (element.type !== 'custom:ApprovalTask') {
return;
}
const { width, height } = element;
// 创建审批节点的特殊形状
const group = this.svgCreate('g');
// 主体矩形
const rect = this.svgCreate('rect', {
x: 0,
y: 0,
width: width,
height: height,
rx: 15,
fill: '#fff3e0',
stroke: '#f57c00',
'stroke-width': 2
});
// 审批图标
const icon = this.svgCreate('path', {
d: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
fill: '#f57c00',
transform: `translate(${width/2 - 12}, ${height/2 - 12})`
});
// 状态指示器
const statusCircle = this.svgCreate('circle', {
cx: width - 10,
cy: 10,
r: 5,
fill: this.getStatusColor(element)
});
group.appendChild(rect);
group.appendChild(icon);
group.appendChild(statusCircle);
parentNode.appendChild(group);
return group;
}
getStatusColor(element) {
const status = element.businessObject.approvalStatus;
switch(status) {
case 'pending': return '#ffc107';
case 'approved': return '#4caf50';
case 'rejected': return '#f44336';
default: return '#9e9e9e';
}
}
}
7. 面试要点总结
7.1 核心概念
- 模块化架构:基于依赖注入的松耦合设计
- 渲染器机制:通过优先级控制自定义渲染逻辑
- 事件驱动:完整的事件系统支持交互扩展
- 元模型扩展:支持BPMN标准的自定义扩展
7.2 技术难点
- SVG操作:熟练掌握SVG DOM操作和路径绘制
- 依赖注入:理解didi框架的工作原理
- 事件传播:掌握事件的冒泡和捕获机制
- 模块注册:理解模块的生命周期和初始化顺序
7.3 最佳实践
- 性能优化:合理使用渲染优先级,避免重复渲染
- 代码组织:模块化拆分,职责单一
- 扩展性设计:预留扩展点,支持插件化开发
- 错误处理:完善的错误边界和降级策略
7.4 常见问题
-
如何实现自定义形状?
- 继承BaseRenderer,实现canRender和drawShape方法
- 使用SVG API创建自定义图形
-
如何添加工具面板按钮?
- 实现PaletteProvider接口
- 返回工具项配置对象
-
如何处理自定义属性?
- 扩展moddle元模型
- 实现PropertiesProvider
-
如何实现拖拽创建?
- 在PaletteProvider中配置dragstart事件
- 使用create服务启动拖拽
这套扩展机制使得bpmn.js具有极强的可定制性,能够满足各种复杂的业务流程建模需求。
AI时代(更智能)
节点类型、数据类型(多模态)、
动态路径选择 :基于AI推理结果动态选择执行分支
关键词汇
- 技术栈演进:Diagram.js → FlowGram
- 架构模式:事件驱动 → 数据流驱动
- 核心挑战:类型系统、异步调度、性能优化
- 发展趋势:多模态、Agent编排、自然语言编程
面试表达:AI时代流程编辑器技术总结
开场介绍(第一段)
“关于AI时代的流程编辑器,我认为这是一个非常有意思的技术演进话题。传统的BPMN流程编辑器主要解决的是业务流程标准化问题,比如我们项目中使用的基于diagram.js的流程设计器,通过依赖注入实现模块化扩展。但AI工作流编辑器解决的是完全不同的问题域——它是为AI模型编排和数据处理管道而设计的。从技术架构上看,传统编辑器是事件驱动的流程引擎,而AI工作流编辑器是数据流驱动的DAG调度器。”
主流产品分析(第二段)
“目前主流的AI工作流编辑器中,字节跳动的Coze工作流是比较典型的代表。它的核心特点是低代码设计,用户可以通过拖拽LLM节点、工具节点、逻辑节点来构建复杂的AI应用。技术栈上,前端使用React + Canvas渲染,后端是自研的执行引擎,支持实时调试和版本管理。除了Coze,还有开源的LangFlow基于LangChain + ReactFlow,Dify支持多模态和RAG集成,ComfyUI专注于图像生成工作流。这些产品都体现了从传统流程图向智能化编排平台的转变。”
技术实现深度(第三段)
“从技术实现角度,AI工作流编辑器的核心挑战在于节点系统设计和数据流管理。节点需要支持强类型的输入输出,比如text、image、audio、embedding等数据类型,还要处理异步执行、错误重试、并行调度等复杂场景。前端通常基于React Flow或类似的图形库,需要实现自定义节点组件、连线验证、实时预览等功能。执行引擎则需要构建DAG,进行拓扑排序,管理执行上下文。相比传统的BPMN编辑器,这里的技术复杂度主要体现在数据类型系统和异步执行调度上。”
发展趋势展望(第四段)
“我认为AI工作流编辑器的发展趋势有几个方向:首先是多模态融合,未来会支持文本、图像、音频、视频的统一处理;其次是Agent编排,从单一模型调用发展到多智能体协作;第三是自然语言编程,用户可以用自然语言描述需求,AI自动生成工作流;最后是边缘计算集成,支持在边缘设备上部署轻量化的AI工作流。这个领域还在快速发展中,技术栈也在不断演进,对前端开发者来说是一个很有挑战性的方向。”
个人技术见解(第五段)
“基于我在BPMN流程设计器上的开发经验,我觉得AI工作流编辑器和传统流程编辑器在架构设计上有很多相通之处,比如模块化设计、依赖注入、事件系统等。但也有本质差异,AI工作流更注重数据流的类型安全和执行性能。如果让我来设计一个AI工作流编辑器,我会重点关注几个方面:一是建立完善的类型系统,确保节点间数据流的类型安全;二是实现高效的虚拟化渲染,支持大规模节点的流畅操作;三是设计灵活的插件系统,支持第三方节点扩展;四是提供完善的调试和监控能力,帮助用户快速定位问题。”