WeightManager 项目总结
项目概述 #
项目名称: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-picker的allowsEditing: 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. 键盘弹起后底部留白 #
问题描述:
- 输入框聚焦后键盘弹起,底部出现空白区域
- 影响用户体验
解决方案:
- 调整
KeyboardAvoidingView的keyboardVerticalOffset参数 - 移除 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 秒
- 简单的"你好"消息也需要很长时间
排查过程:
- 怀疑是网络问题 → curl 测试排除
- 怀疑是历史消息太多 → 减少到 5 条无效
- 怀疑是输出 tokens 太多 → 减少 max_tokens 无效
- 发现返回结果中有
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. 渐进式优化 #
做法:
- 先减少历史消息数量(20→5)
- 再减少 max_tokens(500→200)
- 最后找到根本原因(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% |
四、关键经验总结 #
调试方法论 #
- 先隔离问题:用 curl 测试确认是服务端还是客户端
- 加日志:每个步骤都加计时,快速定位瓶颈
- 查文档:接入新服务前先阅读官方文档
- 写测试:核心功能要有测试覆盖
代码设计 #
- 显式传参:避免闭包陷阱
- 单一职责:组件要小而专注
- 配置统一:明确配置来源,避免多来源混淆
- 版本管理:每个功能点都要有 tag
React Native 特殊注意事项 #
- 网络权限:项目创建后第一件事
- 平台差异:iOS/Android 行为可能不同
- 键盘适配:
KeyboardAvoidingView需要调整 offset - 图片压缩:移动端必须处理大图
五、最终架构 #
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 项目状态:阶段性完成,功能稳定