案例:用户评论功能¶
项目背景:一个内容平台,Next.js 14 + PostgreSQL + Prisma,已有文章列表和详情页。
任务目标:为文章详情页添加评论功能,包括展示评论列表、发表评论、删除自己的评论。
实际耗时:约 2.5 小时(含两次跑偏纠正)
第一步:拆分任务¶
评论功能看起来是一个任务,实际上是四个独立任务:
每个任务独立完成、独立验证,不一次性全部交给 AI。
任务一:数据库迁移¶
规格:
目标:创建 comments 表
字段:id, article_id, user_id, content(text), created_at
约束:article_id 外键关联 articles.id(级联删除),user_id 外键关联 users.id
范围:只创建迁移文件,不修改现有 Schema
验收:prisma migrate dev 执行成功,prisma migrate reset 可回滚
结果:一次通过,AI 生成了正确的 Prisma Schema 和迁移文件。
任务二:评论 API¶
规格:
背景:Next.js 14 App Router,API 路由在 src/app/api/,
认证使用 NextAuth,session 中有 user.id。
现有接口参考:src/app/api/articles/route.ts(附上内容)
目标:实现三个接口
GET /api/articles/[id]/comments → 返回评论列表(按时间倒序)
POST /api/articles/[id]/comments → 发表评论(需登录)
DELETE /api/comments/[id] → 删除评论(只能删自己的)
范围:创建以下文件
src/app/api/articles/[id]/comments/route.ts
src/app/api/comments/[id]/route.ts
约束:
- 沿用现有的 prisma client 实例(src/lib/prisma.ts)
- 未登录用户访问 POST/DELETE 返回 401
- 删除他人评论返回 403(不是 404)
- 评论内容不能为空,超过 1000 字返回 400
验收:运行 pnpm test src/app/api/articles 全部通过
跑偏 #1:AI 修改了 NextAuth 配置¶
AI 生成了正确的接口代码,但同时修改了 src/lib/auth.ts,添加了一个它认为"缺少的" session 字段。
发现方式:验证时运行 git diff --stat,看到了不在规格范围内的文件被修改。
根因:规格里写了"session 中有 user.id",但没有附上实际的 auth 配置,AI 猜测 session 结构并做了"修复"。
纠正:
1. git checkout src/lib/auth.ts 回退这个文件
2. 重新发请求,这次附上了 src/lib/auth.ts 的实际内容,并在约束中加了"不修改 src/lib/auth.ts"
沉淀:涉及认证的任务,必须附上 auth 配置文件,并在约束中明确禁止修改它。
任务三:CommentList 组件¶
规格:
背景:项目使用 React + Tailwind CSS,
参考现有组件:src/components/ArticleCard.tsx(附上内容)
目标:创建 CommentList 组件
Props:
- articleId: string
- currentUserId?: string(未登录时为 undefined)
行为:
- 调用 GET /api/articles/[id]/comments 获取数据
- 展示评论列表,每条评论显示:头像、用户名、时间、内容
- 当前用户的评论右侧显示删除按钮
- 删除后乐观更新(先从列表移除,失败时恢复)
- 加载中显示 3 个骨架屏
- 空状态显示"还没有评论,来发表第一条吧"
范围:创建 src/components/CommentList.tsx
约束:不引入新依赖,使用 fetch API(不用 SWR 或 React Query)
验收:TypeScript 无错误,在文章详情页正常渲染
结果:一次通过,风格与现有组件一致。
任务四:CommentForm 组件¶
规格:
背景:同上,参考 src/components/ArticleCard.tsx
目标:创建 CommentForm 组件
Props:
- articleId: string
- onSuccess: () => void(发表成功后调用,用于刷新列表)
行为:
- 文本框 + 发表按钮
- 未登录时显示"登录后发表评论",点击跳转 /login
- 发表中禁用按钮,显示 loading 状态
- 发表成功后清空文本框,调用 onSuccess
- 发表失败显示错误提示
范围:创建 src/components/CommentForm.tsx
约束:不引入新依赖
验收:TypeScript 无错误,登录/未登录状态均正常
跑偏 #2:AI 在组件内部实现了登录状态检查¶
AI 在 CommentForm 内部调用了 getSession(),而不是通过 Props 接收 currentUserId。这导致组件内部有异步逻辑,与项目其他组件的模式不一致。
发现方式:代码审查时注意到组件内有 useEffect + getSession(),与规格中"未登录时显示登录提示"的实现方式不符。
根因:规格里没有说明登录状态从哪里来,AI 自己决定了获取方式。
纠正:修改规格,明确 Props 增加 isLoggedIn: boolean,登录状态由父组件传入。重新请求,一次通过。
沉淀:组件的数据来源(Props vs 内部获取)必须在规格中明确,不能让 AI 自己决定。
整合与验证¶
四个任务完成后,在文章详情页整合:
// src/app/articles/[id]/page.tsx 中新增
<CommentList articleId={article.id} currentUserId={session?.user?.id} />
<CommentForm articleId={article.id} isLoggedIn={!!session} onSuccess={refreshComments} />
整合本身没有让 AI 参与,手动写了 5 行代码。
最终验证:
- [ ] 未登录:看到评论列表,看到"登录后发表评论"
- [ ] 登录后:可以发表评论,评论出现在列表顶部
- [ ] 删除自己的评论:列表立即更新
- [ ] 尝试删除他人评论:返回 403(通过 curl 验证)
- [ ] TypeScript 检查:pnpm tsc --noEmit 无错误
复盘总结¶
有效的: - 把"评论功能"拆成 4 个独立任务,每个任务边界清晰,AI 没有越界 - 附上现有同类代码作为风格参考,生成结果与项目一致
两次跑偏的共同根因: - 规格里有隐含假设(session 结构、登录状态来源),没有显式说明 - 修复方式:把隐含假设变成显式约束
沉淀到提示词库的规则: 1. 涉及认证的任务 → 附上 auth 配置,约束中禁止修改 2. 组件的数据来源 → 在 Props 定义中明确,不留给 AI 决定 3. 整合步骤 → 手动写,不交给 AI(5 行代码,不值得写规格)