1. 电商返利系统的多联盟API对接挑战
在电商返利系统的开发过程中,最头疼的问题莫过于要同时对接多个电商平台的联盟API。淘宝联盟、京东联盟、拼多多联盟这些平台,每个都有自己的接口规范、参数格式和签名机制。就拿最简单的商品查询接口来说:
- 淘宝联盟用的是Taobao Open API,需要传app_key、timestamp这些参数,返回XML格式数据
- 京东联盟则是HTTP+JSON接口,要求每个请求都要带签名
- 拼多多的API又完全是另一套玩法
更麻烦的是,这些平台的SDK和接口还经常升级变动。记得去年双11前,淘宝突然更新了商品详情接口,导致我们系统直接瘫痪了半天。这种强耦合的架构让维护成本居高不下,每次对接新平台或者接口变动,都得把业务逻辑改一遍。
2. 适配器模式的核心设计思路
2.1 统一接口抽象
我们采用适配器模式(Adapter Pattern)来解决这个问题。首先定义一个统一的联盟接口UnionApi,抽象出两个核心能力:
public interface UnionApi { // 商品查询 UnionItem queryItem(String itemId); // 生成推广链接 UnionLink generatePromotionLink(String itemId, String adzoneId); }这个接口就像是各种联盟API的"通用插座",不管底层对接的是哪个平台,对外都提供统一的调用方式。UnionItem和UnionLink则是我们自定义的统一数据模型:
public class UnionItem { private String itemId; // 商品ID private String title; // 商品标题 private BigDecimal couponAmount; // 优惠券金额 private BigDecimal commissionRate; // 佣金比例 // 其他字段... }2.2 具体平台适配器实现
对于每个电商平台,我们实现一个对应的适配器类。以淘宝联盟为例:
@Component("taobaoUnionApi") public class TaobaoUnionApiAdapter implements UnionApi { @Value("${union.taobao.app-key}") private String appKey; @Override public UnionItem queryItem(String itemId) { // 调用淘宝官方SDK DefaultTaobaoClient client = new DefaultTaobaoClient(API_URL, appKey, appSecret); TbkItemInfoGetRequest req = new TbkItemInfoGetRequest(); req.setNumIids(itemId); // 将淘宝返回的数据转换为我们统一的UnionItem TbkItemInfoGetResponse resp = client.execute(req); UnionItem item = new UnionItem(); item.setItemId(resp.getResults().get(0).getNumIid()); item.setTitle(resp.getResults().get(0).getTitle()); // 其他字段转换... return item; } }京东联盟的适配器稍有不同,因为京东提供的是HTTP接口:
@Component("jdUnionApi") public class JdUnionApiAdapter implements UnionApi { @Override public UnionItem queryItem(String skuId) { // 构造京东API请求 String url = "https://router.jd.com/api"; Map<String, String> params = new HashMap<>(); params.put("method", "jingdong.service.promotion.get"); params.put("skuIds", skuId); // 添加签名等必要参数 // 发送请求并转换响应 ResponseEntity<Map> resp = restTemplate.getForEntity(url, Map.class, params); Map data = (Map) resp.getBody().get("jingdong_service_promotion_get_responce"); // 转换为统一模型 UnionItem item = new UnionItem(); item.setItemId(skuId); item.setTitle((String) data.get("wareName")); // 其他字段... return item; } }3. 上下文路由与动态调用
3.1 平台路由工厂
为了让业务代码能透明地调用不同平台的适配器,我们实现了一个UnionApiContext:
@Component public class UnionApiContext { @Resource private Map<String, UnionApi> unionApiBeans; // Spring会自动注入所有UnionApi实现 public UnionApi getApi(String platform) { switch (platform.toLowerCase()) { case "taobao": return unionApiBeans.get("taobaoUnionApi"); case "jd": return unionApiBeans.get("jdUnionApi"); case "pdd": return unionApiBeans.get("pddUnionApi"); default: throw new IllegalArgumentException("Unsupported platform"); } } }3.2 业务层调用示例
业务代码只需要知道要调用哪个平台,完全不用关心具体API细节:
@Service public class ItemService { private final UnionApiContext unionApiContext; public UnionItem getItemInfo(String platform, String itemId) { UnionApi api = unionApiContext.getApi(platform); return api.queryItem(itemId); } public UnionLink generateLink(String platform, String itemId, String adzoneId) { UnionApi api = unionApiContext.getApi(platform); return api.generatePromotionLink(itemId, adzoneId); } }4. 实战中的经验与优化
4.1 性能优化技巧
在实际运行中,我们发现频繁创建API客户端会影响性能。比如淘宝的DefaultTaobaoClient每次创建都要初始化一些资源。优化方法是使用对象池:
@Component public class TaobaoClientPool { private final ObjectPool<DefaultTaobaoClient> pool; public TaobaoClientPool() { this.pool = new GenericObjectPool<>(new BasePooledObjectFactory<>() { @Override public DefaultTaobaoClient create() { return new DefaultTaobaoClient(API_URL, appKey, appSecret); } }); } public <T> T execute(Function<DefaultTaobaoClient, T> callback) { DefaultTaobaoClient client = pool.borrowObject(); try { return callback.apply(client); } catch (Exception e) { throw new RuntimeException("API调用失败", e); } finally { pool.returnObject(client); } } }适配器中可以这样使用:
public UnionItem queryItem(String itemId) { return clientPool.execute(client -> { TbkItemInfoGetRequest req = new TbkItemInfoGetRequest(); req.setNumIids(itemId); TbkItemInfoGetResponse resp = client.execute(req); // 转换逻辑... }); }4.2 异常处理策略
不同平台的异常处理方式也不统一。我们定义了统一的异常体系:
public class UnionApiException extends RuntimeException { private String platform; private String errorCode; // 构造方法等... } // 在适配器中转换异常 try { // 调用平台API } catch (TaobaoApiException e) { throw new UnionApiException("taobao", e.getErrorCode(), e.getMessage(), e); } catch (JdApiException e) { throw new UnionApiException("jd", e.getCode(), e.getMsg(), e); }业务层可以统一捕获UnionApiException,根据platform和errorCode做相应处理。
4.3 监控与日志
为了监控各平台API的调用情况,我们使用Spring AOP做了切面:
@Aspect @Component public class ApiMonitorAspect { @Around("execution(* juwatech.cn.union.spi.UnionApi.*(..))") public Object monitorApiCall(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); String methodName = pjp.getSignature().getName(); String platform = pjp.getTarget().getClass().getSimpleName() .replace("UnionApiAdapter", "").toLowerCase(); try { Object result = pjp.proceed(); long duration = System.currentTimeMillis() - start; log.info("{} API调用成功 - {}: {}ms", platform, methodName, duration); metrics.recordSuccess(platform, methodName, duration); return result; } catch (Exception e) { metrics.recordError(platform, methodName); throw e; } } }5. 扩展新平台的实践
当需要接入新平台时,比如最近要加的抖音电商,只需要:
- 实现UnionApi接口
- 添加新的适配器类
- 在UnionApiContext中添加路由逻辑
完全不需要修改现有业务代码。以抖音为例:
@Component("douyinUnionApi") public class DouyinUnionApiAdapter implements UnionApi { @Override public UnionItem queryItem(String itemId) { // 调用抖音联盟API DouyinProduct product = douyinClient.getProduct(itemId); // 转换为统一模型 UnionItem item = new UnionItem(); item.setItemId(product.getProductId()); item.setTitle(product.getTitle()); // 其他字段... return item; } } // 在UnionApiContext中添加 case "douyin": return unionApiBeans.get("douyinUnionApi");6. 配置管理的优化
随着对接平台增多,配置项也越来越多。我们改用配置中心管理:
# union-api-config taobao: app-key: ${TAOBAO_APP_KEY} app-secret: ${TAOBAO_APP_SECRET} api-url: https://eco.taobao.com/router/rest jd: app-key: ${JD_APP_KEY} secret: ${JD_SECRET} pid: ${JD_PID}适配器通过@ConfigurationProperties注入配置:
@ConfigurationProperties(prefix = "union.taobao") @Data public class TaobaoConfig { private String appKey; private String appSecret; private String apiUrl; } @Component public class TaobaoUnionApiAdapter { private final TaobaoConfig config; public TaobaoUnionApiAdapter(TaobaoConfig config) { this.config = config; } }7. 测试策略
为了确保各平台适配器的稳定性,我们设计了多层测试:
单元测试:针对每个适配器的方法
@Test public void testTaobaoQueryItem() { TaobaoUnionApiAdapter adapter = new TaobaoUnionApiAdapter(config); UnionItem item = adapter.queryItem("12345"); assertNotNull(item); assertEquals("12345", item.getItemId()); }集成测试:测试真实API调用
@SpringBootTest public class TaobaoApiIntegrationTest { @Autowired private UnionApi taobaoApi; @Test public void testRealApiCall() { UnionItem item = taobaoApi.queryItem("真实商品ID"); assertNotNull(item.getTitle()); } }契约测试:确保各平台返回的数据都能正确转换为统一模型
8. 缓存策略
为了减少API调用次数,我们增加了多级缓存:
本地缓存:使用Caffeine缓存常用商品
@Component public class CachedUnionApi implements UnionApi { private final UnionApi delegate; private final Cache<String, UnionItem> itemCache; public UnionItem queryItem(String itemId) { return itemCache.get(itemId, id -> delegate.queryItem(id)); } }分布式缓存:使用Redis缓存推广链接
public UnionLink generatePromotionLink(String itemId, String adzoneId) { String cacheKey = "promo:" + platform + ":" + itemId + ":" + adzoneId; return redisTemplate.opsForValue().get(cacheKey, () -> delegate.generatePromotionLink(itemId, adzoneId), 1, TimeUnit.HOURS); }
9. 限流与降级
为了防止API调用过量或被限流,我们实现了:
令牌桶限流:使用Resilience4j
RateLimiterConfig config = RateLimiterConfig.custom() .limitRefreshPeriod(Duration.ofSeconds(1)) .limitForPeriod(10) // 每秒10次 .build(); RateLimiter limiter = RateLimiter.of("taobao-api", config); public UnionItem queryItem(String itemId) { return limiter.executeSupplier(() -> { // 实际API调用 }); }熔断降级:当API不可用时返回缓存数据或默认值
CircuitBreaker breaker = CircuitBreaker.ofDefaults("taobao-api"); public UnionItem queryItem(String itemId) { return breaker.executeSupplier(() -> { try { return delegate.queryItem(itemId); } catch (Exception e) { return getCachedItem(itemId); // 降级逻辑 } }); }
10. 总结与展望
这套基于适配器模式的多联盟API对接方案,在我们系统中已经稳定运行两年多,对接了8个主流电商平台。最大的优势是:
- 业务代码与平台解耦:新增平台只需添加适配器,不改动业务逻辑
- 统一异常处理:所有平台异常转换为统一格式
- 便于扩展功能:如缓存、限流等可以统一添加
- 降低维护成本:平台API变更只需修改对应适配器
未来计划加入智能路由功能,根据各平台的接口稳定性、响应时间等指标,动态选择最优平台进行查询。同时探索使用GraphQL构建统一的API网关,进一步简化客户端调用。