把 BPMN.js 放进 Vue 页面并不难,难的是让它成为一套可以长期维护的企业级流程设计器。真正需要解决的不是"能不能拖出一个节点",而是模型如何保存、多人修改如何避免覆盖、业务属性如何扩展、发布后如何保持不可变,以及浏览器如何安全地把模型交给 Camunda。
本文给出一条适合私有化部署的精简实践路线:Vue 负责页面与业务交互,BPMN.js 负责标准建模,平台后端负责模型治理,Camunda 只负责部署与执行。四层边界一旦清楚,后续增加审批人、表单、按钮、数据权限和中国特色 OA 能力时,系统才不会被某个前端组件或引擎版本绑死。
一、先给结论:企业级设计器不是一个前端组件
一套可上线的流程设计器至少包含四个协作部分:
| 层次 | 主要职责 | 不应该承担的职责 |
|---|---|---|
| Vue 应用 | 页面布局、工具栏、弹窗、权限提示、保存与发布交互 | 直接保存生产环境密钥、直接部署引擎 |
| BPMN.js | BPMN 图形建模、XML 导入导出、撤销重做、扩展点 | 模型版本、评审、环境权限 |
| 平台后端 | 草稿、版本、校验、审计、发布、幂等与适配 | 代替 BPMN 建模内核 |
| Camunda | 接收 BPMN 资源、形成流程定义并执行实例 | 充当企业模型仓库和设计协作平台 |
最重要的原则是:浏览器只提交模型与发布意图,生产引擎地址、凭证和环境权限全部留在服务端。这样既能支持 Camunda 7,也能把 Camunda 8 或其他引擎接到同一套设计器之后。
二、总体架构怎样分层

图 1 浏览器负责建模体验,可信后端负责模型治理与引擎部署。
推荐把系统划分为"编辑器外壳、建模内核、治理后端、引擎适配器"四层:
- 编辑器外壳维护路由、布局、权限、工具栏和业务弹窗;
- 建模内核只通过 BPMN.js 公共服务修改图形与业务对象;
- 治理后端保存 XML、修订号、校验结果和审计记录;
- 引擎适配器把统一发布请求转换为 Camunda 7 或 Camunda 8 的部署调用。
前端不应直接调用 Camunda。否则引擎凭证会进入浏览器,草稿保存会与生产部署混为一谈,而且更换引擎时页面、接口和权限模型都要重写。
三、Vue 和 BPMN.js 各自负责什么
Vue 适合管理"组件之外"的状态,例如当前模型、编辑权限、未保存提示、候选人弹窗、表单选择器和发布对话框。BPMN.js 内部则已经有自己的画布、命令栈、事件总线和元素注册表,不要再用 Vue 响应式对象复制整棵图。
常用的公共服务包括:
canvas:缩放、定位和视口控制;modeling:更新节点属性、移动和连接元素;commandStack:撤销、重做与变更历史;eventBus:订阅导入、选择和命令变化;elementRegistry:按 ID 查询元素;saveXML()、saveSVG():导出可持久化资源。
属性修改应进入命令栈,而不是直接写 businessObject 或操作 SVG DOM。只有这样,撤销重做、脏状态和扩展模块才能得到一致结果。
js
const commandStack = modeler.get("commandStack");
modeler.on("commandStack.changed", () => {
dirty.value = commandStack.canUndo();
});
组件卸载时必须执行 modeler.destroy(),并清理窗口事件、定时器和自动保存任务,避免重复进入页面后监听器不断累积。
四、初始化 Modeler 与扩展模块
一个可维护的初始化入口应集中注册属性面板、业务模块和 moddle 描述,而不是让各个 Vue 页面分别拼装:
js
const modeler = new Modeler({
container: canvasRef.value,
propertiesPanel: { parent: propertiesRef.value },
additionalModules: [
propertiesPanelModule,
enterprisePropertiesProvider,
enterprisePaletteProvider
],
moddleExtensions: {
camunda,
enterprise: enterpriseDescriptor
}
});
additionalModules 扩展设计时行为,例如属性面板、工具栏、渲染器和右键菜单;moddleExtensions 定义写入 BPMN XML 的数据结构。二者缺一不可:只注册界面模块,属性可能无法序列化;只注册 moddle,用户又没有配置入口。
升级 BPMN.js 时,优先依赖公开服务和官方扩展机制,避免访问 _definitions、私有字段或复制内部源码。私有 API 往往是跨大版本升级成本最高的部分。
五、模型协议与业务属性怎样设计
Camunda 属性用于表达目标引擎的执行语义,企业属性用于表达平台自己的审批语义,两者不要混在同一个命名空间中。例如审批人策略、按钮集合、表单版本和字段权限可以放进 enterprise:* 扩展元素,再由发布转换器生成目标引擎需要的配置。
xml
<bpmn:userTask id="approveTask">
<bpmn:extensionElements>
<enterprise:approval assigneeType="role" roleCode="finance_manager" />
<enterprise:form formKey="expense-form" version="12" />
</bpmn:extensionElements>
</bpmn:userTask>
这种设计有三个好处:业务模型不直接依赖单一引擎;迁移时可以集中转换;查看旧模型时仍能保留未知扩展字段。平台还应为扩展协议维护 schemaVersion,并提供向前迁移函数与 XML 回归样例。
属性面板只是输入层,不能作为唯一校验入口。前端校验用于即时反馈,服务端校验才是发布闸门;服务端应重新解析 XML,检查开始/结束事件、断线节点、表达式、脚本、外部 URL、业务必填项和目标引擎兼容性。
六、草稿、发布版本、部署与实例必须分开

图 2 草稿可以修改,发布版本一经批准应保持不可变。
| 对象 | 是否可变 | 关键数据 |
|---|---|---|
| Draft | 可变 | XML、revision、编辑人、自动保存时间 |
| Release | 不可变 | 版本号、XML、XML hash、校验与审批结果 |
| Deployment | 状态变化 | 环境、引擎类型、幂等键、引擎部署标识 |
| Instance | 按流程运行 | 已部署定义版本、业务键、运行状态 |
草稿保存使用 revision 乐观锁。客户端提交旧修订号时,服务端返回冲突,不能静默覆盖其他人的修改。发布时从草稿冻结出不可变 Release,并计算 XML hash;部署记录则描述"哪个发布版本部署到了哪个环境和引擎"。
新版本只影响新启动实例。存量实例是否迁移,应成为单独的受控运维动作,不能在发布时自动把所有运行实例切到新定义。
七、用统一接口隔离 Camunda 7 与 Camunda 8
Camunda 7 与 Camunda 8 的部署 API、身份认证、返回标识和运行架构不同,不能把它们当成同一个产品的前后版本。平台应在服务端定义稳定接口:
ts
interface EngineAdapter {
validate(release: ProcessRelease): Promise<ValidationResult>;
deploy(release: ProcessRelease, env: Environment): Promise<DeploymentResult>;
queryDeployment(id: string): Promise<DeploymentStatus>;
}
Camunda 7 适配器可调用其 REST Deployment API;Camunda 8 适配器调用 Orchestration Cluster API。前端只看到统一的发布状态和错误结构,不接触引擎原始字段。
| 适配点 | Camunda 7 | Camunda 8 |
|---|---|---|
| 典型形态 | 关系数据库上的嵌入式/服务化引擎 | 分布式流程编排平台 |
| 部署入口 | REST Deployment API | Orchestration Cluster API |
| 模型扩展 | Camunda 7 moddle | Zeebe/Camunda 8 moddle |
| 平台策略 | 适合存量系统适配 | 新项目按实际能力与许可边界评估 |
转换器必须显式报告不兼容元素,不能"尽量部署"。例如旧版表达式、监听器或扩展属性在目标引擎中没有等价语义时,应阻止发布并给出元素 ID 与修复建议。
八、发布部署链路怎样设计

图 3 后端冻结版本并持有引擎凭证,客户端只发起发布请求。
一次可靠发布可归纳为六步:
- 客户端提交模型 ID、草稿修订号和目标环境;
- 后端校验权限与修订号,冻结 Release;
- 执行平台规则、XML 安全和引擎兼容性校验;
- 以
releaseId + environment + engineType生成幂等键; - 调用目标引擎并记录部署结果;
- 返回统一状态,同时写入操作者、版本 hash 和引擎标识。
不要用一个本地数据库事务包住远程 HTTP 调用。先落库为 DEPLOYING,再调用引擎;成功后更新为 DEPLOYED。如果超时,标记为 UNKNOWN 并通过 deployment key 或资源 hash 对账,确认未部署后才能重试。
九、安全、性能与测试的最低要求
安全上,浏览器不能持有生产 Token,目标引擎地址不能由客户端任意传入。BPMN XML 应按不可信输入处理:限制体积,禁止危险实体,审查脚本、表达式、Connector 和 Webhook,并对发布、撤回、重新部署等操作记录审计日志。
性能问题通常来自频繁序列化、属性面板全量刷新和重复事件监听。自动保存应使用 debounce;只读页面使用 Viewer;SVG 预览在保存或发布时生成;大型候选人和表单数据按需搜索。
测试至少覆盖四层:
- 模型夹具:典型 BPMN 导入、导出后语义不丢失;
- 前端组件:初始化、销毁、脏状态、撤销重做和冲突提示;
- 后端契约:修订号、不可变版本、幂等、权限和错误转换;
- 引擎集成:真实部署、启动实例、推进任务并验证路径。
每次升级 BPMN.js、属性面板或 moddle 依赖时,都应运行同一批模型夹具,重点检查未知扩展是否被保留、属性是否能撤销、导出 XML 是否发生非预期变化。
十、从当前项目出发怎样渐进改造
当前项目已经把设计能力封装在 frontend/ych-bpm-designer:入口同时暴露 Modeler、Viewer、NavigatedViewer、属性面板、Camunda provider,以及 simpleDesign、simpleRender 扩展。这说明云程低代码开发平台的流程设计器已经超出"嵌入一张画布"的阶段,具备继续平台化治理的基础。

项目当前仍保留较早的 BPMN.js 与属性面板依赖,并包含本地扩展代码。升级时不宜一次性替换全部页面,建议按以下顺序推进:
- 建立覆盖现有业务属性的 BPMN XML 夹具;
- 把私有字段访问改为公共服务和 CommandStack;
- 为企业属性建立独立 moddle 命名空间与 schemaVersion;
- 把草稿、Release、Deployment 分表管理;
- 把浏览器部署改为后端 EngineAdapter;
- 最后升级属性面板、样式和交互扩展。
这条路线先固定业务语义和发布边界,再替换技术实现,能显著降低旧模型打不开、扩展属性丢失和生产部署不可追踪的风险。