1. 项目缘起与整体设计思路1.1 为什么选“宠物生命周期管理”这个场景宠物生命周期管理这个选题乍一听像是又一个“用AI写个CRUD”的练手项目但真正动手之后你会发现它其实是一个非常适合用来验证AI辅助开发全流程的中等复杂度场景。原因有三点第一它的业务模型天然分层从宠物档案、疫苗记录、驱虫计划、体重曲线、就诊历史到临终关怀每个模块的数据结构和交互逻辑都不一样能覆盖到关系型数据、时间序列数据、文件附件等多种存储形态第二它的用户角色相对简单通常就是宠物主本人加一个可选的“家庭共享”角色不需要设计复杂的权限矩阵能把精力集中在业务逻辑本身第三它的生命周期概念天然带有“状态流转”和“定时提醒”的需求这恰好是检验AI能否正确理解异步任务和状态机的试金石。我这次用的是Claude Code作为主力开发工具从空目录开始一路把项目骨架、数据模型、接口层、前端页面、定时任务和部署配置全部跑通。整个过程下来我对“AI辅助开发”这件事的认知被刷新了不少——它确实能极大加速样板代码的产出但在业务语义理解和边界条件处理上仍然需要人来把关。这篇文章就把整个流程拆开揉碎讲清楚包括我踩过的坑和总结出来的实操技巧。1.2 技术选型的取舍逻辑在动手之前我先花了一个小时做技术选型。这个项目的核心诉求是快速出原型、方便后续扩展、部署成本低。基于这三点我最终确定的方案是层级选型选择理由前端React Vite TailwindCSS组件化开发效率高Vite冷启动快Tailwind省去写CSS的时间后端Node.js Express与前端同语言减少上下文切换成本生态成熟数据库SQLite开发/ PostgreSQL生产开发阶段零配置生产环境可平滑迁移ORMPrismaSchema定义清晰迁移工具好用AI生成模型代码准确率高定时任务node-cron轻量满足疫苗提醒、驱虫提醒等周期性任务需求文件存储本地磁盘开发/ 对象存储生产宠物照片、病历附件等非结构化数据这里重点说一下为什么选Prisma而不是TypeORM或Sequelize。Prisma的schema文件是声明式的语法非常规整Claude Code在读取schema之后生成的查询代码几乎不需要修改就能跑。而TypeORM的装饰器语法和关系配置相对松散AI生成的代码经常出现关系映射错误调试成本反而更高。另外Prisma的migrate dev命令能自动生成迁移文件对于快速迭代阶段非常友好。提示如果你打算后续迁移到PostgreSQL建议在开发阶段就用Prisma的provider切换能力把schema中的datasource配置写成环境变量驱动避免后期改代码。1.3 项目目录结构规划在让Claude Code生成任何代码之前我先手动创建了目录骨架。这一步很关键因为AI在生成代码时会根据现有目录结构推断文件放置位置如果目录是空的它可能会把所有代码堆在一个文件里。我的目录结构是这样的pet-lifecycle/ ├── client/ # 前端React应用 │ ├── src/ │ │ ├── components/ # 通用组件 │ │ ├── pages/ # 页面级组件 │ │ ├── hooks/ # 自定义hooks │ │ ├── api/ # 接口封装 │ │ └── utils/ # 工具函数 │ └── package.json ├── server/ # 后端Express应用 │ ├── src/ │ │ ├── routes/ # 路由定义 │ │ ├── controllers/ # 业务逻辑 │ │ ├── services/ # 领域服务 │ │ ├── jobs/ # 定时任务 │ │ └── middleware/ # 中间件 │ ├── prisma/ │ │ └── schema.prisma │ └── package.json ├── shared/ # 前后端共享类型定义 └── docker-compose.yml这个结构的好处是职责边界清晰Claude Code在生成代码时能准确判断某个逻辑应该放在controller还是service里。我实测下来有明确目录结构的情况下AI生成代码的首次可用率能从大概六成提升到八成以上。2. 核心数据模型与业务逻辑拆解2.1 宠物生命周期管理的领域模型宠物生命周期管理的核心实体其实就一个宠物。但围绕这个核心实体会衍生出一系列关联数据。我在设计数据模型时把整个领域拆成了以下几个模块宠物基础档案名字、品种、性别、出生日期、绝育状态、芯片号、头像健康记录体重记录、疫苗记录、驱虫记录、就诊记录、用药记录日程提醒下次疫苗时间、下次驱虫时间、复诊提醒、用药提醒生活记录喂食记录、遛弯记录、行为观察生命周期事件领养、生日、离世这里有一个设计决策值得展开说体重记录和疫苗记录虽然都是“健康记录”但它们的存储模式完全不同。体重是时间序列数据需要支持折线图展示和趋势分析疫苗记录是事件型数据每条记录有明确的类型和有效期。如果把它们放在同一张表里字段会变得非常稀疏查询逻辑也会很别扭。所以我最终拆成了两张表通过petId关联。2.2 Prisma Schema的编写与AI协作技巧Prisma的schema是整个后端的地基我选择先手写核心模型再让Claude Code补充关联关系和索引。核心的Pet模型是这样的model Pet { id String id default(cuid()) name String species String // 猫、狗、兔等 breed String? gender String // male / female birthDate DateTime? neutered Boolean default(false) chipNumber String? unique avatarUrl String? ownerId String owner User relation(fields: [ownerId], references: [id]) weights WeightRecord[] vaccinations VaccinationRecord[] dewormings DewormingRecord[] visits VetVisit[] reminders Reminder[] createdAt DateTime default(now()) updatedAt DateTime updatedAt index([ownerId]) }写schema的时候有个小技巧我会在字段后面加注释说明业务含义比如species后面标注“猫、狗、兔等”。Claude Code在读取这些注释后生成表单验证逻辑时会更准确比如它会给species字段加上枚举校验而不是随便一个字符串。关联关系这块我让Claude Code帮我检查了一遍级联删除的配置。默认情况下Prisma的关联删除是Restrict也就是说如果宠物下面还有体重记录直接删宠物会报错。对于这个项目来说删除宠物时应该级联删除所有关联记录所以需要在每个关联字段上显式加上onDelete: Cascade。这个细节如果不注意后期删除功能会直接报错。2.3 状态流转与定时提醒的实现思路宠物生命周期管理里最复杂的逻辑是提醒系统。一只宠物可能同时有多个待办提醒下个月要打狂犬疫苗、两周后要驱虫、三天后要复诊。这些提醒的触发规则各不相同疫苗提醒根据疫苗类型和上次接种日期按固定间隔计算下次时间驱虫提醒体内驱虫和体外驱虫周期不同需要分别计算复诊提醒由就诊记录中的医嘱决定是一次性事件用药提醒按疗程计算可能是每天一次持续七天我的实现方案是设计一张统一的Reminder表用type字段区分提醒类型用cronExpression字段存储触发规则用nextTriggerAt字段存储下次触发时间。定时任务每分钟扫描一次找出所有nextTriggerAt小于当前时间的提醒推送到消息队列开发阶段直接用内存队列然后更新nextTriggerAt为下一个周期。// jobs/reminderScanner.js const cron require(node-cron); const { PrismaClient } require(prisma/client); const prisma new PrismaClient(); cron.schedule(* * * * *, async () { const dueReminders await prisma.reminder.findMany({ where: { nextTriggerAt: { lte: new Date() }, enabled: true, }, include: { pet: true }, }); for (const reminder of dueReminders) { await dispatchNotification(reminder); await prisma.reminder.update({ where: { id: reminder.id }, data: { nextTriggerAt: calculateNextTrigger(reminder) }, }); } });这段代码是Claude Code生成的初版我做了两处修改一是加了enabled字段过滤避免已关闭的提醒被触发二是把calculateNextTrigger抽成了独立函数因为不同提醒类型的计算逻辑差异很大放在一起会让代码难以维护。3. 从零搭建的完整实操流程3.1 环境初始化与依赖安装第一步是初始化项目。我习惯用pnpm作为包管理器因为它的硬链接机制能节省大量磁盘空间而且安装速度比npm快不少。整个初始化过程如下mkdir pet-lifecycle cd pet-lifecycle pnpm init pnpm add -D typescript ts-node types/node pnpm add express cors helmet morgan pnpm add prisma/client pnpm add -D prisma pnpm add node-cron前端部分单独在client目录下初始化cd client pnpm create vite . --template react-ts pnpm add axios react-router-dom pnpm add -D tailwindcss postcss autoprefixer npx tailwindcss init -p这里有个细节Vite创建项目时如果当前目录非空会报错所以要先确保client目录是空的。我一开始在根目录直接跑create vite结果它把文件生成到了根目录和server的配置混在一起后来删掉重来才理清楚。依赖装完之后我在根目录建了一个pnpm-workspace.yaml把client和server都纳入工作区管理。这样在根目录跑pnpm install就能一次性装好所有依赖不用来回切换目录。3.2 数据库初始化与迁移Prisma的初始化很简单在server目录下执行npx prisma init --datasource-provider sqlite这会在prisma目录下生成schema.prisma文件。我把前面设计好的模型写进去之后执行第一次迁移npx prisma migrate dev --name init迁移成功后Prisma会自动生成客户端代码。这里有个坑要注意每次修改schema之后都需要重新执行migrate dev否则Prisma Client的类型定义不会更新TypeScript会报错说某个字段不存在。我一开始改完schema直接跑代码被类型错误卡了十几分钟才反应过来。另外开发阶段我建议同时开启Prisma Studio它是一个可视化的数据库管理界面能直接查看和编辑数据调试的时候非常方便npx prisma studio3.3 后端接口层的AI生成与人工修正后端接口我采用了分层架构route层只负责定义路径和中间件controller层处理请求参数校验和响应格式化service层承载业务逻辑。让Claude Code生成代码时我会先给它一个明确的接口清单比如POST /api/pets 创建宠物 GET /api/pets 获取当前用户的宠物列表 GET /api/pets/:id 获取宠物详情 PUT /api/pets/:id 更新宠物信息 DELETE /api/pets/:id 删除宠物 POST /api/pets/:id/weights 添加体重记录 GET /api/pets/:id/weights 获取体重记录列表然后让它按照route-controller-service的分层结构生成代码。实测下来AI生成的controller层代码质量最高因为参数校验和响应格式化是高度模式化的。但service层需要我仔细审查特别是涉及多表关联查询和事务的地方。举个例子删除宠物这个操作AI最初生成的代码是直接调prisma.pet.delete。但我在schema里配置了级联删除所以实际上直接删主表记录就会自动清理关联数据。不过这里有个隐患如果关联数据量很大级联删除可能会超时。更稳妥的做法是在service层用事务手动删除或者先软删除再异步清理。我最终选择了软删除方案给Pet模型加了一个deletedAt字段查询时默认过滤掉已删除的记录。3.4 前端页面的快速搭建前端部分我让Claude Code生成了大部分页面骨架然后自己调整样式和交互细节。核心页面有四个宠物列表页卡片式布局展示每只宠物的头像、名字、品种和下次提醒时间宠物详情页Tab切换展示档案、健康记录、提醒设置添加/编辑宠物页表单页面包含头像上传提醒中心页按时间排序展示所有待办提醒这里分享一个提效技巧我会先让Claude Code生成一个基础的页面组件包含数据获取逻辑和基本的布局结构然后自己用Tailwind调整视觉细节。比如宠物列表页AI生成的版本是用table展示的我改成了grid布局的卡片每张卡片包含头像、名字、品种标签和提醒徽章。这种“AI出结构人调样式”的分工模式效率最高。数据获取我用了React Query现在叫TanStack Query它能自动处理缓存、重试和乐观更新。对于体重记录这种需要频繁刷新的数据React Query的缓存失效机制非常好用——添加一条体重记录后只需要invalidate对应的query key列表和图表就会自动更新。3.5 定时任务与提醒推送的联调定时任务这块我单独跑了一个Node进程和Express服务分开部署。这样做的好处是任务进程可以独立重启不会影响API服务的可用性。任务进程的核心逻辑前面已经展示过了这里补充一下提醒推送的实现。开发阶段我直接用控制台输出模拟推送生产环境可以接入邮件服务或推送服务。推送内容我设计了一个模板系统不同类型的提醒用不同的文案模板const templates { VACCINE: (pet, detail) ${pet.name}的${detail.vaccineType}疫苗即将到期请及时安排接种。, DEWORMING: (pet, detail) ${pet.name}的${detail.dewormingType}驱虫时间到了别忘了处理。, VET_VISIT: (pet, detail) ${pet.name}有一个复诊预约在${detail.visitDate}请提前准备。, };联调的时候我发现一个问题如果提醒的nextTriggerAt设置得过于集中比如所有疫苗提醒都设在凌晨零点触发会导致任务进程在那一瞬间处理大量通知。解决方案是在计算nextTriggerAt时加一个随机偏移量把触发时间打散到不同分钟。这个细节AI不会主动帮你考虑需要自己根据实际场景补充。4. 常见问题与排查技巧实录4.1 AI生成代码的典型问题与修正方法用Claude Code开发这个项目的过程中我总结了几类高频问题问题类型具体表现修正方法字段名不一致AI生成的代码用了camelCase但schema里是snake_case在schema中统一用camelCasePrisma会自动映射缺少错误处理async函数没有try-catch数据库报错直接崩溃统一加asyncHandler中间件包裹关联查询遗漏查询宠物列表时没有include关联数据前端拿不到提醒信息在service层显式指定include类型定义重复前后端各自定义了Pet类型字段不一致在shared目录统一维护类型定义分页逻辑缺失列表接口没有分页数据量大时性能差统一用cursor-based分页其中字段名不一致这个问题最隐蔽因为TypeScript编译能过但运行时Prisma会报字段不存在的错误。我的解决办法是在项目初期就定好命名规范并且在CLAUDE.md文件里写清楚让AI每次生成代码前都读取这个规范文件。4.2 数据库迁移中的踩坑记录Prisma迁移有几个坑我踩过这里列出来供参考第一个坑是修改字段类型时的数据丢失。比如把birthDate从String改成DateTimePrisma会提示需要重置数据库。开发阶段无所谓但如果有测试数据就会很麻烦。解决方案是先用migrate dev生成迁移文件手动编辑SQL加入数据转换逻辑再用migrate deploy应用。第二个坑是SQLite不支持某些PostgreSQL特性比如数组类型和JSONB。我在开发阶段用SQLite时定义了一个String数组字段迁移到PostgreSQL时才发现需要改成Json类型。建议在schema设计阶段就考虑生产环境的数据库类型避免后期大改。第三个坑是迁移文件的顺序问题。团队协作时如果两个人同时修改schema迁移文件可能会冲突。解决方案是每次pull代码后先跑migrate dev检查是否有未应用的迁移有冲突就手动合并。4.3 前端状态管理的边界划分React Query负责服务端状态但客户端状态比如表单输入、UI开关还是需要useState或useReducer来管理。我一开始把所有状态都塞进React Query结果发现表单的临时输入也被缓存了切换页面再回来数据还在体验很奇怪。后来我明确了边界凡是需要和服务端同步的数据用React Query纯客户端的临时状态用useState。比如宠物编辑表单初始值从React Query获取但用户的输入用useState管理提交成功后再invalidate query触发重新获取。这样既保证了数据一致性又避免了表单状态被意外缓存。4.4 部署上线的注意事项部署这块我用了Docker Compose把前端、后端和数据库打包在一起。前端用Nginx做静态文件服务后端用Node Alpine镜像数据库用PostgreSQL官方镜像。几个关键配置后端服务的健康检查路径设为/api/healthDocker会根据这个接口判断服务是否就绪数据库连接串通过环境变量注入不要硬编码在代码里前端构建产物放在Nginx的html目录配置try_files支持前端路由定时任务进程单独一个容器避免和API服务耦合上线后我遇到过一次内存泄漏原因是定时任务进程里的Prisma Client没有正确释放连接。解决方案是在进程退出时调用prisma.$disconnect()并且给容器设置合理的内存限制和重启策略。5. 项目扩展方向与个人经验总结这个项目跑通之后我发现它其实可以作为一个通用的“AI辅助全栈开发”模板来复用。把宠物换成任何其他生命周期管理对象——比如植物养护、车辆保养、设备维护——核心架构几乎不需要改动只需要替换领域模型和业务规则。这也是我觉得这个选题特别有价值的地方它足够简单能快速跑通全流程又足够复杂能暴露AI辅助开发中的真实问题。后续我打算在这几个方向继续扩展一是加入图片识别功能用多模态能力自动识别宠物品种和疫苗本照片二是做数据导出功能把宠物的完整健康记录导出成PDF报告方便就诊时给兽医看三是加入家庭共享机制让多个家庭成员共同管理同一只宠物的记录。最后分享一个我在这个项目里体会最深的心得AI辅助开发的核心不是“让AI写代码”而是“让AI写你不想写的代码”。样板代码、重复的CRUD、格式化的响应处理这些交给AI效率极高但业务逻辑的边界条件、状态流转的异常处理、数据一致性的保障这些必须自己把关。把精力花在真正需要思考的地方才是AI时代开发者应该有的工作方式。