4.3 导出分享:图片与只读链接

白板画到一半想给人看、或整版交付,就靠导出分享:导成图片、生成只读链接。本篇讲导出的几种形态、只读链接怎么鉴权、怎么把导出资源走 CDN 加速。

导出的几种形态

形态 用途 数据源
整板图片 交付、存档 快照渲染
区域截图 局部分享 实时渲染
只读链接 在线查看 事件流 + 只读权限
回放链接 看过程 日志流

导出本质是「把事件流 / 快照变成可分享的静态或只读形态」。

只读链接怎么做

只读分享不能暴露「编辑入口」,要权限分级: | 权限 | 能做什么 | 链接特征 | |---|---|---| | 编辑 | 画、撤 | 需登录 / token | | 只读 | 看 | 公开 token | | 限时 | 到期失效 | 带 TTL |

只读链接带一个短期 token,服务端校验后回放「当前整板」,但不允许写。把「读」和「写」的入口彻底分开。

导出资源走 CDN

导出图、回放数据都是「写一次、读多次」,天然适合 COS 加 CDN: | 步骤 | 动作 | |---|---| | 1 | 渲染 / 打包导出物 | | 2 | 上传 COS(私有读) | | 3 | 生成签名 URL | | 4 | 走 CDN 下发 |

签名 URL 加 CDN,既保证私有资源不外泄,又让分享点开快、带宽省。

大图导出的工程细节

白板大了,整板渲染是重活: | 问题 | 做法 | |---|---| | 分辨率 | 按倍率出,别一把梭 | | 分片 | 超大板分区渲染拼接 | | 异步 | 导出走任务,别阻塞在线 | | 进度 | 回传百分比 |

大导出务必异步 + 分片,前端看进度,服务端慢慢渲。

踩坑提示

最坑是「只读链接也能改」:权限没分层,分享出去的人顺手把白板改乱了。必须「只读只读、只回看不写」。再一个坑:导出签名 URL 过期——COS 私有读靠签名,TTL 短了用户点开 403;分享场景 TTL 要留够,或走 CDN 缓存一份,两者取其一即可。

小结

导出分享 = 图片 / 区域 / 只读 / 回放多形态,权限分级、资源走 COS 加 CDN、大导出异步分片。把「画完」变成「拿得走、看得快、改不了」,白板才真正可交付。

只读链接的鉴权怎么做

只读链接的核心是「读写入口物理分离」:编辑走登录态的连接,只读走独立的查看令牌。令牌本身不含用户身份,只含「哪块板、什么权限、多久过期」:

// 生成只读令牌:板 + 权限 + TTL
const token = sign(
  { boardId, scope: 'read', ttl: 7 * 24 * 3600 },
  SECRET
);
const link = `https://board.example.com/v/${boardId}?t=${token}`;

// 查看服务端:校验后只回放,不开写
io.of('/view').use(async (socket, next) => {
  const payload = verify(socket.handshake.query.t, SECRET);
  if (payload.scope !== 'read') return next(new Error('forbidden'));
  socket.join(`view:${payload.boardId}`);
  next();
});

关键在于:查看命名空间下根本没有处理绘制事件的入口,从协议层面断掉「只读链接也能改」的可能。

签名 URL 与 CDN 怎么配合

私有读 COS 直接甩原始域名,既慢又暴露签名参数。套一层 CDN 效果完全不同: | 配置项 | 建议 | 原因 | |---|---|---| | 缓存键 | 忽略 query 中的签名参数 | 同一对象复用缓存 | | 边缘缓存 | 大图 1 小时 | 写一次读多次 | | 回源鉴权 | 开启 | 防绕过 CDN 直连 | | Referer | 限制业务域名 | 防盗链 |

配置完有一条要单独验证:忽略签名参数后私有资源会不会被白嫖。答案是不会——CDN 回源时仍带签名,边缘缓存的只是对象本体,但没有有效的访问凭证依然拿不到。

大导出怎么异步化

整板渲染动辄几秒,绝不能卡在在线请求里: | 步骤 | 动作 | 说明 | |---|---|---| | 提交 | 前端创建导出任务 | 立刻返回任务 id | | 排队 | 任务进队列 | 渲染器独立伸缩 | | 进度 | 按分片百分比回传 | 前端显示进度条 | | 交付 | 完成后推签名 URL | 走 CDN 下发 |

这套结构和我们用 Node.js 搭协作站点时的任务队列同构,更多异步任务设计可参见 Node.js 云开发实战:手搭一个协作小站。

水印与审计

对外分享的图,默认带半透明团队水印,既不挡内容又便于追溯。导出动作全部写审计日志:谁、什么时候、导出了哪块板、给了谁——出了事有据可查,团队协作才敢把白板往外发。

回放链接和多端预览

回放链接比只读更进一步:读者看到的是「画的整个过程」,而不是结果。实现上就是按事件流顺序快进,锚点机制让回放不必从第一笔开始。移动端打开分享链接时,注意把视口和缩放单独适配,否则小屏上整板图根本看不清。

区域截图的坐标换算

区域截图看着简单,坑全在坐标系:白板画布是「世界坐标」,截图框是「屏幕坐标」,两者差一个缩放和平移。服务端重绘时必须把框换算回世界坐标,否则截出来的区域永远对不上。稳妥做法是前端把「框的世界坐标 + 当前视口」一起传给服务端,服务端不做任何换算只负责渲染: | 参数 | 含义 | 谁算 | |---|---|---| | 框坐标(世界系) | 要截的区域 | 前端 | | 视口(缩放 / 平移) | 当前查看位置 | 前端 | | 渲染范围 | 框在世界系的位置 | 服务端 |

凡是「服务端猜坐标」的实现,最后都在缩放场景里翻过车。

分享链接的统计与回收

分享出去之后还有两件事:看不看得到效果、能不能收回来。给每个只读令牌记一条「最近访问时间」,长期零访问的链接自动失效,既安全又能省下 CDN 缓存。访问数据还能反过来当产品指标看:哪类白板被分享最多、平均看多久。

指标 看什么
打开率 链接被点开的次数
停留时长 平均查看时间
零访问链接 超过 30 天未访问即回收

导出资源的清理

导出图不是越多越好:签名 URL 过期后,旧的导出物就是纯垃圾。配一条清理任务,按生成时间滚动删除 7 天前的临时导出,只保留用户主动「收藏」或绑定到作品的那些。COS 账单和分享列表都会因此清爽很多。

导出格式怎么选

图片不是唯一形态,按用途挑格式能少很多返工: | 格式 | 适用 | 备注 | |---|---|---| | PNG | 线稿、文字多的白板 | 无损,文件偏大 | | JPEG | 大面积配色的板 | 体积小,有压缩痕 | | PDF | 打印、正式交付 | 支持多页分片 | | JSON + 事件包 | 二次编辑、数据迁移 | 可重放可回放 |

默认 PNG 起步,交付场景上 PDF,内部协作留 JSON——这套组合能覆盖九成需求,剩下的边角情况按上面的表格对号入座即可,不必临时拍脑袋。

移动端打开分享链接

分享链接有一半是在微信里打开的,适配不做等于白分享。三个最小可用的点:视口锁定缩放、首屏出快照图(不是空白画布等数据)、横屏提示。快照图先顶上是关键——文字和图片加载远比事件流快,读者点开三秒看到内容,就愿意等你把增量补完;反之空白半分钟,再好的白板也会被当成坏掉的链接关掉。

分享出去的那一刻

分享功能的完成度,不在导出本身,而在「对方点开后的三秒」。快图顶上、权限锁死、链接可回收,这三件事齐了,白板才从「自己画的工具」变成「能拿去交付的产物」。把每一次分享当作一次线上发布去对待,协作白板才真正长出交付能力——这也是本章三篇(持久化、快照、导出)最终汇合的地方:存得住、定得准、拿得走,缺一环都交付不出去。

延伸阅读