微信小程序 WebView 接入
tuo-visual-viewer 依赖浏览器 DOM、Canvas 和 WebGL,不能直接作为原生小程序组件运行。小程序第一版推荐使用 web-view 打开业务域名下的 H5 Viewer 页面,由 H5 页面加载 UMD Viewer 并访问业务 API。
推荐架构
小程序不保存 Project Key,也不直接调用需要 Project Key 的接口。Project Key、Access Token 和模型权限都由业务后端管理。
小程序页面
pages/viewer/viewer.wxml:
<web-view src="{{viewerUrl}}" bindmessage="onViewerMessage" />
pages/viewer/viewer.js:
Page({
data: { viewerUrl: '' },
onLoad(options) {
// modelId 只作为业务查询标识;不要把 Project Key 放入 URL。
const modelId = options.modelId;
if (!modelId) return;
// 向业务后端申请一次性、短时浏览会话。
wx.request({
url: 'https://your-business.example.com/api/viewer-session',
method: 'POST',
data: { modelId },
success: ({ data }) => {
// sessionId 有效期短,并且只允许访问当前用户有权限的模型。
const query = encodeURIComponent(data.sessionId);
this.setData({
viewerUrl: `https://your-business.example.com/viewer/index.html?session=${query}`,
});
},
});
},
onViewerMessage(event) {
// H5 页面通过 wx.miniProgram.postMessage 回传事件。
const messages = event.detail?.data || [];
console.log('viewer event', messages[messages.length - 1]);
},
});
H5 Viewer 页面
viewer/index.html:
<script src="https://resource.api.tiangongtuxue.com/tuo-visual-viewer/1.0.0/tuo-visual-viewer.umd.js"></script>
<div id="viewer" style="height:100vh"></div>
<script>
const params = new URLSearchParams(location.search);
const sessionId = params.get('session');
const container = document.querySelector('#viewer');
async function loadViewer() {
if (!sessionId) throw new Error('缺少浏览会话');
// H5 页面使用一次性 session 换取短时结果地址。
const response = await fetch('/api/viewer-session/result', {
headers: { 'X-Viewer-Session': sessionId },
});
if (!response.ok) throw new Error(`结果获取失败:${response.status}`);
const result = await response.json();
const viewer = new window.TuoVisualViewer.TuoVisualViewer(container);
await viewer.load(result.url);
// 将 Viewer 事件通知给小程序页面。
if (window.wx?.miniProgram) {
viewer.on('selectionchange', (event) => {
window.wx.miniProgram.postMessage({
data: [{ type: 'selectionchange', payload: event }],
});
});
}
}
loadViewer().catch((error) => {
document.body.textContent = error.message;
});
</script>
后端一次性会话接口
业务后端可以使用短期随机 sessionId 保存以下信息:
| 字段 | 说明 |
|---|---|
sessionId | 高熵随机值,建议只使用一次 |
userId | 当前小程序用户 |
projectId | 工程归属 |
modelId | 可访问的模型 |
expiresAt | 短期过期时间,例如 5 分钟 |
后端返回:
{
"sessionId": "<one-time-session>",
"expiresIn": 300,
"viewerUrl": "https://your-business.example.com/viewer/index.html"
}
后端换取结果时应自行完成:
- 校验小程序登录态和用户身份;
- 校验
modelId属于当前用户可访问的工程; - 校验
sessionId未过期且未使用; - 代表业务方调用天工图学 API;
- 只返回业务结果地址或短时结果数据;
- 不把 Project Key 或长期 Access Token 返回给 H5 页面。
参数传递方式
推荐:一次性 session 参数
https://your-business.example.com/viewer/index.html?session=<短期一次性值>
优点是小程序只传递临时标识,H5 页面无法据此获得工程长期权限。
不推荐:直接传 Project Key
viewer.html?projectKey=...
这种方式会把 Project Key 暴露在 URL、WebView 历史、代理日志和错误监控中,禁止用于生产环境。
传递展示配置
可以传递不敏感的 UI 配置,例如:
viewer.html?session=...&theme=light&toolbar=measure,section
配置项必须经过白名单校验,不能把任意 URL、脚本或凭据作为参数传入。
WebView 域名和发布配置
- 在小程序后台配置 H5 业务域名,必须使用 HTTPS;
- H5 页面加载的 Viewer CDN 地址应具备 HTTPS 和稳定缓存策略;
- 业务 API 域名、H5 页面域名和图片/模型结果域名都应加入允许列表;
- 不要使用
localhost、内网 IP 或 HTTP 地址进行正式验收; - 检查 iOS 和 Android WebView 的 WebGL、文件下载和返回行为;
- 页面销毁时调用
viewer.destroy(),避免切换页面后继续占用 WebGL 上下文。
最佳实践
- Project Key 只保存在业务后端密钥管理系统;
- Access Token 只在后端短期缓存;
- 小程序只携带业务模型 ID 或一次性 sessionId;
- sessionId 有效期建议 1~5 分钟,使用后立即失效;
- 每次打开 Viewer 前重新获取结果地址,不缓存永久地址;
- H5 页面只展示当前用户有权访问的模型;
- 不在小程序日志、H5 控制台和 URL 中打印 Key、Token 或临时签名地址;
- 结果未完成时展示状态页,成功后再初始化 Viewer;
- 监听
ready、progress、error、selectionchange,并将必要事件通过postMessage回传小程序; - 页面退出时清理 session、Object URL 和 Viewer 实例;
- 对弱网环境设置超时、重试和友好错误提示。
与原生小程序能力的边界
WebView 方案适合浏览、选择、隐藏、聚焦、测量和视图控制。如果业务需要原生小程序的 3D 渲染、离线模型或深度系统集成,需要另行开发原生渲染适配层,不能直接复用浏览器 UMD Viewer。