fix(imap): UIDVALIDITY 改为持久化随机值,修复数据库重建后客户端缓存永不失效

根因:Status() 硬编码 UidValidity=1。数据库重建后消息 ID 空间
完全变化(uid 2-166 对应完全不同的邮件),但 UIDVALIDITY 不变,
Thunderbird 认为本地缓存(旧 uid 1-166)仍然有效,只下载"新增"
(uid 167:*)与"缺失"(2,4,8,9)的少数邮件——列表只剩 1-3 封,
每次刷新数量随机。

修复(RFC 3501 UIDVALIDITY 语义):新增 mailbox_states 表,
每(用户,文件夹)首次访问随机生成并持久化 UIDVALIDITY;数据库
重建后新值触发客户端丢弃缓存全量重同步。0 值修正,不同用户/
文件夹互相独立。

新增 TestMailboxStateUidValidity 单元测试。
This commit is contained in:
dsh
2026-08-19 09:36:55 -04:00
parent 3a4636c2c5
commit 7ede29a18a
5 changed files with 143 additions and 4 deletions
+10 -4
View File
@@ -354,7 +354,6 @@ func (m *imapMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, erro
if err != nil {
return nil, err
}
status.Messages = uint32(len(messages))
var unseenCount uint32
@@ -370,7 +369,16 @@ func (m *imapMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, erro
return nil, err
}
status.UidNext = uint32(maxID + 1)
status.UidValidity = 1
// UIDVALIDITY 持久化随机值(RFC 3501):数据库重建导致消息 ID 空间
// 变化时该值随之改变,客户端才会丢弃旧缓存全量重同步。此前硬编码 1,
// 数据库重建后 Thunderbird 等客户端缓存永不失效(只下载"新增"的
// UID),表现为列表只剩少量邮件。
uidValidity, err := m.stores.MailboxState.UidValidity(m.user.id, m.name)
if err != nil {
log.Printf("IMAP: 获取 UIDVALIDITY 失败 folder=%s: %v", m.name, err)
uidValidity = 1
}
status.UidValidity = uidValidity
return status, nil
}
@@ -394,7 +402,6 @@ func (m *imapMailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []imap.F
if err != nil {
return err
}
if len(dbMessages) == 0 {
return nil
}
@@ -567,7 +574,6 @@ func (m *imapMailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([
}
}
}
return results, nil
}