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)
}
}
总结
通过适配器模式,我们成功地解决了多渠道消息适配的难题。这种设计的优势包括:
- 统一接口:所有渠道都实现了相同的接口,上层调用无需关心具体实现
- 易于扩展:新增渠道只需实现NotificationChannel接口
- 松耦合:各渠道实现相互独立,互不影响
- 可替换性:可以轻松替换不同服务商的实现
在下一节中,我们将深入探讨事务消息的设计与实现,确保关键业务通知的可靠送达。