跳转至

案例:用户评论功能

项目背景:一个内容平台,Next.js 14 + PostgreSQL + Prisma,已有文章列表和详情页。
任务目标:为文章详情页添加评论功能,包括展示评论列表、发表评论、删除自己的评论。
实际耗时:约 2.5 小时(含两次跑偏纠正)


第一步:拆分任务

评论功能看起来是一个任务,实际上是四个独立任务:

1. 数据库:comments 表迁移
2. 后端:评论 CRUD API(3 个接口)
3. 前端:CommentList 组件
4. 前端:CommentForm 组件 + 发表逻辑

每个任务独立完成、独立验证,不一次性全部交给 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 行代码,不值得写规格)