AI辅助开发实战:从零搭建宠物生命周期管理系统
发布时间:2026/10/13 6:32:12 锦皓数字建站

1. 项目缘起与整体设计思路1.1 为什么选宠物生命周期管理这个方向宠物生命周期管理这个选题乍看像是个小众赛道但真正做过调研就会发现它覆盖的需求链条极长。从宠物领养或购买那一刻起疫苗提醒、驱虫周期、体重曲线、发情期记录、配种管理、孕期跟踪、幼崽档案、老年病监测、临终关怀一直到离世后的纪念页面每一个环节都是独立的功能模块又彼此存在数据依赖。这种“长链路、多状态、强时序”的业务特征恰好是检验一套AI辅助开发流程是否真正能落地的绝佳试金石。我选择用Claude Code从零搭建这个项目核心目的不是做一个能上架的商业App而是想验证一件事在完全没有现成代码骨架的前提下仅靠自然语言描述和逐步交互能不能把一套包含数据建模、状态机设计、前后端联调、测试用例生成的完整项目跑通。如果能那这套方法论就可以复用到其他领域如果不能也能清楚知道卡点在哪里。1.2 技术选型的底层逻辑技术栈的选择上我定了几个硬约束第一必须支持快速迭代因为AI生成的代码需要频繁调整第二类型系统要足够强否则AI在生成复杂状态流转时容易出错第三生态要成熟遇到问题能快速找到参考方案。最终选型如下层级技术选择选型理由前端框架React TypeScript组件化程度高AI对React代码的生成质量明显优于其他框架状态管理Zustand轻量样板代码少AI不容易在冗余逻辑上犯错后端框架Fastify TypeScript性能好插件体系清晰类型推导友好数据库PostgreSQL Prisma关系型建模适合生命周期这种强关联场景Prisma的schema描述语言对AI极其友好测试Vitest Playwright单元测试和端到端测试覆盖AI生成测试用例的准确率较高部署Docker Compose本地一键拉起全栈环境减少环境差异导致的调试成本这里重点说一下为什么选Prisma。宠物生命周期管理涉及大量一对多、多对多的关系比如一只宠物对应多条疫苗记录、多条体重记录、多个健康事件。如果用MongoDB这类文档数据库AI在生成聚合查询时经常会把关联逻辑写错。而Prisma的schema文件本身就是一种结构化描述AI在读取schema后能准确理解实体关系生成的查询代码正确率提升非常明显。1.3 项目模块划分与AI协作策略整个项目我拆成了六个核心模块每个模块单独与AI进行一轮完整对话宠物档案模块基础信息、品种、生日、绝育状态、芯片编号健康记录模块疫苗、驱虫、体检、用药、过敏史成长追踪模块体重、身高、饮食量、活动量繁殖管理模块发情周期、配种记录、孕期跟踪、幼崽档案提醒引擎模块基于时间规则和事件规则的双引擎提醒纪念与归档模块离世记录、纪念页面、数据导出每个模块的协作流程固定为四步先让AI输出数据模型我人工审核关系是否合理再让AI生成API接口定义然后生成前端页面和交互逻辑最后生成测试用例并跑通。这个流程看起来机械但实测下来能大幅减少返工因为数据模型一旦错了后面全错。注意不要一次性把六个模块全部丢给AI让它一口气生成。上下文窗口再大AI在多模块交叉时也会出现实体引用混乱。分模块推进每个模块结束后把关键接口固化下来再进入下一个模块。2. 数据建模与核心状态机设计2.1 宠物生命周期实体的建模要点宠物生命周期管理的核心实体是Pet但它不是一个孤立的表。围绕Pet我需要建立一套完整的关系网络。在Prisma schema中我让AI先根据我的自然语言描述生成初版然后我逐条审核并调整。初版AI生成的核心结构大致如下经过我人工修正后的版本model Pet { id String id default(cuid()) name String species Species breed String? birthDate DateTime? gender Gender neutered Boolean default(false) chipNumber String? unique status PetStatus default(ACTIVE) ownerId String owner Owner relation(fields: [ownerId], references: [id]) healthRecords HealthRecord[] growthLogs GrowthLog[] breedingLogs BreedingLog[] reminders Reminder[] memorial Memorial? createdAt DateTime default(now()) updatedAt DateTime updatedAt } enum Species { DOG CAT BIRD RODENT REPTILE OTHER } enum Gender { MALE FEMALE UNKNOWN } enum PetStatus { ACTIVE PREGNANT NURSING CHRONIC_ILLNESS DECEASED ARCHIVED }这里有几个关键决策需要解释。第一status字段用枚举而不是布尔值因为宠物状态不是简单的“活着/死了”而是存在孕期、哺乳期、慢性病管理等中间态。这些状态直接影响提醒引擎的行为比如孕期宠物的疫苗提醒需要自动推迟。第二chipNumber设为唯一索引因为芯片编号是宠物的法定身份标识重复会导致数据混乱。第三memorial设计为可选的一对一关系只有宠物离世后才会创建避免在活跃宠物上产生冗余数据。2.2 健康记录的多态设计健康记录模块是最容易设计过度的地方。疫苗、驱虫、体检、用药这四类记录有大量共同字段日期、备注、执行机构但也有各自特有字段疫苗有批号、驱虫有药品类型、体检有各项指标、用药有剂量和频次。我最初让AI生成的是四张独立的表但很快发现查询“某只宠物最近三个月的所有健康事件”时需要四次JOIN而且前端展示时间线时要合并四个数据源逻辑很啰嗦。后来改成单表加类型字段加JSON扩展字段的方案model HealthRecord { id String id default(cuid()) petId String pet Pet relation(fields: [petId], references: [id]) type HealthRecordType recordDate DateTime provider String? notes String? metadata Json? attachments String[] createdAt DateTime default(now()) } enum HealthRecordType { VACCINE DEWORMING CHECKUP MEDICATION SURGERY ALLERGY }metadata字段用JSON存储类型特有的数据。比如VACCINE类型存{ vaccineName: 狂犬疫苗, batchNumber: 2024A001, nextDueDate: 2025-06-01 }CHECKUP类型存{ weight: 5.2, temperature: 38.5, heartRate: 120 }。这样时间线查询只需要一次查询前端根据type字段决定渲染哪种卡片。实操心得JSON字段虽然灵活但一定要在应用层做schema校验。我用Zod为每种type定义了metadata的结构AI生成API时自动带上校验逻辑避免脏数据写入。2.3 提醒引擎的状态机设计提醒引擎是整个项目最复杂的部分。它不是简单的定时任务而是需要根据宠物当前状态、历史记录、用户偏好动态计算下一次提醒时间。我设计了一个双引擎架构时间规则引擎和事件规则引擎。时间规则引擎处理固定周期提醒比如“每年一次狂犬疫苗”事件规则引擎处理依赖其他事件的提醒比如“配种成功后第21天提醒做孕检”。提醒的状态流转如下PENDING - TRIGGERED - ACKNOWLEDGED - COMPLETED \- SNOOZED - TRIGGERED \- DISMISSED这个状态机是AI帮我梳理的我给它描述了业务场景后它输出了状态图和转换条件。我审核时补充了一条SNOOZED状态必须记录snoozeCount超过三次自动转为DISMISSED并通知用户避免用户无限推迟导致提醒失效。model Reminder { id String id default(cuid()) petId String pet Pet relation(fields: [petId], references: [id]) type ReminderType title String description String? dueDate DateTime status ReminderStatus default(PENDING) snoozeCount Int default(0) ruleConfig Json triggeredAt DateTime? completedAt DateTime? createdAt DateTime default(now()) } enum ReminderStatus { PENDING TRIGGERED ACKNOWLEDGED SNOOZED COMPLETED DISMISSED }ruleConfig存储提醒的生成规则比如{ engine: TIME, interval: YEARLY, anchorDate: 2024-06-01 }或{ engine: EVENT, triggerEvent: BREEDING_SUCCESS, offsetDays: 21 }。这样当宠物信息变更时可以重新计算所有关联提醒的dueDate。3. 核心功能模块的实操实现3.1 宠物档案创建与校验逻辑宠物档案创建看起来简单但实际涉及大量校验。我让AI生成创建接口时明确要求它处理以下边界情况生日不能晚于今天、芯片编号格式校验15位数字、品种与物种的匹配校验比如物种是猫品种不能选“金毛”。AI生成的校验逻辑初版用了大量if-else我让它重构成Zod schemaconst createPetSchema z.object({ name: z.string().min(1).max(50), species: z.nativeEnum(Species), breed: z.string().optional(), birthDate: z.coerce.date().max(new Date(), 生日不能晚于今天).optional(), gender: z.nativeEnum(Gender), neutered: z.boolean().default(false), chipNumber: z.string().regex(/^\d{15}$/, 芯片编号必须为15位数字).optional(), ownerId: z.string().cuid(), }).refine((data) { if (data.species Species.CAT data.breed) { return CAT_BREEDS.includes(data.breed); } if (data.species Species.DOG data.breed) { return DOG_BREEDS.includes(data.breed); } return true; }, { message: 品种与物种不匹配, path: [breed] });这里有个细节值得说z.coerce.date()会自动把前端传来的ISO字符串转成Date对象省去了手动转换。refine用于跨字段校验品种列表我维护了一个常量文件AI生成时自动引用了这个文件。注意事项芯片编号的校验规则各地不同我这里用的是15位纯数字的通用格式。实际项目中应该把校验规则做成可配置的因为不同地区的芯片标准确实存在差异。3.2 健康记录时间线的查询优化健康记录时间线是用户使用频率最高的页面。初始实现是简单的findMany加orderBy但当记录超过500条时页面加载明显变慢。我让AI分析查询计划后做了三处优化。第一在petId和recordDate上建复合索引index([petId, recordDate(sort: Desc)])第二分页改用游标分页而不是偏移分页。偏移分页在数据量大时性能下降明显因为数据库需要扫描并跳过前面的行。游标分页基于recordDate和id做锚点const records await prisma.healthRecord.findMany({ where: { petId }, orderBy: [{ recordDate: desc }, { id: desc }], take: 20, ...(cursor ? { skip: 1, cursor: { id: cursor } } : {}), });第三前端用虚拟列表渲染。AI帮我生成了基于react-window的虚拟列表组件只渲染可视区域内的记录卡片。实测下来1000条记录的页面加载时间从3.2秒降到了0.4秒。3.3 提醒引擎的定时任务实现提醒引擎的后端实现我用了node-cron加数据库轮询的混合方案。纯cron的问题是精度不够而且服务重启后内存中的任务会丢失。纯轮询的问题是频率太高会给数据库压力。最终方案是每小时执行一次扫描任务查询未来24小时内所有PENDING状态的提醒将它们加入一个内存队列队列中的任务用setTimeout精确触发。服务重启时扫描任务会自动重建队列。async function scanUpcomingReminders() { const now new Date(); const tomorrow new Date(now.getTime() 24 * 60 * 60 * 1000); const reminders await prisma.reminder.findMany({ where: { status: ReminderStatus.PENDING, dueDate: { gte: now, lte: tomorrow }, }, include: { pet: { include: { owner: true } } }, }); for (const reminder of reminders) { scheduleReminder(reminder); } } function scheduleReminder(reminder: Reminder { pet: Pet { owner: Owner } }) { const delay reminder.dueDate.getTime() - Date.now(); if (delay 0) { triggerReminder(reminder.id); return; } const timer setTimeout(() { triggerReminder(reminder.id); }, delay); activeTimers.set(reminder.id, timer); }triggerReminder负责更新状态为TRIGGERED并发送推送通知。推送渠道我抽象了一个NotificationChannel接口目前实现了站内信和邮件两种后续可以扩展短信或即时通讯工具。实操心得setTimeout的最大延迟值是24.8天左右超过这个时间的定时器会立即触发。所以扫描窗口设为24小时是安全的。如果你需要更长的提前量必须用分层扫描策略。3.4 繁殖管理模块的状态流转繁殖管理是业务逻辑最复杂的模块。一只母犬从发情到配种到生产中间涉及多个状态转换每个转换都有时间窗口和校验条件。我让AI根据以下自然语言描述生成状态机“母犬发情期持续约21天最佳配种时间是发情第10到14天。配种后第21天可以做B超确认怀孕。怀孕期约63天预产期前一周进入待产状态。生产后进入哺乳期哺乳期约42天。”AI生成的状态机包含六个状态HEAT、MATING、PREGNANCY_CHECK、PREGNANT、LABOR、NURSING。每个状态有对应的enterCondition和exitCondition。我审核时发现它漏掉了“假孕”这个状态实际中母犬配种后未受孕但表现出怀孕症状的情况很常见需要单独处理。补充后状态变为七个。const breedingStateMachine { HEAT: { next: [MATING, ENDED], enterCondition: (pet) pet.gender FEMALE !pet.neutered, maxDurationDays: 21, }, MATING: { next: [PREGNANCY_CHECK, ENDED], enterCondition: (record) record.matingDate ! null, maxDurationDays: 3, }, PREGNANCY_CHECK: { next: [PREGNANT, FALSE_PREGNANCY], enterCondition: (record) daysSince(record.matingDate) 21, maxDurationDays: 7, }, // ... 其余状态 };每个状态转换都会触发提醒引擎重新计算相关提醒。比如进入PREGNANT状态后自动生成“预产期提醒”“孕检提醒”“营养补充提醒”三条新提醒。4. 常见问题与排查技巧实录4.1 AI生成代码的典型错误模式在跟Claude Code协作的过程中我总结了它最容易犯的几类错误以及对应的排查方法。第一类Prisma关系字段命名不一致。AI在生成schema时有时会把关系字段命名为healthRecords但在查询代码里写成healthRecord。这种错误TypeScript编译器会直接报错排查成本低。我的做法是每生成一个模块的schema后立即运行prisma generate让类型系统先过一遍。第二类异步逻辑中的竞态条件。比如提醒触发时AI生成的代码先查询提醒状态再更新状态中间没有加锁。如果两个定时任务同时触发同一条提醒会导致重复通知。修复方法是用Prisma的updateMany加状态条件做原子更新const result await prisma.reminder.updateMany({ where: { id: reminderId, status: ReminderStatus.PENDING }, data: { status: ReminderStatus.TRIGGERED, triggeredAt: new Date() }, }); if (result.count 0) { // 说明已经被其他任务处理了直接返回 return; }第三类日期时区处理错误。AI生成的代码经常混用本地时间和UTC时间。比如new Date()返回的是本地时间但数据库存的是UTC。修复方案是统一用UTC存储前端展示时再转本地时区。我在项目里封装了一个toUTC和toLocal工具函数所有日期操作必须经过这两个函数。4.2 数据库迁移中的踩坑记录Prisma的迁移在生产环境需要格外小心。我遇到过一次迁移导致数据丢失的情况AI生成的迁移脚本在修改枚举类型时直接删除了旧枚举并创建新枚举导致所有引用旧枚举的记录被置空。正确的做法是分步迁移先添加新枚举值再迁移数据最后删除旧值。Prisma支持用--create-only参数生成迁移文件后手动编辑npx prisma migrate dev --create-only --name add_false_pregnancy_status然后手动编辑生成的SQL确保数据安全。这个坑让我养成了一个习惯任何涉及枚举变更或字段删除的迁移必须先在生产数据的副本上跑一遍。4.3 前端状态同步的常见问题宠物档案修改后关联的提醒列表需要同步更新。AI最初生成的代码是在修改成功后手动调用refetchReminders()但这样容易遗漏而且多个组件同时修改时会产生竞态。后来改用Zustand的订阅机制当pet store中的宠物信息变更时自动触发reminder store的重新计算usePetStore.subscribe( (state) state.currentPet, (pet) { if (pet) { useReminderStore.getState().recalculateForPet(pet.id); } } );这样无论哪个组件修改了宠物信息提醒列表都会自动同步不需要手动调用刷新。4.4 常见问题速查表问题现象可能原因排查方法解决方案提醒重复触发竞态条件检查triggerReminder是否原子更新用updateMany加状态条件时间线加载慢缺少复合索引用EXPLAIN分析查询计划添加petIdrecordDate索引日期显示偏差时区混用检查数据库存储值和前端展示值统一UTC存储前端转本地品种校验失效品种列表未更新检查常量文件是否包含新品种将品种列表改为数据库配置状态机卡死缺少超时转换检查每个状态的maxDurationDays添加定时任务强制转换超时状态迁移丢数据枚举直接替换检查迁移SQL是否包含DROP分步迁移先加后删避坑技巧每次AI生成完一个模块的代码后不要急着写下一个模块。先跑一遍完整的测试用例确保当前模块的数据流是通的。我吃过亏连续生成了三个模块后才发现第一个模块的数据模型有问题返工成本翻了三倍。5. 测试策略与部署上线5.1 AI辅助生成测试用例的实践测试用例的生成是我觉得AI帮助最大的环节之一。我只需要描述业务场景AI就能生成覆盖正常流程和边界情况的测试代码。比如提醒引擎的测试我描述了“宠物疫苗到期前7天应触发提醒到期当天应再次提醒逾期3天未处理应标记为逾期”AI生成了以下测试describe(ReminderEngine, () { it(应在疫苗到期前7天触发提醒, async () { const pet await createTestPet(); const vaccine await createHealthRecord(pet.id, { type: VACCINE, recordDate: new Date(2024-01-01), metadata: { nextDueDate: 2024-07-01 }, }); await advanceTimeTo(2024-06-24); const reminders await getActiveReminders(pet.id); expect(reminders).toContainEqual( expect.objectContaining({ type: VACCINE_DUE, status: TRIGGERED }) ); }); it(逾期3天未处理应标记为逾期, async () { // ... }); });这里用了advanceTimeTo这个测试工具它通过mock系统时间来实现时间旅行。AI生成的初版直接用了jest.useFakeTimers()但我们的定时任务用了setTimeoutfake timers会导致测试挂起。后来改用了sinonjs/fake-timers可以精确控制时间推进而不影响异步任务。5.2 端到端测试的关键场景Playwright的端到端测试我重点覆盖了三个场景完整的新宠物建档流程、健康记录添加与时间线展示、提醒的触发与处理。这三个场景覆盖了80%的用户操作路径。AI生成的Playwright测试初版选择器用了CSS类名但我们的样式用了CSS Modules类名是哈希化的测试很不稳定。后来统一改用>await page.getByTestId(pet-name-input).fill(小白); await page.getByTestId(species-select).selectOption(DOG); await page.getByTestId(submit-pet).click(); await expect(page.getByTestId(pet-card)).toContainText(小白);实操心得让AI生成测试时明确告诉它使用>FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app COPY --frombuilder /app/dist ./dist COPY --frombuilder /app/node_modules ./node_modules COPY package*.json ./ EXPOSE 3000 CMD [node, dist/main.js]镜像体积降到了280MB。数据库的持久化用volume挂载备份策略是每天凌晨用pg_dump导出到宿主机目录保留最近30天的备份。5.4 上线后的监控与告警上线后我加了一个简单的监控面板追踪几个关键指标提醒触发成功率、API平均响应时间、数据库连接池使用率。提醒触发成功率低于99%时会触发告警因为漏提醒对宠物健康管理来说是严重问题。监控数据用Prometheus采集Grafana展示。AI帮我生成了Prometheus的配置和Grafana的dashboard JSON我只需要调整阈值和告警规则。这套监控跑下来发现过一次数据库连接池泄漏的问题原因是某个查询没有正确释放连接修复后连接池使用率从95%降到了40%。6. 个人实操体会与后续扩展方向这套流程跑下来我最大的体会是AI辅助开发的核心不在于AI能写多少代码而在于你能否把业务逻辑拆解成AI能准确理解的粒度。我试过一次性给AI描述整个提醒引擎的需求它生成的代码结构混乱、边界处理缺失。但当我把它拆成“状态定义”“转换条件”“触发逻辑”“通知渠道”四个独立对话后每个部分的代码质量都明显提升。另一个体会是关于数据模型的重要性。在宠物生命周期管理这个场景里数据模型一旦确定后面的API、前端、测试都是水到渠成的事。我建议任何用AI从零搭建项目的朋友把至少40%的时间花在数据建模和审核上这个投入的回报率是最高的。后续这个项目还可以往几个方向扩展。一是加入多宠物家庭的支持目前的数据模型是Owner直接关联Pet可以加一个Household层来支持多人共同管理宠物。二是接入宠物智能硬件的数据比如智能项圈的活动量数据、智能猫砂盆的如厕记录这些数据可以自动填充成长追踪模块。三是做一个开放API让第三方宠物服务商医院、寄养机构可以授权访问部分数据减少用户重复录入的麻烦。最后分享一个小技巧跟AI协作时养成“先让它解释再让它实现”的习惯。比如生成提醒引擎之前我先让AI用自然语言描述它理解的提醒触发流程确认理解一致后再让它写代码。这个习惯帮我避免了很多“代码写得很漂亮但逻辑完全不对”的情况。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。