做开发这些年我越来越习惯在动手前先问自己一个问题这个项目真正值得写代码的部分到底是哪一块拿到“宠物生命周期管理App”这个想法时我决定换一种方式——用Claude Code从需求梳理、项目初始化、数据库设计、后端接口、前端页面一直到测试部署全程让AI辅助参与顺便验证一下“全流程AI开发”到底靠不靠谱。文章会完整还原这次项目从零搭建的过程包括技术选型、数据模型、接口设计、页面联调、问题排查和部署注意事项。适合对AI辅助开发感兴趣、想尝试把真实项目交给AI的同学也适合本来就打算做宠物类工具产品、想快速出Demo的开发者。如果你手里装好了Claude Code这类AI编程助手这其实就是一场可以随时复现的实战。1. 先想清楚需求清单怎么用AI拆出来1.1 宠物生命周期管理的真实核心场景宠物主人最常做的动作并不是“管理宠物”而是记不住事情。疫苗什么时候打的、下一针什么时候打、驱虫药上次是几月几号、最近一次体检结果怎么样、体重有没有异常变化——这些零散信息一旦没有记录就只能靠回忆。所以这个App的核心价值不是做一个漂亮的宠物档案而是做一个围绕时间轴的健康追踪工具。生命周期管理这个词听起来大落到场景上其实就三类记录类宠物档案、疫苗记录、驱虫记录、体检记录、体重记录。提醒类疫苗到期提醒、驱虫到期提醒、定期体检提醒、日常喂食提醒。分析类基于体重曲线判断健康趋势、根据年龄判断当前处于哪个生命周期阶段、按阶段给出养护建议。明确了这几点后续所有功能设计才不会跑偏。我建议你在让AI写代码之前先把这些核心场景写进提示词。AI编程助手最怕的不是给它太多信息而是给它一个“做一个宠物App”这种过于宽泛的目标那它只能靠猜。我自己习惯的方式是先用自然语言把场景描述给Claude Code让它输出一份功能清单然后我再人工删减。第一次让它生成的功能里往往会有“宠物商城”“社交分享”这类很容易让项目失焦的东西直接砍掉就好。1.2 让AI把模糊想法翻译成功能模块我给它的大致描述是“我想做一个宠物生命周期管理App用户能添加多只宠物记录生日、种类、疫苗、驱虫、体重系统要根据生日自动判断宠物处于幼年/成年/老年哪个阶段并且能生成待办提醒。”Claude Code给出的功能模块拆解很接近我想要的宠物管理支持添加、编辑、删除宠物档案每个档案包含名称、种类、品种、生日、性别、是否绝育。健康时间轴疫苗、驱虫、体检、就诊记录统一按时间排列展示。体重记录每次记录体重和日期自动绘制曲线。提醒中心根据疫苗有效期、驱虫周期和用户自定义规则生成近期提醒。生命周期判断根据当前日期和宠物生日计算月龄/年龄输出阶段标签和养护建议。多宠切换首页支持宠物列表详情页可切换当前宠物。这份清单就是我接下来所有提示词的主干。在后续开发中我会反复把这份清单作为上下文粘回对话里让AI生成新代码时始终围绕核心功能而不是自由发挥。一个小经验让AI输出功能模块时可以明确要求它“不要展开技术方案只描述业务功能”避免它在需求阶段就开始聊数据库表结构那样容易把需求讨论带偏。2. 技术选型与项目脚手架哪些事值得交给AI2.1 这套技术栈为什么适合全流程AI开发项目技术栈的选择直接决定了AI生成代码的准确率。我最终选了这样一套组合前端React Vite TypeScript移动端优先的响应式布局。后端Node.js Express。数据库SQLite Prisma ORM。测试Vitest React Testing Library接口层用Jest Supertest。部署容器化部署用一套简单的Compose配置跑后端、前端和数据库。选择理由很简单这套技术栈足够主流、资料足够多、AI训练数据里覆盖率也足够高。你让Claude Code写一个React组件或Express中间件它基本不会翻车但如果选了一个特别冷门的框架AI生成的内容就需要你花大量时间手工修正。对宠物生命周期管理App这个规模的项目来说用SQLite做开发期数据库是性价比很高的选择。它就是一个文件不需要单独安装数据库服务Prisma迁移也很方便等真到了需要多实例部署的阶段再换PostgreSQL也不迟。2.2 从空目录到可运行骨架的执行步骤我的初始化过程全程记录如下第一步在空目录里创建前端基础工程。我直接让Claude Code给出命令然后自己执行npm create vitelatest pet-fe --template react-ts cd pet-fe npm install npm install react-router-dom axios recharts date-fns这里选React TS模板是因为TypeScript能帮AI和我都提前发现很多低级错误比如字段名打错、类型对不上。后面接管工程时类型报错比运行时崩溃好处理得多。第二步创建后端服务目录和前端放在同一个仓库下管理方便之后用一个Compose文件统一部署mkdir server cd server npm init -y npm install express cors prisma/client prisma zod第三步让Claude Code生成基础目录结构。我给它指定了大概的目录层次它很快输出了一棵文件树。我根据这棵树手工调整了少量命名保证前后端目录语义一致。pet-project/ ├── pet-fe/ # 前端 │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── components/ # 通用组件 │ │ ├── pages/ # 页面级组件 │ │ ├── types/ # TS类型定义 │ │ └── utils/ # 工具函数 │ └── package.json └── server/ # 后端 ├── prisma/ │ └── schema.prisma # 数据模型 ├── src/ │ ├── routes/ # 路由 │ ├── controllers/ # 业务逻辑 │ ├── services/ # 数据服务层 │ └── utils/ # 校验与工具 └── package.json第四步让AI生成前后端各自的启动脚本和基础配置。包括前端Vite代理配置、后端Express入口、统一错误处理中间件。这一阶段我不要求一次性生成完整功能只要确保npm run dev能把服务跑起来。这里要提醒一句AI生成的项目骨架质量挺高但它有时会把启动方式建立在它自己假设的脚本名上。所以每跑一步之前我都要求它“把完整的命令和预期输出写出来”避免来回试错。3. 数据模型与后端API让AI替你搭好数据层3.1 设计PetCare的数据模型数据模型是整个项目的地基这部分我花的时间最多也最不建议全盘交给AI。我的做法是先自己理清楚实体关系让Claude Code根据我的描述生成Prisma Schema然后逐字段复核。最终的数据模型核心是这几张表Pet宠物档案HealthEvent健康事件疫苗、驱虫、体检、就诊共用WeightRecord体重记录Reminder提醒规则ReminderLog提醒发送记录Prisma Schema长这样这里展示核心部分model Pet { id String id default(cuid()) name String species String // CAT / DOG / OTHER breed String? birthday DateTime gender String? // MALE / FEMALE neutered Boolean default(false) avatarUrl String? healthEvents HealthEvent[] weightRecords WeightRecord[] reminders Reminder[] createdAt DateTime default(now()) updatedAt DateTime updatedAt } model HealthEvent { id String id default(cuid()) petId String pet Pet relation(fields: [petId], references: [id]) type String // VACCINE / DEWORM / CHECKUP / CLINIC title String eventDate DateTime nextDate DateTime? notes String? createdAt DateTime default(now()) } model WeightRecord { id String id default(cuid()) petId String pet Pet relation(fields: [petId], references: [id]) weightKg Float recordedAt DateTime default(now()) } model Reminder { id String id default(cuid()) petId String pet Pet relation(fields: [petId], references: [id]) title String remindAt DateTime repeatRule String? // DAILY / WEEKLY / MONTHLY completed Boolean default(false) createdAt DateTime default(now()) }这里有几个细节值得展开。健康事件没有单独建疫苗表、驱虫表而是统一放进HealthEvent并用type字段区分原因是这些记录的字段高度相似拆成多张表反而让查询和提醒生成更麻烦用一张时间轴表后续做“按类型筛选”也只是一次简单的where查询。体重记录单独建表是为了方便画曲线、算增长率并且它和健康事件没有必然关系不应该混在同一个表里。还应该给相关字段建索引比如HealthEvent的petId和eventDateWeightRecord的petId和recordedAt因为列表页和高频查询都会用到。让AI生成schema时我明确要求它“把所有列表查询会用的外键和日期字段都加上索引”。3.2 API接口规划与AI生成要点API设计我遵循一个很朴素的原则前端页面需要什么数据后端就提供什么接口。核心接口列表GET /api/pets POST /api/pets GET /api/pets/:id PUT /api/pets/:id DELETE /api/pets/:id GET /api/pets/:id/events POST /api/pets/:id/events GET /api/pets/:id/weights POST /api/pets/:id/weights GET /api/reminders/upcoming POST /api/reminders PUT /api/reminders/:id/complete这些接口没有过度设计。每一个接口都能在页面上找到对应入口宠物列表对应首页健康事件对应档案页的时间轴体重记录对应图表提醒接口对应提醒卡片。生成API代码时我给Claude Code的提示词包含几个固定要求所有输入必须用zod做校验非法请求返回统一错误格式。所有数据库操作放到service层路由不做业务逻辑。响应统一使用{ data: ... }或{ error: ... }结构。日期统一用ISO 8601字符串输出由前端负责格式化展示。以创建宠物为例AI生成的service代码大概是这样export async function createPet(input: CreatePetInput) { const parsed createPetSchema.parse(input); const pet await prisma.pet.create({ data: { name: parsed.name, species: parsed.species, breed: parsed.breed ?? null, birthday: new Date(parsed.birthday), gender: parsed.gender ?? null, neutered: parsed.neutered ?? false, }, }); return pet; }这一段看起来简单但里面有一个容易踩坑的细节前端的birthday传过来一般是2023-05-12如果直接存字符串Prisma会报类型错误所以这里必须手动用new Date()包一层。我让AI生成时明确加了“注意把前端传入的日期字符串转换成Date对象再入库”这一条它才没有犯常见的错误。4. 前端页面与交互把AI输出变成真正可用的界面4.1 页面结构与核心组件前端一共规划了四个页面首页宠物卡片列表 今日提醒区域。宠物详情页基本信息、生命周期阶段卡片、体重曲线、健康时间轴。添加/编辑宠物页表单页。添加记录页支持健康事件和体重记录录入。页面结构确认后我让Claude Code先输出组件清单再逐个生成。这样它能保持组件命名统一不会在五个页面里出现五个不同风格的“空状态提示”。体重曲线用的是Recharts核心代码量不大但有一个交互细节值得提因为宠物体重可能跨度很大而用户录入的日期往往集中在最近几个月图表横轴如果直接用时间戳会出现数据点挤成一团的问题。解决方法是按月份格式化刻度并使用intervalpreserveStartEnd保证首尾日期能显示出来。生命周期阶段判断是前端还是后端做我的选择是在后端计算并返回。原因是生命周期的计算规则可能会变如果放在前端改一次规则就得重新发版放在后端前端只要展示服务端返回的lifeStage和stageAdvice字段就行。计算逻辑也很直接function getLifeStage(birthday: Date): LifeStageInfo { const months differenceInMonths(new Date(), birthday); if (months 12) { return { stage: PUPPY, label: 幼年, advice: 注意定期接种疫苗和驱虫 }; } if (months 96) { return { stage: ADULT, label: 成年, advice: 保持规律运动和年度体检 }; } return { stage: SENIOR, label: 老年, advice: 建议每半年做一次体检 }; }为什么用月份而不是直接按年因为小型犬和中大型犬的老年期起点差异很大即便是简化版本也应该至少按“不足12月龄”“12至96月龄”“96月龄以上”做三档用月份计算可以让后面的精细调整更灵活。4.2 联调过程中AI与人怎么协作前后端联调是最容易让AI开发翻车的阶段。我的经验是把联调动作拆成三步每一步都明确交给AI一部分任务。第一步统一接口类型。约定后端每个接口返回的数据结构后我让Claude Code把前端对应的TypeScript类型定义也生成一遍然后放在src/types目录里。这样在写页面时接口字段一旦对不上类型检查立刻会报错。第二步配置开发环境代理。前端Vite开发服务器默认跑在5173端口后端跑在3001端口直接请求会触发跨域问题。我用Vite的代理配置把/api转发到后端同时后端也启用cors中间件双保险// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3001, changeOrigin: true, }, }, }, });第三步按页面接入数据。我一次只让Claude Code完成一个页面的数据接入比如先做宠物列表再做详情页。每次只改一个页面能让它更专注也便于我在每个页面完成后立刻做手测。这里有一个值得记录的教训AI生成表单时经常忘记处理“提交中”状态用户连续点两次提交就会创建两条一样的数据。我后来要求所有提交按钮在请求期间禁用并加了isSubmitting状态这个问题才算真正解决。5. 测试、调试与问题排查全流程开发最考验功力的一环5.1 针对核心业务场景的测试方案很多人用AI写代码时忽略测试我的看法是正因为代码是AI写的测试才更重要。AI能快速产出功能代码但它对业务边界的理解不一定准确测试就是我们把关的最好方式。我让Claude Code重点补了三类测试第一个是后端接口测试。覆盖创建宠物、创建健康事件、获取体重列表等主流程重点验证日期字段和校验逻辑。比如创建一个缺少species的宠物应该返回400而不是成功入库。describe(POST /api/pets, () { it(should create a pet with valid payload, async () { const res await request(app) .post(/api/pets) .send({ name: 阿黄, species: DOG, birthday: 2022-04-01 }); expect(res.status).toBe(201); expect(res.body.data.name).toBe(阿黄); }); it(should reject missing species, async () { const res await request(app) .post(/api/pets) .send({ name: 阿黄, birthday: 2022-04-01 }); expect(res.status).toBe(400); }); });第二个是生命周期计算单元测试。直接对getLifeStage函数做断言输入不同的生日检查输出的阶段是否符合预期。第三个是前端组件测试。重点不是渲染而是交互未填写必填项时表单是否能阻止提交、提醒列表在空数据时是否显示空状态、体重图表在无数据时是否不会崩溃。我建议不要一开始就追求测试覆盖率数字先把最容易回归的核心场景覆盖住比如“创建宠物”“查看提醒”“记录体重”这三个是用户使用频率最高的动作它们不出错项目就立得住。5.2 我在这个项目里踩过的坑和修复思路整个开发过程中我记录到的比较典型的问题有这么几个都是AI代码容易踩、人工审查时也容易忽略的。第一个坑是Prisma迁移时SQLite外键约束失败。原因是AI生成的schema里某个模型的外键类型不一致一个是String另一个是Int。这种情况在纯代码层面看不出问题必须运行prisma migrate dev才会暴露。修复方法是统一所有ID字段类型为String id default(cuid())删除旧的迁移文件后重新生成。第二个坑是时区偏移。前端录入体重日期时用的是本地日期比如2025-06-01但new Date(2025-06-01)解析出来是UTC零点在本地时区展示时就变成了前一天或后一天。这个问题躲过了接口测试但在页面展示时特别明显。最后统一约定所有日期在接口传输时使用ISO字符串在数据库保存时使用Date对象展示时用date-fns按本地时区格式化。第三个坑是提醒功能在开发环境正常、部署后不触发。原因是我一开始用Node的setInterval做定时任务进程一重启或者休眠就失效。解决方案是改成“惰性检查”每次前端请求提醒列表时后端动态计算remindAt在未来24小时内的提醒并返回。这样不需要后台常驻任务也完全够用。第四个坑是体重图表空数据崩溃。Recharts在数据数组为空时本身不会崩溃但如果数据里有一项的weightKg是null渲染时会直接报错。我让AI在数据接入层写了一个normalizeWeightData函数把所有空值过滤掉同时也把日期转成时间戳保证图表数据始终合格。第五个坑是AI生成代码时的“重复实现”。有几次我让它修复一个bug它选择新增一个工具函数而没有发现项目里已经有了类似函数。后来我每轮对话开始前会先把涉及的文件结构贴给它并明确写一句“优先复用现有工具函数不要新创建”这个问题的出现频率立刻下降。6. 部署上线与迭代准备给项目一个交付闭环6.1 一套不需要高成本的部署方案宠物生命周期管理App这种中小型项目部署原则是简单、可迁移、成本低。我用的是容器化的方式把前端、后端、SQLite文件都放进同一个Docker Compose里内存占用控制得很低域名加HTTPS后就可以正式对外使用。关键思路是前端构建产物交给Nginx托管后端单独一个Node容器SQLite数据文件挂载成本地卷保证容器重启数据不丢。services: server: build: ./server ports: - 3001:3001 volumes: - pet-data:/app/data environment: - DATABASE_URLfile:/app/data/petcare.db web: build: ./pet-fe ports: - 8080:80 depends_on: - server前端Nginx里需要配置反向代理把/api请求转发到后端容器。这一步我让Claude Code生成的Nginx配置基本可用但我特意检查了几处有没有写死容器IP、有没有配置client_max_body_size后面如果要传宠物头像会用到、有没有设置Cache-Control来保证静态资源缓存策略正确。部署有一个细节值得单独说后端容器里运行Prisma迁移。最简单的方式是让容器启动命令先执行prisma migrate deploy再启动服务npx prisma migrate deploy node dist/index.js这样每次发布新版本只要重新构建镜像并重启就能自动把数据库结构同步到新版本不需要手动登录服务器执行命令。6.2 下一轮迭代值得优先做的功能清单项目能跑起来之后我把后续迭代方向也整理了一遍优先级按“能否解决真实痛点”排第一优先是宠物头像和照片记录。宠物主人很愿意记录毛孩子的日常拍照上传是天然需求。现在没做上传主要是想先把核心数据链路跑通下一轮可以用对象存储加预签名上传的方式实现。第二优先是多用户家庭共享。现在整个应用是单用户模式但现实中一个家庭往往好几个人共同照顾宠物。这块涉及数据权限模型建议在用户量起来之前就规划好否则后面改造的成本会很高。第三优先是更细的提醒规则比如“每30天提醒一次体外驱虫”“每年5月提醒打狂犬疫苗”。现在只支持简单的一次性提醒后续需要引入可循环的提醒规则和日历订阅导出。第四优先是健康周报。当用户积累的数据足够多以后每周自动生成一封简报包含体重变化趋势、即将到期的事项、阶段养护建议这会显著提升用户留存。这些功能如果继续用Claude Code做我依然会坚持同一个节奏先描述场景再拆模块再生成代码最后补测试。全流程AI开发的效率优势在迭代阶段会更明显因为旧代码都是AI自己生成的它理解起来比阅读别人遗产代码快得多。我个人在这次的实操里最深的体会是Claude Code最大的价值不是替代你思考而是帮你把思考快速落地。它写代码很快但需求拆解、数据结构、边界条件的判断仍然需要我们心里有数。每让AI生成一段代码我都会追问自己一句“如果它这里写错了我能不能在测试阶段发现”答案是能我才会继续往下走。最后再分享一个实用技巧在项目根目录建一个docs/prompts.md把每一轮给AI的核心提示词、关键决策和踩坑记录都存下来。下次继续开发或换一台机器接着写只需要把这份文件喂给它它就能快速恢复上下文。这个习惯帮我省了大量重复解释的时间也是我这轮全流程开发里最值得保留的一个做法。