ENZH

29 天,5 个散装 App 变成一个平台

第 1 篇讲了为什么零领域知识做算命平台。现在说怎么做的。

猫咪建筑师将五个分散的小楼合并成一座统一平台猫咪建筑师将五个分散的小楼合并成一座统一平台

不是精选集锦。真实的架构决策、最棘手的 bug、让每天 39 个 commit 成为可能的基础设施。


起步是 5 个独立的 Next.js 应用,5 个独立的仓库。八字、星座、塔罗、解梦、占卜。每个有自己的认证、Supabase 客户端、Tailwind 配置。

用户得登录五次。五个独立的收费站,不是一个平台。

第 1 天搭 Turborepo。21 个 commit。概念简单——应用搬进 apps/,共享代码提进 packages/——每条 import 路径都炸了。修 import 和验证构建占了 80% 的时间。

第 2 天,CSS 地狱。Tailwind 配置统一到 packages/config,每个依赖自定义 design token 的应用全崩。不是明显的红色报错,是微妙的"为什么这按钮小 2px"。38 个 commit,每次在修视觉回归。

不过第 2 天也产出了第一批真成果:提取了四个共享 package——PDF 生成、聊天记录、共享聊天 UI、通用 React hooks。看着 5 个应用里重复的代码收敛成单一数据源,极其爽。

第 3 天加了 MBTI 性格测试成第 6 个应用。做了微信浏览器检测——中国互联网很大流量来自微信内置浏览器。UA 嗅取得加固,微信 UA 在 Android、iOS 和桌面端各不同。

第 4 天,56 个 commit——统一 API package。packages/api 标准化了所有应用跟 AI 模型通信的方式。52 条 API 路由迁移到统一模式:认证检查、积分扣除、模型选择、SSE 流式输出、错误处理。

Claude Code 看了一个示例之后迁移了全部 52 条。实际耗时大约一小时,手动至少一整天。

第 4 天也搞定了 GDPR 合规、中英双语法律页面、第一版基于 DFA 的敏感词过滤器。过滤器是刚需——玄学内容天然含会触发简单关键词过滤的词(算命、破财、桃花劫),需要合理的白名单方案。


全 monorepo 用 Vitest。优先级想得很清楚。

先测共享 package——API 流式输出 helper、Supabase 客户端、积分管理。承重墙。

再测领域计算——八字计算、塔罗抽牌、六壬占卜。没法验证正确性(零领域知识),验证确定性输入产出预期输出就行。

最后测 API 路由——mock 认证 token 做集成测试。

GitHub Actions CI:每 PR 跑 lint、类型检查、构建、测试。到正式开始里程碑的时候,测试套件已有 34 个文件、1,922 个测试。

品牌也在这个阶段确定下来。PanPanMao(盼盼猫)——会算命的猫。"猫"主题一确定,一切顺理成章。每个应用有猫主题装饰,积分货币变成了小鱼干。积分抽象层很关键——人们更愿意"花 5 条小鱼干",不愿看到具体的美元金额。预置了未激活的人民币套餐,为中国市场准备。


手面相识别是技术难度最高的功能。PR #1:56 个文件变更,6,881 行新增。

MediaPipe 全跑在浏览器端。检测不需要服务端往返。hooks 架构:useMediaPipeLoader 懒加载 WASM(约 3MB),模块级缓存;useFaceDetector 实时 BlazeFace,60fps 摄像头预览;useFaceLandmarker 478 点面部网格,拍照跑一次;useHandLandmarker 21 点手部关键点,拍照跑一次。

PR #50 把 WASM 和模型文件本地化到 public/mediapipe/,中国用户访问 cdn.jsdelivr.netstorage.googleapis.com 不稳。PR #35 加了掌纹检测 GPU→CPU 降级,解决 WebGL 失败设备的兼容。

多模态 AI prompt 不能说"看看手"。prompt 四层结构:角色定义 → 知识注入(五官、三停、十二宫)→ 交叉验证 → 结构化输出格式。

积分经济这边,匿名用户转正最头疼。PR #2(M1 Foundation)100 个文件变更。核心挑战是匿名用户转正式用户的账号合并。新用户进来就是 Supabase 匿名账户。兑换码或登录时,积分、算命记录、聊天历史要无缝合并。兑换流程状态机:idle → checking → redeeming/logging_in → success/error。

跨标签页积分同步用 Supabase 实时订阅。一个隐蔽的 bug:标签页买了积分,切另一标签页,旧余额还在。用户以为购买失败,又买一次。双重扣费。修复:实时订阅一秒内同步所有标签页。

18 条流式路由大统一。到这已有 18 条 SSE 流式路由,每条自己实现 ReadableStream。PR #16 大重构:15 条迁移到共享的 createAIStreamResponse() helper,消除约 1,200 行重复样板代码。加了生命周期钩子:initEventsonCompleteonErrorrefundCreditsOnError。三条确需自定义处理:MBTI 聊天(accumulated-buffer-delta 模式做信号剥离)、梦境小说(多步骤生成协议)、每日运势(非流式,带服务端缓存)。

AI 语气大改是全场最难做对的事。PR #38 由真用户反馈触发:解读太"讨好型"了。什么都正面。每次说成功。健康永远没问题。感情永远有希望。真算命先生不这样说话。修复:全 6 个领域 prompt 做系统性改造——整体语气从"偏积极正面"改成"好说好,不好明确指出"。健康解读从"混合分析"改"中立偏负面,直接健康警示"。负面指标直接用凶/不利,后面跟化解方案。少数 AI 没法独立完成的改动。语气校准需要人对文化期望的判断。


2 月 12 日合并了 15 个 PR。M1(积分经济)、M2(每日枢纽)、M3(PostHog 数据分析)同一天全上线。

每日枢纽(PR #4)是留存设计。算命天然是事件驱动的——有问题来,得答案走。枢纽给用户每天回来的理由。核心洞察:北京时间午夜 cron 预生成内容,不按需生成。用户早上打开,内容瞬间加载。没有转圈。即时和等 3 秒之间,差的不是 3 秒,是用户对产品的信任。

接下来加了流年运程(PR #85),一个全新领域——生肖计算器、太岁分析、流年飞星、AI 运势解读。48 个文件,162 个领域测试。

Portal 主题 bug 是我最喜欢的工程故事。每个应用有自己的 CSS 主题(.theme-hub.theme-tarot)。React portal 通过 createPortal(content, document.body) 渲染——DOM 节点跑在应用主题 wrapper 的外面。CSS 变量在 portal 内容里不可见。最初修法:检测 closest('.theme-hub') 硬编码 overlay 类。9 个主题、13 个 portal 组件,不可扩展。PR #67 方案:ThemeProvider React context 声明当前主题类,ThemedPortal 组件用 MutationObserver 把正确主题类和暗色模式类包裹 portal 内容。干净、可扩展、正确。

弹性 AI 流式传输是最后的大基础设施 PR(#105)。服务端流式缓冲。19 条 AI 流式路由通过 StreamBufferManager 将数据块缓冲到数据库。客户端中途断开?GET /api/stream/{id} 恢复完整响应。统一 useAIStream hook 替换 14 个组件中 3 种 SSE 消费模式。结果:加了整套弹性基础设施,代码净减 186 行。

内容过滤器三次进化:V1(第 4 天)DFA 关键词过滤,敏感消息整条拦截;V2(PR #78)流感知过滤,遇敏感词截断整个流——用户积分扣了,只拿到残缺解读;V3(PR #104)行内遮蔽,敏感词替换 **检测到违禁词**,流不中断。双过滤架构:严格输入过滤器(广告+政治+色情+犯罪),宽松输出过滤器(排除"广告"类别,防误杀算命术语)。边界安全遮蔽处理跨 chunk 边界的词汇。


速度数据:1,134 个 commit,29 天(日均 39,峰值 98)。109 个 PR,66 个带详细描述。9 个产品线,都有真业务逻辑。85 个 API 端点,19 条 SSE 流式路由。约 284,000 行代码,16 个 package。2,021 个测试通过(最终计数)。0 个零 commit 日。

共享 package 是速度倍增器。新增产品线自动获得认证、积分、AI 模型选择、内容过滤、流式传输、错误处理和 UI 组件。新产品的边际成本几乎全在 prompt engineering。

第 3 篇是真实的教训——哪些事意外,哪些事做错,一切让我对未来怎么做产品有了新的认识。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0