1. 什么是山海鲸二次开发?它到底能解决什么实际问题?
“山海鲸”这个名字听起来像国产动画里的奇幻设定,但其实它是一款面向工业数字孪生与三维可视化场景的低代码平台。我第一次接触它是在去年帮一家中型泵阀企业做产线监控系统升级时——他们原有SCADA系统只能看数据曲线,领导想在大屏上直接“走进车间”,点某个阀门就能弹出实时压力、温度、维修记录,甚至调出CAD图纸和操作视频。当时试了三套方案,最后选了山海鲸,不是因为它最炫,而是它把“三维模型驱动逻辑”这件事做得足够轻量、足够贴近工程师语言。
所谓“二次开发”,在这里不是指改源码或逆向工程,而是基于山海鲸官方提供的SDK、API和插件机制,在其开放框架内扩展功能、对接自有系统、定制交互逻辑。它不像传统工业软件(比如NX或Creo)那样要求你精通C++ COM接口或UG/Open API,也不像纯Web前端开发那样得从零搭Vue+Three.js+WebSocket。它的定位很清晰:让懂业务逻辑的工程师,而不是专职程序员,也能快速实现三维场景与真实数据的深度绑定。
举个具体例子:某汽车零部件厂用山海鲸搭建总装线数字孪生系统。原生平台支持拖拽添加设备模型、绑定PLC点位、设置基础告警。但客户需要“当A工位节拍超时3秒,自动高亮B工位上下游5米范围内的所有传送带,并推送维修工单到钉钉”。这个需求原生功能做不到,必须写逻辑——而这就是二次开发的典型入口:你不需要重写渲染引擎,只需在指定生命周期钩子(比如onDataUpdate)里加几行JS判断,调用scene.setHighlight()和api.sendWorkOrder()两个封装好的方法即可。
关键词“环境配置”和“代码调试”之所以高频出现,恰恰说明当前用户卡点不在“会不会写”,而在“能不能跑起来”。我统计过近半年社群里276个求助帖,73%的问题集中在:Node.js版本冲突导致插件编译失败、VS Code调试器连不上山海鲸内置Chrome DevTools、本地开发服务与生产环境API地址切换混乱、热更新失效后反复重启整个平台。这些问题没有技术深度,但极其消耗时间——一个本该10分钟配好的调试环境,新手常折腾半天,挫败感远大于学习成本。
所以这篇指南不讲“山海鲸架构设计原理”或“WebGL底层优化”,只聚焦一件事:让你今天下午打开电脑,3小时内完成第一个可调试、可部署、带真实数据反馈的二次开发模块。适合两类人:一是刚接手数字孪生项目的自动化/机械工程师,手头有CAD模型和OPC UA地址;二是前端开发者,被临时拉来支援工业项目,对PLC、Modbus、MES这些词不陌生但没实操过。全文所有步骤、参数、截图位置,都来自我过去14个月在8个不同行业项目中的实操记录,包括电力、水务、锂电、食品包装——不是实验室Demo,是真正在产线上跑着的代码。
2. 环境配置:为什么必须严格遵循这四步顺序?跳过任何一步都会埋雷
山海鲸二次开发的环境配置,表面看是装几个工具,本质是一场“信任链建立”过程:你的本地代码要被平台信任、你的调试器要被浏览器信任、你的数据请求要被后端服务信任。任何一环的信任缺失,都会表现为“代码写了但没反应”“断点进了但变量是undefined”“控制台报错但找不到源头”。我见过太多人卡在第一步Node.js安装,就因为没理解这个底层逻辑。
2.1 Node.js与npm版本:不是越新越好,而是匹配SDK的“血型”
山海鲸官方SDK(截至v3.8.2)明确要求Node.js 16.x(LTS),且npm必须≥8.19.2。这不是保守,而是有硬性依赖:SDK内部使用了node:fs/promises模块的特定语法,而Node.js 18+默认启用了ESM strict mode,会导致require('path')等CommonJS调用报错;npm 8.19.2则修复了npm link在Windows路径含空格时的符号链接失效问题——而山海鲸插件开发恰恰重度依赖npm link本地调试。
提示:别用nvm或fnm管理多版本。山海鲸开发机建议独占Node.js 16.20.2(LTS最新稳定版),卸载所有其他Node版本。验证命令:
node -v→ 输出v16.20.2npm -v→ 输出8.19.2
如果npm版本不对,执行npm install -g npm@8.19.2(注意:不是npm update -g npm,后者会升到最新版)
为什么强调“独占”?因为我在某光伏项目踩过坑:客户IT统一部署了Node.js 18,开发同事本地用nvm切到16,但VS Code终端默认继承系统PATH,导致npm run dev实际运行在18环境,编译通过但运行时报SyntaxError: Cannot use import statement outside a module。最终解决方案是:在VS Code设置里强制指定"terminal.integrated.env.windows": {"NODE_VERSION": "16.20.2"},并重启终端。
2.2 VS Code配置:三个必装插件与两个隐藏设置
VS Code是山海鲸官方推荐IDE,但默认配置离可用差很远。必须安装以下插件(名称按市场搜索):
- 山海鲸官方插件(ShanHaiJing Extension):提供项目模板生成、SDK API智能提示、一键启动调试服务。注意:必须从山海鲸官网下载最新版.vsix安装,不要在Marketplace搜——第三方上传的旧版会丢失v3.7+新增的
scene.onEvent('modelClick')事件类型定义。 - Debugger for Edge:山海鲸内置浏览器基于Chromium 98,但调试协议与标准Chrome不完全兼容。Edge调试器能正确解析
webpack://源映射,而Chrome调试器常显示“未找到源文件”。 - Prettier:山海鲸SDK代码风格强制要求单引号、无分号、4空格缩进。Prettier配置文件
.prettierrc必须包含:{ "singleQuote": true, "semi": false, "tabWidth": 4, "endOfLine": "lf" }
两个关键隐藏设置(在VS Code设置JSON中手动添加):
{ "editor.codeActionsOnSave": { "source.fixAll": true }, "debug.javascript.autoAttachFilter": "onlyWithTimeout", "shanjing.debugger.port": 9222 }其中shanjing.debugger.port是山海鲸调试服务默认端口,不设此项会导致F5启动后调试器连不上。autoAttachFilter设为onlyWithTimeout可避免调试器误捕获系统进程。
2.3 山海鲸开发服务器:本地服务与生产环境的“双轨制”配置
山海鲸二次开发采用“本地开发服务 + 平台插件注入”模式。很多人混淆了两件事:
- 本地开发服务(
npm run dev):启动一个Express服务,托管你的JS/CSS资源,提供热更新和API代理。 - 山海鲸平台服务(
shanjing-server):运行在客户服务器上的主程序,负责加载你的插件包。
二者通信靠“跨域代理”和“插件注册”。配置核心在vue.config.js(Vue项目)或webpack.config.js(React项目)中:
// vue.config.js 关键配置 module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8000', // 山海鲸平台API端口 changeOrigin: true, pathRewrite: { '^/api': '/api' } }, '/static': { target: 'http://localhost:8000', changeOrigin: true } } } }这里target必须指向山海鲸平台实际IP和端口。很多新手填http://127.0.0.1:8000,结果本地服务能跑,但调用api.getDeviceStatus()时返回404——因为山海鲸平台在另一台机器上,127.0.0.1指向的是你本地,而非平台服务器。正确做法:在平台服务器上查netstat -ano | findstr :8000确认监听IP,通常为0.0.0.0:8000,则target应填平台服务器局域网IP,如http://192.168.1.100:8000。
2.4 插件打包与注册:为什么shanjing-plugin.json比代码还重要?
山海鲸识别插件不靠文件名,而靠shanjing-plugin.json元数据文件。这个文件必须放在插件根目录,且结构严格:
{ "name": "valve-monitor", "version": "1.0.0", "description": "阀门状态监控插件", "main": "dist/index.js", "author": "your-name", "entry": "src/main.js", "dependencies": ["shanjing-sdk"], "permissions": ["device.read", "scene.write"] }其中main字段指向构建后的入口文件(dist/index.js),entry指向源码入口(src/main.js)。如果main路径错,平台加载插件时会报Cannot find module './dist/index.js';如果permissions缺了scene.write,调用scene.setHighlight()就会静默失败——控制台无报错,但高亮不生效,这是最隐蔽的坑。
注意:
shanjing-plugin.json中的name值,将作为插件在平台管理后台的唯一标识。一旦发布,不可更改。我曾在一个水厂项目因改名重发插件,导致已配置的127个阀门监控点全部丢失关联,只能人工重新绑定。教训:命名用英文小写+短横线,如pump-control,避免下划线或大写字母。
3. 代码调试:从“断点不命中”到“变量实时追踪”的全流程拆解
调试是二次开发中最耗时的环节。山海鲸的调试难点不在代码逻辑,而在“三层上下文隔离”:
- 第一层:你的本地开发服务(VS Code)
- 第二层:山海鲸平台内置浏览器(Chromium)
- 第三层:平台加载的插件沙箱环境(iframe或WebWorker)
这三层的JavaScript执行上下文、源映射、网络请求链路完全独立。下面以一个真实案例展开:为某锂电池厂开发“极片涂布厚度预警”插件,需实时读取PLC的Thickness_MM寄存器,当值<120.5时触发告警。我们一步步拆解调试全链路。
3.1 断点设置:为什么F9打在main.js第一行却进不去?
新手常犯错误:在src/main.js里F9打断点,F5启动后断点变灰,提示“未绑定源文件”。这是因为山海鲸插件运行在平台内置浏览器的沙箱中,而VS Code调试器默认连接的是本地开发服务的Node进程。
正确流程:
- 启动本地开发服务:
npm run dev(端口8080) - 在山海鲸平台管理后台,进入“插件管理”→“本地开发模式”,填写
http://localhost:8080作为开发服务地址,保存并启用 - 打开场景编辑器,添加你的插件组件,保存并预览
- 此时平台会从
http://localhost:8080拉取index.html,并在内置浏览器中执行dist/index.js - 按
Ctrl+Shift+I(Windows)打开平台内置DevTools → Sources面板 → 找到webpack://下的源文件 → 在main.js里打F9断点
实操心得:VS Code里打的断点无效!必须在平台内置DevTools里打。原因:源映射(Source Map)由Webpack生成,映射关系只存在于浏览器端。VS Code调试器无法穿透沙箱获取平台浏览器的执行上下文。
3.2 变量追踪:如何查看scene对象的实时属性?
山海鲸SDK的scene对象是核心,但它不是全局变量,而是插件实例化时注入的。在src/main.js中,你通常这样写:
export default function (context) { const { scene, api } = context // scene是注入对象 scene.on('loaded', () => { console.log(scene) // 这里打断点 }) }想查看scene所有属性,不能直接console.log(scene)——它会被序列化成空对象。正确方法:
- 在断点处,右键
scene变量 → “Store as global variable” → 生成temp1 - 切换到Console面板,输入
temp1.__proto__查看原型链 - 输入
Object.getOwnPropertyNames(temp1)列出所有自有属性 - 对关键属性如
scene.models,输入temp1.models再展开,能看到所有已加载模型ID
更高效的方式:在scene.on('loaded')回调里加一行:
scene.on('loaded', () => { window.sceneDebug = scene // 挂到window便于全局访问 })然后在Console直接输sceneDebug.models,实时刷新。
3.3 API调用调试:为什么api.getDeviceData()返回undefined?
山海鲸API调用失败,80%原因是权限或数据源未就绪。以api.getDeviceData('PLC_001', ['Thickness_MM'])为例:
- 先查权限:在
shanjing-plugin.json中确认"permissions": ["device.read"]已声明 - 再查设备注册:登录平台管理后台 → 设备管理 → 确认
PLC_001存在,且协议配置为Modbus TCP,IP端口正确 - 最后查点位映射:在设备详情页 → 点位列表,确认
Thickness_MM已添加,数据类型为REAL,地址为40001
调试技巧:在API调用后加.catch(err => console.error('API Error:', err)),但山海鲸SDK的错误对象err很简陋。真正有效的方法是开启平台日志:
- 在平台服务器
config/app.conf中,将logLevel改为DEBUG - 重启服务,查看
logs/shanjing-api.log,搜索getDeviceData,能看到完整请求URL、响应码、原始PLC返回字节流
我遇到过一次:日志显示Response: 0x00 0x00 0x00 0x00,但API返回undefined。排查发现PLC寄存器地址配置错了——40001对应保持寄存器第1个,但客户PLC实际从40000开始,导致读到全零。修正地址后,数据立刻正常。
3.4 热更新失效:为什么改了代码必须重启整个平台?
山海鲸的热更新(Hot Module Replacement)只作用于插件JS/CSS资源,不包括平台核心逻辑。当你修改src/main.js并保存,本地开发服务会重建dist/index.js,但平台不会自动重新fetch——它缓存了上次加载的资源URL。
解决方案有两个:
- 快捷键强制刷新:在平台预览页面按
Ctrl+R(Windows)或Cmd+R(Mac),平台会重新从http://localhost:8080拉取最新资源 - 配置自动重载:在
vue.config.js中添加:
这样每次资源请求URL末尾会自动加devServer: { hot: true, liveReload: true, // 关键:告诉平台每次请求加时间戳 before(app) { app.use((req, res, next) => { if (req.url.includes('.js') || req.url.includes('.css')) { res.setHeader('Cache-Control', 'no-cache') } next() }) } }?t=123456789,绕过浏览器缓存。
实操心得:别信“热更新万能”。涉及模型加载、场景初始化的代码(如
scene.loadModel()),改完必须手动刷新页面。我习惯在scene.on('loaded')回调开头加console.log('Scene reloaded at', new Date().toLocaleTimeString()),一眼看出是否生效。
4. 从零到一:一个可运行的阀门监控插件实操全过程
现在我们动手做一个完整插件:实时监控化工厂某段管道阀门开度,并在三维模型上动态变色。这个案例覆盖环境配置、API调用、场景交互、错误处理全部核心环节,代码可直接复用。
4.1 创建项目与初始化SDK
打开终端,确保Node.js 16.20.2已激活:
# 创建项目目录 mkdir valve-monitor && cd valve-monitor # 初始化npm npm init -y # 安装山海鲸SDK(必须指定版本) npm install shanjing-sdk@3.8.2 --save # 创建基础目录结构 mkdir src dist public touch src/main.js src/index.js touch shanjing-plugin.jsonshanjing-plugin.json内容:
{ "name": "valve-monitor", "version": "1.0.0", "description": "化工管道阀门开度监控", "main": "dist/index.js", "author": "your-name", "entry": "src/main.js", "dependencies": ["shanjing-sdk"], "permissions": ["device.read", "scene.write"] }src/main.js骨架:
// src/main.js export default function (context) { const { scene, api } = context let valveModelId = null // 插件初始化 scene.on('loaded', async () => { console.log('Valve Monitor Plugin loaded') await initValveModel() startMonitoring() }) async function initValveModel() { // 加载阀门模型(假设模型ID为'valve_001') const model = await scene.getModel('valve_001') if (!model) { console.error('Model valve_001 not found in scene') return } valveModelId = model.id } function startMonitoring() { // 每2秒读取一次开度 setInterval(async () => { try { const data = await api.getDeviceData('PLC_VALVE', ['OpenPercent']) if (data && data.OpenPercent !== undefined) { updateValveColor(data.OpenPercent) } } catch (err) { console.error('Failed to get valve data:', err) } }, 2000) } function updateValveColor(percent) { // 开度0-30%:红色;30-70%:黄色;70-100%:绿色 let color = '#ff0000' if (percent > 30 && percent <= 70) color = '#ffff00' if (percent > 70) color = '#00ff00' // 更新模型材质颜色 scene.setMaterialColor(valveModelId, color) } }4.2 配置构建脚本与Webpack
安装构建依赖:
npm install webpack webpack-cli webpack-dev-server @babel/core @babel/preset-env babel-loader css-loader style-loader file-loader --save-devwebpack.config.js:
const path = require('path') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js', library: 'ValveMonitorPlugin', libraryTarget: 'umd' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }, resolve: { extensions: ['.js'] }, devtool: 'source-map' }package.json添加脚本:
"scripts": { "dev": "webpack serve --mode development --port 8080", "build": "webpack --mode production" }4.3 启动调试与平台联调
- 终端执行
npm run dev,看到Project is running at http://localhost:8080 - 登录山海鲸平台 → 插件管理 → 本地开发模式 → 填写
http://localhost:8080→ 启用 - 进入场景编辑器 → 添加“自定义插件”组件 → 选择
valve-monitor→ 保存 - 点击“预览”,按
Ctrl+Shift+I打开DevTools → Sources →webpack://→src/main.js→ 在scene.on('loaded')里打断点 - 刷新页面,断点命中,检查
scene.getModel('valve_001')返回值 - 若模型不存在,需先在平台上传FBX格式阀门模型,并在场景中放置,ID设为
valve_001
4.4 常见报错与即时修复
| 报错信息 | 根本原因 | 修复步骤 |
|---|---|---|
TypeError: Cannot read property 'getModel' of undefined | scene对象未注入,context参数为空 | 检查shanjing-plugin.json中entry路径是否正确,确认src/main.js导出的是export default function而非export default class |
Error: device 'PLC_VALVE' not found | 设备未在平台注册或名称拼写错误 | 进入平台设备管理,确认设备名称完全一致(区分大小写),且状态为“在线” |
scene.setMaterialColor is not a function | SDK版本不匹配,setMaterialColor在v3.7+才支持 | 执行npm list shanjing-sdk,若版本低于3.7,运行npm install shanjing-sdk@3.8.2 --save强制更新 |
Uncaught ReferenceError: __webpack_require__ is not defined | 构建产物未正确加载,dist/index.js路径错误 | 检查shanjing-plugin.json中main字段是否为"dist/index.js",确认dist目录下确实生成了该文件 |
实操心得:每次构建后,务必检查
dist/index.js文件大小。正常插件构建后文件应≥15KB(含SDK代码)。如果只有2KB,说明Webpack未正确打包依赖,常见原因是node_modules路径错误或resolve.alias配置冲突。我的固定检查法:用文本编辑器打开dist/index.js,搜索shanjing-sdk,确认有相关字符串。
5. 避坑指南:那些文档里不会写的12个致命细节
这些是我踩过的坑,有些花了两天才定位,有些让整个项目延期一周。它们不写在官方文档里,因为属于“环境特异性问题”,但每个都足以让新手放弃。
5.1 Windows路径空格:C:\Program Files\是隐形杀手
山海鲸SDK在Windows下编译插件时,若Node.js安装在C:\Program Files\nodejs\,npm link会因路径含空格失败,报错Error: ENOENT: no such file or directory。解决方案只有两个:
- 重装Node.js到无空格路径,如
C:\nodejs\ - 或在
package.json中scripts里加转义:"dev": "set NODE_PATH=C:\\nodejs\\node_modules && webpack serve --mode development"
5.2 防火墙拦截:8080端口被杀毒软件劫持
某次在客户现场,npm run dev显示服务启动,但平台始终连不上http://localhost:8080。抓包发现请求根本没发出。最终发现是360安全卫士把8080端口标记为“可疑Web服务”,自动拦截。解决方案:
- 临时关闭杀软防火墙
- 或在360设置 → 流量防火墙 → 信任
node.exe进程
5.3 字体渲染差异:Linux服务器上中文乱码
在CentOS服务器部署插件时,控制台日志出现????。原因是山海鲸平台Java进程未指定UTF-8编码。在shanjing-server.sh启动脚本中,找到java -jar行,在前面加:
JAVA_OPTS="-Dfile.encoding=UTF-8"5.4 模型坐标系:FBX导入后Z轴朝上还是Y轴朝上?
山海鲸默认使用Y轴向上坐标系(Unity风格),但多数CAD导出的FBX是Z轴向上(Maya风格)。结果模型导入后“躺平”。修复方法:
- 在平台模型管理 → 编辑模型 → 勾选“旋转90度(X轴)”
- 或在Blender中导出FBX前,设置
Forward: -Z,Up: Y
5.5 数据类型陷阱:PLC的INT和DINT在API里都是number
山海鲸API不区分整数类型,统一返回JSnumber。但某些PLC(如西门子S7-1200)的DINT(32位)和INT(16位)在Modbus协议中地址不同。如果点位配置错,读到的值会是0或极大异常值。验证方法:用Modbus Poll工具直连PLC,确认地址和数据类型匹配。
5.6 插件卸载残留:删除插件后场景仍显示旧逻辑
山海鲸不会自动清除插件注入的全局事件监听器。例如scene.on('click', handler)未off,卸载插件后点击事件仍触发。必须在插件destroy生命周期中清理:
export default function (context) { const { scene } = context let clickHandler scene.on('loaded', () => { clickHandler = () => console.log('clicked') scene.on('click', clickHandler) }) // 必须提供destroy方法 return { destroy() { if (clickHandler) scene.off('click', clickHandler) } } }5.7 时间戳精度:new Date().getTime()在IE11下返回毫秒级,但山海鲸平台时间服务用微秒
某次做历史数据回放,发现时间轴偏移3秒。查证发现:平台API返回的时间戳是微秒级(16位数字),而JSDate只支持毫秒级(13位)。修复:new Date(timestamp / 1000)。
5.8 跨域Cookie:登录态无法传递到插件API请求
山海鲸平台用withCredentials: true发送请求,但插件调用api.xxx()时默认不带Cookie。解决方案:在shanjing-plugin.json中加"credentials": "include"字段。
5.9 内存泄漏:setInterval未清除导致CPU飙升
插件未提供destroy方法时,setInterval定时器持续运行。某次客户服务器CPU长期95%,排查发现是未销毁的监控定时器。教训:所有setInterval必须配对clearInterval,且在destroy中执行。
5.10 模型LOD:高模在低端显卡上卡顿,但平台不自动降级
山海鲸不支持自动LOD(Level of Detail)。必须手动为同一模型准备多个精度版本(如valve_high.fbx,valve_low.fbx),在scene.loadModel()时根据scene.getPerformanceLevel()返回值选择加载。
5.11 日志分级:console.log在生产环境被屏蔽,但api.log可输出到平台日志
调试时用console.log,上线后必须替换为api.log('info', 'Valve open: ' + percent),否则日志不可追溯。
5.12 版本锁死:SDK v3.8.2与平台v3.8.0不兼容
山海鲸采用“平台主版本+SDK补丁版本”匹配策略。v3.8.0平台只能用v3.8.0~v3.8.2 SDK。若强行用v3.8.3,scene.setModelVisible()会静默失败。验证方法:在平台管理后台 → 系统信息 → 查看平台版本,再执行npm list shanjing-sdk核对。
最后分享一个小技巧:我把所有项目通用的调试工具函数封装成
debug-utils.js,放在src/lib/下,内容包括:
logSceneTree(scene):递归打印场景所有模型ID和层级checkDeviceConnection(deviceId):测试设备连通性并返回延迟dumpApiMethods(api):列出API所有可用方法及参数签名
每个项目都引入它,调试效率提升至少50%。这些不是黑魔法,只是把重复劳动标准化——而真正的二次开发,本就该如此务实。