1. 为什么我选择用对话式编程从零搭一个宠物生命周期管理App
宠物生命周期管理这个需求,乍一听好像挺小众,但真正养过宠物的人都知道,从接回家第一天到最终告别,中间涉及的事情琐碎到让人崩溃。疫苗什么时候打、驱虫周期怎么算、体重曲线是否正常、发情期记录、体检报告归档、用药历史、甚至临终关怀阶段的情绪记录——这些信息如果全靠脑子记或者散落在各种备忘录里,基本等于没有。
我身边养猫养狗的朋友,几乎每个人手机里都装着三四个App:一个记疫苗、一个记体重、一个存照片、一个做提醒。数据孤岛不说,关键是每个App都只解决一个切片问题,没有一个能贯穿宠物完整生命周期的工具。这就是我决定自己动手的原因。
但问题来了:我是一个人,没有团队,前端后端数据库全干,还要兼顾日常本职工作。如果按传统方式从零写,光是搭架子、配环境、写CRUD就能耗掉我所有业余时间。所以我选择了另一条路——用对话式编程工具,把整个开发流程压缩成“我描述需求、它生成代码、我验证调整”的循环。
这篇文章要讲的就是这个完整过程。不是那种“Hello World”级别的演示,而是一个真实可用的宠物生命周期管理App从零到一的全流程。我会把每个阶段的关键决策、踩过的坑、以及对话式编程在实际项目中的边界和最佳实践都摊开来讲。无论你是想学怎么用这类工具提效,还是单纯想做一个自己的小项目,这篇内容都能直接抄作业。
2. 动手之前先把需求拆成AI能理解的粒度
2.1 宠物生命周期管理到底要管什么
很多人一上来就跟AI说“帮我做一个宠物管理App”,结果生成出来的东西要么是个玩具,要么方向完全跑偏。问题不在于AI能力不够,而在于需求粒度太粗。对话式编程的核心逻辑是:你给的信息越结构化、越具体,它输出的代码越接近可用状态。
我先把宠物生命周期拆成几个核心阶段:
- 幼年期:疫苗接种记录、驱虫周期、体重增长曲线、社会化训练打卡
- 成年期:日常喂养记录、体检提醒、发情/绝育记录、行为观察日志
- 老年期:慢性病用药管理、复查提醒、活动量监测、饮食调整记录
- 告别期:临终关怀记录、情绪日记、纪念相册
每个阶段的数据结构有重叠也有差异。比如体重记录贯穿全阶段,但幼年期更关注增长速率,老年期更关注下降趋势。这些差异直接决定了数据库表设计和前端展示逻辑。
2.2 把需求翻译成技术栈描述
拆完业务需求之后,下一步是把它翻译成技术栈层面的描述。这一步很关键,因为对话式编程工具需要知道你想用什么技术来实现。我的选择逻辑是这样的:
| 层面 | 选型 | 理由 |
|---|---|---|
| 前端框架 | React + TypeScript | 生态成熟,AI训练数据多,生成质量高 |
| 样式方案 | Tailwind CSS | 原子化类名,AI生成时不容易样式冲突 |
| 后端 | Node.js + Express | 轻量,和前端同语言,减少上下文切换 |
| 数据库 | SQLite(开发)/ PostgreSQL(生产) | 开发阶段零配置,上线可平滑迁移 |
| ORM | Prisma | Schema声明式定义,AI生成准确率高 |
| 状态管理 | Zustand | 比Redux轻,AI不容易写出冗余代码 |
这个选型不是拍脑袋决定的。我试过让AI用一些比较新的框架生成代码,结果它要么版本号写错,要么API用法过时。React + Express + Prisma这套组合因为社区大、文档全,AI生成出来的代码质量明显更稳定。
提示:选技术栈时优先选“AI见过足够多代码”的方案,而不是“你觉得最酷”的方案。前者能让你少改一半的bug。
2.3 用结构化Prompt替代模糊描述
需求拆完之后,我写了一个结构化的Prompt模板,后面每次跟AI对话都套这个模板:
项目背景:[一句话说明] 当前阶段:[正在做哪个模块] 技术约束:[框架、版本、目录结构] 数据模型:[涉及哪些表、字段、关系] 接口需求:[需要哪些API、入参出参] UI要求:[页面布局、交互逻辑] 验收标准:[怎么判断做完了]这个模板看起来简单,但它解决了一个核心问题:AI不会“猜”你的意图。你给的信息越完整,它生成的代码越不需要返工。我实测下来,用这个模板之后,单次生成的代码可用率从大概40%提升到了75%以上。
3. 项目骨架搭建:从空目录到能跑起来的第一版
3.1 初始化项目时AI最容易犯的三个错
让AI帮你初始化项目,听起来很简单,但实际上有几个坑我踩过不止一次。
第一个坑是目录结构混乱。如果你不指定,AI可能会把前后端代码混在一起,或者把组件放在pages目录下、把页面放在components目录下。我的做法是在Prompt里直接写死目录结构:
/client /src /components /pages /hooks /stores /utils /types /server /src /routes /controllers /services /middlewares /prisma第二个坑是依赖版本冲突。AI有时候会生成一些互相不兼容的包版本。我的经验是:在Prompt里明确指定“使用当前稳定版本,不要指定具体版本号”,让包管理器自己去解析依赖树。
第三个坑是环境变量遗漏。数据库连接串、端口号、密钥这些东西,AI经常忘记在示例文件里写出来。我后来养成了一个习惯:每次生成完配置相关代码,都追问一句“还需要哪些环境变量?请列出完整的.env.example文件内容”。
3.2 数据库Schema设计:宠物生命周期的数据建模
数据库设计是整个项目的地基。我让AI根据之前拆解的业务需求生成Prisma Schema,但第一版出来之后我做了大量调整。核心表结构大概是这样:
model Pet { id String @id @default(cuid()) name String species String // 猫、狗、兔等 breed String? birthDate DateTime gender String weight Float? avatarUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt vaccinations Vaccination[] dewormings Deworming[] weightLogs WeightLog[] medicalRecords MedicalRecord[] reminders Reminder[] diaryEntries DiaryEntry[] } model Vaccination { id String @id @default(cuid()) petId String pet Pet @relation(fields: [petId], references: [id]) vaccineName String date DateTime nextDueDate DateTime? vetName String? notes String? createdAt DateTime @default(now()) } model WeightLog { id String @id @default(cuid()) petId String pet Pet @relation(fields: [petId], references: [id]) weight Float date DateTime notes String? createdAt DateTime @default(now()) }这里有几个设计决策值得展开说。第一,我用cuid而不是自增ID,因为后面如果要做数据同步或者导入导出,cuid不会冲突。第二,体重记录单独建表而不是存在Pet表里,因为体重是时间序列数据,需要记录变化趋势。第三,疫苗的nextDueDate字段允许为空,因为有些疫苗只需要打一次。
AI第一版生成的Schema把疫苗记录和驱虫记录合并成了一张表,用type字段区分。我改成了两张独立表,原因是它们的字段差异比较大——疫苗有nextDueDate,驱虫有频率周期,合并之后会有大量空字段。
3.3 后端API骨架:先跑通一条完整链路
Schema定好之后,我让AI生成后端的CRUD接口。这里我的策略是:不要一次性生成所有模块的接口,而是先跑通一条完整链路——比如“创建宠物 → 查询宠物列表 → 更新宠物信息 → 删除宠物”。
为什么这样做?因为一次性生成所有接口,如果基础架构有问题(比如中间件配置错误、错误处理不统一),你需要改很多地方。先跑通一条链路,确认架构没问题之后,剩下的接口就是复制粘贴改改字段的事。
AI生成的第一个版本用了Express的默认错误处理,所有错误都返回500。我让它改成统一的错误处理中间件:
// server/src/middlewares/errorHandler.js const errorHandler = (err, req, res, next) => { const statusCode = err.statusCode || 500; const message = err.message || 'Internal Server Error'; console.error(`[Error] ${statusCode} - ${message}`); res.status(statusCode).json({ success: false, error: { code: err.code || 'UNKNOWN_ERROR', message, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) } }); }; module.exports = errorHandler;这个中间件看起来简单,但它让前端能统一处理错误提示,不用每个接口单独判断。这是我踩过坑之后的经验:错误处理一定要在项目初期就统一,后期再改成本极高。
4. 核心功能模块的对话式实现过程
4.1 疫苗提醒模块:从需求描述到可运行代码
疫苗提醒是宠物管理App里最核心的功能之一。我的需求描述是这样的:
用户添加疫苗记录时,需要填写疫苗名称、接种日期、下次接种日期(可选)。系统需要在下次接种日期前7天、前3天、当天分别发送提醒。提醒方式先做站内通知,后续可扩展推送。
AI生成的代码里,提醒逻辑是写在一个定时任务里的。我看了之后发现一个问题:如果服务器重启,定时任务会丢失状态。所以我让它改成了基于数据库查询的方案——每次用户打开App时,后端查询所有即将到期的疫苗记录,动态生成提醒。
// server/src/services/reminderService.js const getUpcomingVaccinations = async (petId) => { const today = new Date(); const sevenDaysLater = new Date(today); sevenDaysLater.setDate(sevenDaysLater.getDate() + 7); const vaccinations = await prisma.vaccination.findMany({ where: { petId, nextDueDate: { gte: today, lte: sevenDaysLater } }, orderBy: { nextDueDate: 'asc' } }); return vaccinations.map(v => { const daysUntilDue = Math.ceil( (new Date(v.nextDueDate) - today) / (1000 * 60 * 60 * 24) ); return { ...v, daysUntilDue, urgency: daysUntilDue <= 1 ? 'high' : daysUntilDue <= 3 ? 'medium' : 'low' }; }); };这个方案的好处是不依赖服务器常驻进程,每次请求都是实时计算。缺点是如果用户不打开App就收不到提醒。但对于第一版来说,这个取舍是合理的——先跑通核心逻辑,推送后面再接。
4.2 体重曲线可视化:让AI选图表库并生成配置
体重曲线是我个人最看重的功能,因为宠物的体重变化是最直观的健康指标。我让AI推荐图表库,它给了几个选项:Chart.js、Recharts、ECharts、Victory。
我的选择逻辑是:项目已经用了React,Recharts是React原生组件,集成成本最低。ECharts功能最强但配置复杂,Chart.js需要额外封装。最终选了Recharts。
AI生成的体重曲线组件第一版是这样的:
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; const WeightChart = ({ data }) => { return ( <ResponsiveContainer width="100%" height={300}> <LineChart data={data}> <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="date" /> <YAxis domain={['auto', 'auto']} /> <Tooltip /> <Line type="monotone" dataKey="weight" stroke="#8884d8" strokeWidth={2} /> </LineChart> </ResponsiveContainer> ); };这个版本能跑,但有几个问题:X轴日期格式不友好、没有参考区间、没有异常值标记。我逐步让AI优化:
- 日期格式改成“MM月DD日”
- 根据宠物种类和年龄,在图表上画出正常体重范围区间
- 体重骤降超过10%的数据点用红色标记
这些优化都是一轮一轮对话加上去的。我的体会是:不要指望AI一次生成完美代码,而是把它当成一个执行力很强但需要明确指令的初级开发者。
4.3 多宠物切换与数据隔离
养多只宠物的人很多,所以多宠物切换是刚需。这个功能的技术核心是数据隔离——每只宠物的疫苗、体重、病历数据必须严格分开。
AI第一版实现是在前端做过滤:把所有数据拉回来,然后根据当前选中的petId过滤。这个方案在数据量小的时候没问题,但宠物多了之后性能会急剧下降。
我让它改成后端过滤:
// server/src/controllers/petController.js const getPetData = async (req, res) => { const { petId } = req.params; const { include } = req.query; const pet = await prisma.pet.findUnique({ where: { id: petId }, include: { vaccinations: include?.includes('vaccinations'), weightLogs: include?.includes('weightLogs') ? { orderBy: { date: 'desc' }, take: 100 } : false, medicalRecords: include?.includes('medicalRecords'), } }); if (!pet) { return res.status(404).json({ success: false, error: { message: '宠物不存在' } }); } res.json({ success: true, data: pet }); };这里用了一个include参数来控制返回哪些关联数据,避免一次性拉取所有数据。体重记录默认只取最近100条,因为画曲线不需要全部历史数据。
注意:多宠物场景下,前端状态管理要特别注意“当前选中宠物”的持久化。我一开始没做持久化,用户每次刷新页面都回到第一只宠物,体验很差。后来用Zustand的persist中间件把selectedPetId存到localStorage才解决。
5. 踩坑实录:对话式编程中那些AI不会主动告诉你的问题
5.1 上下文丢失:为什么AI写着写着就忘了前面的约定
这是对话式编程最大的坑,没有之一。当对话轮次多了之后,AI会逐渐“忘记”前面定好的约定。比如你一开始说了所有API返回格式是{ success: boolean, data: any },但写到第十个接口的时候,它突然返回了裸数据。
我的应对策略有三个:
第一,关键约定写进代码而不是只写在对话里。比如我创建了一个responseHelper.js,所有接口都通过这个helper返回数据。这样即使AI忘了约定,它生成的代码也会因为引用了helper而保持格式一致。
第二,每完成一个模块就开新对话。不要在一个对话里做完整项目,那样上下文会越来越长,AI的注意力会被稀释。我的做法是:Schema设计一个对话、后端接口一个对话、前端页面一个对话、每个复杂组件单独一个对话。
第三,在Prompt里重复关键约束。每次开新对话,我都会把技术栈、目录结构、返回格式这些关键信息重新贴一遍。虽然麻烦,但能避免大量返工。
5.2 依赖版本地狱:AI生成的package.json为什么跑不起来
AI生成package.json的时候,经常会写出一些不存在的版本号组合。比如它可能写"react": "^18.3.0"但实际最新稳定版是18.2.0,或者它写的两个包之间存在peer dependency冲突。
我的解决方案是:不让AI直接生成完整的package.json,而是让它生成安装命令。比如:
npm install react react-dom typescript @types/react @types/react-dom npm install -D tailwindcss postcss autoprefixer npm install express prisma @prisma/client cors dotenv然后我自己跑这些命令,让npm去解析版本。这样虽然多了一步,但避免了版本冲突的问题。装完之后再把package.json贴给AI,让它基于实际版本生成代码。
5.3 数据库迁移:开发环境和生产环境的Schema同步
Prisma的迁移功能很好用,但AI生成的迁移命令有时候会出问题。我遇到过一次:AI让我跑prisma migrate dev,但因为Schema里有个字段类型改了,迁移直接失败,还锁了数据库。
后来我养成了一个习惯:每次改Schema之前先跑prisma migrate diff看看差异,确认没问题再执行迁移。另外,开发阶段我直接用prisma db push同步Schema,不生成迁移文件。等到要上线的时候再统一生成迁移。
# 开发阶段:直接同步,快速迭代 npx prisma db push # 上线前:生成正式迁移文件 npx prisma migrate dev --name init npx prisma migrate deploy这个流程让我在开发阶段省了很多时间,同时保证了上线时的迁移文件是干净的。
5.4 前端路由与权限:AI容易忽略的边界情况
宠物管理App虽然不需要复杂的权限系统,但有一个边界情况AI经常忽略:用户直接访问一个不存在的宠物ID对应的页面。
AI生成的路由组件通常是这样的:
<Route path="/pet/:petId" element={<PetDetail />} />然后在PetDetail组件里直接请求数据。如果petId不存在,后端返回404,前端就白屏了。我让它加了一个错误边界和404处理:
const PetDetail = () => { const { petId } = useParams(); const { data: pet, error, isLoading } = usePet(petId); if (isLoading) return <LoadingSpinner />; if (error || !pet) return <NotFound message="没有找到这只宠物" />; return <PetDetailContent pet={pet} />; };这个改动看起来小,但它决定了用户遇到异常时是看到白屏还是看到友好的提示。类似这样的边界情况,AI不会主动帮你考虑,需要你在Prompt里明确提出来。
6. 从能跑到好用:上线前的优化与收尾
6.1 性能优化:图片压缩与懒加载
宠物App少不了照片。用户上传的宠物照片动辄几MB,如果直接存原图,不仅占空间,加载也慢。我让AI加了图片压缩逻辑:
// server/src/services/imageService.js const sharp = require('sharp'); const compressImage = async (buffer, maxWidth = 800) => { return sharp(buffer) .resize(maxWidth, null, { withoutEnlargement: true }) .jpeg({ quality: 80 }) .toBuffer(); };前端则用React的lazy和Suspense做组件懒加载,图片用loading="lazy"属性。这些优化单独看都不复杂,但组合起来对用户体验的提升很明显。
6.2 数据导出:让用户能带走自己的数据
这个功能是我自己加的,AI一开始没生成。我觉得宠物数据对用户来说是有情感价值的,应该允许导出。实现方式很简单:后端提供一个接口,把所有数据打包成JSON返回,前端触发下载。
// server/src/controllers/exportController.js const exportPetData = async (req, res) => { const { petId } = req.params; const pet = await prisma.pet.findUnique({ where: { id: petId }, include: { vaccinations: true, dewormings: true, weightLogs: { orderBy: { date: 'asc' } }, medicalRecords: true, diaryEntries: { orderBy: { createdAt: 'asc' } } } }); res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', `attachment; filename="${pet.name}_data.json"`); res.json(pet); };这个功能实现成本很低,但对用户来说是一个重要的信任信号——数据是你的,你随时可以拿走。
6.3 部署上线的最后几步
部署我选了最简方案:前端打包成静态文件,后端跑在Node环境,数据库用云端的PostgreSQL。具体步骤不展开,但有几个点值得注意:
- 环境变量一定要在部署平台上配置,不要硬编码在代码里
- 数据库连接串要用连接池模式,避免连接数耗尽
- 前端打包时确认API地址指向正确的后端域名
- 上线后先跑一遍核心流程:注册 → 添加宠物 → 记录疫苗 → 查看体重曲线
我在部署阶段遇到的最大问题是跨域。开发阶段前端跑在3000端口,后端跑在4000端口,跨域配置没问题。但上线后前端是静态文件,后端是另一个域名,CORS配置需要调整。AI生成的CORS配置是origin: '*',我改成了白名单模式。
7. 对话式编程做完整项目的个人体会
这个项目从零到能跑起来,我大概花了三个周末的业余时间。如果按传统方式写,我估计至少需要两到三倍的时间。但对话式编程不是“一键生成”,它更像是一个放大器——你的需求越清晰、约束越明确,它的产出质量越高。
我最大的体会是:不要试图让AI替你思考,而是让它替你执行。需求拆解、技术选型、数据建模这些决策必须你自己做,AI可以给你建议,但最终判断在你。一旦决策定了,把它翻译成结构化的Prompt,AI的执行效率远超手动写代码。
另一个体会是:代码审查比写代码更重要。AI生成的代码看起来往往很合理,但隐藏的边界问题、性能问题、安全问题需要你自己去发现。我在这个项目里花在审查和调整上的时间,大概占总时间的40%。
最后分享一个实用技巧:每次AI生成完一个模块,让它自己写测试用例。不是因为它写的测试有多完善,而是写测试的过程会迫使它重新审视自己的代码,往往能发现一些它自己都没注意到的问题。这个习惯帮我省了很多调试时间。
这个项目后续我还会继续迭代,比如加上多设备同步、宠物社交功能、AI健康建议等。但核心框架已经跑通了,后面的功能都是在这个基础上做加法。如果你也想做一个自己的小项目,我的建议是:先跑通最小闭环,再逐步扩展。不要一上来就追求完美架构,那样你永远开始不了。