2.1 业务方接入方案:让所有团队都能快速集成通知服务

1 阅读8分钟

2.1 业务方接入方案:让所有团队都能快速集成通知服务

引言

在构建平台类服务时,如何让业务方能够快速、安全地接入是至关重要的。一个好的接入方案不仅需要提供简单易用的API,还需要具备完善的安全机制、灵活的配置管理以及良好的文档支持。

本节我们将深入探讨通知平台的业务方接入方案设计,包括认证授权机制、API设计、配置管理、SDK开发等方面,确保所有团队都能快速集成通知服务。

接入方案的核心要素

一个优秀的业务方接入方案应该具备以下核心要素:

  1. 简单易用:提供简洁明了的API接口和完整的文档
  2. 安全可靠:实现完善的认证授权机制,保护平台安全
  3. 灵活配置:支持业务方根据自身需求进行个性化配置
  4. 监控告警:提供完整的监控和告警机制
  5. SDK支持:提供多种语言的SDK,降低接入成本

认证与授权机制

安全是平台服务的基石。我们需要设计一套完善的认证与授权机制来保护平台资源。

API密钥认证

``go // APIKeyAuth API密钥认证 type APIKeyAuth struct { store APIKeyStore }

// APIKey API密钥 type APIKey struct { ID string json:"id" gorm:"primary_key" BusinessID string json:"business_id" gorm:"index" Key string json:"key" gorm:"unique_index" Name string json:"name" Status string json:"status" // active, inactive, expired Permissions []string json:"permissions" // 权限列表 CreatedAt time.Time json:"created_at" UpdatedAt time.Time json:"updated_at" ExpiredAt time.Time json:"expired_at" }

// Authenticate 认证 func (a *APIKeyAuth) Authenticate(apiKey string) (*APIKey, error) { // 1. 从存储中获取API密钥 key, err := a.store.GetAPIKey(apiKey) if err != nil { return nil, fmt.Errorf("invalid api key: %v", err) }

// 2. 检查密钥状态
if key.Status != "active" {
    return nil, fmt.Errorf("api key is not active: %s", key.Status)
}

// 3. 检查是否过期
if time.Now().After(key.ExpiredAt) {
    return nil, errors.New("api key has expired")
}

return key, nil

}

// Authorize 授权 func (a *APIKeyAuth) Authorize(apiKey APIKey, permission string) bool { // 检查是否拥有指定权限 for _, p := range apiKey.Permissions { if p == permission || p == "" { return true } }

return false

}


### JWT Token认证

对于更复杂的场景,我们可以使用JWT Token认证:

``go
// JWTAuth JWT认证
type JWTAuth struct {
    secretKey []byte
    store     BusinessStore
}

// Business 业务方信息
type Business struct {
    ID          string   `json:"id" gorm:"primary_key"`
    Name        string   `json:"name"`
    Status      string   `json:"status"` // active, inactive, suspended
    Permissions []string `json:"permissions"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
}

// GenerateToken 生成Token
func (j *JWTAuth) GenerateToken(businessID string, expireDuration time.Duration) (string, error) {
    // 1. 获取业务方信息
    business, err := j.store.GetBusiness(businessID)
    if err != nil {
        return "", fmt.Errorf("failed to get business: %v", err)
    }
    
    // 2. 检查业务方状态
    if business.Status != "active" {
        return "", fmt.Errorf("business is not active: %s", business.Status)
    }
    
    // 3. 生成JWT Token
    claims := &JWTClaims{
        BusinessID:  businessID,
        Permissions: business.Permissions,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(expireDuration).Unix(),
            IssuedAt:  time.Now().Unix(),
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(j.secretKey)
}

// ValidateToken 验证Token
func (j *JWTAuth) ValidateToken(tokenString string) (*JWTClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
        return j.secretKey, nil
    })
    
    if err != nil {
        return nil, fmt.Errorf("failed to parse token: %v", err)
    }
    
    if !token.Valid {
        return nil, errors.New("invalid token")
    }
    
    claims, ok := token.Claims.(*JWTClaims)
    if !ok {
        return nil, errors.New("invalid token claims")
    }
    
    return claims, nil
}

// JWTClaims JWT声明
type JWTClaims struct {
    BusinessID  string   `json:"business_id"`
    Permissions []string `json:"permissions"`
    jwt.StandardClaims
}

认证中间件

``go // AuthManager 认证管理器 type AuthManager struct { apiKeyAuth *APIKeyAuth jwtAuth *JWTAuth }

// AuthMiddleware 认证中间件 func (a *AuthManager) AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 1. 从请求头获取认证信息 authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"}) c.Abort() return }

    // 2. 解析认证信息
    parts := strings.SplitN(authHeader, " ", 2)
    if len(parts) != 2 {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization header"})
        c.Abort()
        return
    }
    
    authType := parts[0]
    authValue := parts[1]
    
    var businessID string
    var permissions []string
    
    // 3. 根据认证类型进行认证
    switch authType {
    case "ApiKey":
        // API密钥认证
        apiKey, err := a.apiKeyAuth.Authenticate(authValue)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
            c.Abort()
            return
        }
        businessID = apiKey.BusinessID
        permissions = apiKey.Permissions
        
    case "Bearer":
        // JWT Token认证
        claims, err := a.jwtAuth.ValidateToken(authValue)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
            c.Abort()
            return
        }
        businessID = claims.BusinessID
        permissions = claims.Permissions
        
    default:
        c.JSON(http.StatusUnauthorized, gin.H{"error": "unsupported authorization type"})
        c.Abort()
        return
    }
    
    // 4. 将认证信息存储到上下文中
    c.Set("business_id", businessID)
    c.Set("permissions", permissions)
    
    c.Next()
}

}


## API接口设计

良好的API设计是业务方快速接入的关键。我们需要提供简洁、一致、易用的API接口。

### RESTful API设计

``go
// NotificationController 通知控制器
type NotificationController struct {
    notificationService *NotificationService
    transactionalService *TransactionalMessageSender
    authManager *AuthManager
}

// SendRequest 发送请求
type SendRequest struct {
    Channel    string            `json:"channel" binding:"required"`
    Receivers  []string          `json:"receivers" binding:"required"`
    Content    string            `json:"content"`
    TemplateID string            `json:"template_id"`
    Params     map[string]string `json:"params"`
    Priority   int               `json:"priority"`
}

// SendResponse 发送响应
type SendResponse struct {
    TaskID string `json:"task_id"`
    Status string `json:"status"`
}

// Send 普通发送接口
func (nc *NotificationController) Send(c *gin.Context) {
    var req SendRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    businessID, _ := c.Get("business_id")
    
    // 构造通知请求
    notificationReq := &NotificationRequest{
        BusinessID: businessID.(string),
        Channel:    req.Channel,
        Receivers:  req.Receivers,
        Content:    req.Content,
        TemplateID: req.TemplateID,
        Params:     req.Params,
        Priority:   req.Priority,
    }
    
    // 发送通知
    resp, err := nc.notificationService.SendNotification(c, notificationReq)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, SendResponse{
        TaskID: resp.TaskID,
        Status: resp.Status,
    })
}

// TransactionalSendRequest 事务发送请求
type TransactionalSendRequest struct {
    Channel    string            `json:"channel" binding:"required"`
    Receivers  []string          `json:"receivers" binding:"required"`
    Content    string            `json:"content"`
    TemplateID string            `json:"template_id"`
    Params     map[string]string `json:"params"`
}

// TransactionalSendResponse 事务发送响应
type TransactionalSendResponse struct {
    MessageID string    `json:"message_id"`
    Status    string    `json:"status"`
    ExpireAt  time.Time `json:"expire_at"`
}

// PreSend 预发送接口
func (nc *NotificationController) PreSend(c *gin.Context) {
    var req TransactionalSendRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    businessID, _ := c.Get("business_id")
    
    // 构造事务消息请求
    transactionReq := &TransactionMessageRequest{
        BusinessID: businessID.(string),
        Channel:    req.Channel,
        Receivers:  req.Receivers,
        Content:    req.Content,
        TemplateID: req.TemplateID,
        Params:     req.Params,
    }
    
    // 预发送消息
    resp, err := nc.transactionalService.PreSendMessage(transactionReq)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, TransactionalSendResponse{
        MessageID: resp.MessageID,
        Status:    resp.Status,
        ExpireAt:  resp.ExpireAt,
    })
}

// ConfirmRequest 确认请求
type ConfirmRequest struct {
    MessageID string `json:"message_id" binding:"required"`
}

// ConfirmResponse 确认响应
type ConfirmResponse struct {
    MessageID string    `json:"message_id"`
    Status    string    `json:"status"`
    SendAt    time.Time `json:"send_at"`
}

// Confirm 确认发送接口
func (nc *NotificationController) Confirm(c *gin.Context) {
    var req ConfirmRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // 确认发送消息
    resp, err := nc.transactionalService.ConfirmSendMessage(req.MessageID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, ConfirmResponse{
        MessageID: resp.MessageID,
        Status:    resp.Status,
        SendAt:    resp.SendAt,
    })
}

API路由配置

``go // SetupRoutes 配置路由 func SetupRoutes(router *gin.Engine, controller *NotificationController) { // 公共路由 router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) })

// 受保护的路由组
protected := router.Group("/")
protected.Use(controller.authManager.AuthMiddleware())
{
    // 普通发送接口
    protected.POST("/send", controller.Send)
    
    // 事务消息接口
    protected.POST("/transactional/pre-send", controller.PreSend)
    protected.POST("/transactional/confirm", controller.Confirm)
    protected.POST("/transactional/cancel", controller.Cancel)
    
    // 查询接口
    protected.GET("/message/:task_id", controller.QueryMessage)
    protected.GET("/message/:task_id/status", controller.QueryStatus)
}

}


## 配置管理

为了让业务方能够灵活配置通知服务,我们需要提供完善的配置管理功能。

### 业务方配置

``go
// BusinessConfig 业务方配置
type BusinessConfig struct {
    BusinessID     string          `json:"business_id" gorm:"primary_key"`
    RateLimit      *RateLimitConfig `json:"rate_limit"`      // 限流配置
    ChannelConfigs []*ChannelConfig `json:"channel_configs"` // 渠道配置
    TemplateConfigs []*TemplateConfig `json:"template_configs"` // 模板配置
    CreatedAt      time.Time       `json:"created_at"`
    UpdatedAt      time.Time       `json:"updated_at"`
}

// RateLimitConfig 限流配置
type RateLimitConfig struct {
    GlobalLimit    int           `json:"global_limit"`    // 全局限流
    PerChannelLimit map[string]int `json:"per_channel_limit"` // 每个渠道的限流
    WindowDuration time.Duration `json:"window_duration"` // 时间窗口
}

// ChannelConfig 渠道配置
type ChannelConfig struct {
    Channel     string `json:"channel"`
    Enabled     bool   `json:"enabled"`     // 是否启用
    MaxPriority int    `json:"max_priority"` // 最大优先级
}

// TemplateConfig 模板配置
type TemplateConfig struct {
    TemplateID string            `json:"template_id"`
    Channel    string            `json:"channel"`
    Content    string            `json:"content"`
    Params     []string          `json:"params"`
    Variables  map[string]string `json:"variables"`
}

// ConfigManager 配置管理器
type ConfigManager struct {
    store ConfigStore
    cache *cache.Cache
}

// GetBusinessConfig 获取业务方配置
func (cm *ConfigManager) GetBusinessConfig(businessID string) (*BusinessConfig, error) {
    // 1. 先从缓存中获取
    if config, found := cm.cache.Get(businessID); found {
        return config.(*BusinessConfig), nil
    }
    
    // 2. 从存储中获取
    config, err := cm.store.GetBusinessConfig(businessID)
    if err != nil {
        return nil, fmt.Errorf("failed to get business config: %v", err)
    }
    
    // 3. 存入缓存
    cm.cache.Set(businessID, config, 5*time.Minute)
    
    return config, nil
}

// UpdateBusinessConfig 更新业务方配置
func (cm *ConfigManager) UpdateBusinessConfig(businessID string, config *BusinessConfig) error {
    // 1. 更新存储
    if err := cm.store.UpdateBusinessConfig(businessID, config); err != nil {
        return fmt.Errorf("failed to update business config: %v", err)
    }
    
    // 2. 更新缓存
    cm.cache.Set(businessID, config, 5*time.Minute)
    
    return nil
}

SDK开发

为了降低业务方的接入成本,我们需要提供多种语言的SDK。

Go SDK示例

``go // NotificationClient 通知客户端 type NotificationClient struct { baseURL string apiKey string httpClient *http.Client }

// NewNotificationClient 创建通知客户端 func NewNotificationClient(baseURL, apiKey string) *NotificationClient { return &NotificationClient{ baseURL: baseURL, apiKey: apiKey, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } }

// Send 发送通知 func (nc *NotificationClient) Send(req *SendRequest) (*SendResponse, error) { // 构造请求 body, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %v", err) }

// 发送HTTP请求
httpResponse, err := nc.makeRequest("POST", "/send", body)
if err != nil {
    return nil, fmt.Errorf("failed to send request: %v", err)
}
defer httpResponse.Body.Close()

// 解析响应
var resp SendResponse
if err := json.NewDecoder(httpResponse.Body).Decode(&resp); err != nil {
    return nil, fmt.Errorf("failed to decode response: %v", err)
}

return &resp, nil

}

// PreSend 预发送事务消息 func (nc *NotificationClient) PreSend(req *TransactionalSendRequest) (*TransactionalSendResponse, error) { // 构造请求 body, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %v", err) }

// 发送HTTP请求
httpResponse, err := nc.makeRequest("POST", "/transactional/pre-send", body)
if err != nil {
    return nil, fmt.Errorf("failed to send request: %v", err)
}
defer httpResponse.Body.Close()

// 解析响应
var resp TransactionalSendResponse
if err := json.NewDecoder(httpResponse.Body).Decode(&resp); err != nil {
    return nil, fmt.Errorf("failed to decode response: %v", err)
}

return &resp, nil

}

// makeRequest 发送HTTP请求 func (nc *NotificationClient) makeRequest(method, path string, body []byte) (*http.Response, error) { // 构造URL url := fmt.Sprintf("%s%s", nc.baseURL, path)

// 构造请求
var req *http.Request
var err error

if body != nil {
    req, err = http.NewRequest(method, url, bytes.NewBuffer(body))
} else {
    req, err = http.NewRequest(method, url, nil)
}

if err != nil {
    return nil, fmt.Errorf("failed to create request: %v", err)
}

// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "ApiKey "+nc.apiKey)

// 发送请求
return nc.httpClient.Do(req)

}


### 使用示例

``go
// 业务方使用示例
func main() {
    // 1. 创建客户端
    client := NewNotificationClient("https://api.notification.com", "your-api-key")
    
    // 2. 发送普通通知
    sendReq := &SendRequest{
        Channel:   "sms",
        Receivers: []string{"13800138000"},
        Content:   "您的验证码是:123456",
        Priority:  1,
    }
    
    sendResp, err := client.Send(sendReq)
    if err != nil {
        log.Printf("Failed to send notification: %v", err)
        return
    }
    
    log.Printf("Notification sent, task ID: %s", sendResp.TaskID)
    
    // 3. 发送事务消息
    transReq := &TransactionalSendRequest{
        Channel:   "email",
        Receivers: []string{"user@example.com"},
        TemplateID: "order_confirmation",
        Params: map[string]string{
            "order_id": "123456",
            "amount":   "99.99",
        },
    }
    
    transResp, err := client.PreSend(transReq)
    if err != nil {
        log.Printf("Failed to pre-send transactional message: %v", err)
        return
    }
    
    log.Printf("Transactional message pre-sent, message ID: %s", transResp.MessageID)
    
    // 4. 确认发送(在业务逻辑处理完成后)
    confirmReq := &ConfirmRequest{
        MessageID: transResp.MessageID,
    }
    
    confirmResp, err := client.Confirm(confirmReq)
    if err != nil {
        log.Printf("Failed to confirm transactional message: %v", err)
        return
    }
    
    log.Printf("Transactional message confirmed, send at: %v", confirmResp.SendAt)
}

总结

通过本节的学习,我们了解了如何设计一个完善的业务方接入方案:

  1. 认证授权机制:通过API密钥和JWT Token两种方式实现安全认证
  2. API接口设计:提供简洁一致的RESTful API接口
  3. 配置管理:支持业务方灵活配置各种参数
  4. SDK开发:提供多种语言的SDK降低接入成本

这套接入方案能够满足不同业务方的需求,既保证了平台的安全性,又提供了良好的用户体验。在实际应用中,我们可以根据具体业务场景对这套方案进行调整和优化。

在下一节中,我们将探讨安全防护体系,防止API被恶意调用和刷量。