做跨境电商系统快五年了,从最早的单平台爬虫到现在的多平台 API 对接,踩过的坑能写一本小册子。最近帮团队重构 bidfans 系统的日本电商对接层,感触挺深的,索性把这段时间的技术思考整理出来。
最早做日本代购业务的时候,我们只接了日本雅虎(yahoo auction)一个平台。那时候代码写得很直接,所有逻辑都揉在一个 service 里。后来业务扩展,要接 mercari(煤炉)、乐天中古、日亚(日本亚马逊),问题就来了 —— 每个平台的认证方式、请求格式、回调机制都不一样,改一个平台的逻辑生怕影响到另一个。
这时候就意识到,必须做一层抽象。我们的思路是:定义一套统一的内部领域模型,然后每个平台实现自己的 Adapter。上层业务只跟领域模型打交道,不关心底层是哪个平台。
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class AuctionItem:
item_id: str
title: str
current_price: float
currency: str
end_time: datetime
seller_id: str
platform: str # yahoo, mercari, rakuten, amazon
class PlatformAdapter(ABC):
@abstractmethod
def search_items(self, keyword: str, page: int = 1) -> List[AuctionItem]:
pass
@abstractmethod
def get_item_detail(self, item_id: str) -> Optional[AuctionItem]:
pass
@abstractmethod
def place_bid(self, item_id: str, price: float) -> bool:
pass这个基类定义了三个核心能力:搜索、详情、出价。每个平台的 Adapter 去实现它。比如雅虎代拍的实现走 Yahoo Auction API,煤炉秒切的实现走 Mercari 的官方接口。上层做煤炉自动代拍业务的时候,根本不用知道底层是怎么调的。
日本的电商平台认证机制各有各的脾气。日本雅虎用的是 OAuth 1.0a,mercari 用的是 OAuth 2.0 + 自定义签名,乐天中古干脆是 API Key + IP 白名单。如果每个 Adapter 自己管认证,代码会很乱。
我们的做法是抽一个 TokenManager 出来,统一管理各平台的令牌刷新、过期重试、签名生成。
class TokenManager:
def __init__(self):
self._token_store = {}
self._lock = threading.Lock()
def get_token(self, platform: str) -> str:
with self._lock:
token_info = self._token_store.get(platform)
if not token_info or self._is_expired(token_info):
token_info = self._refresh_token(platform)
self._token_store[platform] = token_info
return token_info["access_token"]
def _is_expired(self, token_info: dict) -> bool:
# 提前5分钟刷新,避免边界问题
return token_info["expires_at"] - time.time() < 300这里有个小细节:令牌过期判断要留余量。我们之前踩过坑 —— 刚好卡着过期时间去请求,网络延迟个几百毫秒,请求就失败了。提前 5 分钟刷新,稳定很多。
做日淘全品类平台的对接,网络抖动是家常便饭。特别是煤炉秒切这种对时效性要求高的场景,请求失败了要不要重试?重试会不会导致重复出价?
关键是幂等性。每个出价请求我们都会生成一个唯一的 request_id,存在 Redis 里。如果重试的时候发现这个 request_id 已经处理过了,就直接返回上次的结果,不会真的再发一次请求。
def place_bid_with_idempotency(self, item_id: str, price: float, request_id: str) -> bool:
redis_key = f"bid:idempotent:{request_id}"
# 尝试设置锁,成功说明是第一次处理
if redis_client.set(redis_key, "processing", ex=3600, nx=True):
try:
result = self.adapter.place_bid(item_id, price)
redis_client.set(redis_key, "success" if result else "failed", ex=86400)
return result
except Exception as e:
redis_client.delete(redis_key)
raise e
else:
# 已经处理过,等待结果
for _ in range(10):
status = redis_client.get(redis_key)
if status == "success":
return True
elif status == "failed":
return False
time.sleep(0.5)
raise TimeoutError("等待幂等结果超时")日本雅虎和 mercari 的 API 都有调用频率限制。雅虎代拍的限制大概是每分钟 60 次,煤炉的更严一些。如果不做好限流,轻则被降权,重则 API Key 被封。
我们用令牌桶算法做限流,每个平台独立配置速率。同时加了降级逻辑 —— 如果某个平台的 API 连续失败超过阈值,就暂时切换到备用方案(比如降级到爬虫模式),保证 bidfans 代拍代购的核心业务不受影响。
说个真实的例子:去年乐天中古的 API 改版,没提前通知,我们的系统连续报错。幸好有降级机制,自动切到了备用数据源,用户那边基本没感知到。运维同学查日志的时候才发现,已经自动切回去了。
做平台对接这件事,说难也难,说简单也简单。难的是每个平台都有自己的 "个性",文档写得参差不齐,坑得自己一个个踩。简单的是只要把抽象层做好,后面接新平台就快很多。
现在 bidfans 系统已经接了日本雅虎、mercari、乐天中古、日亚这几个主流平台,其实技术架构是通的,都是日淘全品类平台那套东西,差别只在具体的 API 细节。
如果你也在做类似的系统,或者对煤炉代购、乐天代购这些业务的技术实现感兴趣,欢迎在评论区交流。我后面还会写一写煤炉自动代拍的并发控制、日本直邮的物流追踪系统设计这些话题,感兴趣的话可以关注一下。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。