Skip to main content

WeightManager 项目总结

·5 mins

项目概述 #

项目名称:WeightManager - 智能体重管理助手 技术栈:Expo SDK 54 + React Native 0.81.5 + TypeScript + expo-sqlite 开发周期:2026-02-26 ~ 2026-03-01(约 4 天) 最终版本:v3.0.0


一、遇到的坑(按时间顺序) #

1. Expo SDK 版本兼容性问题 #

问题描述

  • 用户安装 Expo Go 后发现版本不兼容,提示需要 SDK 54
  • 项目最初使用 SDK 52,与新版 Expo Go 不兼容

解决方案

  • 更新 package.json 中所有 Expo 相关依赖至 SDK 54 兼容版本
  • 运行 npx expo install --fix 自动修复依赖版本

教训:开发前先确认 Expo Go 版本和 SDK 版本匹配


2. API URL 404 错误 #

问题描述

  • API 调用返回 404 错误
  • 日志显示请求 URL 为 https://api.openai.com/v1/v1/chat/completions(重复 /v1

根本原因

  • base_url 配置已包含 /v1 路径
  • 代码无脑追加 /v1/chat/completions 导致路径重复

解决方案

// 修复前
const url = `${baseUrl}/v1/chat/completions`;

// 修复后:先检查是否已包含 /v1
if (baseUrl.endsWith('/v1')) {
  baseUrl = baseUrl.slice(0, -3);
}
const url = `${baseUrl}/v1/chat/completions`;

教训:处理用户输入的 URL 时,要考虑多种格式情况


3. iOS 图片选择器编辑模式无确定按钮 #

问题描述

  • iOS 上选择图片后,编辑界面没有"确定"按钮
  • 用户无法完成图片选择流程

根本原因

  • expo-image-pickerallowsEditing: true 在 iOS 上有 bug

解决方案

// 修复前
const result = await launchImageLibraryAsync({
  allowsEditing: true,  // 问题所在
  quality: 0.8,
});

// 修复后
const result = await launchImageLibraryAsync({
  allowsEditing: false,  // 关闭编辑模式
  quality: 0.8,
});
// 改用 Alert.alert 弹窗确认

教训:React Native 某些 API 在不同平台行为不一致,需要测试


4. 键盘弹起后底部留白 #

问题描述

  • 输入框聚焦后键盘弹起,底部出现空白区域
  • 影响用户体验

解决方案

  • 调整 KeyboardAvoidingViewkeyboardVerticalOffset 参数
  • 移除 InputBar 组件中多余的 paddingBottom

教训:React Native 键盘适配需要针对不同平台调整 offset 值


5. 图片 URI 闭包问题 #

问题描述

  • 图片选择后传递给 onSend 回调时,imageUri 为 undefined
  • 用户输入描述后图片丢失

根本原因

  • 回调函数闭包捕获了旧的 state 值

解决方案

// 修复前:闭包问题
const handleSend = () => {
  onSend(description);  // imageUri 在闭包中丢失
};

// 修复后:直接传递参数
const handleSend = () => {
  onSend(description, imageUri);  // 显式传递
};

教训:React 回调中要注意闭包陷阱,必要时显式传参


6. 网络权限缺失导致请求失败 #

问题描述

  • 手机端 API 请求约 1 秒就失败
  • 错误信息为 Network request failed
  • 电脑端 curl 测试正常

根本原因

  • app.json 缺少 iOS/Android 网络权限配置
  • React Native 默认不允许网络请求

解决方案

// app.json 添加权限
{
  "ios": {
    "infoPlist": {
      "NSAppTransportSecurity": {
        "NSAllowsArbitraryLoads": true
      }
    }
  },
  "android": {
    "permissions": ["INTERNET", "ACCESS_NETWORK_STATE"]
  }
}

教训:React Native 项目创建后第一件事应该是配置网络权限


7. expo-image-manipulator 插件配置错误 #

问题描述

  • 添加 expo-image-manipulator 后编译失败
  • 报错找不到 config plugin

根本原因

  • expo-image-manipulator 没有 config plugin,不需要在 app.json 中配置

解决方案

  • app.json 中移除 expo-image-manipulator 的 plugins 配置
  • 只需要在 package.json 中安装依赖即可使用

教训:不是所有 Expo 库都需要 config plugin,添加前查阅官方文档


8. 配置来源混淆(数据库 vs .env) #

问题描述

  • 修改 .env 文件后,App 中的 API 配置没有变化
  • 排查了半天 API URL 不一致的问题

根本原因

  • 配置存储在 SQLite 数据库中(app_config 表)
  • .env 文件仅用于本地测试,不是运行时配置来源

解决方案

  • 明确告知用户需要通过 Settings 页面修改配置
  • 在文档中说明配置来源

教训:项目设计时要明确配置策略,避免多来源混淆


9. qwen3.5-plus thinking 模式导致响应超慢 #

问题描述

  • API 响应时间 20-35 秒,甚至 40 秒
  • 简单的"你好"消息也需要很长时间

排查过程

  1. 怀疑是网络问题 → curl 测试排除
  2. 怀疑是历史消息太多 → 减少到 5 条无效
  3. 怀疑是输出 tokens 太多 → 减少 max_tokens 无效
  4. 发现返回结果中有 reasoning_tokens 字段 → 定位问题

根本原因

  • 阿里云 qwen3.5-plus 默认开启 thinking/reasoning 模式
  • 约 50% 的 tokens 用于"思考",导致响应变慢

解决方案

const body: LLMRequest = {
  model: config.model_name,
  messages,
  enable_thinking: false,  // 关键!
  max_tokens: 200,
};

效果:响应时间从 20-35 秒降至 3-4 秒

教训

  • 接入新模型前要仔细阅读文档了解特性
  • 可以先用 curl 测试确认模型行为

10. 尝试错误的参数名 disable_thinking #

问题描述

  • 尝试了 disable_thinking: true 参数,但 thinking 仍然开启

根本原因

  • 阿里云 API 的参数名是 enable_thinking,不是 disable_thinking

解决方案

  • 改用 enable_thinking: false

教训:API 参数名要认真核对,不要想当然


11. 历史消息图片传递逻辑错误 #

问题描述

  • 用户发送新图片后,AI 回复的是上一张图片的内容
  • 图片没有正确传递

根本原因

  • find() 从前往后遍历,找到的是最旧的图片
  • 应该找最新的图片

解决方案

// 修复前:找第一条
const msgWithImage = recentMessages.find(m => m.image_uri);

// 修复后:从后往前找最后一条
for (let i = recentMessages.length - 1; i >= 0; i--) {
  if (recentMessages[i].image_uri) {
    imageToSend = recentMessages[i].image_uri;
    break;
  }
}

教训:数组查找时要注意方向,确保找的是"最新"还是"最旧"


12. 图片压缩后 base64 仍然过大 #

问题描述

  • 原图 10MB+ 压缩后仍有几 MB
  • 导致 API 请求失败或超时

解决方案

  • 添加图片压缩(最大宽度 800px,JPEG 质量 70%)
  • 添加图片大小限制检查(3MB)
  • 降低图片质量到 0.5

教训:移动端图片处理要做好压缩和大小限制


二、做对的地方 #

1. 用 curl 测试隔离问题 #

做法

  • 遇到 API 问题时,先用 curl 测试确认是服务端还是客户端问题
  • 避免一开始就怀疑自己代码

效果

  • 快速定位到网络权限缺失问题
  • 确认 API 本身响应时间,排除代码问题

2. 添加详细日志 #

做法

console.log('🚀 ========== 开始发送消息 ==========');
console.log('✅ [1/6] 保存消息到数据库:', Date.now() - start, 'ms');
console.log('✅ [2/6] 更新 UI 显示:', Date.now() - start, 'ms');
// ...

效果

  • 快速定位性能瓶颈
  • 便于排查问题

3. 渐进式优化 #

做法

  1. 先减少历史消息数量(20→5)
  2. 再减少 max_tokens(500→200)
  3. 最后找到根本原因(thinking 模式)

效果

  • 虽然前两步没有根本解决问题,但为定位问题提供了思路
  • 最终找到真正原因

4. Agent Loop 架构设计 #

做法

  • 在 system prompt 中定义特殊标记 [REQUEST_IMAGE]
  • 让模型主动请求资源,而非客户端猜测用户意图
  • 客户端检测到标记后自动重试

效果

  • 更智能、更准确的图片处理
  • 减少无效请求

5. 图片传递策略 #

做法

  • 本次有图用本次的
  • 本次无图用历史最后一张
  • 历史消息只传文本,避免重复传大图片

效果

  • 减少 API 负载
  • 提高响应速度
  • 保证上下文完整性

6. 完善的测试覆盖 #

做法

  • 编写 5 个测试文件,54 个测试用例
  • 覆盖核心功能:weightParser、timeUtils、api、queries、StatusCard

效果

  • 重构时有信心
  • 避免引入 regression bug

7. Git 版本管理 #

做法

  • 每个功能完成后创建 tag
  • 版本号规则明确:fix→小版本、feat→中版本、refactor→大版本
  • tag 描述详细

效果

  • 可以随时回退到稳定版本
  • 便于追踪功能演进

8. 组件化设计 #

做法

  • 提取可复用组件:OnboardingForm、ImageDescriptionModal、ImageViewerModal
  • 单一职责原则

效果

  • 代码复用性高
  • 维护成本低

三、如果重新开发,最优路径 #

1. 项目初始化阶段 #

问题:权限、依赖、版本兼容性

最优解决方式

# 1. 创建项目时直接指定最新 SDK
npx create-expo-app@latest --template tabs

# 2. 第一时间配置网络权限
# app.json 添加 iOS/Android 网络权限

# 3. 确认 Expo Go 版本匹配

预计时间:30 分钟(原:2 小时)


2. API 集成阶段 #

问题:URL 处理、配置来源、模型特性

最优解决方式

# 1. 先用 curl 测试 API 是否正常
curl -X POST "$API_URL" -H "Authorization: Bearer $KEY" -d '{"model":"xxx","messages":[{"role":"user","content":"hi"}]}'

# 2. 创建测试脚本 test-api.js 验证图片上传
node test-api.js

# 3. 阅读模型文档,确认特殊参数(如 enable_thinking)

预计时间:1 小时(原:4 小时)


3. 图片处理阶段 #

问题:压缩、base64、多模态格式、闭包陷阱

最优解决方式

// 1. 参考 OpenAI 官方示例代码,不要自己猜测格式
const content = [
  { type: 'text', text: '描述' },
  { type: 'image_url', image_url: { url: base64Url } }
];

// 2. 添加压缩和大小限制
const result = await ImageManipulator.manipulateAsync(
  uri,
  [{ resize: { width: 800 } }],
  { compress: 0.7, format: 'jpeg' }
);

// 3. 显式传参,避免闭包陷阱
onSend(description, imageUri);  // 不要依赖 state

预计时间:2 小时(原:6 小时)


4. 性能优化阶段 #

问题:响应慢、不知道哪一步慢

最优解决方式

// 1. 每个步骤加日志和计时
const start = Date.now();
// ... 操作
console.log('✅ 操作完成:', Date.now() - start, 'ms');

// 2. 用 curl 测试确认是服务端还是客户端问题
// 3. 查阅模型文档了解默认行为(如 thinking 模式)

预计时间:1 小时(原:3 小时)


5. 整体开发时间对比 #

阶段 实际时间 最优时间 节省
项目初始化 2h 0.5h 75%
API 集成 4h 1h 75%
图片处理 6h 2h 67%
UI 组件 4h 3h 25%
性能优化 3h 1h 67%
测试 & 文档 2h 1.5h 25%
总计 21h 9h 57%

四、关键经验总结 #

调试方法论 #

  1. 先隔离问题:用 curl 测试确认是服务端还是客户端
  2. 加日志:每个步骤都加计时,快速定位瓶颈
  3. 查文档:接入新服务前先阅读官方文档
  4. 写测试:核心功能要有测试覆盖

代码设计 #

  1. 显式传参:避免闭包陷阱
  2. 单一职责:组件要小而专注
  3. 配置统一:明确配置来源,避免多来源混淆
  4. 版本管理:每个功能点都要有 tag

React Native 特殊注意事项 #

  1. 网络权限:项目创建后第一件事
  2. 平台差异:iOS/Android 行为可能不同
  3. 键盘适配KeyboardAvoidingView 需要调整 offset
  4. 图片压缩:移动端必须处理大图

五、最终架构 #

WeightManager/
├── app/                    # 页面路由
│   ├── _layout.tsx         # Tab 导航布局
│   ├── index.tsx           # 首页(聊天)
│   ├── status.tsx          # 历史页
│   └── settings.tsx        # 设置页
├── src/
│   ├── components/         # UI 组件
│   │   ├── ChatBubble.tsx
│   │   ├── InputBar.tsx
│   │   ├── ImageViewerModal.tsx
│   │   ├── ImageDescriptionModal.tsx
│   │   └── OnboardingForm.tsx
│   ├── database/           # 数据库层
│   │   ├── index.ts
│   │   ├── schema.ts
│   │   └── queries.ts
│   ├── utils/              # 工具函数
│   │   ├── api.ts          # API 调用
│   │   ├── timeUtils.ts    # 时间处理
│   │   └── weightParser.ts # 体重解析
│   └── types/              # TypeScript 类型
│       └── index.ts
└── CLAUDE.md               # 开发指南

六、版本历史 #

版本 日期 主要变更
v1.0.0 02-26 调通大模型调用和功能的第一版
v1.1.0 02-26 底部 Tab 导航重构
v1.1.1 02-26 修复图片上传 bug
v1.1.2 02-26 添加自动版本管理
v2.0.0 02-28 重构组件,添加图片描述 Modal
v2.0.1 02-28 网络错误修复
v2.0.2 02-28 测试改进
v2.1.0 02-28 图片缩略图功能
v2.1.1~v2.1.6 03-01 图片上传问题排查和修复
v3.0.0 03-01 阶段性胜利版本:API 响应优化 + Agent Loop

文档生成时间:2026-03-01 项目状态:阶段性完成,功能稳定