资讯动态

AI辅助开发实战:跨平台宠物生命周期管理应用从零搭建

发布时间:2026/10/13 15:36:52 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么选“宠物生命周期管理”这个场景宠物生命周期管理这个选题乍看像是个小众的垂直领域但真正动手做起来会发现它几乎涵盖了移动应用开发中所有典型的业务形态档案管理、时间轴记录、提醒调度、数据统计、多媒体存储、多端同步。换句话说拿它当练手项目比做一个纯增删改查的待办清单有价值得多。我最初起这个项目的动机很朴素——身边养宠的朋友几乎都在用纸笔或者备忘录记录疫苗时间、驱虫周期、体重变化信息散落在各处换手机就丢。市面上虽然有一些宠物类App但要么功能太重、要么广告太多、要么数据锁在云端不放心。所以我想做一个结构清晰、逻辑自洽、能完整跑通“从建档到日常记录再到数据回顾”全链路的应用。这个项目的核心用户画像很明确养了一只或几只宠物的普通用户他们需要的是低负担记录和及时提醒而不是复杂的社交或电商功能。所以整个设计围绕三个关键词展开档案、时间轴、提醒。1.2 用AI辅助开发新项目的整体思路这次我刻意尝试了一种新的开发方式把AI编码助手当作“结对程序员”来用而不是简单的代码补全工具。具体来说我把整个开发过程拆成几个阶段每个阶段先和AI对齐目标和约束再让它生成骨架代码然后我逐段审查、修改、补充业务逻辑。这样做的好处是AI负责处理那些重复性高、模式固定的代码比如数据模型定义、CRUD接口、表单校验我则把精力集中在业务规则设计、状态流转、边界条件处理上。实测下来一个原本需要两周左右的中等规模项目压缩到了五天左右完成主体功能。当然这里有个前提你必须对项目整体架构有清晰的规划否则AI生成的代码会像散落的拼图拼不到一起。所以第一步不是急着让AI写代码而是先把数据模型和页面流转图想清楚。1.3 技术选型与架构分层技术栈方面我选择了跨平台方案一套代码同时跑在移动端和桌面端降低维护成本。具体来说前端框架Flutter理由是UI一致性好热重载开发效率高且对本地数据库支持成熟。本地存储SQLite通过drift库做ORM映射既能享受类型安全又保留了手写SQL的灵活性。状态管理Riverpod相比Provider更灵活依赖注入清晰测试友好。提醒调度本地通知插件配合系统日历不依赖服务端推送。数据导出支持JSON和CSV两种格式方便用户迁移。架构上采用经典的三层结构数据层Repository、业务层Service、表现层UI。这样分层的好处是AI在生成代码时每一层的职责边界很清晰不会出现业务逻辑写在Widget里的混乱情况。提示跨平台方案虽然省事但涉及本地通知、文件存储这类平台相关功能时仍然需要写平台通道代码。建议提前把这类插件选型确认好避免中途换库。2. 核心数据模型与业务规则拆解2.1 宠物档案表的设计考量宠物档案是整个系统的根实体所有记录都挂在它下面。设计这张表时我重点考虑了三点字段的可扩展性、软删除机制、以及多宠物场景下的排序需求。核心字段包括宠物ID主键、名称、物种猫/狗/其他、品种、性别、生日或估算年龄、绝育状态、头像路径、备注、创建时间、更新时间、是否归档。这里有个细节值得展开生日字段我用了可空设计因为很多领养来的宠物无法确定准确出生日期。当生日为空时系统会根据用户填写的“估算年龄”反推一个近似生日并在界面上标注“估算”。这个逻辑看似简单但如果不提前设计后期做年龄统计时就会遇到大量空值异常。另一个细节是软删除。宠物档案一旦被删除关联的所有记录疫苗、体重、就诊都会变成孤儿数据。所以我用了is_archived字段代替物理删除归档后的宠物不出现在主列表但历史数据仍然保留用户可以在设置里查看或恢复。CREATE TABLE pets ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, species TEXT NOT NULL, breed TEXT, gender TEXT, birthday TEXT, estimated_age_months INTEGER, is_neutered INTEGER DEFAULT 0, avatar_path TEXT, note TEXT, is_archived INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL );2.2 时间轴事件表的统一抽象宠物生命周期里会发生很多类型的事件疫苗、驱虫、体检、就诊、体重记录、洗澡、剪指甲、用药等等。如果每种事件建一张表后期查询和展示会非常痛苦。所以我做了一层统一抽象所有事件都存到一张events表里用event_type区分类型用extra_data字段JSON格式存储类型特有的属性。这样做的好处是时间轴查询只需要一条SQL就能按时间倒序拉出所有记录不需要union多张表。代价是类型特有的字段无法做数据库层面的约束需要在业务层做校验。权衡下来对于这种记录类应用查询性能和数据聚合的便利性比严格的字段约束更重要。CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, pet_id INTEGER NOT NULL, event_type TEXT NOT NULL, title TEXT NOT NULL, occurred_at TEXT NOT NULL, next_due_at TEXT, extra_data TEXT, note TEXT, created_at TEXT NOT NULL, FOREIGN KEY (pet_id) REFERENCES pets(id) );next_due_at这个字段是提醒功能的核心。比如打了一针疫苗下次接种时间就是一年后这个日期存在next_due_at里提醒调度器会扫描所有next_due_at临近的事件生成通知。2.3 提醒规则的触发逻辑提醒功能看起来简单实际上坑很多。我最初的想法是用户设置一个提醒时间到点弹通知。但实际使用中用户需要的是“提前几天提醒”而且不同类型的提醒提前量不一样——疫苗可能提前一周驱虫可能提前一天。所以我把提醒规则设计成独立的一张表和事件解耦CREATE TABLE reminders ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_id INTEGER, pet_id INTEGER NOT NULL, remind_at TEXT NOT NULL, advance_days INTEGER DEFAULT 1, repeat_rule TEXT, is_enabled INTEGER DEFAULT 1, is_dismissed INTEGER DEFAULT 0, FOREIGN KEY (event_id) REFERENCES events(id), FOREIGN KEY (pet_id) REFERENCES pets(id) );repeat_rule字段用简单的字符串描述重复规则比如monthly:3表示每三个月重复一次。这样设计是为了避免引入复杂的RRULE标准对于宠物场景来说固定周期的重复已经够用了。触发逻辑上我用了一个后台任务每天凌晨扫描未来七天内到期的提醒生成具体的通知时间点写入系统通知队列。这样做比实时计算更稳定也避免了应用被杀后台后提醒丢失的问题。注意本地通知在不同平台上的行为差异很大。有的平台限制后台任务频率有的平台对通知权限管理严格。建议在项目早期就在真机上验证通知链路不要等到最后才测。3. 从零搭建的完整实操流程3.1 项目初始化与依赖配置第一步是创建项目骨架。我用的是标准的跨平台项目初始化命令然后手动调整目录结构。这里有个经验不要完全依赖脚手架生成的目录而是根据业务分层重新组织。我的目录结构大致如下lib/ data/ models/ repositories/ database/ domain/ services/ entities/ presentation/ pages/ widgets/ providers/ core/ utils/ constants/ theme/依赖配置方面除了前面提到的drift、riverpod、本地通知插件还引入了几个辅助库path_provider用于获取文件路径image_picker用于选择宠物头像intl用于日期格式化fl_chart用于体重变化曲线。这里有个小技巧在pubspec.yaml里给每个依赖加上注释说明用途和版本锁定原因。AI在后续生成代码时会参考这些注释减少引入不必要依赖的概率。3.2 数据库层与迁移策略数据库初始化我用了drift的代码生成方案。先定义表结构然后运行代码生成命令自动产出DAO和数据类型。这个过程AI帮了大忙——我只需要描述表结构它就能生成对应的Dart类。但这里有个关键点数据库迁移必须手动设计。drift提供了迁移API但迁移脚本的逻辑需要你自己写。我的策略是每次修改表结构都新增一个迁移步骤而不是直接改原始建表语句。这样老用户升级时数据不会丢。override MigrationStrategy get migration { return MigrationStrategy( onCreate: (m) async { await m.createAll(); }, onUpgrade: (m, from, to) async { if (from 2) { await m.addColumn(pets, pets.isArchived); } if (from 3) { await m.createTable(reminders); } }, ); }实测下来迁移逻辑最好在项目早期就建立起来哪怕一开始只有一张表。等到后期表多了再补很容易漏掉某个版本。3.3 核心页面的搭建顺序页面搭建我遵循“从数据到展示”的顺序而不是从UI开始画。具体来说先做宠物列表页因为它是入口依赖最少。再做宠物详情页展示档案信息和时间轴。然后做事件添加页这是最复杂的表单页面。接着做提醒管理页依赖事件数据。最后做统计页依赖所有历史数据。这个顺序的好处是每完成一个页面都能立即在真机上跑起来验证不会出现“写了十个页面但一个都跑不通”的情况。宠物列表页的核心逻辑是查询未归档的宠物按创建时间倒序排列。这里我加了一个细节如果宠物头像为空根据物种显示不同的默认图标。这个判断逻辑放在Widget层而不是数据层保持数据层的纯净。事件添加页是最容易出问题的地方。因为事件类型多每种类型的表单字段不一样。我的做法是用一个动态表单生成器根据event_type返回不同的字段配置列表然后统一渲染。这样新增事件类型时只需要加一份配置不用改UI代码。ListFormFieldConfig getFieldsForType(String type) { switch (type) { case vaccine: return [ FormFieldConfig.text(疫苗名称), FormFieldConfig.date(接种日期), FormFieldConfig.number(下次间隔月数), ]; case weight: return [ FormFieldConfig.number(体重(kg)), FormFieldConfig.date(测量日期), ]; default: return [FormFieldConfig.text(标题), FormFieldConfig.date(日期)]; } }3.4 提醒调度的实现细节提醒调度这块我踩了不少坑。最初我用的是应用内定时器结果应用一退到后台定时器就失效了。后来改用系统级本地通知才解决了这个问题。具体实现分三步第一步在应用启动时请求通知权限。这里要注意权限请求的时机很关键不要在应用一打开就弹窗而是等用户第一次创建提醒时再请求通过率会高很多。第二步在事件保存时根据next_due_at和提醒规则计算出具体的通知时间点调用通知插件注册。第三步在应用每次启动时重新扫描所有未完成的提醒重新注册通知。这一步是为了防止系统清理了通知队列。Futurevoid scheduleReminder(Reminder reminder) async { final notifyTime reminder.remindAt .subtract(Duration(days: reminder.advanceDays)); if (notifyTime.isBefore(DateTime.now())) return; await notificationPlugin.zonedSchedule( reminder.id, 宠物提醒, 有一条待处理的提醒事项, tz.TZDateTime.from(notifyTime, tz.local), NotificationDetails( android: AndroidNotificationDetails( pet_reminder_channel, 宠物提醒, importance: Importance.high, ), ), ); }提示通知ID必须唯一否则后注册的通知会覆盖前面的。我用的是提醒记录的数据库ID天然唯一。4. 常见问题排查与避坑经验4.1 数据库并发写入导致的锁表问题这个问题我在测试阶段遇到了当用户快速连续添加多条记录时偶尔会抛出数据库锁异常。原因是drift默认的SQLite连接不支持并发写入多个写操作同时进行时会互相阻塞。解决方案有两种一是把所有写操作串行化用一个队列管理二是开启WAL模式提高并发读性能。我两个都做了。WAL模式下读操作不会阻塞写操作写操作之间仍然串行但通过队列管理后用户几乎感知不到延迟。// 开启WAL模式 await customStatement(PRAGMA journal_modeWAL);另外批量导入数据时一定要用事务包裹。我试过逐条插入一千条体重记录耗时将近十秒改成事务后降到不到一秒。4.2 日期时区处理引发的提醒偏移时区问题是个隐形杀手。用户设置的提醒时间是本地时间但数据库存储的是UTC时间如果转换逻辑写错提醒就会偏移几个小时。我的处理原则是数据库统一存UTC展示和计算时转本地。所有涉及日期比较的地方都先转成同一时区再比较。这个规则听起来简单但在实际编码中很容易忘记尤其是AI生成的代码它默认可能用本地时间。所以我在项目里加了一个工具类所有日期转换必须经过它禁止直接调用DateTime.now()做业务判断。class TimeUtils { static DateTime nowUtc() DateTime.now().toUtc(); static DateTime toLocal(DateTime utc) utc.toLocal(); static String formatDate(DateTime dt) DateFormat(yyyy-MM-dd).format(dt.toLocal()); }4.3 AI生成代码的审查要点用AI辅助开发最大的风险不是它写错代码而是它写出的代码“看起来对但实际有隐患”。我总结了几个必须人工审查的点第一空值处理。AI倾向于假设数据总是存在但实际业务中空值很常见。所有从数据库读出的可空字段在使用前必须判空。第二边界条件。比如分页查询的页码从0还是1开始日期范围的起止是否包含边界这些AI经常搞混。第三资源释放。数据库连接、文件句柄、通知订阅这些资源在页面销毁时必须释放AI生成的代码经常漏掉。第四错误处理。AI写的try-catch往往只打印日志没有给用户友好的提示。实际项目中每个可能失败的操作都要有对应的用户反馈。我的做法是AI生成代码后先跑一遍静态分析然后手动过一遍上述四点最后再写单元测试覆盖核心逻辑。4.4 常见问题速查表问题现象可能原因排查方向解决方案应用启动白屏数据库初始化阻塞主线程检查初始化是否在异步中执行把数据库初始化放到启动页异步加载提醒不触发通知权限未授予检查系统通知设置引导用户开启通知权限时间轴排序错乱日期存储格式不统一检查是否混用本地时间和UTC统一用UTC存储展示时转换头像不显示文件路径失效检查文件是否被系统清理把头像复制到应用私有目录统计数据偏差软删除数据未排除检查查询是否过滤归档记录所有统计查询加归档过滤条件表单重复提交按钮未做防抖检查提交按钮状态管理提交时禁用按钮完成后恢复这张表是我在实际调试中一点点积累的每次遇到新问题就补一行。建议你也维护一份自己的速查表比翻聊天记录高效得多。4.5 性能优化的几个实用技巧宠物生命周期管理应用的数据量不会特别大但如果不注意时间轴页面在记录多了之后仍然会卡顿。我做了几项优化一是分页加载。时间轴默认只加载最近三十条下拉时再加载更多。这样首屏渲染时间从原来的八百毫秒降到了不到两百毫秒。二是图片压缩。宠物头像原图可能好几兆直接存本地既占空间又拖慢加载。我在保存时统一压缩到最长边五百像素质量百分之八十体积降到几十KB。三是索引优化。events表的pet_id和occurred_at字段建了联合索引时间轴查询速度提升明显。CREATE INDEX idx_events_pet_time ON events(pet_id, occurred_at DESC);四是懒加载统计。统计页面的图表数据不是实时计算的而是每次事件变更时异步更新缓存用户打开统计页时直接读缓存。5. 功能扩展与后续迭代方向5.1 多宠物数据隔离与切换项目初期我只考虑了单宠物场景后来发现很多用户养了好几只。多宠物场景下最关键的是数据隔离要彻底——切换宠物时时间轴、提醒、统计都要跟着切换不能串数据。我的实现方式是在全局状态里维护一个currentPetId所有查询都带上这个条件。切换宠物时刷新所有依赖该状态的Provider。Riverpod的ref.watch机制在这里帮了大忙状态一变相关页面自动重建。但这里有个细节切换宠物时如果当前页面正在编辑一条记录直接切换会导致数据错乱。所以我在切换前加了确认弹窗提示用户保存或放弃当前编辑。5.2 数据导出与备份的完整方案数据安全是用户最关心的问题之一。我提供了两种导出方式JSON格式用于完整备份和恢复CSV格式用于在表格软件里查看。导出逻辑本身不复杂但有几个坑一是文件编码CSV必须带BOM头否则中文在部分软件里会乱码二是图片资源JSON里只存路径导出时需要把图片一起打包三是大文件写入要分块写避免内存溢出。FutureFile exportToJson(int petId) async { final pet await petRepo.getById(petId); final events await eventRepo.getByPetId(petId); final reminders await reminderRepo.getByPetId(petId); final data { version: 1, exported_at: DateTime.now().toUtc().toIso8601String(), pet: pet.toJson(), events: events.map((e) e.toJson()).toList(), reminders: reminders.map((r) r.toJson()).toList(), }; final file File(${await getExportDir()}/pet_${petId}_backup.json); await file.writeAsString(jsonEncode(data)); return file; }导入时要做版本校验如果导入文件的版本号高于当前应用支持的版本要提示用户升级应用而不是强行导入导致数据损坏。5.3 后续可以扩展的方向这个项目的主体功能已经跑通但还有不少可以继续打磨的地方。比如健康趋势分析根据体重、食欲、活动量等数据生成健康趋势报告异常时给出提示。就诊记录结构化把就诊时的诊断、用药、费用等信息结构化存储方便回顾。多设备同步目前是纯本地存储后续可以加一个可选的同步方案让用户在换设备时无缝迁移。桌面小组件把最近的提醒直接展示在系统桌面上减少打开应用的次数。这些扩展不需要推翻现有架构只需要在对应的层里增加模块即可。这也是分层设计带来的好处——业务逻辑和数据逻辑解耦后新功能的接入成本很低。我个人在实际操作中的体会是用AI辅助开发新项目最大的价值不在于省了多少敲键盘的时间而在于它逼着我把需求想得更清楚。因为只有你想清楚了才能给AI准确的指令而AI生成的代码又会反过来暴露你设计中的漏洞。这个来回迭代的过程比单纯手写代码学到的更多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑