2.1 业务方接入方案:让所有团队都能快速集成通知服务
引言
在构建平台类服务时,如何让业务方能够快速、安全地接入是至关重要的。一个好的接入方案不仅需要提供简单易用的API,还需要具备完善的安全机制、灵活的配置管理以及良好的文档支持。
本节我们将深入探讨通知平台的业务方接入方案设计,包括认证授权机制、API设计、配置管理、SDK开发等方面,确保所有团队都能快速集成通知服务。
接入方案的核心要素
一个优秀的业务方接入方案应该具备以下核心要素:
- 简单易用:提供简洁明了的API接口和完整的文档
- 安全可靠:实现完善的认证授权机制,保护平台安全
- 灵活配置:支持业务方根据自身需求进行个性化配置
- 监控告警:提供完整的监控和告警机制
- 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)
}
总结
通过本节的学习,我们了解了如何设计一个完善的业务方接入方案:
- 认证授权机制:通过API密钥和JWT Token两种方式实现安全认证
- API接口设计:提供简洁一致的RESTful API接口
- 配置管理:支持业务方灵活配置各种参数
- SDK开发:提供多种语言的SDK降低接入成本
这套接入方案能够满足不同业务方的需求,既保证了平台的安全性,又提供了良好的用户体验。在实际应用中,我们可以根据具体业务场景对这套方案进行调整和优化。
在下一节中,我们将探讨安全防护体系,防止API被恶意调用和刷量。