🏗️《电商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()}")
五、设计要点
- 策略模式解耦鉴权:4种策略互不依赖,新增平台只需写1个策略实现
- 责任链组合限流:日配额+令牌桶+预充值可叠加,按and逻辑裁决
- 重试策略统一:429走指数退避+jitter,5xx走快速失败,AuthError不重试
- 平台注册表:新增平台=1行register_platform(),核心代码零改
- 统一异常:调用方只catch GatewayError,不用关心底层是淘宝还是亚马逊
六、和前几篇的衔接
把本篇 ApiGateway 作为前篇 four_platform_middleware 的底层传输层:
每个Adapter不再自己写签名/限流/重试,而是调 gw.call(platform, method, params)
新增快手/苏宁/微店 = 在 PLATFORM_REGISTRY 加3行配置
三重守卫(前篇)的熔断逻辑嵌入 RateLimiter.acquire() 中
一个网关,九家统一,新增平台不改核心代码。
要不要我把 ApiGateway 扩成 异步版本(asyncio+aiohttp)+ Redis中心化限流(多容器共享)+ 调用链追踪(OpenTelemetry),直接替换你前面所有Adapter的底层传输实现?