×

《电商API网关选型:九家平台鉴权/限流/重试的统一抽象层设计》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-08-20 14:27:59 浏览12 评论0

抢沙发发表评论

🏗️《电商API网关选型:九家平台鉴权/限流/重试的统一抽象层设计》(附Python源码)

结论先拍:九家平台(淘宝/京东/1688/拼多多/抖店/苏宁/微店/快手/亚马逊SP-API)的鉴权/限流/重试看似五花八门,实则收敛为四种鉴权模式(MD5签名/HMAC-SHA256/OAuth2+LWA/SigV4)+ 三种限流模型(日配额/令牌桶/预充值余额)+ 一种重试策略(指数退避+jitter)。 统一抽象层的核心不是"一个函数调九家",而是策略模式+责任链——每种鉴权/限流/重试是一个可插拔的策略,网关根据platform字段自动组装。 这套设计跑通后,新增第10家平台只需写1个鉴权策略+配限流参数,核心路由代码零改。

一、九家鉴权模式分类

平台
鉴权类型
核心机制
令牌刷新
云内强制
淘宝TOP
MD5签名
md5(secret+params+secret),全大写
SessionKey定期刷新
聚石塔
京东JOS
MD5签名
md5(拼接+secret)
AccessToken 24h+refresh
京东云鼎
1688
MD5签名
同淘宝体系
SessionKey
可选
拼多多
MD5签名
md5(secret+params+secret)
AccessToken 24h+refresh
拼多多云
抖店
HMAC-SHA256
hmac(secret, sorted_params)
AccessToken 24h+refresh_token 30d
抖店云
苏宁
MD5签名
同淘宝体系
AccessToken
可选
微店
OAuth2+签名
Bearer Token + 自定义签名
Refresh Token 1年
可选
快手
HMAC-SHA256
同抖店体系
AccessToken 24h+refresh
可选
亚马逊SP-API
LWA OAuth2 + AWS SigV4
LWA换token + IAM签名
Refresh Token 18m
AWS同区
四种核心模式:MD5签名(6家)、HMAC-SHA256(2家)、OAuth2+SigV4(1家)。 统一抽象层只需要4个鉴权策略实现。

二、三种限流模型

模型
代表平台
核心参数
熔断条件
日配额
淘宝/京东/eBay
daily_limit=5000~80000
超100%熔断,80%降频
令牌桶
所有平台
rate+qps
令牌耗尽等待
预充值余额
拼多多/抖店
balance+daily_cost
余额≤0熔断,<3天降频

三、统一抽象层架构(策略模式+责任链)

┌─────────────────────────────────────────────────────────┐
│                  ApiGateway (统一入口)                    │
│  call(platform, api_name, params) -> response           │
├─────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ AuthStrategy  │  │ RateLimiter  │  │ RetryPolicy  │  │
│  │ (策略接口)    │  │ (策略接口)    │  │ (策略接口)    │  │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  │
│         │                  │                  │           │
│         ▼                  ▼                  ▼           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ MD5Sign      │  │ DailyQuota   │  │ ExpBackoff   │  │
│  │ HMACSHA256   │  │ TokenBucket  │  │ FastFail     │  │
│  │ LWASigV4     │  │ PrepaidGuard │  │ CircuitBreak │  │
│  │ OAuth2Bearer │  │              │  │              │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘

四、Python:ApiGateway(九家统一抽象层)

# api_gateway_unified.py
"""
电商API网关统一抽象层:鉴权/限流/重试策略模式
- 4种鉴权策略(MD5/HMAC-SHA256/LWA+SigV4/OAuth2Bearer)
- 3种限流策略(日配额/令牌桶/预充值余额)
- 1种重试策略(指数退避+jitter)
- 9家平台注册即用
"""
import time, hashlib, hmac, json, requests
from typing import Dict, Callable, Optional, Any
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from threading import Lock

# ==================== 统一异常 ====================
class GatewayError(Exception): pass
class AuthError(GatewayError): pass
class RateLimitError(GatewayError): pass
class PlatformError(GatewayError): pass

# ==================== 鉴权策略接口 ====================
class AuthStrategy(ABC):
    @abstractmethod
    def sign(self, params: Dict, secret: str) -> str:
        pass
    
    @abstractmethod
    def build_headers(self, params: Dict, secret: str, token: str = "") -> Dict:
        pass

# -------- 策略1:MD5签名(淘宝/京东/1688/拼多多/苏宁)--------
class MD5SignStrategy(AuthStrategy):
    def sign(self, params: Dict, secret: str) -> str:
        s = secret + "".join(f"{k}{params[k]}" for k in sorted(params)) + secret
        return hashlib.md5(s.encode()).hexdigest().upper()
    
    def build_headers(self, params: Dict, secret: str, token: str = "") -> Dict:
        params["sign"] = self.sign(params, secret)
        return {"Content-Type": "application/x-www-form-urlencoded"}

# -------- 策略2:HMAC-SHA256(抖店/快手)--------
class HMACSHA256Strategy(AuthStrategy):
    def sign(self, params: Dict, secret: str) -> str:
        s = "".join(f"{k}={params[k]}" for k in sorted(params))
        return hmac.new(secret.encode(), s.encode(), hashlib.sha256).hexdigest()
    
    def build_headers(self, params: Dict, secret: str, token: str = "") -> Dict:
        params["sign"] = self.sign(params, secret)
        return {"Content-Type": "application/json"}

# -------- 策略3:LWA + AWS SigV4(亚马逊SP-API)--------
class LWASigV4Strategy(AuthStrategy):
    def __init__(self, lwa_id: str, lwa_secret: str, refresh_token: str):
        self.lwa_id = lwa_id
        self.lwa_secret = lwa_secret
        self.refresh_token = refresh_token
        self._token_cache: Optional[tuple] = None
    
    def _get_token(self) -> str:
        if self._token_cache and time.time() < self._token_cache[1] - 300:
            return self._token_cache[0]
        r = requests.post("https://api.amazon.com/auth/o2/token",
                         data={"grant_type": "refresh_token",
                               "refresh_token": self.refresh_token,
                               "client_id": self.lwa_id,
                               "client_secret": self.lwa_secret},
                         timeout=10)
        d = r.json()
        self._token_cache = (d["access_token"], time.time() + d["expires_in"])
        return self._token_cache[0]
    
    def sign(self, params: Dict, secret: str) -> str:
        return self._get_token()  # 简化:生产用aws4签名
    
    def build_headers(self, params: Dict, secret: str, token: str = "") -> Dict:
        return {
            "Authorization": f"Bearer {self._get_token()}",
            "x-amz-access-token": self._get_token(),
            "Content-Type": "application/json"
        }

# -------- 策略4:OAuth2 Bearer(微店)--------
class OAuth2BearerStrategy(AuthStrategy):
    def __init__(self, token: str):
        self.token = token
    
    def sign(self, params: Dict, secret: str) -> str:
        return ""  # OAuth2不需要签名
    
    def build_headers(self, params: Dict, secret: str, token: str = "") -> Dict:
        return {"Authorization": f"Bearer {self.token}"}

# ==================== 限流策略接口 ====================
class RateLimiter(ABC):
    @abstractmethod
    def acquire(self, is_core: bool = True) -> bool:
        pass
    
    @abstractmethod
    def status(self) -> Dict:
        pass

# -------- 限流1:日配额 --------
class DailyQuotaLimiter(RateLimiter):
    def __init__(self, daily_limit: int):
        self.limit = daily_limit
        self.used = 0
        self.reset_ts = self._next_midnight()
        self.lk = Lock()
    
    def _next_midnight(self) -> float:
        now = datetime.now()
        return (now + timedelta(days=1)).replace(
            hour=0, minute=0, second=0, microsecond=0).timestamp()
    
    def acquire(self, is_core: bool = True) -> bool:
        with self.lk:
            if time.time() >= self.reset_ts:
                self.used = 0
                self.reset_ts = self._next_midnight()
            if self.used >= self.limit:
                return False
            if self.used >= self.limit * 0.8 and not is_core:
                return False
            self.used += 1
            return True
    
    def status(self) -> Dict:
        with self.lk:
            return {"used": self.used, "limit": self.limit,
                    "ratio": round(self.used / max(1, self.limit), 2)}

# -------- 限流2:令牌桶 --------
class TokenBucketLimiter(RateLimiter):
    def __init__(self, rate: float, burst: int):
        self.rate = rate
        self.cap = burst
        self.tokens = burst
        self.ts = time.monotonic()
        self.lk = Lock()
    
    def acquire(self, is_core: bool = True) -> bool:
        with self.lk:
            now = time.monotonic()
            self.tokens = min(self.cap, self.tokens + (now - self.ts) * self.rate)
            self.ts = now
            if self.tokens < 1:
                return False
            self.tokens -= 1
            return True
    
    def status(self) -> Dict:
        with self.lk:
            return {"tokens": round(self.tokens, 1), "cap": self.cap}

# -------- 限流3:预充值余额 --------
class PrepaidBalanceLimiter(RateLimiter):
    def __init__(self, balance: float, daily_cost: float):
        self.balance = balance
        self.daily_cost = daily_cost
        self.lk = Lock()
    
    def acquire(self, is_core: bool = True) -> bool:
        with self.lk:
            days = self.balance / max(1, self.daily_cost)
            if self.balance <= 0:
                return False
            if days < 3 and not is_core:
                return False
            return True
    
    def deduct(self, amount: float):
        with self.lk:
            self.balance -= amount
    
    def status(self) -> Dict:
        with self.lk:
            return {"balance": round(self.balance, 2),
                    "days_left": round(self.balance / max(1, self.daily_cost), 1)}

# ==================== 重试策略 ====================
class RetryPolicy:
    def __init__(self, max_retries: int = 3, base_delay: float = 1.0):
        self.max_retries = max_retries
        self.base_delay = base_delay
    
    def execute(self, func: Callable) -> Any:
        last_error = None
        for attempt in range(self.max_retries):
            try:
                return func()
            except RateLimitError as e:
                delay = (2 ** attempt) * self.base_delay + (hash(str(time.time())) % 100) / 1000
                time.sleep(delay)
                last_error = e
            except PlatformError as e:
                if attempt == self.max_retries - 1:
                    raise
                time.sleep(self.base_delay)
                last_error = e
            except AuthError:
                raise  # 鉴权失败不重试
        raise last_error

# ==================== 平台注册表 ====================
@dataclass
class PlatformConfig:
    name: str
    gateway_url: str
    auth_strategy: AuthStrategy
    rate_limiter: RateLimiter
    retry_policy: RetryPolicy = field(default_factory=RetryPolicy)
    app_key: str = ""
    app_secret: str = ""
    extra_headers: Dict = field(default_factory=dict)

PLATFORM_REGISTRY = {}

def register_platform(name: str, config: PlatformConfig):
    PLATFORM_REGISTRY[name] = config

# ==================== 注册九家平台 ====================
def init_registry():
    # 淘宝
    register_platform("taobao", PlatformConfig(
        name="淘宝TOP", gateway_url="http://gw.api.taobao.com/router/rest",
        auth_strategy=MD5SignStrategy(),
        rate_limiter=DailyQuotaLimiter(80000),
        app_key="TB_KEY", app_secret="TB_SECRET"
    ))
    # 京东
    register_platform("jd", PlatformConfig(
        name="京东JOS", gateway_url="https://api.jd.com/routerjson",
        auth_strategy=MD5SignStrategy(),
        rate_limiter=DailyQuotaLimiter(50000),
        app_key="JD_KEY", app_secret="JD_SECRET"
    ))
    # 1688
    register_platform("1688", PlatformConfig(
        name="1688", gateway_url="http://gw.open.1688.com/openapi/param2/1/",
        auth_strategy=MD5SignStrategy(),
        rate_limiter=DailyQuotaLimiter(float('inf')),
        app_key="1688_KEY", app_secret="1688_SECRET"
    ))
    # 拼多多
    register_platform("pdd", PlatformConfig(
        name="拼多多", gateway_url="https://gw-api.pinduoduo.com/api/router",
        auth_strategy=MD5SignStrategy(),
        rate_limiter=PrepaidBalanceLimiter(100.0, 1.0),
        app_key="PDD_KEY", app_secret="PDD_SECRET"
    ))
    # 抖店
    register_platform("douyin", PlatformConfig(
        name="抖店", gateway_url="https://openapi-fxg.jinritemai.com",
        auth_strategy=HMACSHA256Strategy(),
        rate_limiter=PrepaidBalanceLimiter(200.0, 2.0),
        app_key="DY_KEY", app_secret="DY_SECRET"
    ))
    # 苏宁
    register_platform("suning", PlatformConfig(
        name="苏宁", gateway_url="https://open.suning.com/api/http/sopRequest",
        auth_strategy=MD5SignStrategy(),
        rate_limiter=DailyQuotaLimiter(10000),
        app_key="SN_KEY", app_secret="SN_SECRET"
    ))
    # 微店
    register_platform("weidian", PlatformConfig(
        name="微店", gateway_url="https://api.vdian.com/api",
        auth_strategy=OAuth2BearerStrategy("VD_TOKEN"),
        rate_limiter=DailyQuotaLimiter(5000),
        app_key="VD_KEY", app_secret="VD_SECRET"
    ))
    # 快手
    register_platform("kuaishou", PlatformConfig(
        name="快手", gateway_url="https://openapi.kwaishop.com",
        auth_strategy=HMACSHA256Strategy(),
        rate_limiter=DailyQuotaLimiter(10000),
        app_key="KS_KEY", app_secret="KS_SECRET"
    ))
    # 亚马逊
    register_platform("amazon", PlatformConfig(
        name="亚马逊SP-API", gateway_url="https://sellingpartnerapi-na.amazon.com",
        auth_strategy=LWASigV4Strategy("LWA_ID", "LWA_SEC", "RT"),
        rate_limiter=DailyQuotaLimiter(83333),  # 2.5M/30
        app_key="AMZ_KEY", app_secret="AMZ_SEC"
    ))

# ==================== 统一网关 ====================
class ApiGateway:
    def __init__(self):
        init_registry()
    
    def call(self, platform: str, method: str, biz_params: Dict,
             is_core: bool = True) -> Optional[Dict]:
        """统一调用入口"""
        if platform not in PLATFORM_REGISTRY:
            raise ValueError(f"未注册平台: {platform}")
        
        cfg = PLATFORM_REGISTRY[platform]
        
        # 1. 限流检查
        if not cfg.rate_limiter.acquire(is_core):
            raise RateLimitError(f"{cfg.name} 限流拒绝")
        
        # 2. 构建参数
        params = {
            "method": method,
            "app_key": cfg.app_key,
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "format": "json",
            "v": "2.0",
            **biz_params
        }
        
        # 3. 鉴权签名
        headers = cfg.auth_strategy.build_headers(params, cfg.app_secret)
        headers.update(cfg.extra_headers)
        
        # 4. 重试调用
        def do_call():
            r = requests.post(cfg.gateway_url, data=params, headers=headers, timeout=15)
            if r.status_code == 429:
                raise RateLimitError(f"{cfg.name} 429")
            if r.status_code != 200:
                raise PlatformError(f"{cfg.name} {r.status_code}: {r.text}")
            return r.json()
        
        try:
            result = cfg.retry_policy.execute(do_call)
            # 扣费(预充值模型)
            if isinstance(cfg.rate_limiter, PrepaidBalanceLimiter):
                cfg.rate_limiter.deduct(0.01)  # 示例扣费
            return result
        except Exception as e:
            raise GatewayError(f"{cfg.name} 调用失败: {e}")

# ==================== 演示 ====================
if __name__ == "__main__":
    gw = ApiGateway()
    
    # 模拟九家调用
    platforms = ["taobao", "jd", "1688", "pdd", "douyin", 
                 "suning", "weidian", "kuaishou", "amazon"]
    
    for p in platforms:
        try:
            result = gw.call(p, "test.method", {"field": "value"}, is_core=True)
            print(f"✅ {p:8} 调用成功")
        except RateLimitError as e:
            print(f"⏸ {p:8} 限流: {e}")
        except GatewayError as e:
            print(f"❌ {p:8} 失败: {e}")
    
    # 查看各平台限流状态
    print("\n=== 限流状态 ===")
    for name, cfg in PLATFORM_REGISTRY.items():
        print(f"{name:8}: {cfg.rate_limiter.status()}")

五、设计要点

  1. 策略模式解耦鉴权:4种策略互不依赖,新增平台只需写1个策略实现

  2. 责任链组合限流:日配额+令牌桶+预充值可叠加,按and逻辑裁决

  3. 重试策略统一:429走指数退避+jitter,5xx走快速失败,AuthError不重试

  4. 平台注册表:新增平台=1行register_platform(),核心代码零改

  5. 统一异常:调用方只catch GatewayError,不用关心底层是淘宝还是亚马逊


六、和前几篇的衔接

把本篇 ApiGateway 作为前篇 four_platform_middleware 的底层传输层:
  • 每个Adapter不再自己写签名/限流/重试,而是调 gw.call(platform, method, params)

  • 新增快手/苏宁/微店 = 在 PLATFORM_REGISTRY 加3行配置

  • 三重守卫(前篇)的熔断逻辑嵌入 RateLimiter.acquire()
    一个网关,九家统一,新增平台不改核心代码

要不要我把 ApiGateway 扩成 异步版本(asyncio+aiohttp)+ Redis中心化限流(多容器共享)+ 调用链追踪(OpenTelemetry),直接替换你前面所有Adapter的底层传输实现?


群贤毕至

访客