第七回:搬家搭架子呆码农两天盖了一座楼,晚八点巧媳妇来挂窗帘了
Y 用两天把游戏从一个静态页面搬进了 Cloudflare 全家桶——Worker 路由、D1 数据库、用户账号、排行榜、遥测、CI/CD、日志系统一口气全上了。L 在第二天晚上八点出现,把按钮重新排了一遍,给排行榜改了名,又顺手造了个弹窗组件。
以下情节纯属虚构,如有雷同纯属巧合。
开场
到 3 月 7 号为止,修仙模拟器还是一个纯静态页面。
没有后端。没有数据库。没有用户系统。玩家的存档在浏览器 localStorage 里,换个设备就没了。排行榜?不存在。页面挂在 GitHub Pages 上,改一次代码等五分钟部署。L 这头越改越嗨,彩蛋、元婴、飞升、天道对话全塞进去了,但整个项目的底子还是一张 HTML 加几个 JS 文件,跟她第一天用 Kimi 搓出来的那版没有结构性的区别。
Y 看了一段时间了。他知道这个东西迟早得搬家。不是因为有什么紧急问题。是因为他能看到下一步——如果要排行榜,就得有数据库;如果要数据库,就得有后端;如果要后端,就不能继续挂在 GitHub Pages 上。
3 月 7 号早上十点,他打开 Codex,开始动手了。
搬家
Y 选的方案是 Cloudflare。Pages 托管静态文件,Worker 做路由和 API,D1 做数据库,KV 做缓存。不用管服务器,不用管运维,全部跑在边缘节点上。他之前没用过 Cloudflare 全家桶。但架构他想得很清楚,剩下的就是让 Codex 帮他一块一块搭。
第一个提交很大。GitHub Actions 工作流、Worker 路由入口、D1 初始化迁移、Wrangler 配置文件、环境变量模板——一口气全推上去了。Worker 的入口文件 `index.mjs` 负责分发所有请求:首页走 SSR,`/xiuxian/*` 走静态代理,`/api/*` 走 API 路由。Pages 的上游重定向要保留 `/xiuxian` 前缀,所以还得加一层 `rewriteUpstreamRedirect()`。
然后是部署脚本。`stage.sh` 推到 staging,`release.sh` 推到 production,ff-only,不允许回退。他还写了一个 WSL 启动脚本,把 apt 包、NVM、Node、wrangler 一键装好,以防哪天需要在另一台机器上开发。
首页也重写了。原来的英文导航页换成了中文:"今天,修哪一世?"。标签写着"单局 3-8 分钟""无下载,点开即玩""支持移动端"。一个叫"HOT"的角标挂在修仙模拟器的卡片上。
到上午十一点,搬家基本完成。整个项目从 GitHub Pages 迁到了 Cloudflare,staging 和 production 两条线都通了。
Y 没停。
他下午造了一个用户系统
同一天下午,Y 继续跟 Codex 对话,开始搭用户账号。
数据库里多了两张表。`users` 存用户,`user_game_assets` 存存档。每个用户有一个 `vibe_uid`,UUID 生成,全局唯一。登录不需要密码——发邮件验证码,八位数字,十分钟有效,KV 里存过期时间。发送走 Resend API。防刷用 KV 计数器加 TTL 窗口。
前端页面上多了几个按钮。起始页写着"神魂印记:未绑定(仅本机存档)"。旁边有"寻回进度"和"邮箱绑定神魂印记"。结算页多了"留名上榜"。
然后是遥测。新的 `telemetry_events` 表,带游戏维度、漏斗名称、步骤索引、状态标记。每个事件有白名单字段过滤,按游戏分配。老的 `events_log` 做兼容镜像。前端加了 `telemetry-client.js`,在引擎初始化之前加载,自动往 `/api/telemetry/collect` 推事件。
到傍晚六点,用户系统和遥测管线全部就位。
然后他开始做排行榜。
三个榜
排行榜的设计很有意思。不是一个榜。是三个。
"卷王榜"——谁用最短时间修到元婴。取最小值。描述写的是"此子恐怖如斯!"
"苟道榜"——谁活得最久。取最大值。描述写的是"遇事不决,闭关千年……"
"肝帝榜"——谁转世次数最多。取总和。描述写的是"孟婆汤都快被你喝出抗体了!"
这三个名字一看就是 L 起的。Y 之前跟她聊过排行榜要做几个维度,L 一口气报了三个名字,Y 直接用了。榜的定义存在 D1 里,用 seed 脚本预置。提交走 JWT 鉴权,HS256 签名,Worker 里自己签自己验。前端有 modal,三个 tab 切换,每行显示排名、名字、分数。结算的时候底下还会算一个百分位:"你超过了全服 87% 的玩家。"
到晚上九点,排行榜上线。一天之内:Cloudflare 迁移、用户系统、遥测管线、三个排行榜。Y 关了电脑。
第二天他开始拆自己写的东西
3 月 8 号上午,Y 回来看了一眼 Worker。
`index.mjs` 已经超过 2200 行了。路由、鉴权、passport、排行榜、遥测、首页渲染,全挤在一个文件里。昨天赶工的时候能用就行,今天看着就不行了。
他跟 Codex 商量了一下拆分策略,花了一上午把它拆成八个模块:`helpers.mjs` 做响应和验证,`auth.mjs` 做 JWT 解析,`passport.mjs` 做用户资料和 OTP,`leaderboard.mjs` 做排名和提交,`telemetry.mjs` 做事件摄取,`homepage.mjs`、`proxy.mjs`、`db.mjs` 各管各的。
拆完顺手做了几件事。
安全加固:存档接口必须带 JWT,不再信任客户端传的 `vibe_uid`。排行榜拒绝 `yuan_ying_age < 10` 或大于终身年龄的提交。遥测健康接口补了 CORS 头。昵称验证过滤控制字符,前后端一致。
错误日志:新建 `error_log` 表,`logger.mjs` 模块负责写入,fire-and-forget,绝不阻塞请求。顶层 `fetch()` 包了一层 error boundary,九个静默 catch 块全接上了日志。每天凌晨三点定时清理七天前的记录。
CI/CD:D1 迁移改成远程执行。加了 schema gate,部署前检查所有表是否存在、seed 数据是否到位。passport 冒烟测试。Pages 部署分支动态解析。wrangler 版本钉死 4.71.0。
还做了一个 OTP 弹窗,用来替代浏览器原生 `prompt()`。邮箱输入、发送验证码、输入验证码,全在页面内完成。
然后他把邮箱绑定的入口从 UI 上删了。
他建了又拆了
这个操作挺有意思。
Y 前一天建了完整的邮箱绑定和找回流程:发验证码、绑邮箱、换设备用邮箱恢复存档。前端有按钮、有弹窗、有状态提示。后端有 OTP 生成、有 KV 限流、有 Resend 投递。整条链路全通了。
然后他在同一天晚上把前端入口全砍了。起始页的"邮箱找回存档"按钮、结算页的"邮箱绑定"按钮、OTP 弹窗和相关样式——全部移除。后端接口还在,但用户看不到了。
为什么?大概是因为做完之后发现,对于一个修仙模拟器来说,强制邮箱绑定太重了。玩家来了就想玩,不想先填邮箱。匿名 token 够用了。邮箱以后再说。
这种"先建完再砍"的事情在工程里不算罕见。建的过程帮你想清楚边界在哪。砍的时候心里才有数。
晚八点,L 来了
3 月 8 号晚上八点零三分。L 的第一个提交进来了。
`天道金榜名称调整`。
她让 Kimi 把排行榜在 UI 上的名字统一成了"天道金榜"。Y 之前写的是技术性的"排行榜",L 觉得不够修仙。天道金榜,四个字,一下子就对了。
然后她开始让 Kimi 调页面。
起始页:把"转世重修"放到最大最显眼的位置,"轮回天书"和"天道金榜"并排放在下面,按钮大小和间距重新排了一遍。
结算页:优先级重新排了。最上面是分享截图,然后是"再入轮回",最下面才是"提交成绩"和"天道金榜"。她的逻辑是:玩完一局,第一件事是截图发朋友圈,第二件事是再来一局,第三件事才是看排名。
速度按钮:原来的"1x"文字按钮换成了一组 radio 样式的切换,"1x"和"10x"各一个格子,选中的高亮。暂停符号从 `II` 换成了 Unicode 的 `⏸`。
属性栏:hover 提示删了,换成了每个属性下面直接写一行小字说明。stat 行改成上下排列,名字大、描述小。flex 布局加了 `min-width: 0` 防止长名字挤扁按钮。
按钮样式:新增了 `.btn-flat` 类,透明底色加边框,用在"轮回天书"和排行榜入口这种浏览性质的按钮上。跟实色的主操作按钮拉开了层次。
最后她让 Kimi 造了一个通用弹窗组件。`#game-modal`,支持消息文本、输入框、确认取消按钮,`modalSystem.show()` 一个方法搞定。把之前散落在各处的 `prompt()` 和 `safeAlert()` 全替换掉了。
这些提交从晚上八点持续到九点。L 改完一个就提一个,提完继续改下一个。Y 那会儿还在合她的 PR、更新文档。两个人就这样交替提交了一个多小时。
第三天收尾
3 月 9 号,Y 让 Codex 帮他做收尾。
起始页底部加了三个小图标链接:小红书、反馈、买咖啡。透明度 0.52,视觉上很轻,不抢戏。URL 写在 config 里,运营可以随时换。
排行榜加了自动提交:游戏结束后不用手动点按钮,系统自动尝试提交成绩。第一次上榜的时候弹一个庆祝弹窗,带金色标题和状态提示。
反馈功能:一个弹窗,用户填内容,走 Resend API 发到运营邮箱。不需要登录。限流用 IP。最大 1000 字。
最后一个提交是开发日志系统。D1 里新建了 `devlog_authors` 和 `devlog_posts` 两张表。Worker 里加了一个 651 行的 `devlog.mjs` 模块,负责 Markdown 解析、列表页 SSR、详情页渲染。slug 路由,分页查询,按游戏和作者筛选。样式是浅色羊皮纸底,衬线字体。
这个开发日志系统,就是后来发这篇文章用的那个。
两天盖了一座楼
从 3 月 7 号到 9 号,三天,大约五十个提交。
Y 一个人搭了:Cloudflare Pages 静态托管、Worker 路由和 API、D1 数据库四张核心表加四个迁移脚本、KV 缓存和限流、用户账号体系、JWT 鉴权、OTP 邮件验证、三个排行榜、遥测管线、错误日志系统、请求追踪、CI/CD 流水线、冒烟测试、本地开发工具、部署脚本、WSL 启动脚本、健康检查、运维文档、反馈收集、开发日志系统。
L 在第二天晚上出现了四十分钟。改了名字、排了按钮、造了弹窗、定了优先级。
这两个人的节奏差异到这里已经很明显了。Y 会憋几天不动,然后一口气把整个基础设施翻一遍。L 每天都在改,但都是在改游戏本身——文案、数值、手感、UI。两个人碰上的时候,通常是 Y 搭好了架子,L 来决定窗帘挂哪儿。
排行榜叫什么名字,按钮按什么顺序排,玩完第一件事应该干什么——这些事 Y 不会去想。他会让 Codex 写一个能跑的默认值,然后等 L 来改。L 也不会去想 Worker 该拆几个模块、JWT 用什么算法、D1 迁移怎么做——她只要最后那个页面上的东西长得对、摸着对就行,中间的活交给 Kimi。
谁都没等对方。Y 指挥 Codex,L 指挥 Kimi。两个人用着不同的 AI,干着不同的活,但拼在一起刚好是一个完整的东西。