GeoJSONcn
GeoJSONcn / 技术专栏 / 下载要付费、导出却不能有水印?客…

下载要付费、导出却不能有水印?客户端鉴权与资源交付

发布于 2026-07-15 · GeoJSONcn 技术团队 · 阅读约 5 分钟

做内容付费的同学迟早会遇到一个拧巴的需求:用户没付钱之前想看个大概(最好带个水印防截图),付了钱下载的文件必须干干净净、绝对不能带水印。我在 GeoJSONcn 上就是这么设计的,在线预览免费看,下载付费,但下载的 GeoJSON/PNG 一张水印都没有。

这矛盾吗?技术上一点也不矛盾,关键是把水印和数据分成两层。这篇聊聊思路和实现。

先厘清:水印是"显示层"的事,不是"数据层"的事

很多人踩坑,是因为把水印烤进了数据文件。比如给导出的 PNG 直接 ctx.fillText('水印') 写死,结果付费用户拿到的图也有水印,这就错了。

正确的理解是:

两者数据源是同一份,区别只在于画的时候有没有叠水印层。这就是屏幕有水印、导出无水印能同时成立的根本原因。

鉴权放哪:客户端能信吗?

付费下载的鉴权,理论上该在服务端。但纯前端项目(比如我这种静态托管加边缘 Worker)怎么办?

我的做法是客户端轻鉴权加服务端校验兜底:

1. 支付成功(支付宝沙箱回调或前端确认),把订单记进客户端的 localStorage(标记已购此资源); 2. 下载时前端检查这个标记,有就放行导出; 3. 关键资源(比如高精度的原始数据文件)实际托管在需要鉴权的路径,真正的交付由 Worker 侧按订单校验。

function canDownload(resourceId) {
  const paid = JSON.parse(localStorage.getItem('paidOrders') || '[]');
  return paid.includes(resourceId) || isPassOwner(); // 通行证
}

提醒一句:纯客户端标记只能挡普通用户顺手点,挡不住刻意抓包的人。真金白银的交付必须服务端校验(签名 URL / 鉴权 token)。客户端鉴权是体验优化,不是安全边界。这点别搞混。

价格档位怎么设计

我在 GeoJSONcn 上是两档:

逻辑就是判断 paidOrders 里有没有这个资源,或者通行证是否在有效期。简单透明,用户不迷糊。

导出"重画"的代码骨架

function exportPng(geojson, withWatermark) {
  const cv = document.createElement('canvas');
  const ctx = cv.getContext('2d');
  drawGeoJSON(ctx, geojson);          // 画数据
  if (withWatermark) {                // 只有预览才 true
    ctx.fillStyle = 'rgba(0,0,0,0.15)';
    ctx.fillText('GeoJSONcn.com 下载无水印', 20, 30);
  }
  return cv.toDataURL('image/png');
}
// 预览:exportPng(data, true)
// 下载:exportPng(data, false)  ← 干净文件

就这一个 withWatermark 参数,屏幕和文件彻底隔离。

最后

水印是显示层概念,别烤进数据;导出用原始数据重画,天然无水印;客户端鉴权管体验,真交付必须服务端校验;按次加通行证两档最省心。

这套"预览免费看、下载付费、文件纯净"的组合,在卖数据/设计素材/报告类站点上很通用。下一篇聊个更硬核的:几万静态页怎么让百度乖乖收录,canonical 和 sitemap 那些坑。