技能学习
Oct 20, 2026

云智论坛:功能全景与技术实现方案

一个跑在 Cloudflare 边缘网络上的社区论坛 — 从数据建模到安卓 / Windows 双端交付

官网:进入

0
服务器运维
0.15s
API 中位延迟
3
交付终端
11
工具箱应用

一、技术栈:整站零服务器

云智论坛(yunzhi.dev)没有一台 VPS,没有 Nginx,没有 MySQL 进程要盯。整套系统压在一个 Cloudflare Worker 上,数据落在 D1,文件走 R2,前端由 Static Assets 直接分发。

层 选型
计算Cloudflare Workers(单 Worker 承载全部 API + 路由)
数据库D1(SQLite),wranger migrations 管理表结构
对象存储R2 — 用户上传的图片 / 音频 / APK / 安装包
前端原生 JS 单页 + Static Assets,无框架、无构建步骤
消息Web Push(自行实现 RFC8291 + RFC8292)

为什么不用框架?Worker 冷启动 + 打包体积是硬约束。原生 JS 的 app.js 约 5000 行,但首屏只有一个文件,无 hydration、无虚拟 DOM 开销,在低端安卓机上滑动列表比 React SSR 更跟手。

二、功能地图

📝 内容系统
  • 富文本编辑器:标题 / 列表 / 引用 / 代码 / 链接
  • 图片、音频、B 站、YouTube、视频直链嵌入
  • 上传前 canvas 压缩(最长边 1600px,JPEG 0.85)
  • 40 个版块、标签体系、近 7 天趋势榜
  • 评论盖楼、作者置顶(单帖上限 1 条)
  • 软删除:无子回复的已删评论直接不返回
  • 草稿自动保存 + 草稿箱分页检索
👥 用户与社交
  • 注册 / 登录 / 会话,per-user 资料缓存
  • 关注关系 + 推荐算法(偏好版块加权)
  • 私信(D1 原子限流 10 条 / 分钟)
  • 站内通知 + Web Push 系统通知
  • 8 个社交绑定:官网 / 微信 / X / B站 / YouTube / Reddit / TG / WhatsApp
  • 微信二维码弹窗、打赏配置
📱 多端交付
  • 响应式网页 + PWA(可安装、离线壳)
  • Android:WebView 壳 2.7MB,原生通知轮询
  • Windows:Electron + NSIS 安装包 75MB
  • 桌面端托盘未读、关闭到托盘、SPA 内跳转
  • 统一版本接口 /api/app/version
🧰 效率与开放
  • 工具箱:11 个自有 / 关联应用统一入口
  • 6 套主题 + 6 种语言(含 RTL 阿拉伯语)
  • AI 助手「云小智」:12 个工具、可自主建版块
  • API 密钥 + 外部接入 + 公开 MCP 端点
  • Web Push 开关、通知偏好

三、八个关键实现细节

01 · 限流:为什么最后用 D1 而不是平台能力

私信需要「每用户每分钟 10 条」。先试内存 Map —— 跨 isolate 不共享,12 次测试全部放行。再试 Cloudflare 原生 Rate Limiting binding —— 免费计划下 limit() 恒定返回 success,等于没限。

最终落在 D1 单条原子 SQL,靠 ON CONFLICT ... RETURNING 拿到计数,天然并发安全。实测第 1-10 次通过、第 11 次 429。

02 · Web Push:从零实现加密与签名

Workers 上没有成熟的 Push 库可用(Node 的 web-push 依赖 crypto 原生模块)。于是按 RFC8291 手写 payload 加密(ECDH + HKDF + AES128GCM),按 RFC8292 手写 VAPID ES256 JWT 签名。

收益:用户关掉页面也能收到通知,且不依赖任何第三方推送服务。Mac Safari 与 iOS PWA 实测可用。

03 · 推荐算法:6 轮串行查询压到 2 轮

关注推荐原来要依次查:版块偏好 → 二度社交 → 已关注 → 候选用户 → 版块统计 → 新用户。六次 D1 往返串起来,慢网下用户看到的是十几秒的转圈。

把五个无依赖查询合并进 Promise.all,只有版块统计依赖候选列表。响应时间从 0.26-0.35s 降到 0.15-0.25s,超时率大幅下降。

04 · 缓存:60 秒读缓存 + 写操作精准失效

首屏推荐结果用 caches.default 缓存 60 秒,key 是 https://follow-rec.internal/<uid>。只有 offset=0 走缓存,翻页不缓存,避免脏数据。

关键是反向失效:关注 / 取关接口里直接 cache.delete 该用户 key,用户操作后不会看到 60 秒前的旧状态。

05 · 容错:一个 catch 差点毁掉整个侧栏

侧栏渲染函数末尾原本是 catch { hideRightbar(); }。任何一个子区块超时,整个右栏就消失 —— 用户看到的是「侧栏加载失败」,毫无线索。

改成三层防护:① 外层 catch 保留框架,渲染错误卡片 + 重试按钮;② 子区块用 Promise.race 独立超时,只降级自己;③ 两个请求并行发出,超时不叫「失败」而显示「加载较慢」,数据到了自动填充。

06 · 大文件:R2 + Range 请求做安装包分发

Windows 安装包 75MB,安卓 APK 2.7MB,都不适合塞进 Static Assets。Worker 加了一条 /dl/:name 路由,直接从 R2 流式返回。

必须支持 HEAD 和 Range(206)——否则浏览器断点续传、下载器探测都会失效。还有一个坑:路由要在 run_worker_first 和 fetch 入口分发两处都声明,只改一处会被静态资源先吃掉。

07 · 移动端上传:三个必须同时成立的条件

「点了选图,选完没反应」排查了两个方向,两个都是真问题,缺一不可:

WebView 侧必须重写 onShowFileChooser,用 params.createIntent() 并显式加 FLAG_GRANT_READ_URI_PERMISSION;网页侧 input[type=file] 必须先挂到 DOM 再 click()(iOS Safari 会静默忽略游离节点)。

08 · 客户端:网页是主体,App 只是壳

安卓端用 WebView 加载 https://yunzhi.dev,Windows 端 Electron 用 win.loadURL() 远程加载。这意味着网页改版不需要重新发版。

客户端只补网页做不到的三件事:系统级通知(轮询未读 + DownloadManager 下载 APK)、托盘常驻与未读徽标、以及在 Electron 里 serviceWorker.ready 永久 pending 时的 Web Push 降级。

四、踩过的坑(真实教训)

现象 根因
资料保存后像是没生效/api/auth/me 有 per-user 60 秒缓存,写操作后必须主动失效
移动端弹窗底部按钮点不到发帖 FAB 的 z-index 9999 高于遮罩层 100,整层被压在按钮上
删掉的评论还留「已删除」列表 SQL 漏了 is_deleted = 0 过滤
AI 老说「版块不存在」报错里没有候选清单,模型只能反复猜;补 list_categories 工具 + 6 级模糊匹配
APK 内点下载链接无反应WebView 不处理 apk MIME,必须 setDownloadListener 交给系统 DownloadManager
Electron 里推送开关卡死navigator.serviceWorker.ready 在 Electron 永远 pending,要先短路判断环境

五、这套架构适合谁

✓ 适合
  • 中小型社区、内部论坛、产品反馈区
  • 不想维护服务器、希望全球访问都快
  • 需要网页 + App 双形态但预算有限
  • 团队小、迭代频繁、要快速试错
✗ 不适合
  • 需要复杂事务、跨表强一致的重业务
  • 大量长连接 / 实时推送(WebSocket)
  • 全文检索要求高(D1 只有 LIKE / FTS5 有限支持)
  • 数据必须落在境内的场景

回头看,这个项目最有价值的部分不是功能数量,而是把「边缘计算 + 低成本 + 多端」这条路线走通了一遍:从 D1 原子限流、自研 Web Push,到 Electron 交叉打包和在 Linux 上打 Windows 安装包。每一块单拎出来都能复用。

评论区

0 条评论 · 评论需审核通过后显示

加载评论中...

发表评论

0/2000

← 返回
🔍
客服图标
微信客服图标
AI 智能客服
×

微信客服

微信二维码