Files
mailx/manager.go
2026-08-15 01:38:05 +08:00

219 lines
5.4 KiB
Go

package mailx
import (
"context"
"fmt"
"sync"
"time"
)
// SenderInfo 描述一个已注册通道实例的信息
type SenderInfo struct {
// Name 实例名,是管理器中的唯一标识,用于 SendWith/Unregister/SetDefault 路由
Name string
// Type 通道类型(底层 Sender.Name()),如 "smtp" / "aliyun" / "aws" / "mailgun"
Type string
}
// senderEntry 管理器内部存储的通道条目
type senderEntry struct {
instance string // 实例名(管理器内的唯一标识)
sender Sender
}
// Manager 多通道管理器:注册多个发送通道实例,发送时按实例名路由
type Manager struct {
mu sync.RWMutex
senders map[string]senderEntry // key 为实例名
def string
loggerMu sync.RWMutex
logger Logger
}
// NewManager 创建多通道管理器
func NewManager() *Manager {
return &Manager{
senders: make(map[string]senderEntry),
logger: noopLogger{},
}
}
// New 创建多通道管理器(NewManager 的别名,简化调用)
func New() *Manager {
return NewManager()
}
// Register 注册一个发送通道,实例名即通道的 Name()。
// 若需同一类型通道注册多份不同配置,请使用 RegisterNamed。
func (m *Manager) Register(s Sender) error {
if s == nil {
return ErrInvalidConfig
}
return m.RegisterNamed(s.Name(), s)
}
// RegisterNamed 以指定实例名注册一个发送通道。
// 实例名是管理器内的唯一标识;允许同一通道类型(如 smtp)注册多份不同配置,
// 例如 "smtp-main" 与 "smtp-backup"。
func (m *Manager) RegisterNamed(name string, s Sender) error {
if s == nil {
return ErrInvalidConfig
}
if name == "" {
return fmt.Errorf("%w: sender instance name is empty", ErrInvalidConfig)
}
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.senders[name]; ok {
return fmt.Errorf("%w: %q already registered", ErrInvalidConfig, name)
}
m.senders[name] = senderEntry{instance: name, sender: s}
if m.def == "" {
m.def = name
}
return nil
}
// Unregister 注销指定实例名的通道
func (m *Manager) Unregister(name string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.senders, name)
if m.def == name {
m.def = ""
for n := range m.senders {
m.def = n
break
}
}
}
// SetDefault 设置默认通道实例
func (m *Manager) SetDefault(name string) error {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.senders[name]; !ok {
return fmt.Errorf("%w: %q", ErrSenderNotFound, name)
}
m.def = name
return nil
}
// Sender 按实例名获取已注册的通道;name 为空时返回默认实例
func (m *Manager) Sender(name string) (Sender, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if name == "" {
name = m.def
}
if name == "" {
return nil, ErrSenderNotFound
}
e, ok := m.senders[name]
if !ok {
return nil, fmt.Errorf("%w: %q", ErrSenderNotFound, name)
}
return e.sender, nil
}
// Registered 判断指定实例名的通道是否已注册
func (m *Manager) Registered(name string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
_, ok := m.senders[name]
return ok
}
// Names 返回所有已注册通道的实例名(顺序不保证稳定)。
// 如需包含通道类型信息,请使用 Senders。
func (m *Manager) Names() []string {
m.mu.RLock()
defer m.mu.RUnlock()
names := make([]string, 0, len(m.senders))
for n := range m.senders {
names = append(names, n)
}
return names
}
// Senders 返回所有已注册通道实例的信息列表(含实例名与通道类型)。
func (m *Manager) Senders() []SenderInfo {
m.mu.RLock()
defer m.mu.RUnlock()
infos := make([]SenderInfo, 0, len(m.senders))
for _, e := range m.senders {
infos = append(infos, SenderInfo{Name: e.instance, Type: e.sender.Name()})
}
return infos
}
// Default 返回当前默认通道实例名(无默认时返回空串)
func (m *Manager) Default() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.def
}
// SetLogger 设置管理器全局日志器
func (m *Manager) SetLogger(l Logger) *Manager {
if l != nil {
m.loggerMu.Lock()
m.logger = l
m.loggerMu.Unlock()
}
return m
}
// Logger 返回管理器当前的日志器
func (m *Manager) Logger() Logger {
m.loggerMu.RLock()
defer m.loggerMu.RUnlock()
return m.logger
}
// Send 使用默认通道发送
func (m *Manager) Send(ctx context.Context, msg *Message) error {
return m.SendWith(ctx, "", msg)
}
// SendWith 使用指定实例名的通道发送(发送时指定配置/通道)
func (m *Manager) SendWith(ctx context.Context, name string, msg *Message) error {
s, err := m.Sender(name)
if err != nil {
return err
}
return m.dispatch(ctx, s, msg)
}
// SendBy 使用临时传入的通道发送,无需提前注册(发送时指定配置)
func (m *Manager) SendBy(ctx context.Context, s Sender, msg *Message) error {
if s == nil {
return ErrInvalidConfig
}
return m.dispatch(ctx, s, msg)
}
// dispatch 统一校验消息并注入日志器后发送
func (m *Manager) dispatch(ctx context.Context, s Sender, msg *Message) error {
if msg == nil {
return ErrInvalidMessage
}
if err := msg.Validate(); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidMessage, err)
}
logger := m.Logger()
start := time.Now()
err := s.Send(WithLogger(ctx, logger), msg)
elapsed := time.Since(start)
if err != nil {
logger.Errorf(ctx, "mailx: send via %q failed in %v: %v", s.Name(), elapsed, err)
return err
}
logger.Infof(ctx, "mailx: sent via %q to %v in %v", s.Name(), msg.To, elapsed)
return nil
}