1. 为什么我选择用AI辅助从零搭建一个宠物生命周期管理App
宠物生命周期管理这个方向,我在两年前就想过要做。当时家里养了一只猫,从接回家到日常疫苗、驱虫、体检、换粮、体重记录,信息散落在备忘录、相册、微信聊天记录里,想查个上次驱虫时间得翻半天。市面上宠物类App不少,但大多数要么偏社交、要么偏电商,真正围绕“一只宠物从进家门到老去”这条完整时间线做管理的工具,体验都差点意思。
这次我决定不再拖了,直接动手。但和以往不同的是,我这次全程用Claude Code作为主力开发工具,从零开始搭建整个项目。为什么这么做?一方面是想验证一下当前AI辅助编程在真实项目里的边界到底在哪,另一方面也是想给自己一个约束——不追求大而全,先把核心链路跑通。
这篇文章我会把整个搭建过程拆开讲清楚:项目怎么设计、数据模型怎么定、Claude Code在每个环节怎么用、哪些地方它帮了大忙、哪些地方它反而添乱、我最后怎么绕过去的。如果你也在考虑用AI工具做一个完整的小项目,或者你本身就想做一个宠物管理类应用,这篇内容应该能让你少走不少弯路。
需要提前说明的是,我不是要教你“如何用一句话让AI生成一个App”,那种内容你看完还是不知道怎么做。我要讲的是真实的工程流程:需求拆解、技术选型、数据建模、页面实现、联调测试,以及AI在每一步里扮演的角色。
2. 项目整体设计与技术选型思路
2.1 核心需求拆解:一只宠物的完整时间线
我先把这个App要解决的问题定义清楚。宠物生命周期管理,核心是围绕“时间线”做记录和提醒。具体拆成四个模块:
- 基础档案:品种、生日、性别、绝育状态、芯片号、领养日期。这些是静态信息,但决定了后续所有提醒的基准。
- 健康记录:疫苗、驱虫、体检、就诊、用药、过敏史。每一条都带时间戳,且部分记录会触发下一次提醒。
- 日常记录:体重、喂食、排便、精神状态。这部分是高频但轻量的输入,重点是快速记录和趋势展示。
- 提醒系统:基于健康记录和用户设定的周期,自动计算下一次疫苗、驱虫、体检的时间,并推送通知。
我特意没有做社交、商城、宠物百科这些模块。原因很简单:一个独立开发者做全功能必死,先把单点做到极致。宠物生命周期管理的核心价值就是“不让主人忘记重要的事”,这个点打透了,用户自然留下来。
2.2 技术选型:为什么选这套组合
技术栈的选择我考虑了三个维度:开发速度、跨平台能力、长期维护成本。
| 维度 | 方案A | 方案B | 最终选择 |
|---|---|---|---|
| 前端框架 | Flutter | React Native | Flutter |
| 后端方案 | 自建服务 | 云开发 | 云开发 |
| 数据库 | MySQL | 文档型数据库 | 文档型数据库 |
| 状态管理 | Provider | Riverpod | Riverpod |
| 本地存储 | SQLite | 键值存储 | SQLite |
选Flutter的原因很直接:一套代码跑双端,UI一致性有保障,而且Claude Code对Dart和Flutter的代码生成质量相当高,我实测下来它写Flutter页面的准确率比写React Native要高出一截。后端用云开发是因为我不想在项目初期就折腾服务器运维,云开发的数据库和云函数能覆盖90%的需求,等用户量起来再迁移也不迟。
数据库选文档型是因为宠物记录的数据结构不固定——不同宠物的健康记录字段可能不一样,用关系型数据库反而要频繁改表。状态管理用Riverpod是因为它的编译期安全性和测试友好度比Provider好,虽然学习曲线稍陡,但项目一旦复杂起来,Riverpod的优势就体现出来了。
提示:如果你也是独立开发,技术选型的第一原则不是“哪个最强”,而是“哪个能让你最快跑通闭环”。我见过太多项目死在选型阶段,纠结三个月最后什么都没做出来。
2.3 用Claude Code做项目脚手架的正确姿势
很多人用Claude Code的方式是打开对话框,输入“帮我做一个宠物管理App”,然后期待它吐出一个完整项目。我试过,结果是一堆看起来能跑但结构混乱的代码,改起来比从头写还累。
我的做法是:把Claude Code当成一个“执行力极强但需要明确指令的初级工程师”。具体来说,我先把项目骨架手动搭好,包括目录结构、依赖配置、路由框架,然后让Claude Code在既定框架内填充具体实现。
项目目录结构是这样的:
lib/ core/ constants/ theme/ utils/ data/ models/ repositories/ datasources/ features/ pet_profile/ health_record/ daily_log/ reminder/ shared/ widgets/ providers/这个结构是我根据经验定的,Claude Code在这个框架下生成代码时,会自动把文件放到对应目录,不会乱。如果你不给它结构,它就会把所有东西堆在一个文件里,后期维护是灾难。
3. 核心数据模型与AI辅助建模实操
3.1 宠物档案模型的设计细节
数据模型是整个项目的地基,这部分我花了最多时间和Claude Code反复讨论。宠物档案模型看起来简单,但有几个字段的设计直接影响后续功能。
class PetProfile { final String id; final String name; final String species; // 猫、狗、兔等 final String breed; final DateTime birthday; final String gender; final bool isNeutered; final String? chipNumber; final DateTime adoptionDate; final String? avatarPath; final double? currentWeight; final DateTime createdAt; final DateTime updatedAt; }这里有几个设计决策值得展开说。birthday用DateTime而不是字符串,是因为后续计算年龄、疫苗周期都要基于日期运算。isNeutered用bool而不是枚举,是因为绝育状态只有两种,枚举反而增加复杂度。currentWeight冗余存储最新体重,是为了在列表页快速展示,不用每次去查体重记录表。
我把这个模型描述给Claude Code时,它一开始建议加一个age字段直接存年龄。我否掉了,因为年龄是随时间变化的派生数据,存下来就要定时更新,不如每次从birthday计算。这个判断需要人来把关,AI在数据建模上容易犯“冗余存储派生数据”的错误。
3.2 健康记录模型与提醒触发逻辑
健康记录模型是整个App最复杂的部分,因为它要同时承担“记录”和“触发提醒”两个职责。
class HealthRecord { final String id; final String petId; final HealthRecordType type; // vaccine, deworming, checkup, medication final DateTime recordDate; final DateTime? nextDueDate; final String? note; final String? vetName; final List<String> attachments; final Map<String, dynamic> extra; }nextDueDate是提醒系统的核心。比如打了一针疫苗,记录时填入下次接种日期,系统就会在那个日期前三天推送提醒。extra字段用Map存储类型特有的数据,比如疫苗记录存疫苗名称和批号,驱虫记录存药品名称和剂量。这样不用为每种记录类型建一张表,扩展性也好。
Claude Code在这里帮了我大忙。我让它根据这个模型生成提醒计算的逻辑,它给出了一个相当完整的实现:
DateTime? calculateNextDueDate(HealthRecord record) { if (record.nextDueDate != null) return record.nextDueDate; switch (record.type) { case HealthRecordType.vaccine: return record.recordDate.add(const Duration(days: 365)); case HealthRecordType.deworming: return record.recordDate.add(const Duration(days: 90)); case HealthRecordType.checkup: return record.recordDate.add(const Duration(days: 180)); default: return null; } }这段逻辑我检查了一遍,默认周期设置合理,但有个问题:不同疫苗的接种周期不一样,狂犬疫苗是一年,猫三联可能是三年。所以我在extra里加了cycleDays字段,优先读取用户填写的周期,没有才用默认值。这个修正是我基于领域知识做的,AI不知道这些细节。
3.3 用AI生成模型代码的注意事项
用Claude Code生成数据模型代码时,我总结了几个经验:
- 先定字段再让AI写代码:不要让它自由发挥字段设计,你先把字段列表列出来,让它只负责写序列化和反序列化逻辑。
- 要求生成fromJson和toJson:文档型数据库存的是JSON,这两个方法必须要有,而且要处理null值。
- 让它生成copyWith方法:Flutter里更新模型对象时,copyWith比重新构造方便得多。
- 检查日期字段的序列化:AI经常忘记把DateTime转成ISO字符串,直接存DateTime对象会导致数据库报错。
注意:AI生成的模型代码一定要手动检查字段类型和序列化逻辑,这部分出错后期排查成本极高。我踩过一次坑,体重字段被生成为int,结果用户输入62.5kg直接崩溃。
4. 核心功能模块的实现过程
4.1 宠物档案页:从表单到图片上传
宠物档案页是用户第一次打开App就要用的页面,体验好坏直接决定留存。我让Claude Code先生成一个基础表单,然后在此基础上改。
基础表单生成得很快,但有几个问题需要手动调整。第一,品种选择用下拉框还是输入框?AI默认给了输入框,但宠物品种是有限集合,输入框会导致数据混乱。我改成了“常用品种快捷选择+自定义输入”的组合。第二,生日选择器AI用了普通日期选择,但宠物生日可能不精确,我加了“大概年龄”的选项,允许用户只填年份。
图片上传部分Claude Code给的方案是直接用image_picker插件,但没处理图片压缩。宠物头像原图可能好几MB,直接上传浪费流量也拖慢加载。我加了压缩逻辑:
Future<File> compressImage(File file) async { final result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, file.absolute.path + '_compressed.jpg', quality: 70, minWidth: 800, minHeight: 800, ); return File(result!.path); }质量70、最小宽高800,这个参数是我实测下来的平衡点。再低头像会糊,再高文件还是太大。
4.2 健康记录录入:快速记录的设计哲学
健康记录录入的频率不高,但每次录入的信息量大。我的设计原则是:常用记录三步内完成,复杂记录不超过一屏。
Claude Code帮我生成了记录类型选择器、日期选择器、备注输入框这些基础组件。但它在布局上给了一个很长的表单,所有字段平铺。我改成了分步式:第一步选类型,第二步填核心信息(日期+关键字段),第三步选填备注和附件。
这里有个细节值得说:疫苗记录的“下次接种日期”我做了自动计算。用户选完疫苗类型和接种日期后,系统根据预设周期自动填入下次日期,用户可以直接确认或手动修改。这个逻辑Claude Code写得很顺,因为它就是简单的日期加法。
void onVaccineTypeChanged(String vaccineType, DateTime recordDate) { final cycleDays = vaccineCycles[vaccineType] ?? 365; final nextDate = recordDate.add(Duration(days: cycleDays)); setState(() { nextDueDate = nextDate; }); }vaccineCycles是一个预设的Map,狂犬365天、猫三联1095天、犬瘟热365天等等。这些数据是我查资料整理的,AI不知道具体数值,但它能帮你把逻辑框架搭好。
4.3 提醒系统的实现与本地通知配置
提醒系统是宠物生命周期管理的灵魂。我用的方案是本地通知+服务端兜底。本地通知负责准时触发,服务端负责在用户换设备后同步提醒。
本地通知用flutter_local_notifications插件,Claude Code生成的初始化代码基本可用,但有几个坑我踩过:
- 时区问题:默认用的是UTC,必须显式设置本地时区,否则提醒时间会差8小时。
- 权限申请时机:不要在App启动就申请通知权限,用户还没理解App价值就弹权限框,拒绝率极高。我改成用户创建第一条提醒时才申请。
- 通知ID冲突:每条提醒要用唯一的ID,我用的是记录ID的hashCode,但要注意hashCode可能为负,需要取绝对值。
Future<void> scheduleReminder(HealthRecord record) async { if (record.nextDueDate == null) return; final scheduledDate = record.nextDueDate!.subtract(const Duration(days: 3)); if (scheduledDate.isBefore(DateTime.now())) return; await flutterLocalNotificationsPlugin.zonedSchedule( record.id.hashCode.abs(), '宠物健康提醒', '${record.petName}的${record.type.label}即将到期', tz.TZDateTime.from(scheduledDate, tz.local), const NotificationDetails( android: AndroidNotificationDetails( 'pet_health_channel', '宠物健康提醒', importance: Importance.high, priority: Priority.high, ), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, ); }提前三天提醒这个设计也是我基于经验定的。太早提醒用户会忘,太晚提醒来不及预约。三天是个比较舒服的窗口。
4.4 体重趋势图:数据可视化的轻量实现
体重记录是高频操作,趋势图是用户最常看的内容。我用fl_chart插件做折线图,Claude Code生成的图表代码结构没问题,但默认样式太丑,坐标轴、网格线、数据点都需要调。
调样式这部分我花了大概一个小时,主要是让图表看起来干净。我的做法是:去掉网格线、只保留横向参考线、数据点用小圆点、折线用平滑曲线。颜色用主题色,和App整体风格统一。
LineChartData( gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 1, getDrawingHorizontalLine: (value) => FlLine( color: Colors.grey.withOpacity(0.2), strokeWidth: 1, ), ), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 40), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 7, getTitlesWidget: (value, meta) => Text( DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(value.toInt())), style: const TextStyle(fontSize: 10), ), ), ), ), lineBarsData: [ LineChartBarData( spots: weightRecords.map((r) => FlSpot( r.date.millisecondsSinceEpoch.toDouble(), r.weight, )).toList(), isCurved: true, curveSmoothness: 0.3, dotData: FlDotData(show: true, dotSize: 4), belowBarData: BarAreaData(show: true, color: themeColor.withOpacity(0.1)), ), ], )curveSmoothness设0.3是我试出来的,太高曲线会失真,太低看起来像折线。belowBarData加一层淡色填充,视觉上更有层次。
5. 用Claude Code开发时踩过的坑与应对策略
5.1 AI代码的“看起来对”陷阱
Claude Code生成的代码有个特点:语法正确、逻辑通顺、看起来完全能用,但跑起来就是有问题。我遇到最典型的一次是状态管理。
它给我生成了一段Riverpod的Provider代码,用StateNotifierProvider管理宠物列表。代码本身没问题,但它没有处理异步加载状态。用户打开列表页时,数据还在加载,页面直接显示空列表,然后数据到了才刷新。体验上就是闪一下。
我改成用AsyncNotifierProvider,配合when方法处理loading、error、data三种状态。这个修正需要理解Riverpod的状态模型,AI知道怎么写代码,但它不理解“用户体验上不能闪”这个隐含需求。
提示:AI生成的代码一定要在真机上跑一遍,模拟器有时候看不出体验问题。我养成了每完成一个模块就真机测试的习惯,虽然麻烦,但能提前发现大量问题。
5.2 依赖版本冲突的排查方法
Flutter项目依赖多,版本冲突是家常便饭。Claude Code生成pubspec.yaml时,经常给的是它训练数据里的版本号,和你项目里其他依赖不兼容。
我的应对方法是:不让AI写版本号,只让它写包名,版本号我自己用flutter pub add命令加,让pub自动解析兼容版本。如果已经冲突了,用flutter pub deps看依赖树,找到冲突的包,手动降级或升级。
有一次image_picker和另一个插件冲突,我排查了半小时。最后发现是image_picker的某个版本依赖了旧版的http包,和项目里的http版本不兼容。解决办法是在pubspec.yaml里用dependency_overrides强制指定http版本。这个技巧AI不会主动告诉你,得自己踩过才知道。
5.3 数据库查询性能的优化实录
项目初期数据量小,查询怎么写都快。但我模拟了1000条健康记录后,列表页明显卡顿。排查发现是每次查询都全量拉取,然后在内存里过滤。
优化方案是:查询时加条件、加索引、分页加载。文档型数据库的查询条件要提前设计好,比如按petId查询、按recordDate排序,这些字段都要建索引。
Future<List<HealthRecord>> getRecordsByPet( String petId, { int limit = 20, DateTime? startAfter, }) async { var query = db.collection('health_records') .where('petId', isEqualTo: petId) .orderBy('recordDate', descending: true) .limit(limit); if (startAfter != null) { query = query.startAfter([startAfter]); } final snapshot = await query.get(); return snapshot.docs.map((doc) => HealthRecord.fromJson(doc.data())).toList(); }分页加载配合滚动监听,用户滑到底部自动加载下一页。这个改动之后,1000条记录的列表页滑动流畅,内存占用也降下来了。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 通知不触发 | 时区未设置 | 打印tz.local | 初始化时设置本地时区 |
| 图片上传失败 | 文件过大 | 打印文件大小 | 加压缩逻辑 |
| 列表页闪空 | 未处理loading状态 | 检查Provider类型 | 改用AsyncNotifierProvider |
| 依赖冲突 | 版本不兼容 | flutter pub deps | dependency_overrides |
| 查询卡顿 | 未建索引 | 查看查询计划 | 给查询字段建索引 |
| 日期计算错误 | 时区偏移 | 打印DateTime | 统一用本地时间 |
这张表是我在实际开发中遇到问题后整理的,每个问题都真实发生过。建议你开发时也维护一张自己的问题表,下次遇到类似问题直接查表,效率高很多。
6. 项目收尾与后续扩展方向
6.1 测试与上线前的检查清单
项目功能跑通后,我没有急着上线,而是花了两天做测试。测试清单包括:
- 新用户从注册到创建第一只宠物的完整流程
- 添加健康记录后提醒是否正确触发
- 体重记录连续添加后趋势图是否正常
- 多宠物切换是否流畅
- 断网状态下App是否崩溃
- 后台运行超过24小时后通知是否正常
其中断网测试发现了三个问题:图片上传没做失败重试、数据库查询没做超时处理、提醒列表没做本地缓存。这三个问题在联网状态下完全看不出来,但用户在地铁、电梯里就会遇到。
修复方案分别是:上传加指数退避重试、查询加5秒超时、提醒列表本地缓存一份。这些改动不大,但体验提升明显。
6.2 后续可以扩展的功能
当前版本是MVP,核心链路跑通了,但还有很多可以加的东西。我列几个优先级比较高的:
- 多宠物家庭共享:一个家庭多个成员共同管理宠物,需要做权限和数据同步。
- 健康报告导出:把宠物的健康记录导出成PDF,看病时直接给医生看。
- 智能提醒优化:根据宠物的年龄、品种、病史,动态调整提醒周期。
- 宠物成长相册:按时间线展示宠物照片,和健康记录关联。
这些功能我不会一次性做完,而是根据用户反馈逐个迭代。独立开发最忌讳的就是闭门造车做一堆没人用的功能。
6.3 关于AI辅助开发的个人体会
用Claude Code做完这个项目,我最大的感受是:AI确实能极大提升开发效率,但它替代不了人的判断。它能在你明确需求后快速生成代码,能在你遇到报错时给出排查方向,能在你写文档时帮你整理结构。但它不知道“用户打开App时不能闪”,不知道“疫苗周期因种类而异”,不知道“断网时不能崩溃”。
这些判断来自经验,来自对用户的理解,来自对领域的熟悉。AI是放大器,它放大你的能力,也放大你的盲区。你越清楚自己要做什么,它越好用;你越模糊,它越容易带你跑偏。
所以我的建议是:用AI做项目,先把需求想清楚,把架构定下来,把关键决策做了,然后让AI去执行。执行过程中保持怀疑,每段代码都跑一遍,每个体验都自己试一遍。这样下来,AI就是你的超级助手;反过来,你就是AI的调试员,累且低效。
这个宠物生命周期管理App我还在持续迭代,后面有新的经验和踩坑,我会继续整理出来。如果你也在做类似的项目,或者对某个环节有疑问,欢迎交流。