Cesium入门(十九)CesiumJS1.142部署踩坑:新版Sandcastle2为何必须开8080和8081两个端口

Cesium入门(十九)CesiumJS 1.142 部署踩坑:新版 Sandcastle2 为何必须开 8080 和 8081 两个端口

核心关键词:CesiumJS 1.142、Sandcastle2、静态部署、postMessage、origin 隔离 难度级别:中等


“环境配不对,代码全白费。”

升级到 CesiumJS 1.142 后,我按老习惯把 release 包丢进 http-server,一个端口打开 Sandcastle——结果编辑器出来了,示例却一个都跑不起来,右侧预览区一片空白。折腾了半天才搞明白:新版 Sandcastle2 已经从”一个静态页面”重构为”双页面应用”,必须同时开 8080 和 8081 两个端口,而且端口还不能随便改


一、现象:单端口部署,示例全部白屏

老版本(1.140 及以前)的 Sandcastle 位于 Apps/Sandcastle/,是纯静态的 HTML 页面。随便一个静态服务器(http-server、Live Server、nginx),用任意端口发布,打开就能用,代码和预览都在同一个页面里。

1.142 的 release 包则完全不同:

  • Sandcastle 的目录从 Apps/Sandcastle/ 变成了 Apps/Sandcastle2/
  • 只开一个静态服务器时,编辑器、示例库、设置这些 UI 都能正常显示
  • 但点击运行示例,右侧的预览 iframe 白屏,控制台报错指向 8081 端口连接失败

如果只看页面结构,很难想通:UI 都在,代码也是同一份文件,为什么预览跑不起来?

快速定位的方法也很简单:按 F12 打开开发者工具,选中预览区域的 iframe,看它的 src 属性指向哪个地址。只要指向的是 http://localhost:8081/...,而你的 8081 端口并没有服务,就能立刻锁定是部署方式的问题,而不是代码问题。

二、根因:Sandcastle2 不是”一个页面”,而是”两个页面”

1.142 把 Sandcastle 从零重写,技术栈换成了 React + Vite,并且在架构上拆成了两部分:

  • Sandcastle App:负责编辑器、示例库、设置等 UI(入口是 index.html
  • Viewer:真正运行示例代码的页面(入口是 templates/bucket.html),被放在一个 <iframe> 里

App 和 Viewer 之间通过 window.postMessage 通信(源码里的 IframeBridge 封装),运行示例时的大致流程是:

// App 侧:把用户写的代码通过 iframe 发给 Viewer
iframe.contentWindow.postMessage(
    {
        id: "sandcastle-bridge",
        message: { type: "runCode", code: code, html: html }
    },
    "http://localhost:8081" // 目标 origin,必须是 Viewer 所在的端口
);

关键就在这里:iframe 里的 Viewer 页面,并不是从你打开 App 的那个端口加载的,而是被硬编码指向另一个端口。

三、为什么非要跨源:这全是安全设计

看到这里你可能想问:同一个端口不行吗?非要拆两个端口多麻烦。

Cesium 官方在源码注释里写得很直白。server.js 中启动 8081 端口时注释:

// This "mirror" server runs on a separate port to create origin separation between
// the main Sandcastle app and the viewer page for security

而在 Sandcastle 的 README 中也有明确说明:App 和 Viewer 可以同源,但不推荐,因为会失去跨源安全隔离带来的好处。具体来说有三点:

  1. localStorage 隔离:示例代码是用户自己写的,如果与 App 同源,它就能直接读写 App 的 localStorage——包括 Sandcastle Copilot 保存的 API Key(BYOK 模式下你的 Gemini/Claude Key 都存在这里)
  2. 防止示例代码篡改 App:同源时,iframe 里的代码可以直接操作父页面 DOM,改掉编辑器状态
  3. postMessage 本来就适合跨源:通信时显式校验消息来源,反而更安全

所以跨源不是设计缺陷,而是刻意为之的隔离。

四、端口为什么固定在 8080 和 8081

两个端口之所以是”写死”的,是因为 origin 在构建阶段就被编译进了 JS,而不是运行时读取的。

Sandcastle 构建脚本 buildStatic.js 中有这样一段:

config.define = {
    __OUTER_ORIGIN__: JSON.stringify(outerOrigin), // App 所在的 origin
    __INNER_ORIGIN__: JSON.stringify(innerOrigin), // Viewer iframe 所在的 origin
};

__OUTER_ORIGIN__ 和 __INNER_ORIGIN__ 在打包时被 Vite 直接替换成字符串常量。而 release 包在 gulpfile.makezip.js 中构建 Sandcastle2 时,传入的参数是:

buildSandcastleApp({
    outerOrigin: "http://localhost:8080",
    innerOrigin: "http://localhost:8081",
});

也就是说,你下载的 Cesium-1.142.zip 里,App 和 Viewer 的 origin 已经被固化。iframe 的地址按下面这个规则生成(Bucket.tsx):

const bucketUrl = new URL(
    "/Apps/Sandcastle2/templates/bucket.html",
    "http://localhost:8081" // __INNER_ORIGIN__
);

所以无论你在哪个端口打开 App,预览 iframe 都只会去 http://localhost:8081/Apps/Sandcastle2/templates/bucket.html 加载。8081 端口没有服务,就必然白屏。同时 postMessage 发送和接收时都会严格校验这个固化 origin——这就是为什么 8080 和 8081 两个端口一个都不能少,也不能换成别的端口。

五、解决方案:三种方式都能跑通

方案一:用官方 server.js(最省心)

release 包根目录自带 server.js,它会在启动时自动监听两个端口:主服务 8080,镜像服务 8081。

# 在 Cesium-1.142 解压目录下执行
node server.js --production

看到类似下面的输出就说明两个端口都起来了:

Cesium development server running locally.  Connect to http://localhost:8080/
Sandcastle mirror server running on port 8081

浏览器访问 http://localhost:8080/,点开 Sandcastle 即可正常使用。

方案二:两个 http-server 双开(习惯静态部署就用这个)

开两个命令行窗口,在同一个 release 解压目录下分别执行:

# cmd 窗口 1:主服务,跑 App
http-server -p 8080

# cmd 窗口 2:镜像服务,跑 Viewer iframe
http-server -p 8081

两个窗口指向的是同一份文件,端口必须严格是 8080 和 8081。然后访问 http://localhost:8080/

方案三:nginx 双端口(适合公司内网统一部署)

如果公司内网用 nginx 统一管理静态资源,配置两个 server 块分别监听 8080 和 8081,root 指向同一份 release 目录即可:

# nginx.conf 片段
server {
    listen 8080;
    root /data/cesium/Cesium-1.142;  # release 包解压目录
    location / { try_files $uri $uri/ =404; }
}

server {
    listen 8081;
    root /data/cesium/Cesium-1.142;  # 同一份目录
    location / { try_files $uri $uri/ =404; }
}

改完 nginx -s reload,访问 http://服务器IP:8080/ 即可,镜像服务由 nginx 接管,比多开一个 http-server 进程更稳。

注意事项

  1. 端口不要改:改成 8080/8081 以外的端口,App 虽然能打开,但 iframe 依然会去请求 8081,通信校验也会失败
  2. 局域网访问http-server 默认监听 localhost,局域网内其他人访问不了,加 -a 0.0.0.0 即可(server.js 对应的是 --public
  3. 8081 被占用:Sandcastle 会直接不可用,server.js 会报错退出并提示你释放端口

如果只是偶尔看示例、不需要本地修改,直接访问官方在线版 https://sandcastle.cesium.com 也可以,省去部署这一步。


总结

  • Sandcastle2 是 React 重构的双页面应用,App 与 Viewer 通过 postMessage 跨源通信
  • 跨源隔离是安全设计:隔离 localStorage(保护 Copilot 的 API Key)、防止示例代码篡改 App
  • release 包构建时把 origin 固化:App 固定在 localhost:8080,Viewer 固定在 localhost:8081
  • 单端口静态部署必然白屏,双端口(8080 + 8081)才是正确姿势

“文档要看,源码更要 Debug。”——这个坑的答案,就写在 Cesium 的源码注释里。

专业服务

奇小狐工作室 – 3D 数据处理专家

  • CesiumJS 项目定制开发
  • 3D Tiles 数据生产与优化
  • 三维可视化方案咨询
  • AI + 三维可视化技术落地

联系方式:微信 Elusive57

发表评论