1.2 太震撼了!多渠道消息适配只用一个设计模式就搞定了?

1.2 太震撼了!多渠道消息适配只用一个设计模式就搞定了?

在上一节中,我们介绍了通知平台的整体架构设计,其中提到了使用适配器模式来实现多渠道消息的统一处理。在本节中,我们将深入探讨如何通过适配器模式优雅地解决多渠道消息适配的难题。

适配器模式在通知平台中的应用

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口协同工作。在通知平台中,不同的消息渠道(如短信、邮件、微信等)有着各自不同的API接口和数据格式,我们需要将这些差异封装起来,提供统一的接口给上层调用。

graph TB A[通知服务] --> B(渠道适配器) B --> C[短信服务商A] B --> D[短信服务商B] B --> E[邮件服务商A] B --> F[邮件服务商B] B --> G[微信服务商A]

核心接口设计

首先,我们需要定义统一的接口规范:

go 复制代码
// NotificationChannel 通知渠道接口
type NotificationChannel interface {
    // ChannelName 渠道名称
    ChannelName() string
    
    // Send 发送单条消息
    Send(ctx context.Context, message *Message) (*SendResult, error)
    
    // BatchSend 批量发送消息
    BatchSend(ctx context.Context, messages []*Message) ([]*SendResult, error)
    
    // Query 查询消息状态
    Query(ctx context.Context, messageID string) (*MessageStatus, error)
}

// Message 消息结构
type Message struct {
    // 消息ID
    ID string `json:"id"`
    
    // 接收者列表
    To []string `json:"to"`
    
    // 消息主题(适用于邮件等渠道)
    Subject string `json:"subject,omitempty"`
    
    // 消息内容
    Content string `json:"content"`
    
    // 模板ID(适用于支持模板的渠道)
    TemplateID string `json:"template_id,omitempty"`
    
    // 模板参数(适用于支持模板的渠道)
    TemplateParams map[string]string `json:"template_params,omitempty"`
    
    // 附加参数
    Extra map[string]interface{} `json:"extra,omitempty"`
}

// SendResult 发送结果
type SendResult struct {
    // 消息ID
    MessageID string `json:"message_id"`
    
    // 发送状态
    Status SendStatus `json:"status"`
    
    // 错误信息
    Error string `json:"error,omitempty"`
    
    // 渠道返回的原始信息
    RawResponse interface{} `json:"raw_response,omitempty"`
}

// MessageStatus 消息状态
type MessageStatus struct {
    // 消息ID
    MessageID string `json:"message_id"`
    
    // 状态
    Status MessageState `json:"status"`
    
    // 状态描述
    Description string `json:"description,omitempty"`
    
    // 发送时间
    SendTime time.Time `json:"send_time,omitempty"`
    
    // 送达时间
    DeliverTime time.Time `json:"deliver_time,omitempty"`
}

具体渠道适配器实现

短信渠道适配器

go 复制代码
// SMSChannel 短信渠道适配器
type SMSChannel struct {
    // 短信服务商客户端
    client SMSServiceClient
    
    // 渠道配置
    config *SMSConfig
    
    // 渠道名称
    name string
}

// NewSMSChannel 创建短信渠道适配器
func NewSMSChannel(name string, config *SMSConfig) *SMSChannel {
    return &SMSChannel{
        client: NewSMSServiceClient(config),
        config: config,
        name:   name,
    }
}

func (s *SMSChannel) ChannelName() string {
    return s.name
}

func (s *SMSChannel) Send(ctx context.Context, message *Message) (*SendResult, error) {
    // 构造短信发送请求
    req := &SMSSendRequest{
        PhoneNumbers: message.To,
        SignName:     s.config.SignName,
        TemplateCode: message.TemplateID,
        TemplateParam: func() string {
            if len(message.TemplateParams) > 0 {
                params, _ := json.Marshal(message.TemplateParams)
                return string(params)
            }
            return ""
        }(),
    }
    
    // 发送短信
    resp, err := s.client.SendSMS(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("send sms failed: %w", err)
    }
    
    // 构造发送结果
    result := &SendResult{
        MessageID:   resp.BizId,
        Status:      SendStatusSuccess,
        RawResponse: resp,
    }
    
    // 检查发送结果
    if !resp.CodeSuccess() {
        result.Status = SendStatusFailed
        result.Error = resp.Message
    }
    
    return result, nil
}

func (s *SMSChannel) BatchSend(ctx context.Context, messages []*Message) ([]*SendResult, error) {
    // 批量发送实现
    var results []*SendResult
    for _, msg := range messages {
        result, err := s.Send(ctx, msg)
        if err != nil {
            result = &SendResult{
                Status: SendStatusFailed,
                Error:  err.Error(),
            }
        }
        results = append(results, result)
    }
    return results, nil
}

func (s *SMSChannel) Query(ctx context.Context, messageID string) (*MessageStatus, error) {
    // 查询短信状态
    req := &SMSQueryRequest{
        BizId: messageID,
    }
    
    resp, err := s.client.QuerySMS(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("query sms failed: %w", err)
    }
    
    status := &MessageStatus{
        MessageID:   messageID,
        Status:      convertSMSStatus(resp.Status),
        Description: resp.StatusDesc,
        SendTime:    resp.SendTime,
        DeliverTime: resp.DeliverTime,
    }
    
    return status, nil
}

// convertSMSStatus 转换短信状态
func convertSMSStatus(status string) MessageState {
    switch status {
    case "SUCCESS":
        return MessageStateDelivered
    case "FAILED":
        return MessageStateFailed
    default:
        return MessageStateSent
    }
}

邮件渠道适配器

go 复制代码
// EmailChannel 邮件渠道适配器
type EmailChannel struct {
    // 邮件服务商客户端
    client EmailServiceClient
    
    // 渠道配置
    config *EmailConfig
    
    // 渠道名称
    name string
}

// NewEmailChannel 创建邮件渠道适配器
func NewEmailChannel(name string, config *EmailConfig) *EmailChannel {
    return &EmailChannel{
        client: NewEmailServiceClient(config),
        config: config,
        name:   name,
    }
}

func (e *EmailChannel) ChannelName() string {
    return e.name
}

func (e *EmailChannel) Send(ctx context.Context, message *Message) (*SendResult, error) {
    // 构造邮件发送请求
    req := &EmailSendRequest{
        From:    e.config.FromAddress,
        To:      message.To,
        Subject: message.Subject,
        Content: message.Content,
        // 如果有模板ID,则使用模板发送
        TemplateID: message.TemplateID,
        TemplateParams: func() map[string]interface{} {
            params := make(map[string]interface{})
            for k, v := range message.TemplateParams {
                params[k] = v
            }
            return params
        }(),
    }
    
    // 发送邮件
    resp, err := e.client.SendEmail(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("send email failed: %w", err)
    }
    
    // 构造发送结果
    result := &SendResult{
        MessageID:   resp.MessageID,
        Status:      SendStatusSuccess,
        RawResponse: resp,
    }
    
    // 检查发送结果
    if !resp.Success {
        result.Status = SendStatusFailed
        result.Error = resp.ErrorMessage
    }
    
    return result, nil
}

func (e *EmailChannel) BatchSend(ctx context.Context, messages []*Message) ([]*SendResult, error) {
    // 邮件批量发送实现
    var results []*SendResult
    for _, msg := range messages {
        result, err := e.Send(ctx, msg)
        if err != nil {
            result = &SendResult{
                Status: SendStatusFailed,
                Error:  err.Error(),
            }
        }
        results = append(results, result)
    }
    return results, nil
}

func (e *EmailChannel) Query(ctx context.Context, messageID string) (*MessageStatus, error) {
    // 查询邮件状态
    req := &EmailQueryRequest{
        MessageID: messageID,
    }
    
    resp, err := e.client.QueryEmail(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("query email failed: %w", err)
    }
    
    status := &MessageStatus{
        MessageID:   messageID,
        Status:      convertEmailStatus(resp.Status),
        Description: resp.StatusDescription,
        SendTime:    resp.SendTime,
        DeliverTime: resp.DeliverTime,
    }
    
    return status, nil
}

// convertEmailStatus 转换邮件状态
func convertEmailStatus(status string) MessageState {
    switch status {
    case "DELIVERED":
        return MessageStateDelivered
    case "FAILED":
        return MessageStateFailed
    case "SENT":
        return MessageStateSent
    default:
        return MessageStateUnknown
    }
}

渠道管理器

为了更好地管理各种渠道适配器,我们需要一个渠道管理器:

go 复制代码
// ChannelManager 渠道管理器
type ChannelManager struct {
    // 渠道映射
    channels map[string]NotificationChannel
    
    // 默认渠道
    defaultChannel NotificationChannel
    
    // 互斥锁
    mutex sync.RWMutex
}

// NewChannelManager 创建渠道管理器
func NewChannelManager() *ChannelManager {
    return &ChannelManager{
        channels: make(map[string]NotificationChannel),
    }
}

// RegisterChannel 注册渠道
func (cm *ChannelManager) RegisterChannel(channel NotificationChannel) {
    cm.mutex.Lock()
    defer cm.mutex.Unlock()
    
    name := channel.ChannelName()
    cm.channels[name] = channel
    
    // 如果还没有默认渠道,则将第一个注册的渠道设为默认渠道
    if cm.defaultChannel == nil {
        cm.defaultChannel = channel
    }
}

// GetChannel 获取渠道
func (cm *ChannelManager) GetChannel(name string) NotificationChannel {
    cm.mutex.RLock()
    defer cm.mutex.RUnlock()
    
    if channel, exists := cm.channels[name]; exists {
        return channel
    }
    
    return nil
}

// GetDefaultChannel 获取默认渠道
func (cm *ChannelManager) GetDefaultChannel() NotificationChannel {
    cm.mutex.RLock()
    defer cm.mutex.RUnlock()
    
    return cm.defaultChannel
}

// GetAllChannels 获取所有渠道
func (cm *ChannelManager) GetAllChannels() []NotificationChannel {
    cm.mutex.RLock()
    defer cm.mutex.RUnlock()
    
    var channels []NotificationChannel
    for _, channel := range cm.channels {
        channels = append(channels, channel)
    }
    
    return channels
}

使用示例

go 复制代码
// 初始化渠道管理器
channelManager := NewChannelManager()

// 注册短信渠道
smsConfig := &SMSConfig{
    AccessKeyID:     "your-access-key-id",
    AccessKeySecret: "your-access-key-secret",
    SignName:        "your-sign-name",
    Endpoint:        "your-sms-endpoint",
}
smsChannel := NewSMSChannel("aliyun_sms", smsConfig)
channelManager.RegisterChannel(smsChannel)

// 注册邮件渠道
emailConfig := &EmailConfig{
    SMTPHost:     "smtp.example.com",
    SMTPPort:     587,
    SMTPUsername: "your-username",
    SMTPPassword: "your-password",
    FromAddress:  "no-reply@example.com",
}
emailChannel := NewEmailChannel("smtp_email", emailConfig)
channelManager.RegisterChannel(emailChannel)

// 使用渠道发送消息
message := &Message{
    To:      []string{"13800138000"},
    Content: "您的验证码是:123456",
}

channel := channelManager.GetChannel("aliyun_sms")
if channel != nil {
    result, err := channel.Send(context.Background(), message)
    if err != nil {
        log.Printf("发送短信失败: %v", err)
    } else {
        log.Printf("短信发送结果: %+v", result)
    }
}

总结

通过适配器模式,我们成功地解决了多渠道消息适配的难题。这种设计的优势包括:

  1. 统一接口:所有渠道都实现了相同的接口,上层调用无需关心具体实现
  2. 易于扩展:新增渠道只需实现NotificationChannel接口
  3. 松耦合:各渠道实现相互独立,互不影响
  4. 可替换性:可以轻松替换不同服务商的实现

在下一节中,我们将深入探讨事务消息的设计与实现,确保关键业务通知的可靠送达。

相关推荐
@atweiwei1 天前
深入解析gRPC服务发现机制
微服务·云原生·rpc·go·服务发现·consul
Mgx2 天前
我在 Mac 写了个服务,硬要它在 18 岁高龄的 Windows 服务器上跑,结果…
go
少林码僧2 天前
1.1 一个架构师竟然这样设计通知平台,解决了所有业务方的痛点!
go
咬_咬2 天前
go语言学习(环境安装,第一个go程序)
开发语言·学习·golang·go·goland
人间打气筒(Ada)3 天前
「码动四季·开源同行」golang:负载均衡如何提高系统可用性?
算法·golang·开源·go·负载均衡·负载均衡算法
牛奔4 天前
Go + Vue 接入行为验证码完整指南
go
人间打气筒(Ada)4 天前
go:如何实现接口限流和降级?
开发语言·中间件·go·限流·etcd·配置中心·降级
我叫黑大帅5 天前
Go 中最强大的权限控制库(Casbin)
后端·面试·go
古城小栈5 天前
Jenkins+K8s实现Go后端服务自动化部署
go·k8s·jenkins