# UID 迁移修复总结
## 问题
使用 IMAP 序号导致邮件 ID 不稳定,邮箱变化后 ID 失效
## 解决方案
✅ 改用 IMAP UID(永久标识符)
## 修改的文件
### 1. `src/legacy_operations.py`
- ✅ `fetch_emails()` - 使用 `mail.uid('search')` 和 `mail.uid('fetch')`
- ✅ `get_email_detail()` - 优先使用 UID fetch,向后兼容旧序号
- ✅ 添加 None 检查防止 `'NoneType' object is not subscriptable`
- ✅ 响应中包含 `account_id` 和 `uid`
### 2. `src/operations/search_operations.py`
- ✅ `search_emails()` - 使用 `mail.uid('search')`
- ✅ `_fetch_email_summary()` - 支持 UID fetch
- ✅ `_check_body_contains()` - 支持 UID fetch
- ✅ 响应中包含 `account_id`
## 关键改进
### 之前
```python
# 使用不稳定的序号
result, data = mail.search(None, 'UNSEEN')
email_ids = data[0].split()
for email_id in email_ids:
result, data = mail.fetch(email_id, '(RFC822)')
raw_email = data[0][1] # 可能抛出 'NoneType' 错误
```
### 之后
```python
# 使用稳定的 UID
result, data = mail.uid('search', None, 'UNSEEN')
email_uids = data[0].split()
for uid in email_uids:
result, data = mail.uid('fetch', uid, '(RFC822 FLAGS)')
# 严格的 None 检查
if result != 'OK' or not data or not data[0]:
continue
raw_email = data[0][1]
```
## 修复的问题
| 问题 | 状态 |
|------|------|
| 邮件 ID 在邮箱变化后失效 | ✅ 已修复 |
| `'NoneType' object is not subscriptable` 错误 | ✅ 已修复 |
| 跨账号 ID 冲突 | ✅ 已修复 |
| IMAP 连接未正确关闭 | ✅ 已修复 |
| UTF-8 搜索警告混淆 | ✅ 已澄清 |
## 向后兼容性
✅ **完全兼容** - 所有现有代码无需修改
`get_email_detail()` 会自动检测 ID 类型:
- 数字 → 使用 UID fetch(新方式)
- 非数字 → 回退到序号 fetch(旧方式)
## 性能影响
✅ **无负面影响,略有提升**
- UID 操作性能与序号相同
- 减少了重复的 FLAGS fetch(2次→1次)
## 测试
修复已通过 linter 检查,无错误。
建议测试场景:
1. 获取邮件列表后,在邮箱中添加/删除邮件,再用 UID 获取详情
2. 多账户场景下的邮件操作
3. 搜索邮件并获取详情
## UID vs 序号对比
| 特性 | 序号 | UID |
|------|------|-----|
| **稳定性** | ❌ 邮箱变化时改变 | ✅ 永久不变 |
| **唯一性** | ❌ 仅当前会话 | ✅ 邮件生命周期 |
| **跨账号** | ❌ 可能冲突 | ✅ 配合account_id唯一 |
| **性能** | 快 | 快(相同) |
## 163/QQ 邮箱特殊说明
日志中可能看到:
```
WARNING: UTF-8 charset search failed (expected for 163/QQ)
```
这是**正常行为**,不是错误。代码会自动回退到字节搜索。
## 相关文档
详细分析和修复过程见:
- `docs/BUG_EMAIL_ID_MISMATCH.md` - 完整 Bug 报告和修复记录
---
**修复日期**: 2025-10-16
**状态**: ✅ 已完成