我如何构建意刻:第一个全栈产品的开发复盘
从一个拖了几年的题库网站,到用 AI 重建、推翻、重构并上线意刻。这是一次关于技术选型、边界取舍和 All in AI 开发的诚实复盘。
从一个一直没做完的旧项目说起
认识我比较早的朋友,应该知道我上学时研究过 超星学习通 的网课自动答题脚本。我靠它赚过一点零花钱,也顺着别人的脚本接触到题库、检索和问答网站,认识了一些互联网朋友。
大三时,一位朋友一直劝我把这些东西做成网站。我用 ThinkPHP + LayUI 写出了最早的意刻题库检索,它真的带来过收入。数目不大,但那是我第一次确定:原来自己写的网站不只会占硬盘,它也能赚钱。
到了大四,我总想着把它重构掉。技术栈要现代,代码结构要严谨,交互要优雅,最好还能给未来的各种客户端留好接口。想做的东西越来越多,真正动手的次数反而越来越少。
毕业工作后,意刻就这样成了心里一根没拔掉的刺。每学到一个新技术,我都会拿它往意刻身上比画一下:这个能不能用,那个是不是更合适。几年下来,方案积了一堆,项目仍停在原地。
AI 最后给了我一个不再拖延的理由。从 Copilot 的行内补全,到 ChatGPT,再到 Claude Code,我写代码的方式慢慢从“让 AI 帮一下”变成了“让 AI 负责大部分实现”。既然总想验证 All in AI,那就拿这个拖得最久的项目开刀。
意刻到底在做什么
意刻的 slogan 一直没变:搜索问题,理解答案。
旧站解决的是前半句。输入题目,从几十万条数据里找到相近内容,再把已有答案展示出来。新的 imoment.top 仍以搜索为入口,只是把答案、官方解析和多模型 AI 解读放在了同一个问题详情里。
搜索本身免费。用户登录后可以解锁完整答案,也可以创建 API Key,通过 REST API 查询题目、读取带 AI 解读的详情和积分余额。后台负责用户、题目、卡密、积分流水与解读内容的管理,外部调用则有 Node.js、Python、PHP、Go 和 Rust SDK。
意刻不是聊天机器人,也不在每次打开题目时临时问一遍模型。AI 解读会提前生成、校验并写入数据库,用户请求只读取已经准备好的结果。这样更快,也更容易控制内容质量和调用成本。
技术选型折磨人的地方,是每个方案都说得通
这部分大致经历了三个阶段。回头看,变化最大的是我终于肯承认,项目没有想象中那么复杂。
第一阶段:必须前后端分离
当时的想法很朴素:既然行业里有前端、后端岗位的拆分,一个像样的全栈作品当然也要前后端分离。更何况那时的 TL 是前端开发工程师,我很想把低耦合、接口设计和工程结构都展示出来。
于是一个题库检索站还没开始写,就先有了前端、后端、公共核心层、各语言 SDK、小程序和一堆暂时不存在的消费者。图画得很完整,只差业务代码。
clients/
├─ web ──────────┐
├─ miniapp ──────┤
└─ sdk/* ────────┘
│
▼
independent API
│
▼
packages/core
├─ database
└─ search adapter第二阶段:在正确答案之间反复横跳
前端其实没太多悬念,无非是 Vue 和 React。因为我对 Vue 更熟,所以最后选了 React。这个决定听上去不讲道理,但很符合当时的目的:我想借一个真实项目把 React 学深一点,而不是再做一遍已经熟悉的东西。
后端的候选项多得多。Python 有 FastAPI 和 Django,Node.js 这边有 Express、NestJS、Hono、Elysia 和 Next.js。我做过 Django 项目,不太喜欢它把太多东西一起端上来;Express 和 NestJS 对这个项目偏重;Hono 留给自己的工作很多;Elysia 配合 Bun 时类型体验很好,我也确实用它完成过第一版后端。
我最终偏向 Node.js,理由只有一个:类型可以一路传下去。 从数据库字段到接口入参、返回值,再到页面渲染,改动能沿着类型错误一路暴露出来。一个人同时维护前后端时,这比单点性能数字更有用。
搜索引擎也绕过远路。旧系统把数据存在 MySQL,由 Sphinx 定时同步,在几十万条题目的规模下还能用。重构时我先想到 Elasticsearch,随后又开始设计一层兼容接口,想让低配置机器可以替换检索引擎。
数据库倒是很早就定了 PostgreSQL,ORM 在 Prisma 和 Drizzle 之间纠结一阵后选了 Drizzle。
再后来 Next.js 变得很热,我又画出一版看起来很漂亮的架构:Server Functions 和独立后端共用一个 core 包,两边只保留适配与鉴权。这样既有 SSR,也有完整 API,还可以继续接其他客户端。
clients/
├─ browser ──> Next.js Server Functions ──┐
└─ SDKs ─────> independent backend ───────┤
▼
packages/core
├─ PostgreSQL
└─ search adapter纸面上确实漂亮。问题是当时没有任何真实需求需要这套结构。
第三阶段:去他妈的中间层
去他妈的中间层,去他妈的为了分包而分包。
我重新列了一遍意刻真正要做的事:完整的用户体系、足够快的题库检索、舒服的前台和能用的管理后台。先把这些做完,其他能力在出现真实消费者以后再长出来。
最后我没有选 Next.js,而是用了 TanStack Start。之前用过 TanStack Table 和 TanStack Query,我很喜欢这套库的设计。更重要的是,服务端函数要用 createServerFn 明确声明。打开文件就能判断代码运行在哪一侧,对我和 AI 都少了一层猜测。
最终的主应用留在 apps/web,没有再抽一个包住所有业务的 core。仓库从第一天起仍是 workspace,后来也增加了数据转换、Meilisearch 同步和多语言 SDK,只是这些目录对应的都是已经存在的边界,而不是提前想象出来的边界。
apps/web
├─ UI + createServerFn
├─ REST API /v1 <── sdk/{nodejs,python,php,go,rust}
├─ Drizzle ─────────> PostgreSQL
└─ search ──────────> Meilisearch
packages/
├─ transform ───────> PostgreSQL
└─ meilisearch-sync
└─ PostgreSQL ───> Meilisearch现在的技术栈是 Vite+、TanStack Start、TanStack Query、Tailwind CSS、shadcn/ui、PostgreSQL、Drizzle、Meilisearch 和 Docker。它不一定适合所有全栈项目,但很适合此刻的意刻。
搜索不是换个引擎就结束了
从 Sphinx 换到 Meilisearch 很容易写成一行技术栈升级,麻烦都在 PostgreSQL 和搜索索引如何保持一致。
在意刻里,PostgreSQL 是事实来源,题目、答案、AI 解读和权限信息都以它为准。Meilisearch 只保存用于检索的文档。管理后台修改或恢复题目时,会显式同步对应的搜索文档;批量数据则由独立的 meilisearch-sync 工具处理。搜索拿到命中的 UUID 后,再回 PostgreSQL 读取公开字段,并按命中顺序恢复结果。
这比旧系统的定时同步多了一些代码,也少了一种不确定:刚改过的题目不必等下一轮任务,索引出了问题也有清楚的重建路径。
AI 解读先是一条数据流水线
旧题库的数据没有统一格式。有些题干、选项和答案挤在一个字符串里,有些缺答案,有些甚至把几道题混在一起。迁移工具会逐行读取旧 SQL,交给模型整理内容,再用严格的 Zod schema 校验。低置信度、答案与选项对不上、缺少题干或混入多题的记录会被拒绝,合格结果才写入 PostgreSQL。
工具用 SQLite 保存任务状态,记录输入哈希、Prompt 哈希和确定性 UUID,中断后可以继续。模型请求失败会指数退避,数据库写入失败只重试提交,被拒绝与成功插入的内容也各自留下清单。这些机制让我敢把几十万条数据交给程序跑一整晚。
两次 All in AI,为什么一次烂尾,一次上线
2025 年:代码很多,项目没有变得更清楚
第一次尝试始于 2025 年下半年。11 月 19 日的早期提交里还没有多少真实业务,主要是 Next.js 框架和一份份 PLAN.md;到 11 月 27 日,主要模块已经被 AI 填得差不多了。我几乎没有手写代码,开发过程只剩下一段段会话记录。
然后我得到了一坨改不动、也不想改的东西。
AI 经常分不清自己正在写服务端函数还是客户端函数。更尴尬的是,我也分不清。架构里有太多只存在于计划中的层,每次对话都要重新解释,模型可以在错误方向上写很久,直到代码多到没人愿意收拾。
2026 年:先限制发挥,再让 AI 加速
2026 年 7 月 15 日,我重新建了仓库。第二次依然是全 AI 开发,但我不再把“没手写代码”当成目标。每个模块开始前先确定数据字段、权限、交互位置和验收方式,AI 只在这段边界内实现。自己是第一个用户,自己都觉得别扭的交互就重做。
TanStack Start 的显式服务端边界解决了一部分混乱,另一部分靠验证补上。认证、管理员权限、API Key、题目访问、积分流水和时区迁移都有对应测试或数据库约束。提交记录里也没有“第一版完成”之后一路顺滑的故事,只有连续的重组目录、修漏洞、改时间类型、补同步、加日志。
- 2026.07.15RESET
重新初始化项目
TanStack Start、Vite+、Drizzle 与 better-auth 的基础骨架进入仓库。
- 2026.07.16-17CORE
题目详情、AI 数据转换与搜索成形
基础业务、迁移工具、品牌标识和 Meilisearch 接连加入,目录也完成第一次重组。
- 2026.07.22-29PRODUCT
访问控制与管理后台补齐
用户、题目、API Key、余额、卡密和积分流水逐个完成,同时经历多轮结构调整、安全修复与时区迁移。
- 2026.07.30-31OPERATE
搜索同步、部署与日志进入主线
题目更新可以同步索引,Docker 与 Nginx 配置逐渐稳定,请求开始留下结构化日志。
- 2026.08.05EDGE
迁移链路加上退避与熔断
同一天加入 Node.js、Python、PHP、Go、Rust 五种 SDK,开放 API 有了真实消费者。
- 2026.08.06V0.0.1
部署、文档与版本号准备完成
Docker 构建、API 文档、SEO 和版本管理一起收尾,第一版具备上线条件。
从 7 月 15 日到 8 月 6 日,主线有 35 个提交,落在 12 个实际开发日里。这里面有三次明显的目录重组,也有一次安全扫描后的集中修复。AI 仍然会犯错,只是这次错误暴露得更早,也更容易回退。
从“能搜题”到“可以作为产品运行”
开发到中段以后,花时间最多的已经不是搜索框和题目卡片。
用户体系要处理登录、封禁、管理员权限和资料修改;积分不能只在用户表上放一个数字,还要有开户、卡密兑换、API 扣费和人工调整组成的账本;API Key 要能创建和撤销,每次收费请求要留下扣费前后余额与流水 ID;后台改题以后要同步搜索索引,失败要能追查。
开放 API 只有三个端点:搜索、题目详情和余额查询。为了确认它真的能被 Web 应用之外的东西使用,我又让 AI 生成了五种语言的 SDK。数量看起来有点过头,但它反过来逼着接口错误格式、字段命名和计费元数据保持一致。文档也不再是上线前随手补的一页 README,而是直接成为站内可访问的内容。
日志同样是后加的。认证、搜索、计费和后台操作会写成宽事件,默认以 NDJSON 落盘,也可以通过 OTLP 上报。对只有一个人维护的项目来说,我没办法靠“问一下同事刚才发生了什么”排查线上问题,日志就是那位不存在的同事。
AI 写了代码,我负责什么
如果按键盘输入量算,这个项目的大部分代码确实不是我手写的。Git 历史也很诚实:一个下午可以长出完整后台,几分钟能铺出五套 SDK。纯粹比生成速度,人没有胜算。
我的工作变成了另外几件事:决定项目只解决什么问题,拒绝暂时没有消费者的抽象,逐个确认字段和权限,亲自用每一个页面,再把模糊的不舒服说清楚。AI 可以把“这里不好用”迅速变成几个版本,但它不会替我决定哪一种感觉属于意刻。
代码评审也比以前更重要。生成一段代码很便宜,确认认证边界、扣费事务、时间语义和失败恢复却一点没变便宜。第一版的失败已经证明,会运行和愿意继续维护是两件事。
写在 v0.0.1 之后
2026 年 8 月 6 日,仓库里的所有包统一到了 0.0.1。这不是一个多么成熟的版本号,只表示意刻终于从我脑子里的长期规划,变成了可以部署、可以搜索、可以登录、也可以被外部 API 调用的东西。
它的结构仍会继续变化。仓库第一次提交时就有 workspace,后来删掉了不必要的业务分层,真实需求出现后又长出迁移、同步和 SDK。所谓“化繁为简”,是让复杂度晚一点来,并且来的时候带着理由。
几年前,我靠最早的题库站第一次知道网站可以赚钱。重做意刻,则让我第一次完整走过一个产品从数据、搜索、权限、计费到部署和维护的链路。中间仍然几乎没有手写代码,但每一个留下来的取舍,我都得自己负责。