feat(mailbox): 文件夹数据驱动,Web 通过 IMAP 共享服务层操作邮箱

- 新增 mailboxes 表与 MailboxStore(系统文件夹幂等创建,自定义文件夹 CRUD)
- 提取 MailboxService:IMAP 会话与 Web handler 共用,LIST 返回什么 Web 就显示什么
- IMAP 支持 CREATE/DELETE/RENAME/SUBSCRIBE(系统文件夹禁删改、非空禁删)
- Web 删除改为 IMAP 语义:移入 Trash,新增恢复/彻底删除/清空
- 新增通用 /folder/:name 页面与动态侧边栏,/inbox /sent /drafts 保留兼容重定向
This commit is contained in:
2026-08-20 01:25:40 +08:00
parent 962c5f454c
commit dc164fdf66
22 changed files with 1440 additions and 639 deletions
+14 -3
View File
@@ -37,6 +37,9 @@ type IMAPServer struct {
tlsLoader *tlsutil.Loader
hub *connhub.Hub
// svc 邮箱服务层(文件夹目录/消息操作),IMAP 会话与 Web 共用。
svc *MailboxService
// hubs 按「用户邮箱 + 文件夹」索引的推送中心,会话 SELECT 时加入。
mu sync.Mutex
hubs map[string]*mailboxHub
@@ -53,11 +56,17 @@ func NewIMAPServer(cfg config.IMAPConfig, stores *store.Stores, tlsLoader *tlsut
banCfg: banCfg,
tlsLoader: tlsLoader,
hub: hub,
svc: NewMailboxService(stores),
hubs: make(map[string]*mailboxHub),
sessions: make(map[*imapSession]struct{}),
}
}
// MailboxService 返回邮箱服务层(Web handler 共用)。
func (s *IMAPServer) MailboxService() *MailboxService {
return s.svc
}
// hubKey 生成推送中心索引键。
func hubKey(userEmail, mailbox string) string {
return userEmail + "\x00" + mailbox
@@ -91,15 +100,17 @@ func (s *IMAPServer) unregisterSession(sess *imapSession) {
// NotifyNewMessage 推送新邮件到达通知:EXISTS 计数 + 标记更新。
// 由 SMTP/Web 本地投递成功时调用;无会话选中该邮箱时为 no-op。
// 推送目标邮箱取 msg.Folder(本地投递为 INBOXWeb 移动/恢复时
// 用于通知目标文件夹,如 Trash)。
func (s *IMAPServer) PushNewMessage(userEmail string, msg *db.Message) {
if s == nil || userEmail == "" || msg == nil {
if s == nil || userEmail == "" || msg == nil || msg.Folder == "" {
return
}
hub := s.hubFor(userEmail, "INBOX")
hub := s.hubFor(userEmail, msg.Folder)
if hub == nil {
return
}
if count, err := s.stores.Mails.CountByUserAndFolder(msg.UserID, "INBOX"); err == nil {
if count, err := s.stores.Mails.CountByUserAndFolder(msg.UserID, msg.Folder); err == nil {
hub.enqueue(sessionUpdate{exists: ptrU32(uint32(count))}, nil)
}
}