package mail import ( "context" "fmt" "strings" "sync" "time" "apigo.cc/go/log" ) var ( instances = make(map[string]*Mailbox) instMu sync.RWMutex ) // Mailbox 邮件客户端实例,实现 starter.Service 接口 type Mailbox struct { name string config *MailboxConfig handlers []mailHandler logger *log.Logger lastUID uint32 ctx context.Context cancel context.CancelFunc wg sync.WaitGroup mu sync.RWMutex } type mailHandler struct { opt *RecvOption fn func(Mail) } // Attachment 附件或内嵌资源信息 type Attachment struct { Name string // 文件名 Path string // 本地路径(发送时使用) Data []byte // 文件数据 ContentType string // 内容类型 ContentID string // 内嵌资源 ID (CID) } // Mail 邮件内容,支持富媒体 type Mail struct { UID uint32 From string To []string Cc []string Bcc []string Subject string Date string Text string // 纯文本正文 HTML string // HTML 正文 Attachments []Attachment // 普通附件 Embeds []Attachment // 内嵌资源(如图片) Size int64 // 邮件大小 Flags []string // 邮件标记 Headers map[string][]string // 原始请求头 } // RecvOption 接收选项,支持复杂的过滤规则 type RecvOption struct { Tag uint32 Mailbox string Unseen bool Read bool Answered bool Flagged bool Deleted bool Since string Before string Limit uint32 MarkAsRead bool Subject []string Search []string Body []string From []string To []string Cc []string Bcc []string Not []string } // RecvResult 接收结果 type RecvResult struct { List []Mail Tag uint32 } // NewWithConfig 是 New 的别名 func NewWithConfig(cfg *MailboxConfig) (*Mailbox, error) { return New(cfg) } // MustNew 创建一个邮件客户端实例,如果失败则 panic func MustNew(cfg *MailboxConfig) *Mailbox { m, err := New(cfg) if err != nil { panic(err) } return m } // Send 使用默认邮箱发送邮件 func Send(to []string, subject, content string, option *SendOption) error { m := GetMailbox("default") if m == nil { return fmt.Errorf("default mailbox not configured") } return m.Send(to, subject, content, option) } // MustSend 使用默认邮箱发送邮件,如果失败则 panic func MustSend(to []string, subject, content string, option *SendOption) { if err := Send(to, subject, content, option); err != nil { panic(err) } } // Recv 使用默认邮箱接收邮件 func Recv(opt *RecvOption) (*RecvResult, error) { m := GetMailbox("default") if m == nil { return nil, fmt.Errorf("default mailbox not configured") } return m.Recv(opt) } // MustRecv 使用默认邮箱接收邮件,如果失败则 panic func MustRecv(opt *RecvOption) *RecvResult { res, err := Recv(opt) if err != nil { panic(err) } return res } // GetMailbox 获取或创建一个指定名称的邮件客户端实例 func GetMailbox(name string, configs ...*MailboxConfig) *Mailbox { instMu.RLock() m, ok := instances[name] instMu.RUnlock() if ok { return m } if len(configs) == 0 { return nil } m, _ = New(configs[0]) if m != nil { m.name = name instMu.Lock() instances[name] = m instMu.Unlock() } return m } // New 创建一个邮件客户端实例 func New(cfg *MailboxConfig) (*Mailbox, error) { if cfg == nil { return nil, fmt.Errorf("config is nil") } mCfg := *cfg // 自动补全逻辑保持不变... if mCfg.SMTPHost == "" || mCfg.IMAPHost == "" { a := strings.SplitN(mCfg.Username, "@", 2) if len(a) == 2 { if mCfg.SMTPHost == "" { mCfg.SMTPHost = "smtp." + a[1] } if mCfg.SMTPPort == 0 { mCfg.SMTPPort = 465 } if mCfg.IMAPHost == "" { mCfg.IMAPHost = "imap." + a[1] } if mCfg.IMAPPort == 0 { mCfg.IMAPPort = 993 } } } if mCfg.SenderMail == "" { mCfg.SenderMail = mCfg.Username } m := &Mailbox{ config: &mCfg, logger: log.DefaultLogger, } for _, rule := range cfg.Rules { r := rule m.On(&r, nil) } return m, nil } // On 在指定实例上注册邮件到达回调 func (m *Mailbox) On(opt *RecvOption, handler func(Mail)) { m.mu.Lock() defer m.mu.Unlock() m.handlers = append(m.handlers, mailHandler{opt: opt, fn: handler}) } // Recv 接收邮件 func (m *Mailbox) Recv(opt *RecvOption) (*RecvResult, error) { return m.recv(opt) } // --- starter.Service Implementation --- func (m *Mailbox) Start(ctx context.Context, logger *log.Logger) error { m.mu.Lock() if m.cancel != nil { m.mu.Unlock() return nil } if logger != nil { m.logger = logger } m.ctx, m.cancel = context.WithCancel(ctx) m.mu.Unlock() interval := m.config.PollInterval if interval <= 0 { interval = 1 * time.Minute } m.wg.Add(1) go m.watch(interval) m.logger.Info("Mail service started", "name", m.name, "username", m.config.Username) return nil } func (m *Mailbox) Stop(ctx context.Context) error { m.mu.Lock() if m.cancel == nil { m.mu.Unlock() return nil } m.cancel() m.mu.Unlock() m.wg.Wait() return nil } func (m *Mailbox) Health() error { if m.config == nil { return fmt.Errorf("mailbox not configured") } return nil } // --- Internal Logic --- func (m *Mailbox) watch(interval time.Duration) { defer m.wg.Done() ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-m.ctx.Done(): return case <-ticker.C: m.poll() } } } func (m *Mailbox) poll() { m.mu.RLock() handlers := make([]mailHandler, len(m.handlers)) copy(handlers, m.handlers) m.mu.RUnlock() if len(handlers) == 0 { return } for i := range handlers { h := &handlers[i] opt := *h.opt if opt.Tag == 0 { opt.Tag = m.lastUID } res, err := m.Recv(&opt) if err != nil { m.logger.Error("Mail poll failed", "err", err, "name", m.name) continue } if res.Tag > m.lastUID { m.lastUID = res.Tag } if h.opt.Tag < res.Tag { h.opt.Tag = res.Tag } for _, mail := range res.List { if h.fn != nil { h.fn(mail) } } } }