根因: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 单元测试。
57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
package store
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"encoding/binary"
|
||
|
||
"mail_go/internal/db"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// MailboxStateStore 提供 IMAP 邮箱持久化状态(UIDVALIDITY)的存取。
|
||
type MailboxStateStore interface {
|
||
// UidValidity 返回邮箱的持久化 UIDVALIDITY;首次访问时随机生成并落库。
|
||
UidValidity(userID uint, folder string) (uint32, error)
|
||
}
|
||
|
||
type mailboxStateStoreGorm struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
func newMailboxStateStore(database *gorm.DB) *mailboxStateStoreGorm {
|
||
return &mailboxStateStoreGorm{db: database}
|
||
}
|
||
|
||
// UidValidity 返回邮箱的持久化 UIDVALIDITY;首次访问时随机生成并落库。
|
||
// 随机值保证:邮箱内容身份变化(如数据库重建导致消息 ID 空间变化)时,
|
||
// 新库生成的新值会让客户端丢弃旧缓存全量重同步(RFC 3501 UIDVALIDITY
|
||
// 语义)。绝不返回 0(0 不是合法 UIDVALIDITY)。
|
||
func (s *mailboxStateStoreGorm) UidValidity(userID uint, folder string) (uint32, error) {
|
||
var st db.MailboxState
|
||
err := s.db.Where("user_id = ? AND folder = ?", userID, folder).First(&st).Error
|
||
if err == nil {
|
||
if st.UidValidity != 0 {
|
||
return st.UidValidity, nil
|
||
}
|
||
} else if err != gorm.ErrRecordNotFound {
|
||
return 0, err
|
||
}
|
||
|
||
// 首次访问(或旧数据为 0):随机生成并持久化
|
||
var buf [4]byte
|
||
if _, err := rand.Read(buf[:]); err != nil {
|
||
return 0, err
|
||
}
|
||
v := binary.BigEndian.Uint32(buf[:])
|
||
if v == 0 {
|
||
v = 1
|
||
}
|
||
|
||
st = db.MailboxState{UserID: userID, Folder: folder, UidValidity: v}
|
||
if err := s.db.Save(&st).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return v, nil
|
||
}
|