Files
mailgo/internal/store/purge.go
T
kevin d2b3caa81f fix(quota): 入站投递强制附件配额,删除路径清理附件并回退配额
安全审计 #19/#20(security_todo.md P1):

- 新增 store.PurgeMessages 共享清理入口:删附件文件、回退配额、
  删附件记录、删邮件记录,Web/IMAP/POP3 三条删除路径同源
- IMAP EXPUNGE(含 UID 分支)与 POP3 expungeDeleted 接入,修复
  协议删除只删记录导致的"幽灵配额"与附件文件泄漏
- Web purgeMessages 改为委托同一入口,消除三处重复实现
- SMTP 入站投递前按收件人原子预扣附件配额(TryReserveQuota),
  任一收件人超限整封拒收并返回 452 临时失败(避免 go-smtp 将
  普通 error 映射为 554 永久失败);Sent 副本尽力而为不回滚整封
- saveMessage 返回实际落库字节数,reconcileQuota 对账退还差额
  (覆盖收件人被删/落库失败/部分附件失败)
- NewIMAPServer/NewPOP3Server 注入 AttachmentStorage
- 新增 4 项单测(配额拒收/正常记账/对账回退/POP3 删除清理)
- security_todo.md 勾选 #19/#20,登记存量发现 #27(IncrementFail
  并发首建窗口丢失更新,待决策)
2026-08-28 13:59:50 +08:00

37 lines
1.2 KiB
Go

package store
import (
"log"
"mail_go/internal/db"
"mail_go/internal/storage"
)
// PurgeMessages 永久删除一批邮件:逐封删除附件文件并回退其占用的配额、
// 删除附件记录,最后批量删除邮件记录。Web 删除/清空、IMAP EXPUNGE、
// POP3 删除共用本入口,保证磁盘文件、配额与数据库三者一致。
// att 为 nil 时跳过文件清理(仍回退配额并删除记录,测试与降级场景使用)。
func (s *Stores) PurgeMessages(att *storage.AttachmentStorage, userID uint, msgs []db.Message) error {
ids := make([]uint, 0, len(msgs))
for i := range msgs {
attachments, err := s.Attachments.ListByMessage(msgs[i].ID)
if err != nil {
log.Printf("store: 查询附件失败 msg=%d: %v", msgs[i].ID, err)
}
for _, a := range attachments {
if att != nil {
_ = att.Delete(a.FilePath)
}
_ = s.Users.UpdateUsedBytes(userID, -a.FileSize)
}
if err := s.Attachments.DeleteByMessage(msgs[i].ID); err != nil {
log.Printf("store: 删除附件记录失败 msg=%d: %v", msgs[i].ID, err)
}
ids = append(ids, msgs[i].ID)
}
if len(ids) == 0 {
return nil
}
return s.Mails.DeleteMany(ids)
}