219 lines
5.4 KiB
Go
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
|
||
|
|
}
|