案例研究 2:一个带认证 + 数据库的小型 SaaS 工具
想法
一位自由职业设计师想要一个私人仪表盘,用来按客户记录可计费工时,并导出每月的 CSV。没什么花哨的,但它需要账户(这样她的数据只属于她自己)和持久化存储。
Spec
认证和数据库抬高了赌注,所以 spec 在边界上变得更具体了:
A logged-in web app where a user can: sign up / log in with email,
create clients, log time entries (date, client, hours, note),
see a table of entries filtered by month, and download that month
as CSV. Each user only ever sees their own data. Mobile-friendly.
"Each user only ever sees their own data"看着像一行 UX 描述。它其实是把整个应用的安全模型压缩进了九个词里。把它写进 spec,意味着每次 AI 跑偏时我们都能指回这一句。
技术栈
对一个独立开发者来说,制胜的一手是选一个让认证和数据库都成为托管服务、而非你自己写的代码的技术栈。我们选了一个部署在 serverless 托管平台上的 Next.js 应用,配上一个托管的 Postgres 数据库,以及一个开箱即用的认证提供方,由它替我们处理邮箱登录、会话和密码重置。能写错的代码越少,AI 能替我们写错的代码就越少。认证尤其是那种你几乎从不想跟 AI 一起手搓的类别:它的失败模式是静默的,爆炸半径是所有人的账户,而一个托管提供方早已被几百万次登录把边界情况捶打过。
关键的 prompt
我们让认证提供方自带的模板来挑大梁,然后指挥 AI 在它之上叠加业务逻辑:
We're using [auth provider]'s Next.js starter. Add a Postgres
schema with two tables: clients (id, user_id, name) and
time_entries (id, user_id, client_id, date, hours, note). Every
query MUST filter by the logged-in user's id from the session.
Generate the migration and the typed data-access functions.
"MUST filter by user_id"这一行是整个项目里最重要的一句话。我们在几乎每一个触及数据的 prompt 里都重复了这条约束,因为一个多用户应用里最可怕的单一 bug,就是一个用户看到了另一个用户的数据行。打字时这种重复感觉是冗余的;可正是这份冗余救了你,因为模型并不记得这条约束在一个个独立的 prompt 之间有多要紧。
至于导出:
Add a /api/export route that takes a month (YYYY-MM), pulls the
logged-in user's time_entries for that month joined to client
names, and streams a CSV download. Reject the request if there's
no valid session.
障碍
在测试时,我们创建了两个账户,然后发现账户 B 能在一个下拉菜单里看到账户 A 的客户。这正是我们害怕的那个 bug。我们没有让 AI"修好它",而是先让它把问题证明出来:
Account B is seeing Account A's clients. Show me every database
query in the codebase that reads the clients table, and for each
one tell me whether it filters by the session user_id. Don't fix
anything yet — just audit.
这次审计浮出了一个查询——那个下拉菜单的加载器——它是在我们加上那条约束之前写的,于是溜了过去。我们让它补上了缺失的过滤条件,然后又要了一道守卫:
Add a single helper that every read goes through, which takes the
session and injects the user_id filter, so no future query can
forget it. Refactor the existing queries to use it.
这把一次性的修复变成了一个结构性的保证。接着我们让这个保证变得可测试,因为一道你没法验证的守卫不过是一种指望:
Write a test that creates two users, has each create a client,
then asserts that user A's session can never read user B's client
through any of the data-access functions.
教训:当 AI 引入一个安全 bug 时,别只修补这一个实例——要指挥它去消除这一整类错误,然后用一个一旦有人重新打开就会大声失败的测试,把这一类锁死。
上线
我们灌入了一个月的测试数据,导出了 CSV,在电子表格里打开它以确认数字和编码都正确,然后设置了一个强数据库密码,并把凭据从所有本地文件里轮换出去。我们部署到了 serverless 托管平台,在它的控制面板里加上了生产环境的环境变量,然后把 URL 给了她。她用一次真实的注册完成了自助入驻。整个构建只花了一个周末。