Files
tamabox/outputs/serverless-migration-feasibility-2026-09-27.md
T
2026-10-15 11:41:19 +08:00

9.1 KiB
Raw Blame History

TamaBox-v4 迁移 Cloudflare Workers + D1 可行性评估

  • 日期:2026-09-27
  • 评估对象:将 TamaBox-v4(Go + Flamego + GORM + PG/Redis)改造为 TypeScript + Cloudflare Workers + D1 的 serverless 形态
  • 参照项目:OpenListTeam/OpenList-Worker
  • 范围:只读评估,未改动任何源码

0. 结论速览

项 结论
技术可行性 可行 —— 没有理论上的不可能
工程性价比 🔴 低 —— 是「整体重写」,不是「改造」
主要阻碍 ① 图像管线(cgo WebP / cwebp 子进程 / freetype 中文渲染)② 进程内有状态子系统 ③ PG 专有 SQL
建议 方案 A(不迁移);若必须上边缘,走方案 B(静态+只读下沉)

一句话:OpenList 是「无状态代理」,TamaBox 是「有状态 Web 应用」,两者的边缘适配成本差一个数量级。


1. 先看清参照物:OpenList-Worker 为什么能做成

维度 OpenList(Go 原版) OpenList-Worker(TS)
仓库体积 9.8 MB 21.7 MB(比原版还大)
语言 Go TypeScript
代码量 — 416 个 .ts,约 2.6 MB 源码
Star 24,800 372
立项 — 2025-08-23(约一年)
维护方 官方团队 官方团队

即使是官方团队 + 24.8k star 的项目,重写一年后 Worker 版代码量反而超过原版。而更关键的是应用形态:

OpenList 的本质是无状态的文件列表 + 反代:

  • 没有多用户会话状态(只有一个管理员)
  • 没有服务端可变状态(限流 / 封禁 / 去重全都不需要)
  • 存储是「可插拔后端」,官方甚至支持降级到 map / KV,并明说 serverless 下禁止回退到内存
  • 没有图像处理、没有服务端模板渲染 —— 前端是现成 SPA,Worker 只负责注入 CDN 地址

所以它干的其实是「把无状态代理逻辑搬到边缘」—— 这正是边缘计算最擅长的形态。

TamaBox 恰恰相反,它是一个有状态的多用户 Web 应用。


2. TamaBox 的形态画像

代码规模:34,664 行 Go

模块 LOC 说明
route/ 7,645 业务逻辑(vote 1297 / share 970 / profile 606 …)
internal/db 6,950 GORM 模型 + 仓储,15 张表
internal/i18n 3,689 四语言字典(以数据为主)
internal/security 2,675 审核、自动封禁、IP 情报
internal/route 1,653 中间件与路由装配
internal/template 825 模板函数 / Markdown / minify
internal/shared 758 IP、去重、并发工具
internal/conf 587 配置(ini + 单环境变量)
internal/cmd 482 CLI 子命令(web/censor/uid/ban/restore)
internal/storage 440 R2 / OSS 上传 + WebP 转换
模板 42 个 html 服务端 html/template 渲染
中间件 — Session / CSRF / Redis cache / reCAPTCHA

3. 分层可行性矩阵

P0 —— 硬阻断(必须重构,无法直搬)

# 阻断点 位置 为什么在 Workers 上不行
1 WebP 编码是 cgo internal/storage/s3.go:16,23,192 chai2010/webp 依赖 libwebp 的 C 代码(//go:build cgo)。Workers 是 V8 isolate,跑不了 cgo → 只能换 @jsquash/webp(WASM)
2 GIF→WebP 走 cwebp 子进程 internal/storage/s3.go:200-233 exec.LookPath("cwebp") + os.CreateTemp。Workers 无子进程、无可写磁盘 → 完全不可实现
3 分享图 freetype 渲染中文 route/question/share.go:121-131, 387-720 os.ReadFile(font_path) 读磁盘 OTF + freetype 光栅化 + jpeg 编码。必须整体重写;且大图中文字形光栅化是 CPU 密集,易撞 Workers CPU 上限
4 进程内状态(Session / 缓存 / 限流 / 封禁 / 去重 / 统计) internal/route/route.go:386-395、internal/cache、internal/security/autoban、internal/shared/duplicate.go、internal/route/ratelimit.go Workers 请求间无共享可变状态 → 全部要迁 Durable Objects / KV / D1,成本明显上升
5 cache 中间件硬编码 Redis internal/route/route.go:386-395 无条件走 cacheRedis.Initer(),不判断配置是否为空。Workers 无法用原生 TCP Redis
6 启动期副作用 internal/cmd/web.go:57-105、internal/db/db.go:58-127 ListenAndServe 常驻监听 + AutoMigrate + fixPostgresSequences + backfill。serverless 无常驻启动流程
7 常驻 goroutine 定时任务 ratelimit.go:41、autoban.go:250、duplicate.go:34、share.go:207 for { sleep; cleanup } 模式 → 需改 Cron Triggers + DO alarm
8 PG 专有 SQL db.go:211-234(序列 nextval/setval)、follow_ups.go:68(FOR UPDATE)、各处 datatypes.JSON(JSONB) D1 是 SQLite:无序列、无 FOR UPDATE、无 JSONB
9 SMTP 发信 internal/mail/mail.go:129-147 Workers 禁止裸 TCP 25/465/587 → 须换 Resend / SendGrid 等 HTTP 邮件 API
10 服务端 Go 模板 templates/ 42 个 + internal/template/template.go:29-229 html/template 的上下文敏感转义是安全边界,迁到 JS 模板引擎需谨慎等价实现
11 OTel/Uptrace gRPC 上报 internal/tracing/tracing.go:15-27、internal/cmd/web.go:41-48 Workers 无 gRPC、无后台 batch 上报协程

P1 —— 需换库 / 改写(有明确对应物)

  • S3/R2 上传 → @aws-sdk/client-s3(R2 兼容 S3 协议,可行)
  • 七牛 / 阿里云文本审核 → fetch 直调或 JS SDK(阿里云 tea/darabonba 运行时需重做)
  • Excel 导出 excelize → exceljs
  • CSV 导出 encoding/csv → 手写(简单)
  • QR skip2/go-qrcode → qrcode JS 库
  • Markdown blackfriday + 自研 XSS 白名单 → marked + DOMPurify(白名单规则需逐条复刻,属安全关键)
  • i18n 的 fmt 风格 %s 占位 → 需实现等价格式化
  • 软删除 + 唯一索引语义 → SQLite 下槽位行为需重新设计

P2 —— 基本可直接移植

  • internal/imagecheck(纯标准库像素校验)
  • internal/captcha/math.go(JSON 算数题,非图片、无字体)
  • internal/i18n 字典(纯数据)
  • CSRF(无状态 HMAC,本身可移植)
  • internal/security/ipinfo(离线签名,零网络、零 DNS)
  • internal/shared/ip.go(IP 解析)
  • bodylimit / ipblacklist
  • embed.FS(转 Assets / 模块)

补充:internal/template/minify.go 当前未被路由装配实际使用,迁移时可直接丢弃。


4. 成本量化

  • 源码 34.6k 行 Go → 等价重写预计 40k+ 行 TS
  • templates/ 42 个模板 + 25 个模板函数全部重写
  • 全部测试重写(含 route/vote/vote_stats_test.go 这类带 DB 的门控测试)
  • 参考标定:OpenList 官方团队 + 24.8k star,重写约 一年,产出 416 个 TS 文件 / 2.6 MB
  • TamaBox 的形态比 OpenList 更「重」(有状态 + 关系型 DB + 图像 + 模板),且为单人维护

5. 容易被忽略的坑

  1. 中国大陆可用性:Workers 是海外节点。文本审核走七牛/阿里云(国内 API),海外边缘回源国内延迟高;面向大陆用户还有备案/合规问题(cf. 项目已有 edgeone-noicp-cf-tunnel-guide、edgeone-origin-whitelist-guide)
  2. D1 限额:SQLite 单库上限(付费 10 GB)、写并发受限;questions / vote_submissions 属写密集
  3. Workers CPU 限额:免费 10 ms、付费 30 s/请求 且按 CPU 计费。分享图渲染、Excel 导出都吃 CPU
  4. Durable Objects 是付费项,按请求 + 时长计费 → 把 Session / 限流 / 封禁搬过去后,成本模型从「一台 1C1G」变成「按量」
  5. 当前生产是 1C1G 小服务器:迁移的收益(免运维 / 弹性)对这个体量并不显著

6. 建议方案

方案 A(推荐):不迁移

形态不匹配 + 单人维护 + 当前 1C1G 成本已很低。收益(免运维、弹性)不足以覆盖重写成本与风险。

方案 B(若确要上边缘,渐进式)

只把无状态的部分下沉边缘:

  • 下沉:静态资源(static/、模板产物)+ 只读接口(公开提问页、投票公开页、/explore)→ EdgeOne Pages / Workers
  • 保留源站:写路径、图像处理、审核、邮件、导出 → 继续跑 Go

项目已有 EdgeOne 回源白名单脚本,这套「边缘 + 源站」形态其实已在用。风险最低、可随时回退。

方案 C(完全重写)

仅当能接受功能裁剪(砍分享图 / Excel 导出 / 聊天 / 投票表单)且投入 2–3 个月专职。且建议优先 EdgeOne(腾讯云,有大陆节点) 而非纯 Cloudflare Workers。


附:评估依据

  • 全部源码只读探查:internal/**(110 个 Go 文件)、route/**(22 个)、templates/**(42 个模板)
  • 依赖清单:go.mod(含 chai2010/webp、disintegration/imaging、golang/freetype、xuri/excelize、uptrace/uptrace-go 等)
  • 对照数据来自 GitHub API 与 OpenList-Worker README / 提交历史