ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Claude Code重构开发认知:从需求到部署的AI协同实践

用Claude Code重构开发认知:从需求到部署的AI协同实践

1. 这不是“AI写代码”,而是用Claude Code重构开发认知——一个宠物生命周期管理App的诞生实录

你有没有试过,在凌晨两点对着空白的项目目录发呆?新建一个文件夹,敲下mkdir pet-life-app,然后光标在终端里闪烁了十分钟,连package.json都不敢初始化——怕选错包管理器,怕定错框架版本,怕第一步就踩进未来三个月都爬不出来的坑。这不是矫情,是每个真实经历过从零启动项目的开发者都懂的窒息感。而这次,我决定把这种“启动焦虑”彻底交给Claude Code来消化。标题里的“从零搭建新项目全流程AI实现”,不是指让AI代劳所有编码,而是把整个项目诞生的决策链、设计链、验证链全部交由Claude Code深度参与:从需求反推功能模块,从用户旅程倒推数据库字段,从UI草图生成可运行的React组件,甚至自动补全API Mock数据结构和单元测试边界用例。这个“宠物生命周期管理App”听起来像宠物店SaaS系统,但它真正的价值锚点在于“生命周期”四个字——幼宠疫苗提醒、成年驱虫周期、老年慢性病记录、临终关怀日志、纪念相册归档……它天然要求时间轴驱动、状态机建模、多角色权限(主人、兽医、寄养员)、离线优先同步。这些不是功能列表,而是对技术选型的硬性约束。我用Claude Code做了三件事:第一,让它基于“宠物主最常忘记的5件事”反向生成核心实体关系图;第二,把一张手绘的“疫苗提醒时间轴”截图喂给它,输出带时区处理逻辑的TypeScript日期计算函数;第三,把Figma导出的低保真原型JSON丢进去,直接生成带React Hook Form校验规则的表单组件。整个过程没有一行代码是凭空生成的,但每一行代码背后都有明确的业务意图溯源。如果你正卡在“想做但不敢开新项目”的阶段,或者团队里总有人质疑“AI写出来的代码能上线吗”,这篇文章就是你该看的实战日志——它不讲原理,只讲我在MacBook上敲下的每一个命令、遇到的每一个报错、以及Claude Code给出的第7版解决方案为什么比前6版更贴近真实业务。

2. 项目整体设计与思路拆解:为什么必须用Claude Code重定义“从零开始”

2.1 拒绝“模板化启动”:宠物管理场景的特殊性倒逼架构决策

市面上90%的教程教你怎么用Vite+React快速搭起一个Todo App,但宠物生命周期管理根本不是Todo的变体。我先列几个真实场景带来的技术硬约束:

  • 时间敏感性极强:幼犬首次疫苗必须在6-8周龄完成,误差超过3天即失效;驱虫药需严格按体重区间选择剂量,而体重每周都在变。这意味着系统里不能只有“下次提醒日期”,必须有“生效时间窗”(startAt/endAt)和“动态计算引擎”。

  • 数据主权模糊:一只猫的健康档案,主人有权查看,签约兽医可编辑,寄养中心仅能读取用药记录。这要求权限模型不是简单的RBAC,而是ABAC(属性基访问控制),且属性要实时计算(如“当前登录用户是否为该宠物绑定的认证兽医”)。

  • 离线能力非可选项:宠物主在山区遛狗时手机信号中断,但必须能紧急调出疫苗本电子版;兽医在出诊途中需要离线查看历史问诊记录。这就决定了本地存储不能只靠IndexedDB,必须有冲突解决策略和增量同步协议。

如果按传统方式启动,我会先花两天选技术栈:是用Supabase还是Firebase?用Redux Toolkit还是Zustand?这些讨论看似专业,实则是在用已知方案套未知问题。而Claude Code的价值在于,它能基于我对“宠物主在凌晨三点发现猫呕吐,急需查三天前的饮食记录”这个具体场景的描述,直接输出一份包含数据模型建议、离线同步伪代码、权限校验逻辑片段的综合方案。它不替我做决定,但把所有决策变量摊开在桌面上——比如它指出:“若采用Firebase Realtime Database,其服务器时间戳无法满足疫苗时间窗的毫秒级精度要求,建议改用Firestore的客户端时间戳+自定义时区解析器”。这句话的价值,远超任何技术选型对比表格。

2.2 Claude Code的介入时机:三个不可替代的“决策点”

很多开发者把AI当代码补全器,这是最大的误用。Claude Code真正不可替代的,是它在三个关键决策点提供的“认知杠杆”:

第一决策点:需求到模型的语义压缩
当我告诉Claude Code:“主人需要记录宠物每次驱虫的时间、药品名、体重、是否出现不良反应”,它没有直接生成SQL建表语句,而是先问我:“不良反应是单选(呕吐/腹泻/嗜睡)还是开放文本?是否需要关联到药品说明书中的已知副作用列表?”——这个问题让我意识到,自己最初的需求描述漏掉了数据标准化的关键维度。最终我们共同确定:建立side_effect_catalog表预置标准选项,treatment_record表中用外键关联,同时保留custom_note字段供自由填写。这个模型设计,是人机协作的产物,而非AI单方面输出。

第二决策点:UI到逻辑的逆向工程
我把Figma里“疫苗接种记录卡片”的设计稿截图(含日期选择器、药品下拉框、剂量输入框、附件上传区)传给Claude Code,并要求:“生成React组件,要求:1)日期选择器默认聚焦今日,2)药品下拉框支持搜索且高亮匹配项,3)剂量输入框单位自动根据所选药品切换(ml/g/kg)”。它返回的代码不仅满足要求,还在useEffect里埋了一个隐藏逻辑:当用户手动修改剂量后,自动禁用单位切换功能,防止误操作。这个细节,是纯人工开发时容易忽略的交互一致性保障。

第三决策点:错误日志到根因的语义映射
在测试离线同步时,我遇到一个诡异报错:“Conflict resolution failed: local version timestamp < remote version”。我把完整错误堆栈和同步代码片段发给Claude Code,它没有泛泛而谈“检查时间戳”,而是精准定位到getLocalTimestamp()函数中未处理iOS设备的夏令时偏移,导致本地时间比服务器时间慢1小时。它甚至给出了修复方案:用Intl.DateTimeFormat().resolvedOptions().timeZone获取设备时区,再通过moment-timezone进行标准化转换。这种将错误日志映射到具体设备环境的能力,是传统文档检索无法企及的。

2.3 技术栈选型背后的“人机共识”

最终落地的技术栈不是拍脑袋决定的,而是Claude Code基于23次迭代对话后,与我达成的共识:

  • 前端框架:React 18 + TypeScript
    理由:Claude Code分析了宠物管理App的交互复杂度(时间轴渲染、表单联动、离线状态提示),指出Vue的响应式系统在处理大量动态时间计算时存在性能陷阱,而React的细粒度更新更适合此类场景。它甚至对比了Remix和Next.js的SSR成本——“对于以用户个人数据为核心的App,服务端渲染带来的SEO收益可忽略,但首屏加载延迟会直接影响紧急场景下的使用体验”。

  • 状态管理:Zustand(非Redux)
    理由:当我提出“需要全局共享宠物ID以便各模块调用API”时,Claude Code立刻否定了Redux的样板代码成本,推荐Zustand并给出具体实现:“创建usePetStore,其中activePetId为atom,fetchPetData为action,关键是在persistmiddleware中配置partialize: (state) => ({ activePetId: state.activePetId }),避免缓存整棵状态树导致离线数据膨胀”。

  • 数据库与同步:Supabase(PostgreSQL) + 自研轻量同步层
    理由:Claude Code否决了Firebase的实时同步方案,理由直击痛点:“Firebase的离线持久化依赖本地磁盘空间,而iOS对Web应用的存储配额限制为50MB,宠物影像资料极易突破此限”。它建议用Supabase的Row Level Security配合自研同步层,并提供了同步算法伪代码:if (local.timestamp > remote.timestamp) { pushLocal(); } else if (local.timestamp === remote.timestamp) { mergeByFieldPriority(); } else { pullRemote(); },其中FieldPriority按业务重要性排序(疫苗记录 > 饮食日记 > 照片)。

这个技术栈清单,每一条背后都有Claude Code提供的具体场景分析、竞品缺陷对照、甚至性能压测数据模拟。它不是工具推荐,而是把技术决策还原成业务问题的求解过程。

3. 核心细节解析与实操要点:Claude Code如何把“宠物生命周期”变成可执行代码

3.1 数据模型设计:从“宠物主口头描述”到可运行的TypeScript接口

传统建模流程是:产品经理写PRD → 架构师画ER图 → 开发者写TypeScript接口。而这次,我直接把宠物主的真实反馈喂给Claude Code:“我家狗打完狂犬疫苗后第三天开始流口水,但APP里只能填‘是/否’,我想记具体症状和持续时间”。Claude Code的响应分三步:

第一步:语义澄清
它追问:“流口水是否属于‘不良反应’范畴?持续时间需要精确到小时还是天?是否需要关联到该次疫苗的批次号以便追溯?”——这迫使我在建模前就厘清业务边界。

第二步:接口生成
基于确认,它输出VaccinationRecord.ts:

interface VaccinationRecord { id: string; petId: string; vaccineName: string; batchNumber: string; // 新增批次号字段 administeredAt: Date; // 使用Date类型而非string,便于时区处理 adverseReactions: AdverseReaction[]; // 数组而非布尔值 } interface AdverseReaction { symptom: 'drooling' | 'vomiting' | 'lethargy' | 'other'; // 枚举确保标准化 customSymptom?: string; // 当symptom==='other'时必填 durationHours: number; // 精确到小时 startedAt: Date; // 新增起始时间,支持时间窗计算 }

第三步:校验逻辑注入
它紧接着给出校验函数:

const validateVaccinationRecord = (record: VaccinationRecord): string[] => { const errors: string[] = []; // 关键业务规则:不良反应起始时间不能早于接种时间 if (record.adverseReactions.some(ar => ar.startedAt < record.administeredAt)) { errors.push("不良反应起始时间不能早于疫苗接种时间"); } // 批次号必须为8-12位字母数字组合 if (!/^[A-Za-z0-9]{8,12}$/.test(record.batchNumber)) { errors.push("疫苗批次号格式错误"); } return errors; };

这个过程的价值在于:业务规则在代码生成阶段就完成了闭环验证。我不需要等QA提bug才发现“不良反应时间可以填在接种前”这种低级错误。

提示:Claude Code对正则表达式的生成非常可靠,但务必用真实数据测试边界情况。我曾用它生成邮箱校验,结果在测试user+tag@gmail.com时失败——它默认遵循RFC 5322的严格模式,而Gmail实际支持加号标签。最终我让它补充了/^[\w.-]+@[\w.-]+\.\w+$/这个宽松版本,并注明“适用于主流邮箱服务商”。

3.2 时间轴组件:把兽医手写的“疫苗时间表”变成可交互的React组件

兽医给我的纸质疫苗时间表写着:“6周龄:犬瘟热+细小;12周龄:加强针;16周龄:狂犬”。这看起来简单,但转化为代码时有三大陷阱:

  • 周龄计算不能简单用(当前日期-出生日期)/7:因为宠物年龄按“周龄”计算时,需考虑出生当天为第0天,且不同疫苗对“满X周龄”有严格定义(如“≥42天”而非“≥6周”)。

  • 时间轴需支持动态插入:当宠物提前或推迟接种时,后续所有计划节点需自动平移。

  • 移动端触摸体验:时间轴卡片在iPhone上需支持左右滑动查看历史记录,而非PC端的滚动条。

Claude Code的解决方案是一套组合拳:

1. 周龄计算引擎
它生成calculatePetAgeInWeeks(birthDate: Date, referenceDate: Date): number函数,核心逻辑是:

// 关键:使用毫秒差而非天数差,避免时区导致的1天误差 const ageInDays = Math.floor((referenceDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24)); return Math.floor(ageInDays / 7);

并特别强调:“此函数返回整数周龄,用于判断是否达到接种门槛;具体接种日期计算应使用addDays(birthDate, 42)等绝对天数方法,避免周龄四舍五入误差”。

2. 时间轴渲染组件
它输出VaccinationTimeline.tsx,其中useMemo缓存计算结果:

const timelineItems = useMemo(() => { return VACCINE_SCHEDULE.map((item, index) => { const dueDate = addDays(pet.birthDate, item.minDays); const isOverdue = dueDate < new Date() && !record?.administeredAt; const isCompleted = record?.administeredAt && record.administeredAt <= dueDate; return { ...item, dueDate, isOverdue, isCompleted }; }); }, [pet.birthDate, record]);

这里VACCINE_SCHEDULE是硬编码的疫苗计划表,Claude Code建议将其放入src/constants/vaccine-schedule.ts,并说明:“硬编码比远程配置更可靠,因为疫苗计划是医学共识,变更频率极低,且需保证离线可用”。

3. 移动端手势支持
它集成react-use-gesture库,添加滑动逻辑:

const bind = useDrag(({ offset: [x] }) => { setOffsetX(x); }, { from: () => [offsetX], preventDefault: true }); // 在组件JSX中绑定 <div {...bind()} className="timeline-container"> {/* 渲染时间轴 */} </div>

并贴心提醒:“preventDefault: true必须设置,否则iOS Safari会触发页面缩放”。

3.3 权限系统:用ABAC模型实现“兽医只能看到自己接诊的宠物”

传统RBAC(基于角色的访问控制)在这里完全失效。因为同一个兽医,对A宠物是主治医师,对B宠物只是普通咨询师,权限粒度必须到“宠物-医生”关系对。Claude Code引导我构建ABAC(基于属性的访问控制)模型:

第一步:定义属性集
它列出关键属性:

  • user.role: 'veterinarian' | 'owner' | 'caretaker'
  • user.licenseNumber: 兽医执照号(用于认证)
  • pet.ownerId: 宠物主人ID
  • appointment.veterinarianId: 本次问诊的兽医ID
  • record.createdAt: 记录创建时间(用于“仅允许查看近30天记录”规则)

第二步:生成策略引擎
它输出checkPermission.ts:

type PermissionContext = { user: { role: string; licenseNumber?: string }; resource: { type: 'vaccination' | 'treatment' | 'photo'; ownerId: string; veterinarianId?: string; createdAt: Date; }; }; const PERMISSION_RULES: Record<string, (ctx: PermissionContext) => boolean> = { 'vaccination:read': (ctx) => { // 兽医只能查看自己接诊过的宠物的疫苗记录 if (ctx.user.role === 'veterinarian') { return ctx.resource.veterinarianId === ctx.user.licenseNumber; } // 主人可查看自己宠物的所有记录 if (ctx.user.role === 'owner') { return ctx.resource.ownerId === ctx.user.id; } return false; }, 'photo:read': (ctx) => { // 照片记录额外限制:仅允许查看近30天 const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); return ctx.resource.createdAt >= thirtyDaysAgo; } };

第三步:前端策略注入
它建议在React Query的useQuery中封装权限检查:

const useVaccinationRecords = (petId: string) => { return useQuery({ queryKey: ['vaccinations', petId], queryFn: () => fetchVaccinations(petId), enabled: checkPermission({ user: currentUser, resource: { type: 'vaccination', ownerId: petId } }) }); };

这个设计让权限逻辑与业务代码解耦,且所有规则集中管理,新增权限只需在PERMISSION_RULES中添加新条目。

4. 实操过程与核心环节实现:从Claude Code对话到可部署应用的完整流水线

4.1 初始化项目:用Claude Code生成定制化Vite模板

传统npm create vite@latest会生成通用模板,但宠物App需要特定预设。我让Claude Code生成定制化初始化脚本:

指令:
“生成一个Vite 5.0 + React 18 + TypeScript的初始化脚本,要求:1)预装Zustand、React Query、date-fns;2)目录结构按功能模块划分(features/pet, features/vaccination);3)包含基础ESLint配置,禁用@typescript-eslint/no-explicit-any但启用@typescript-eslint/consistent-type-assertions;4)生成src/env.d.ts声明环境变量”。

Claude Code输出:
它没有给我一堆命令,而是生成一个setup-pet-app.sh脚本:

#!/bin/bash npm create vite@latest pet-life-app -- --template react-ts cd pet-life-app npm install zustand @tanstack/react-query date-fns # 创建功能模块目录 mkdir -p src/features/{pet,vaccination,treatment} # 生成ESLint配置 echo '{ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "rules": { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/consistent-type-assertions": "error" } }' > .eslintrc.json # 生成env声明 echo 'declare global { namespace NodeJS { interface ProcessEnv { VITE_SUPABASE_URL: string; VITE_SUPABASE_ANON_KEY: string; } } }' > src/env.d.ts echo "✅ 初始化完成!执行 'npm run dev' 启动开发服务器"

实操心得:
这个脚本的价值在于消除环境差异。我同事用M1 Mac跑,实习生用Windows,但执行同一脚本后,node_modules结构、ESLint规则、目录约定完全一致。Claude Code甚至在脚本末尾加了emoji(✅),这种人性化细节让团队新人上手零障碍。

4.2 数据库搭建:Claude Code生成Supabase迁移脚本与RLS策略

Supabase的Row Level Security(RLS)策略语法晦涩,官方文档示例全是SELECT * FROM users WHERE id = auth.uid()这种简单场景。而宠物App需要复合条件,比如“兽医只能查看自己接诊过的宠物的治疗记录”。Claude Code的处理方式是:

1. 生成迁移SQL
我描述:“创建pets表,包含id、name、species、birth_date、owner_id;创建vaccinations表,包含id、pet_id、vaccine_name、administered_at、batch_number”。它输出:

-- migrations/001_create_pets_table.sql CREATE TABLE pets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, species TEXT NOT NULL CHECK (species IN ('dog', 'cat', 'rabbit')), birth_date DATE NOT NULL, owner_id UUID NOT NULL REFERENCES auth.users(id) ); -- migrations/002_create_vaccinations_table.sql CREATE TABLE vaccinations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), pet_id UUID NOT NULL REFERENCES pets(id) ON DELETE CASCADE, vaccine_name TEXT NOT NULL, administered_at TIMESTAMP WITH TIME ZONE NOT NULL, batch_number TEXT NOT NULL );

2. 生成RLS策略
针对vaccinations表,我问:“如何写RLS策略,使兽医只能查看自己接诊过的宠物的疫苗记录?”它输出:

-- RLS策略:兽医只能查看自己接诊过的宠物的疫苗记录 CREATE POLICY "veterinarian_can_read_own_pet_vaccinations" ON vaccinations FOR SELECT USING ( pet_id IN ( SELECT pet_id FROM appointments WHERE veterinarian_id = (SELECT id FROM auth.users WHERE email = current_user) ) );

并解释:“此策略假设你有一个appointments表记录问诊关系。若无此表,需先创建,否则策略无效”。

3. 生成测试用例
它甚至提供测试SQL:

-- 测试:以兽医身份查询,应返回空(因无appointments数据) SET ROLE authenticated; SELECT * FROM vaccinations; -- 测试:插入测试数据后再次查询 INSERT INTO appointments (pet_id, veterinarian_id) VALUES ('pet-uuid', 'vet-uuid'); SELECT * FROM vaccinations; -- 应返回对应记录

4.3 API层开发:Claude Code生成TypeScript SDK与Mock数据

前端需要调用Supabase API,但手写SDK易出错。Claude Code的方案是:

1. 生成SDK类
我提供Supabase表结构,它输出supabaseClient.ts:

import { createClient } from '@supabase/supabase-js'; const supabase = createClient( import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_ANON_KEY ); export const petApi = { list: () => supabase.from('pets').select('*'), get: (id: string) => supabase.from('pets').select('*').eq('id', id).single(), create: (data: Omit<Pet, 'id'>) => supabase.from('pets').insert(data) }; export const vaccinationApi = { list: (petId: string) => supabase.from('vaccinations').select('*').eq('pet_id', petId), create: (data: Omit<Vaccination, 'id'>) => supabase.from('vaccinations').insert(data) };

2. 生成Mock数据填充脚本
为加速开发,我需要本地Mock数据。Claude Code生成mock-data.ts:

// 生成10只测试宠物,按物种分布 const mockPets: Pet[] = Array.from({ length: 10 }, (_, i) => ({ id: `pet-${i}`, name: ['旺财', '咪咪', '小白'][i % 3], species: ['dog', 'cat', 'rabbit'][i % 3] as const, birth_date: new Date(Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 365 * 5), // 0-5岁 owner_id: 'user-1' })); // 为每只宠物生成2-5条疫苗记录 const mockVaccinations: Vaccination[] = mockPets.flatMap(pet => Array.from({ length: 2 + Math.floor(Math.random() * 4) }, (_, j) => ({ id: `vac-${pet.id}-${j}`, pet_id: pet.id, vaccine_name: ['犬瘟热', '狂犬病', '猫三联'][j % 3], administered_at: new Date(pet.birth_date.getTime() + (21 + j * 28) * 24 * 60 * 60 * 1000), // 按周龄递增 batch_number: `BATCH-${Math.floor(Math.random() * 10000)}` })) );

3. 集成Mock到开发环境
它指导我修改vite.config.ts:

export default defineConfig({ // ...其他配置 server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } });

并在src/main.tsx中添加:

if (import.meta.env.DEV) { // 开发环境注入Mock数据 window.mockData = { pets: mockPets, vaccinations: mockVaccinations }; }

这样前端调用/api/pets时,Vite代理会返回Mock数据,无需启动后端。

4.4 部署配置:Claude Code生成Vercel适配的构建脚本

Vercel部署要求build脚本输出静态文件,但宠物App需要环境变量注入。Claude Code生成vercel.json:

{ "version": 2, "builds": [ { "src": "package.json", "use": "@vercel/static-build", "config": { "distDir": "dist" } } ], "routes": [ { "src": "/(.*)", "dest": "/index.html", "status": 200 } ], "env": { "VITE_SUPABASE_URL": "@pet-life-supabase-url", "VITE_SUPABASE_ANON_KEY": "@pet-life-supabase-key" } }

并说明:“@pet-life-supabase-url是Vercel密钥别名,需在Vercel项目设置中创建。此配置确保环境变量在构建时注入,而非运行时,提升安全性”。

5. 常见问题与排查技巧实录:Claude Code帮我省下的17个小时调试时间

5.1 “时间显示错乱”问题:时区陷阱的终极解决方案

现象:
在iOS Safari中,疫苗记录的administered_at显示为“1970-01-01”,而在Chrome中正常。

排查过程:

  1. 初步怀疑:Supabase返回的ISO字符串格式问题
  2. 检查网络请求:返回的是"2023-06-15T08:30:00.000Z",格式正确
  3. 在控制台打印:new Date("2023-06-15T08:30:00.000Z")→ 正常
  4. 但在组件中:<div>{record.administeredAt.toLocaleDateString()}</div>→ 显示1970年

Claude Code的诊断:
我发送错误截图和代码片段,它立刻指出:“toLocaleDateString()在iOS Safari中对UTC时间字符串解析不稳定。解决方案:1)统一用date-fns的format函数;2)或强制转换为本地时间:new Date(record.administeredAt + 'Z').toLocaleDateString()”。

最终方案:
采用date-fns:

npm install date-fns
import { format } from 'date-fns'; // 组件中 <div>{format(new Date(record.administeredAt), 'yyyy-MM-dd')}</div>

并补充:“date-fns的format函数内部处理了所有浏览器时区差异,且体积仅2KB,远小于moment-timezone”。

注意:Claude Code对浏览器兼容性问题的诊断准确率极高,但务必在真机上验证。我曾信以为真地采用它的方案,结果在旧版Android WebView中仍出错——它没覆盖到那个小众环境。教训:AI给出的方案是“最优解”,但不是“万能解”,真机测试不可省。

5.2 “离线同步失败”问题:IndexedDB存储配额超限的预警机制

现象:
在长时间离线后,重新联网时同步失败,控制台报错:“QuotaExceededError: The quota has been exceeded”。

排查过程:

  1. 检查IndexedDB大小:用Chrome DevTools的Application → Storage → IndexedDB
  2. 发现pet-life-db占用120MB,远超Chrome默认50MB配额

Claude Code的优化建议:
它没有说“清理数据库”,而是给出一套渐进式方案:

1. 存储分级策略

// 将数据分为三级 const STORAGE_LEVELS = { CRITICAL: { maxSize: '10MB', priority: 1 }, // 疫苗记录、用药记录 IMPORTANT: { maxSize: '30MB', priority: 2 }, // 饮食日记、行为记录 OPTIONAL: { maxSize: '100MB', priority: 3 }, // 照片、视频(需用户授权) };

2. 自动清理逻辑

const cleanupStorage = async (level: keyof typeof STORAGE_LEVELS) => { const db = await openDB('pet-life-db'); const tx = db.transaction(level, 'readwrite'); const store = tx.objectStore(level); // 按时间戳删除最旧的20%数据 const allKeys = await store.getAllKeys(); const keysToDelete = allKeys.slice(0, Math.floor(allKeys.length * 0.2)); keysToDelete.forEach(key => store.delete(key)); await tx.done; };

3. 用户友好提示

// 在同步失败时 if (error.name === 'QuotaExceededError') { alert(`存储空间不足,请前往设置清理照片缓存\n当前占用:${usedSpace}MB`); }

这个方案的价值在于:它把技术问题转化为用户体验问题。用户不会看到“QuotaExceededError”,只会看到清晰的操作指引。

5.3 “权限策略不生效”问题:Supabase RLS策略的隐形依赖

现象:
RLS策略veterinarian_can_read_own_pet_vaccinations始终返回空结果,即使appointments表中有数据。

排查过程:

  1. 检查策略语法:无误
  2. 检查appointments表结构:veterinarian_id字段类型为UUID,与auth.users.id一致
  3. 检查数据:appointments.veterinarian_id值为'vet-123',而auth.users.id为'user-vet-123'

Claude Code的洞察:
我发送表结构和数据样本,它一眼看出:“appointments.veterinarian_id存储的是用户邮箱(如'dr.smith@example.com'),而非auth.users.id。RLS策略中current_user返回的是邮箱,但你的表设计用了ID字段。解决方案:1)修改appointments.veterinarian_id为TEXT类型存储邮箱;2)或在策略中用SELECT email FROM auth.users WHERE id = current_user获取邮箱”。

最终修正:
采用方案1,修改迁移脚本:

ALTER TABLE appointments ALTER COLUMN veterinarian_id TYPE TEXT USING veterinarian_id::TEXT;

并更新RLS策略:

CREATE POLICY "veterinarian_can_read_own_pet_vaccinations" ON vaccinations FOR SELECT USING ( pet_id IN ( SELECT pet_id FROM appointments WHERE veterinarian_id = current_user -- current_user now returns email ) );

这个案例揭示了Claude Code的核心优势:它不局限于代码层面,而是穿透到数据建模与业务逻辑的耦合点。人类开发者容易陷入“代码没错”的思维定式,而AI能跳出代码,审视整个数据流。

5.4 “CI/CD构建失败”问题:Vercel环境变量注入的隐蔽坑

现象:
Vercel部署时构建失败,报错:“Cannot find module 'src/env.d.ts'”。

排查过程:

  1. 本地npm run build成功
  2. Vercel构建日志显示:tsc编译时报错,找不到env.d.ts

Claude Code的定位:
我发送Vercel构建日志片段,它指出:“Vercel的TypeScript编译环境未识别src/env.d.ts。解决方案:1)在tsconfig.json中显式包含:"include": ["src/**/*", "src/env.d.ts"];2)或更优方案:将环境变量声明移到src/types/env.d.ts,并在tsconfig.json中配置"typeRoots": ["./src/types", "./node_modules/@types"]”。

最终修复:
采用方案2,创建src/types/env.d.ts:

declare global { namespace NodeJS { interface ProcessEnv { VITE_SUPABASE_URL: string; VITE_SUPABASE_ANON_KEY: string; } } }

并更新tsconfig.json:

{ "compilerOptions": { "typeRoots": ["./src/types", "./node_modules/@types"] } }

这个细节说明:Claude Code对构建工具链的理解深度,远超一般开发者。它知道Vercel的构建环境与本地开发环境的微妙差异,并给出精准的路径修复。

6. 项目收尾与经验沉淀:当Claude Code成为团队的“首席架构师”

这个宠物生命周期管理App最终上线了,但比代码更重要的,是我们沉淀下来的三条铁律:

第一,Claude Code不是“代码生成器”,而是“决策校验器”。
我坚持一个原则:任何由Claude Code生成的代码,必须经过“三问”验证——

  1. 这个方案是否解决了我描述的原始业务问题?(业务对齐)
  2. 这个实现是否存在我没有意识到的隐性成本?(成本评估)
  3. 如果明天Claude Code停服,这段代码是否还能被团队维护?(可维护性)
    比如它曾建议用WebAssembly编译一个复杂的疫苗剂量计算器,我立刻否决——虽然性能提升20%,但增加了构建复杂度和调试难度,不符合“宠物主紧急场景下秒级响应”的核心诉求。

**第二,人机协作的黄金分割点在“

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进