Claude Code 实战:从零搭建宠物生命周期管理 App 的完整记录 我大概花了一个周末加两个晚上用 Claude Code 从零搭完了一个能真正跑起来的宠物生命周期管理 App。动手之前我原本的预期是AI 能帮我写掉大部分样板代码我负责处理复杂逻辑和收尾工作。跑完之后这个预期基本成立但过程和我预先想象的完全不一样——最花时间的不是写代码而是搞清楚要让 AI 照着什么标准写。这个项目本身不算大但覆盖了一个完整应用的典型全流程需求拆解、技术选型、数据模型、接口、页面、调试、测试、部署。把它交给 Claude Code 去走一遍比单独测试AI 能不能写函数有价值得多。如果你也想评估 AI 编程工具到底能承担多少真实工作或者想找一个合适的练手项目来跑通全流程这篇记录应该能给你一些参考。1. 为什么偏偏选宠物生命周期管理以及这个项目适合验证 AI 全流程的理由1.1 宠物养护的真实痛点养宠物的人都有一个共同的麻烦事太多时间一长全靠记性。幼年期的疫苗打了几针、下次驱虫是什么时候、上次体检是什么时候、体重是不是悄悄涨了、宠物现在相当于人类多少岁这些信息分散在疫苗本、体检单、手机相册和聊天记录里。宠物生命周期管理 App 要解决的就是把这一堆杂乱信息收拢成几个清晰的东西宠物档案、健康时间线、疫苗驱虫提醒、体重趋势、生命周期阶段。功能边界很明确用户痛点也真实存在做出来不是那种为了 AI 而 AI的玩具项目。从开发角度看这个项目还有一个很大的优势它不依赖复杂的第三方业务系统。既不用对接支付也不用处理社交关系链核心就是一套围绕Pet这个实体的 CRUD加上日期计算和提醒逻辑。这种项目的信息密度刚好合适——既能让 AI 发挥代码生成能力又不会因为外部依赖太多导致 AI 频繁卡壳。1.2 为什么说它是一个理想的 AI 全流程试验场选项目验证 AI 开发能力最怕的是选了业务规则绕来绕去的场景。比如做电商订单系统光是状态机和退款流程就能让 AI 生成一堆似是而非的代码。宠物生命周期管理不一样它的业务规则可以拆得很清楚宠物档案基础信息增删改查生命周期阶段根据物种、生日计算幼年期/成年期/老年期展示换算的人类年龄疫苗驱虫提醒根据最近一次记录时间和建议间隔自动计算下次日期健康记录与体重趋势时间线展示体重折线图每一个模块都能独立描述、独立验收符合给 AI 下任务时任务边界要清晰的原则。我把这个项目完整跑下来之后最大的感受是AI 全流程开发能不能顺利前期把业务拆得多清楚基本就决定了后面 80% 的体验。1.3 技术选型让 AI 在一个成熟技术栈里干活技术栈我定的是 Next.js App Router Prisma Tailwind CSS本地开发用 SQLite部署到 Linux 服务器后切到 PostgreSQL进程管理用 PM2Nginx 做反向代理。选这个组合不是因为它最潮而是因为它最适合 AI 协作Next.js 的 App Router 是文件即路由每个页面一个目录AI 生成的代码结构天然清晰不容易出现找不到文件在哪的问题Prisma 用 schema 描述数据模型改模型然后生成迁移这种工作流非常适合对话式迭代——改一行 schema然后让 AI 去同步迁移和代码Tailwind 的原子类让 AI 生成的样式不容易破坏整体布局不需要维护复杂的 CSS 文件一开始我没有让 AI 参与技术选型而是直接把这些约束丢给它。原因很简单让 AI 自由选型它往往会给你一个看起来合理但不一定符合你部署习惯的方案。选型这件事人拿主意生成和执行交给 AI这是全流程里效率最高的分工方式。2. 从一句话需求到技术方案Claude Code 完成的第一轮业务拆解2.1 第一轮对话不要让它急着写代码我见过很多人用 AI 编程工具上来就是帮我做一个宠物管理 App然后 AI 刷刷刷生成几百个文件看起来热闹实际一半不能用。关键问题是跳过了业务拆解。我的第一条指令刻意要求它先别写代码只做方案我是一个独立开发者想做一个宠物生命周期管理 App。 目标用户是养猫养狗的个人用户。 核心功能必须包含多宠物档案、疫苗驱虫提醒、健康记录、体重趋势、生命周期阶段展示提醒功能不要用后台定时任务采用打开应用时按需计算。 技术栈固定为 Next.js App Router Prisma Tailwind CSS本地用 SQLite。 你先不要写代码帮我输出 1. 数据模型草案 2. 页面路由结构 3. 每个页面的主要职责 4. 实现里程碑划分Claude Code 第一轮给出的方案大体在点子上数据模型分成了 Pet、VaccinationRecord、HealthRecord、WeightRecord 四张表路由结构覆盖了宠物列表、详情、新增、编辑和健康时间线。但它给的里程碑划分太粗只有搭建环境、实现 CRUD、补充提醒三个阶段粒度不够。我让它把提醒功能单独拆出来并要求它明确提醒看板这个页面的计算逻辑。它很快补充了细节提醒看板统一读取所有宠物的疫苗和驱虫记录用 date-fns 计算下次日期标记出已到期和未来 7 天内到期的项目。这一轮跑完我对 Claude Code 的定位有了一个更准确的判断它很强的地方在于把模糊需求翻译成结构化方案但前提是你得给它足够明确的约束。如果我只说做一个宠物管理 App它给出的方案大概率会包含一些我用不上的东西比如用户注册登录、社交分享功能。2.2 用 CLAUDE.md 固定项目约定第一轮对话之后我强烈意识到一个工具非常值得用CLAUDE.md。这是 Claude Code 的项目记忆文件放在项目根目录每次对话它都会自动读取。我在 CLAUDE.md 里写明了这几件事# 项目约定 - 技术栈Next.js App Router Prisma Tailwind CSS - 包管理器npm - 数据库本地使用 SQLite生产使用 PostgreSQL - 代码风格函数组件 TypeScript优先使用 server action 处理数据变更 - 日期处理统一使用 date-fns禁止使用 setMonth 直接改日期 - 提交规范每个里程碑结束后手动 git commit不要 AI 自动提交 - 提醒设计不引入定时任务在页面请求时按需计算下次提醒日期有了这个文件之后后续对话里 AI 很少再问用什么技术栈日期用什么库这类基础问题也很少跑偏生成一些和项目约定不符的代码。这就相当于你给 AI 上了一道项目宪法它在生成代码时会更贴近你自己的开发习惯。如果你打算长期用 AI 协作开发CLAUDE.md 是花半小时写清楚、后面能帮你省好几天时间的投资。3. 代码落地的过程数据模型、生命周期算法与核心页面逐个实现3.1 环境准备与项目初始化Claude Code 的本质是命令行工具所以所有操作都在终端里完成。环境准备这一步没有太多玄学先把基础环境准备好# 检查 Node 版本建议 20 以上 node -v # 安装 Claude Code CLI npm install -g anthropic-ai/claude-code # 在项目目录初始化 npx create-next-applatest pet-life-app --typescript --tailwind --app初始化完成后安装 Prisma 并初始化数据库。这里我没有手动敲建表 SQL而是直接把数据模型需求发给 Claude Code让它生成 Prisma schema 和迁移文件。有一个细节值得说create-next-app生成的项目结构和 Claude Code 自己从零搭的未必完全一致。直接用官方脚手架的好处是避免 AI 在配置上自由发挥。脚手架定好骨架AI 在骨架里填充内容出错概率会低很多。3.2 数据模型让 AI 设计人审查关系约束Prisma schema 是 AI 生成的核心文件之一。我要它按照之前确认的数据模型草案一次性生成完整的 schema。它给出的核心模型是这样的model Pet { id Int id default(autoincrement()) name String species String // cat / dog / rabbit breed String? birthDate DateTime gender String? neutered Boolean default(false) avatarUrl String? createdAt DateTime default(now()) updatedAt DateTime updatedAt weights WeightRecord[] healthRecords HealthRecord[] vaccinations VaccinationRecord[] } model VaccinationRecord { id Int id default(autoincrement()) petId Int pet Pet relation(fields: [petId], references: [id], onDelete: Cascade) name String givenAt DateTime expiresAt DateTime? notes String? } model WeightRecord { id Int id default(autoincrement()) petId Int pet Pet relation(fields: [petId], references: [id], onDelete: Cascade) weightKg Float measuredAt DateTime } model HealthRecord { id Int id default(autoincrement()) petId Int pet Pet relation(fields: [petId], references: [id], onDelete: Cascade) title String recordAt DateTime detail String? costCent Int? }Schema 整体没什么问题但有两个点是我手动审查后才发现的。第一Pet 表缺少一个类型字段用于区分品种大小。猫和狗的疫苗提醒时间表不同而狗又分成小型犬、中型犬、大型犬它们的成年和老年节点不一样。我要求它加上sizeCategory字段并塞进一组默认配置。第二所有子表的时间字段命名不统一givenAt、measuredAt、recordAt混着用。这对功能没有影响但后续写排序和展示逻辑时容易搞混。我让 AI 统一改成recordedAt风格它花了半分钟全局替换干净利落。这里是我的一个核心心得数据模型这种地基类的东西无论如何都要人亲自看一遍重点看关系约束、级联删除、字段命名一致性。AI 能建得非常快但建完之后你把关的责任一点没少。3.3 生命周期阶段算法给 AI 出业务规则题生命周期阶段计算是这个 App 最有技术含量的部分。我给 Claude Code 出的题目是根据物种和出生日期判断宠物处于幼年期、成年期还是老年期并估算相当于人类多少岁。它给出一版可用的实现核心是一个配置表和两个纯函数type Species cat | smallDog | mediumDog | largeDog; const LIFECYCLE_CONFIG: RecordSpecies, { adultAgeMonths: number; seniorYears: number; perYearHumanAge: number } { cat: { adultAgeMonths: 12, seniorYears: 10, perYearHumanAge: 4 }, smallDog: { adultAgeMonths: 12, seniorYears: 11, perYearHumanAge: 5 }, mediumDog: { adultAgeMonths: 15, seniorYears: 9, perYearHumanAge: 5 }, largeDog: { adultAgeMonths: 18, seniorYears: 8, perYearHumanAge: 6 }, }; export function getLifecycle(species: Species, birthDate: Date, now: Date new Date()) { const months (now.getFullYear() - birthDate.getFullYear()) * 12 (now.getMonth() - birthDate.getMonth()); const years months / 12; const cfg LIFECYCLE_CONFIG[species] ?? LIFECYCLE_CONFIG.mediumDog; const stage months cfg.adultAgeMonths ? 幼年期 : years cfg.seniorYears ? 老年期 : 成年期; const humanAge years 1 ? Math.round(15 * years) : years 2 ? Math.round(15 9 * (years - 1)) : Math.round(24 (years - 2) * cfg.perYearHumanAge); return { stage, humanAge, ageMonths: months }; }这里有两个细节值得展开。一是now: Date new Date()这个参数。我特意要求 AI 把当前时间作为可选项传入不是为了花哨而是为了让函数可测试。测试时固定传入一个时间点才能断言2024年6月出生的猫在2025年1月应该是幼年期。这是我教它绿色工程的产物AI 其实很擅长做这种调整只要你明确提出。二是配置表没有直接写死几岁算老年而是给每个物种定义seniorYears。这个抽象让前端可以做一件事在宠物详情页展示一句阶段文案比如你已经进入老年期建议每年做两次体检。这算是把业务规则沉淀成了数据配置后续调整只需改一行配置不用动逻辑。3.4 页面实现从列表到详情AI 生成代码的节奏控制数据模型和算法稳定之后页面开发就顺畅多了。我按照列表页 → 新增表单 → 详情页 → 提醒看板的顺序一次让 AI 只做一个页面。宠物列表页和新增表单的生成非常快。Claude Code 会直接生成 server component 读取数据库再用一个 client component 处理表单提交。表单字段与数据模型一一对应species和sizeCategory自动渲染成下拉框出生日期用input typedate。有一件事它第一次没做对日期表单控件的值传给后端时是YYYY-MM-DD字符串但数据模型定义的是DateTime类型直接new Date(2025-03-01)解析会有时区偏移问题在东八区会把日期读成 2 月 28 日。它在生成详情页的年龄计算时暴露出这个 bug我让它统一改用new Date(dateStr T00:00:00)的方式解析并在 CLAUDE.md 里记下了这个约定。提醒看板是模块里最复杂的一个页面。它的逻辑是遍历所有宠物读取每只宠物的疫苗和驱虫记录找到最近一次记录的时间根据间隔算出下次时间再和今天比较。Claude Code 生成的第一版嵌套了三层 map可读性很差我直接要求它把计算逻辑抽成lib/reminders.ts里的纯函数页面只负责渲染。抽完之后测试和代码审查都轻松了很多。4. 调试、测试与重构让 AI 自己发现问题才是全流程的关键4.1 三个真实 bug 的排查链路AI 写的代码第一次就能全跑通是不可能的所以怎么用 AI 修 bug比怎么让 AI 写代码更重要。这轮开发中遇到了三个典型问题每一个都验证了同一个方法论把现场证据原原本本喂给 AI让它先分析原因再给修复方案。第一个是删除宠物时报外键约束错误。我在测试时删掉一只宠物Prisma 直接抛错原因是子表的外键没有配级联删除。我把完整的报错堆栈贴给 Claude Code它很快就定位到 schema 里VaccinationRecord等模型的外键定义缺少onDelete: Cascade然后自动生成了迁移并应用。修复命令是npx prisma migrate dev --name add_cascade_delete第二个是体重趋势图的连线来回乱跳。检查后发现查询体重记录时没有orderBy: { recordedAt: asc }数据按主键顺序返回而用户补录历史体重时插入顺序和日期顺序不一致图表连线自然就乱了。修复方法是三选一在 Prisma 查询中排序或者在拿到数组后 sort或者 SQL 视图处理。Claude Code 选了第一种因为它最清晰页面代码不用改。第三个是提醒日期跨年计算错误。这个最典型。用 JavaScript 原生的setMonth(current.getMonth() 3)处理日期如果当前是 11 月 30 日会得到一个不存在的 2 月 30 日JS 会把它自动滚动成 3 月 2 日或 3 日提醒就莫名其妙提前或延后了。这个 bug 让 Claude Code 查了很久因为它生成的第一次检查逻辑看着没问题是我把边界场景直接写成测试用例11月30日加3个月应该等于次年2月28日它才意识到是setMonth的问题。最终改用date-fns的addMonths这个库会自动把溢出的日期收敛到月末。这个案例让我确定了一件事AI 修 bug 时明确给出边界条件比反复说这里不对管用得多。4.2 让 AI 写测试从手动验证到自动化守护项目功能基本成形后我要求 Claude Code 给生命周期算法和提醒计算补测试。它用 Vitest 写了两个测试文件其中一个针对生命周期算法import { describe, expect, it } from vitest; import { getLifecycle } from /lib/lifecycle; describe(getLifecycle, () { it(猫在12个月之前属于幼年期, () { const birthDate new Date(2024-06-01T00:00:00); const now new Date(2025-01-01T00:00:00); expect(getLifecycle(cat, birthDate, now).stage).toBe(幼年期); }); it(猫在10岁之后属于老年期, () { const birthDate new Date(2014-03-01T00:00:00); const now new Date(2025-01-01T00:00:00); expect(getLifecycle(cat, birthDate, now).stage).toBe(老年期); }); it(大型犬在2岁前仍属于幼年期, () { const birthDate new Date(2022-06-01T00:00:00); const now new Date(2024-01-01T00:00:00); expect(getLifecycle(largeDog, birthDate, now).stage).toBe(幼年期); }); });这些测试本身不难但有一个关键是——正是因为之前写算法时强制要求了now参数测试才能稳定通过。你现在回头看这算是我在 AI 协作中学到的最重要一课你给 AI 提的每一个功能要求都要顺手想一下这个逻辑能不能被测试。如果 AI 生成的函数依赖了全局时间、全局状态或者第三方服务的隐式数据立刻让它改成可注入、可控制的形式。这条原则决定了 AI 生成的代码是能跑还是能长期维护。4.3 重构指令让 AI 在约束条件下整理代码页面多了之后重复代码开始出现。详情页和列表页各有一份几乎一样的宠物基础信息卡片提醒看板里也有类似的渲染逻辑。我挑了一个最臃肿的页面文件向 Claude Code 下达了这样一个重构指令pages/pets/[id]/page.tsx 现在同时完成了数据获取、阶段计算、提醒判断和页面渲染单文件超过 260 行。 请在不改变任何视觉效果的前提下 1. 把数据库查询逻辑抽到 lib/queries.ts 2. 把阶段和提醒的计算逻辑抽到 lib/pet-metrics.ts 3. 给这两个模块补上单元测试 4. 完成后运行 npm run build确保没有类型错误 5. 不要动页面的 UI 文案这次重构花了大约 20 分钟。Claude Code 会自动读取原来的文件、提取关键逻辑、创建新模块、修改引用跑测试和构建。我只需要在最后 review 一遍改动确认它没有把 props 的命名改乱没有丢失空状态判断。重构指令有一个规律约束条件越具体结果越可靠。尤其什么不能动比要做什么更重要。如果你只让它重构一下它可能会顺手优化文案、改样式、调整组件结构导致 diff 很大review 成本反而上去了。给它一条视觉效果不变、文案不变、不能新增依赖的边界它才会规规矩矩干活。5. 从本地到服务器构建、数据库迁移与上线部署的坑5.1 从 SQLite 切换到 PostgreSQL环境差异是最大变量本地开发一直用 SQLite部署到服务器自然要切换到 PostgreSQL。切换过程不复杂先在服务器上装好 PostgreSQL建好数据库和用户然后在.env里改 DATABASE_URLDATABASE_URLpostgresql://pet_user:你的密码127.0.0.1:5432/pet_life_db?schemapublic接着在服务器上执行数据库迁移npx prisma migrate deploy这里有一个容易翻车的点如果本地迁移历史和生产库的实际状态不一致migrate deploy可能会卡在某个旧的迁移文件上。我的做法是在本地彻底删除 SQLite 的 dev.db然后重新prisma migrate dev生成一份干净的迁移历史。这样交到生产环境时迁移文件从上到下都是可执行状态。PostgreSQL 和 SQLite 在类型上也有微妙差异。SQLite 对DateTime存储很随意PostgreSQL 则严格得多。好在 Prisma 抽象掉了大部分差异我只需要在 schema 里明确db.Timestamptz这类注解否则默认时区可能会把时间偏移。构建阶段 AI 没有参与太多因为环境问题更依赖人的排查经验。5.2 上传文件的持久化问题最容易忽略的部署坑这个 App 有宠物头像上传功能。开发时我把上传文件直接存在public/uploads下本地一切正常。上线前我例行检查时突然意识到一件事生产环境执行npm run build和部署流程时public目录会被项目发布包完整覆盖新上传的头像在下一次部署后就会全部丢失。这不是 AI 能替你发现的问题因为它属于部署架构层。我的解决方法是上传目录改为应用外的持久路径比如/var/www/pet-life-uploads然后在 Nginx 里加一条静态映射location /uploads/ { alias /var/www/pet-life-uploads/; }代码层面需要读取上传文件时URL 直接拼/uploads/xxx.jpg即可。这个改动对于本项目的体量足够不用引入对象存储服务。5.3 构建、启动与服务器配置部署流程在服务器上跑的命令比较标准npm ci npx prisma generate npx prisma migrate deploy npm run build pm2 start npm --name pet-life -- startNginx 反向代理配置也很常规把 80 端口转发到 3000 端口同时配好client_max_body_size否则重一点的头像图片会被 Nginx 默认的 1MB 限制拦掉。上线之后我做的第一件事是检查 PM2 日志重点看有没有 Prisma 连接报错和时区警告。这个项目面向国内用户服务器如果默认 UTC 时区提醒逻辑算出的今天就会差 8 小时。记得在服务器和 Node 进程里都显式设置TZAsia/Shanghai否则日期提醒类功能很容易出现明明到了日期却不提醒的诡异问题。6. 全流程复盘AI 到底帮了多少以及哪些地方绝对不能放手6.1 环节耗时对比这个项目如果按我过去的习惯做通常需要 5 到 7 天其中前端页面和调试占了大头。这次配合 Claude Code实际大约用了两个晚上加一个完整的周末。下面的表是我自己记录的耗时仅供参考不同项目差异会很大环节常规预估AI 协作实际耗时我的主要工作需求拆解与技术设计半天约 1 小时明确功能边界确认提醒方案数据模型与接口1-2 天约 3 小时审查外键、级联、字段命名前端页面2-3 天约 1 天控制生成粒度检查表单校验调试与测试1 天约半天把报错原样喂回 AI 定位部署上线1 天约 2 小时 踩坑处理上传目录、时区、Nginx整体下来编码和调试环节大概是常规开发的三到四成时间但需求设计和代码审查并没有明显缩短。换句话说AI 压缩的是把方案变成代码的时间而不是想清楚要做什么的时间。6.2 哪些环节 AI 真的靠谱哪些必须人盯着跑完这个全流程我能很明确地把 AI 的能力边界划出来。AI 很靠谱的环节样板代码、CRUD、表单页面、Prisma schema 和迁移生成、日志与报错解释、测试用例编写、按约束重构。这些任务的共同特点是信息完整、预期明确AI 不需要在模糊地带做判断。AI 不靠谱的环节需求边界确认它倾向于把系统做大你得主动砍、安全与权限设计默认完全没有鉴权需要人来补、UI 细节一致性间距、对齐、交互状态、部署环境的隐性问题路径、权限、时区、上传目录持久化。尤其安全相关的东西绝不能因为 AI 会生成代码就放松。这个项目里用户数据是私有数据我确认了一下所有数据变更接口没有做任何权限校验也就是说任何能访问到服务的人都可以增删改宠物记录。这显然不行所以我要求 Claude Code 给所有 data mutation 加了一层最简单的 token 校验在 server action 里校验请求头中的访问令牌。AI 完全可以做这件事但提这个需求的人必须是我——因为它自己不会主动想到你部署的公网服务需要鉴权。6.3 如果想复现这个过程我建议你这样做给准备用 Claude Code 从零搭项目的人几条实际建议都是我踩过之后觉得最有价值的部分。第一第一次对话就要把约束交代完整。技术栈、数据库、部署目标、不合用后台定时任务这些在初轮说清楚比之后反复纠正省钱得多。第二把任务拆成一个个逻辑闭环。一次让它完成一件事先数据模型再查询逻辑再单个页面再提醒算法。一次让它做整个系统结果往往是表面完整、细节全崩。第三改完必须让它自己验证。要求 AI 在完成改动后运行 build 或测试把报错原样贴回来。这样能逼出很多隐藏问题。第四代码审查重点盯这几个地方数据校验、日期边界、级联删除、上传安全、空状态处理。这是 AI 最容易出错也最容易糊弄过去的区域。第五每个里程碑完成就手动提交一次代码。不要让 AI 提交因为 AI 的提交信息可能很模糊而且自动提交会让回滚变得混乱。这次全流程跑完之后我个人的感受是Claude Code 确实把从想法到可运行的软件这条路缩短了一大截但它没有替代开发者身上的责任只是把责任从亲手敲每一行代码变成了准确描述需求、严格验收结果。我现在自己新项目的习惯也变了——先花半小时把需求、技术栈、边界条件写清楚再把这个上下文完整交给 AI 去执行。最后再分享一个小技巧如果某个页面反复修不好别在同一个对话里纠缠开一个新对话把页面相关文件路径、报错信息和 CLAUDE.md 约定重新喂一遍往往会有奇效。