宠物生命周期管理这类项目,我其实很早就想做了,但一直卡在“想法很完整、落地没时间”的状态。直到我真正把 Claude Code 用进日常开发流程里,才意识到过去那种“先花两周搭框架、再花两个月填功能”的节奏可以压缩到以天为单位。这篇文章就围绕我最近用 Claude Code 从零搭建一个宠物生命周期管理 App 的完整过程展开,包括项目定位、技术选型、核心模块实现、联调踩坑和部署迭代。内容偏实操,适合那些手里有项目想法、但不想把时间耗在重复劳动上的开发者参考。
1. 项目为什么选这个方向
1.1 宠物生命周期管理App解决什么问题
养宠物的人都有一个共同痛点:信息太散。疫苗本在抽屉里、驱虫记录在聊天记录里、体检报告在手机相册里、喂食和体重变化全靠脑子记。等真要给宠物看病、办手续或者日常护理的时候,翻遍所有渠道也拼不出完整的记录。
宠物生命周期管理 App 的核心价值,就是把宠物从出生到老年的所有关键信息统一收纳,并且围绕时间线做主动提醒。它不是简单做一个通讯录式的档案页,而是要解决三个问题:记录难、查找难、遗忘风险高。站在产品角度看,这类工具最适合切入的形态是轻量级记录 + 智能提醒 + 时间线回溯,而不是试图做社区或电商那样的重模式。
1.2 功能范围与MVP边界
做 MVP 的时候我反复提醒自己:先砍掉所有“以后再做”的功能。最初列出的功能清单里有宠物社交、附近医院推荐、在线问诊、用品商城,但后来全部砍掉了。真正留下来的核心功能只有四个:宠物档案、生命周期事件记录、提醒设置、健康时间线。
宠物档案负责基础信息,比如名字、品种、性别、生日、绝育状态、体重曲线。生命周期事件记录覆盖疫苗、驱虫、体检、洗澡、生病、用药、生日纪念等常见场景。提醒设置则围绕这些事件做日期推算和提前通知。健康时间线把零散记录串成一条可回溯的时间轴,方便随时查看“上次驱虫是什么时候”“疫苗到期了没有”。
这个 MVP 的好处是边界清晰,每个模块都能独立交付和验证。坏处是容易让开发者觉得“这不就是个日历吗”,但实际做起来就会发现,事件类型、提醒规则、日期推算是三类完全不同的问题,复杂度没有想象中低。
1.3 技术栈选型思路
技术选型我完全从 Claude Code 的能力半径和团队维护成本两个维度考虑。前端选择了 React Native 配合 Expo 框架,理由很直接:宠物生命周期管理场景天然需要相机拍照、本地通知、日历权限这些原生能力,而 Expo 封装了大部分 API,JS/TS 生态下 AI 生成代码的质量也明显比 Dart 高。后端选择了 Node.js 配 Express,数据库用 PostgreSQL,主要是考虑关系型数据在档案、事件、提醒这类结构化业务上最稳妥。
我见过很多人做小项目喜欢上微服务或 serverless,但对于这种体量的应用,单机部署 + 关系型数据库就是最优解。Claude Code 在 TypeScript 全栈项目里补全、重构、排查错误的连贯性最好,如果强行用多语言多框架,AI 的上下文理解就会断。
2. Claude Code加持下的需求分析与工程初始化
2.1 用对话方式梳理需求
很多人以为 Claude Code 就是个写代码工具,其实它最被低估的是需求梳理能力。我在项目开始前没有写 PRD,而是直接打开终端,用一段长对话把想法全部倒给 Claude Code。我描述的是场景:
“我想要一个 App,用户可以添加多个宠物,每个宠物有档案,包括生日、品种、体重、绝育状态。然后记录每个宠物的疫苗、驱虫、体检事件,不同类型的提醒规则不一样。比如疫苗要按年提醒,驱虫按月,体检按半年。这些事件要有状态,比如已完成、已逾期、待处理。最后按时间线展示。”
Claude Code 能把这些口语化描述转换成三类资产:功能列表、数据模型草案、接口边界。中间它会追问一些细节,比如“疫苗提醒是固定日期还是可配置提前天数”“删除宠物时历史事件是否级联删除”,这些问题恰好都是需求评审时必问的点,AI 帮你提前问出来了。
经过三轮对话,我得到的已经是一份明确到可以直接开工的任务清单。这个过程让我最直观的感受是:以前需求梳理靠开会,现在靠对话,而且对话成本低到可以反复推演。
2.2 项目脚手架搭建
搭建脚手架这一步,传统做法是手动查文档拼命令,用 Claude Code 则可以直接给出带上下文的指令序列。我让它同时完成四件事:初始化 Expo 项目、配置 TypeScript 严格模式、搭建 Express 后端骨架、初始化 PostgreSQL 迁移目录。
举几个关键操作。
Expo 端初始化用的命令是:
npx create-expo-app pet-life-manager --template blank-typescript创建后我让它做的事情包括:安装 React Navigation、接入本地通知库、配置全局主题色和字体、建立统一的 API 请求封装。如果手动做,这些至少需要半天时间熟悉各版本兼容关系,而 Claude Code 的厉害之处在于它知道这些库目前的版本和 Expo SDK 的兼容矩阵,生成的依赖版本直接可用,不需要我再手动调。
后端骨架我让它按模块化结构组织:
src/ routes/ controllers/ services/ models/ middlewares/ utils/每个模块先给一个基础的 CRUD 实现,数据库访问层用 Drizzle ORM。Drizzle 是 TypeScript 生态里写起来最像 SQL 的 ORM,AI 生成查询代码的成功率远高于其他 ORM 比如 Prisma。很多 AI 生成的 Prisma 查询在复杂条件时会冒出莫名其妙的类型问题,而 Drizzle 的写法更贴近 SQL,模型理解成本低。
提示:让 Claude Code 生成脚手架时,一定要先让它列出计划,确认目录结构后再动手。直接描述“帮我搭个项目”会让它自由发挥出奇怪的结构,先让它给方案,你审阅后再执行,效果好很多。
2.3 数据模型设计
数据模型是整个项目最需要考虑清楚的部分,因为生命周期事件类型多、字段差异大。我最初的设想是为每个事件类型建单独的表,比如疫苗表、驱虫表、体检表,但很快被自己否定——这种设计会让前端的查询逻辑碎片化,并且每次加新事件类型都要改表结构。
最终采用了“单表 + 类型区分”的方案。核心表 structure 如下。
宠物表:
export const pets = pgTable('pets', { id: uuid('id').defaultRandom().primaryKey(), name: text('name').notNull(), species: text('species').notNull(), // dog / cat / other breed: text('breed'), birthday: timestamp('birthday'), weightKg: numeric('weight_kg', { precision: 5, scale: 2 }), isNeutered: boolean('is_neutered').default(false), avatarUrl: text('avatar_url'), createdAt: timestamp('created_at').defaultNow(), });事件表:
export const events = pgTable('events', { id: uuid('id').defaultRandom().primaryKey(), petId: uuid('pet_id').notNull().references(() => pets.id, { onDelete: 'cascade' }), type: text('type').notNull(), // vaccine / deworm / checkup / medication / bath / birthday title: text('title').notNull(), happenedAt: timestamp('happened_at').notNull(), nextDueDate: timestamp('next_due_date'), status: text('status').default('pending'), // done / pending / overdue notes: text('notes'), createdAt: timestamp('created_at').defaultNow(), });这个设计的好处是无论多少种事件,查询路径都是统一的,前端拿到数据后只需要按 type 字段做不同展示即可。坏处是“不同事件类型拥有不同扩展字段”的能力需要靠 JSON 字段或者关联表补充。
对于 MVP 阶段的张力,我选择增加一个 metadata 字段存 JSON,不单独拆表。这个决定源于一个经验:不要在产品验证前做过度的数据建模,单表加 JSON 的灵活度足够支撑早期业务演变。
注意:疫苗、驱虫、体检这些事件都有“下次到期日”的概念,但计算规则不同。千万别把这些规则写死在前端,一定要放在后端一个统一的日期推算服务里,否则客户端版本差异会导致提醒不一致。
3. 核心功能模块的实现拆解
3.1 宠物档案模块
宠物档案模块是入口,UI 上需要支持多宠物切换,所以第一个要解决的是“当前宠物上下文”的状态管理。我用 React Context 做了一个当前宠物 ID 的全局状态,所有页面都从这个 Context 读取活动宠物,而不是各自从列表里找。
档案表单里涉及最多的是日期处理和图片上传。生日用日期选择器,体重保留两位小数,图片上传走了 Expo ImagePicker 拍照或相册选择。为了压缩上传体积,我用 Claude Code 生成了一个图片压缩工具函数,限制最长边 1080 像素,质量压缩到 0.7,实测单张图片从 4MB 左右降到 300KB 以内。这个优化不做的话,列表页加载会明显卡顿。
服务端接口设计上,我没有为宠物档案单独做“更新某字段”的细分接口,而是统一用 PUT 提交整个档案对象。这么做在传统 API 设计里容易被吐槽,但在移动端场景里却最务实:表单本来就是整体编辑,按字段更新不仅增加接口数量,还要处理字段级校验,收益很低。
3.2 生命周期事件与提醒
事件模块是业务最重的部分,因为事件类型决定行为差异。疫苗是“接种后推算下一次时间”,驱虫是“按周期生成提醒计划”,体检是“按固定间隔提醒”,用药是“按天数生成疗程”。
我把这些统一抽象成一个服务函数:
function calculateNextDueDate(type: EventType, happenedAt: Date, customIntervalDays?: number): Date | null { const rules: Record<EventType, number | null> = { vaccine: 365, deworm: 90, checkup: 180, medication: customIntervalDays ?? null, bath: null, birthday: 365, }; const days = rules[type]; if (!days) return null; const next = new Date(happenedAt); next.setDate(next.getDate() + days); return next; }这套规则支撑了核心能力:每次记录新事件时,后端自动计算 nextDueDate,前端通过读取这个字段来决定是否展示“即将到期”“已逾期”标签。
提醒触发逻辑选择了本地通知而非推送服务。原因很简单:MVP 阶段不值得引入推送服务成本,本地通知在 Expo 里配置一次就能用,离线也能触发。真正需要远程推送是后续做到多设备同步时的事。
本地通知的一个坑是权限申请必须发生在用户交互之后,我第一次测试时把请求放到了应用启动阶段,结果 iOS 上一直拿不到权限。后来调整为首次进入“提醒设置”页时触发权限弹窗,体验正常了。
3.3 健康记录时间线
时间线页面的核心不是列表渲染,而是数据聚合。我需要把同一个宠物的档案变更、体重记录、疫苗事件、驱虫事件全部揉成一个按时间倒序的信息流。
实现上做了一个服务端聚合查询,把事件表里所有类型的记录按 happenedAt 排序,前端拿到后按照日期分组渲染。还有一个隐藏逻辑:体质曲线。体重记录被拆成了单独一张表,但展示时也合并进时间线。这样页面看起来是一条连续的健康历史。
这个页面让我意识到一个 UI 细节问题:时间线上不同类型的记录需要不同的图标和颜色,但如果完全靠前端硬编码映射,每加一种类型就要发一版。我把图标和颜色的映射表放在了服务端返回的元数据里,客户端做通用渲染。这种“展示元数据下沉到服务端”的思路,对 AI 协作项目非常友好,因为改展示样式不需要重新编译 App。
4. 前后端联调与AI辅助排查
4.1 接口约定与联调流程
传统联调是两个角色各自开发后对接,Claude Code 模式下则完全不同。因为前后端代码都是 AI 配合生成的,我让同一个对话上下文同时维护 API 路由和前端请求封装,这样接口天然对齐,不会出现“前端用了 /events,后端只实现了 /records”这类低级错位。
但联调阶段仍然有真实问题会出现,主要集中在字段格式、日期时区、空值处理三方面。日期时区是最坑的:PostgreSQL 默认存储的是带时区的 timestamp,而前端 JavaScript 的 Date 对象处理 UTC 时会差 8 小时。最终我在后端统一返回 ISO 字符串,前端解析时全部转本地时间,并在所有日期展示组件里加了一个统一格式化函数,避免到处 new Date 导致时区错乱。
空值处理是另一个容易翻车的地方。宠物体重允许为空,疫苗记录允许没有备注,这些空值从数据库到 JSON 再到前端组件,任何一层处理不当都会白屏。我的处理规则是:后端接口确保所有字段要么有值要么为 null,前端组件一律先判空再渲染。
4.2 典型Bug与排查实录
这里记录几个真实踩过的坑,每个都花了时间,希望读者能避开。
第一个是重名路由导致的幽灵更新。我在 Express 中定义了PUT /api/pets/:id,同时又定义了PUT /api/pets/me,结果后一个路由永远匹配不到,请求被前一个路由接住后把“me”当成宠物 ID 解析,产生了一个看起来莫名其妙的 500 错误。排查方向一直放在数据层,最后用 curl 逐个路由测试才发现是路由顺序问题。解决方案是把具体路径放在参数路径之前声明。
第二个是本地通知的 ID 冲突。Expo 本地通知的 identifier 必须是字符串,我用事件 ID 当 identifier,但同一事件可以重复设置提醒,导致新提醒覆盖旧提醒。后来改成“事件 ID + 提醒类型 + 时间戳”的组合,确保每次提醒唯一。
第三个是体重数字类型问题。数据库里 weightKg 用的是 numeric 类型,Drizzle 查询返回的是字符串而不是数字。前端做体重曲线时用减法运算,直接出现 NaN。这个问题让我花了些力气才定位到,因为类型检查阶段完全没报错。之后我在服务端做了一层数字转换,保证所有输出字段保持 JSON 原生类型。
经验:Claude Code 生成的代码整体风格统一、命名清晰,但遇到运行时类型问题,它的静态视角很难察觉,必须靠实际运行和打印日志定位。我建议在自己代码里多加 console.log 或使用调试器断点,把问题现场甩给 AI 一起分析,定位速度比纯人工快得多。
5. 部署、测试与后续迭代
5.1 部署方案
后端部署我选择了一台轻量云服务器,装 Docker 和 Nginx,数据库直接用容器内的 PostgreSQL。这么做不是为了花哨,而是为了能快速复现环境。部署本质上是三步:
对前端来说,Expo 支持直接生成原生项目后通过应用商店发布,但 MVP 阶段我更推荐使用 Expo 的预览分发方式,通过扫码直接安装测试包。迭代验证阶段,这种方式比走商店审核舒服太多了。
推送提醒在本地通知方案下不需要服务器参与,但如果后续要做多设备同步或云备份,需要评估推送到终端的成本。我在部署时预留了一个简单的计划任务,每天凌晨扫描所有需要提醒的事件,生成一个汇总表,这个表未来可以直接挂在通知服务上。
5.2 用Claude Code做测试和迭代
写测试是 Claude Code 的优势区。我让它基于现有的服务逻辑生成了接口层测试用例,覆盖每个模块的 CRUD 和边界条件,比如删除宠物后事件级联删除是否生效、未来日期提醒是否正确计算、空体重时曲线接口是否返回正确结果。
测试框架用了 Vitest 搭配 supertest,命令行跑一次大概十几秒。这种方式在传统开发模式下很难坚持,因为写测试太耗时间了。但在 AI 协作下,测试代码生成成本非常低,回归成本也随之降下来了。
迭代方面,我践行了一个“小步快跑”的规则:每完成一个小需求,立刻让 Claude Code 对照之前的代码差异做增量修改,而不是重新生成整个文件。这样能最大程度保留已有业务逻辑稳定,不让 AI 频繁重构代码导致回归。
5.3 个人体会
这个项目做下来,我最大的体会是:Claude Code 不是一个“替你写代码”的工具,而是一个“拉高你的工程实施下限”的协作体。它适合有想法但受限于时间的团队,也适合脑子里已经有了架构图、但不愿意在琐碎实现上耗费精力的开发者。它会犯错,但大部分错误集中在边界行为和类型细节上,靠实际调试很快就能纠正。
如果让我给后来者建议,核心有两条。一是不要一上来就让 AI 包办一切,先自己把数据模型和模块边界想清楚,否则 AI 生成的代码可能会方向性跑偏。二是要重视对话上下文的维护,同一个功能模块的开发和问题排查尽量保持在同一个会话里完成,让 AI 对代码的局部理解和全局认知始终保持一致。
最后分享一个非常实用的技巧:在项目根目录创建一个specs.md文件,把核心模块、数据字段、接口约定在一开始就写进去。然后每次让 Claude Code 改动代码前,先让它读一遍 specs.md 再动手。这个习惯能避免很多因为提醒上下文超长导致的“AI 开始自由发挥”的问题,我是踩过几次坑之后才总结出来的。
宠物生命周期管理这个方向,做到现在我已经把整个信息闭环跑通了。下一步我计划把提醒能力从本地通知迁移到远程推送支持多设备,再把体重曲线和疫苗接种记录做成可视化的趋势图表。核心架子已经稳定,后续迭代基本就是围绕数据和提醒的深化了。