Node 后端实战 · Cloudflare Workers 踩坑实录TOML、D1 默认 local、CORS 与部署排障各位看官上篇把为什么用 Cloudflare Workers D1 扛多租户 SaaS的架构决策讲了一遍。架构定了真正磨人的是落地——这套 serverless 边缘栈和本地起个 Node 服务 连 Postgres的心智模型差得挺远不少坑是文档没读细和本地线上行为不一致叠出来的。这篇把我在 wrangler 配置、D1、CORS、部署这几个环节实打实绊过的坑摊开能帮同样打算上 Workers 的同学省掉几天查 issue 的时间。先把坑列个速览后面逐条展开坑所在章节一句话提醒TOML binding 冲突一CLI 建议的 binding 是示例值别照抄资源 ID 提交 Git一标识符安全密钥才要保密D1 默认走 local二动线上库必须加--remoteCORS 自定义头漏配三allowHeaders要把每个自定义头列全环境命名暧昧四用 test/prod/dev别用 staging部署后排障五wrangler tail看流式日志数据层命名六代码用缩写库里是全名下划线文档暴露七新环境记得加进SWAGGER_ENVS一、wrangler.toml 配置CLI 的建议别照抄1.1 TOML 重复键binding 冲突第一次用wrangler r2 bucket create之后CLI 很贴心地打印一段建议加到 wrangler.toml 的配置。我顺手复制粘贴结果部署直接报错[ERROR] Invalid TOML document: trying to redefine an already defined table or value根因很朴素建议里的binding telemarketing_audit_archive_test只是举例的 Snake 命名而我代码里读的是c.env.BUCKET正确的 binding 应该是binding BUCKET。同一个[[r2_buckets]]块里出现了两个bindingTOML 直接拒绝。教训CLI 输出的 binding 值是自动生成的示例不代表你该用那个值。动手前先看你代码里c.env.XXX实际读的是什么变量名。同理新增任何绑定KV、R2、Queue都先确认代码侧的读取名和 toml 里的 binding 一字不差——这是部署前花一分钟就能避掉的低级错误。1.2 资源 ID 提交 Git 安全吗刚接触 Cloudflare 的人会担心wrangler.toml 里写着的 D1 UUID、KV Namespace ID、R2 Bucket 名称提交到 Git 仓库会不会泄密结论是安全。这些只是资源标识符没有 Cloudflare API Token 谁也操作不了它们。官方文档和示例仓库都是这么干的。真正要保密的是另外三样这张表分清就不会过度紧张也不会大意类别例子能否提交 Git资源标识符D1 UUID、KV ID、R2 名称✅ 安全可提交注入密钥wrangler secret put注入的 JWT_SECRET 等❌ 绝不本地凭证~/.wrangler/config/default.toml里的 Token❌ 绝不CI 密钥流水线里的CLOUDFLARE_API_TOKEN❌ 绝不想清楚这个边界就不会要么把 ID 全打码、要么把 Token 误提交。二、D1 命令默认走 local忘了 --remote 是常态这是最容易反复栽跟头的地方。d1 migrations apply和d1 execute默认操作的是本地.wrangler/state里的 SQLite 文件要动线上库必须显式加--remote# 操作的其实是本地文件pnpmwrangler d1 migrations apply telemarketing-saas-test--envtest# 动线上库必须 --remotepnpmwrangler d1 migrations apply telemarketing-saas-test--envtest--remote我踩过不止一次本地 migrate 跑完兴冲冲部署线上一查表不存在——因为没加--remote迁移只落在本地文件上。把三种常见情形对照一下命令 / 场景默认目标动线上要加什么d1 migrations apply本地.wrangler/state--remoted1 execute本地 SQLite--remotewrangler dev运行时查数据被 dev 锁定本地文件先停 dev或走 HTTP 接口查另外wrangler dev启动后会锁定本地 D1 文件这时候另开终端跑--local查询经常返回空。别跟空结果较劲先停 dev 或改走db.execute的 HTTP 接口。这里有个容易混的点wrangler dev用的本地 D1 实例和你用wrangler d1 execute --local连的并不是同一个文件所以即便你绕开锁去查看到的数据也可能和 dev 跑起来的对不上。最稳妥的还是停掉 dev 再查或者直接查线上--remote。还有个管道导入的暗坑想把种子数据导进线上库直觉是node scripts/seed.mjs | wrangler d1 execute xxx --remote --file-某些 wrangler 版本直接报Unable to read SQL text file -。解法很土但管用——先输出到临时文件再--file指定nodescripts/seed.mjs/tmp/seed.sqlpnpmwrangler d1 execute telemarketing-saas-test--envtest--remote--file/tmp/seed.sql三、CORS自定义头漏配预检必挂前端用 Cloudflare Pages 托管的 SPA 直连 Workers API本地好好的一上 Pages 就 OPTIONS 预检失败。根因是浏览器发 OPTIONS 问服务器我能带cf-connecting-ip这个头吗但我的 CORS 配置allowHeaders里没列它预检被拒。cf-connecting-ip是 Cloudflare 在边缘自动注入的属于自定义请求头。规则很简单——前端发的每一个自定义头都得在Access-Control-Allow-Headers里显式声明app.use(/*,cors({origin:*,allowMethods:[GET,POST,PATCH,DELETE,OPTIONS],allowHeaders:[Content-Type,Authorization,cf-connecting-ip],// ← 漏了就挂maxAge:86400,}));这条坑不只坑我一次任何前端带自定义头 边缘代理的组合都会遇到记住allowHeaders要全列即可。示例里的maxAge: 86400让浏览器缓存预检结果一天能省掉大量无谓的 OPTIONS 往返——在边缘节点上高频调用时这块省得明显。四、环境管理别用 staging 这种暧昧名字早期我用一个叫staging的环境当线上测试结果开发同学经常搞混这到底是类生产还是纯测试弱密码和测试数据到底能不能往里塞后来改成三环境语义一眼清楚环境触发方式用途数据与密码dev默认本地本地开发随意test--env test线上测试弱密码、测试数据可接受prod--env prod正式生产强密码、真实数据wrangler.toml 里顶层就是 dev再挂[env.test]和[env.prod]。每个环境需要独立的 D1、KV、R2 和密钥——别图省事共用共用迟早串数据。密钥要分别注入wrangler secret put JWT_SECRET --env test和--env prod是两套独立值漏掉一个环境那个环境启动就是 500。这也是为什么排障表里登录 500的第一条就是 JWT_SECRET 未注入。五、部署后排障速查部署完第一步不是庆祝是先curl /health。常见问题这张表基本覆盖现象排查方式常见原因登录 500wrangler tail看日志JWT_SECRET 未注入 / PBKDF2 超限 / 表不存在CORS 报错浏览器 Network 看 OPTIONSallowHeaders缺自定义头401 Unauthorized检查 token 有效期部署后改过 JWT_SECRET404 Not Found检查 URL 路径workers_dev子域名不对wrangler tail是线上排障第一抓手日志直接流式过来比猜强太多。六、数据层命名代码缩写别写进 SQL这个坑不在 wrangler 里但在同一个项目里反复绊人顺带记一笔。项目里角色用代码常量ROLES.PSA、ROLES.TA这类缩写但写进数据库role字段的是全小写下划线全名。一开始我习惯性地拿缩写去查库结果一条都查不到角色代码常量DB 存储值平台超管ROLES.PSAplatform_super_admin租户管理员ROLES.TAtenant_admin经理ROLES.TMtenant_manager员工ROLES.TEtenant_employee-- 错WHERE rolepsa → 查不到-- 对WHERE roleplatform_super_admin这类代码里用枚举、库里用全名的约定最好在项目文档里写清楚或者加一层常量到存储值的映射函数。否则换个同事来查库又得踩一遍。七、本地复位与文档暴露本地 D1 状态乱了rm -rf .wrangler然后重新 migrate seed 是最快复位手段。但有个禁忌不要用 better-sqlite3 之类的库直接打开 D1 文件去改——wrangler 对本地 D1 文件有自己的状态管理和锁better-sqlite3 直接开文件会绕过锁、写进一个 wrangler 不认的状态下次 migrate 就报各种诡异错误。想看数据用wrangler d1 execute --local --command就够了。另外 Swagger 的/docs端点用SWAGGER_ENVS控制哪些环境暴露。每部署一个新环境记得把它加进白名单否则要么线上暴露文档、要么本地死活打不开。八、一个预告边缘运行时的密码学限制最后提一个会专门开一篇讲的坑Cloudflare Workers 的 Web Crypto 对 PBKDF2 迭代次数有硬上限100,000你设 120,000 直接NotSupportedError。这在做密码哈希、双密钥轮换时会正面撞上属于认证安全那一篇的核心素材下篇细聊。小结Workers 这套栈上手不难但本地与线上的行为差D1 默认 local、dev 锁文件、STDIN 管道、配置即代码的细节binding 别照抄、环境别共用、边缘特有约束CORS 自定义头、Web Crypto 限额这三类的坑靠常识是避不开的得实际踩一遍。写这些不是劝退——恰恰相反这些坑每一个都不深踩过一次就再不会忘。真正麻烦的是它们分散在官方文档的不同角落没有一篇踩坑合集帮你看全。希望这篇能当那个合集让你少走几天弯路。下一篇写 D1 自身的坑——尤其是单条 SQL 绑定参数不能超过 100 个这个限制怎么逼出分批写入那才是真正让架构变形的地方。发财的小手点个小赞咱们下篇接着聊 D1 那些事。相关阅读Node 后端实战 · 为什么用 Cloudflare Workers D1 扛起了整个多租户 SaaS 后端架构决策全景复盘NodeJS Koa 后端用户会话管理JWT, Session长短Token本文一次性讲明白node 后端和浏览器前端有关 RSA 非对称加密的完整实践前后端匹配的代码演示Nodejs 实现 Mysql 数据库的全量备份的代码演示安装和配置 Nginx 和 Mysql —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录6PVE 虚拟机安装 Ubuntu Server V24 系统 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录1本文由 FungLeo 主导Deepseek 优化校阅转发请注明首发地址谢谢大家