
一、 为什么电商系统需要独立的API层电商系统通常服务多个前端Web端、H5、小程序、APP未来还可能接入第三方开发者。传统的前端直接调用后端Service模式会带来三个问题接口分散每个前端独立对接后端接口逻辑散落各处无法复用同样的功能在不同端重复开发无法开放第三方接入时需要重新梳理所有接口因此需要建设独立的API层——对内统一多端调用对外提供商业化能力。分层架构┌─────────────────────────────────────────────┐ │ 前端层Web │ H5 │ 小程序 │ APP │ 第三方 │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ API网关层 │ │ 鉴权 │ 限流 │ 路由 │ 日志 │ 灰度 │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ API服务层 │ │ 商品API │ 订单API │ 用户API │ 支付API │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 业务服务层微服务 │ │ 商品中心 │ 订单中心 │ 用户中心 │ 支付中心 │ └─────────────────────────────────────────────┘二、 API设计规范2.1 RESTful路径规范GET /api/v1/products → 商品列表 GET /api/v1/products/{id} → 商品详情 POST /api/v1/orders → 创建订单 GET /api/v1/orders/{id} → 订单详情 PUT /api/v1/orders/{id}/pay → 支付订单 DELETE /api/v1/cart/{skuId} → 删除购物车 版本管理/api/v1/ → 大版本迭代时升级至 /api/v2/2.2 统一响应格式json{ code: 0, msg: success, data: { orderId: ORD20240101001, status: PAID }, timestamp: 1704000000000, requestId: req-abc-123-def }状态码设计code范围含义示例0成功正常返回1xxx参数错误1001参数缺失 1002参数格式错误2xxx业务错误2001库存不足 2002订单不存在3xxx权限错误3001未登录 3002无权限5xxx系统错误5001系统繁忙 5002服务不可用2.3 字段命名规范规范示例使用小驼峰camelCaseorderId,userName,createTime时间统一使用时间戳毫秒1704000000000金额使用整数分9900表示 99.00 元布尔使用 is 前缀isPaid,isDeleted三、 统一鉴权3.1 三种认证方式方式适用端特点JWT TokenAPP、Web端无状态携带用户信息有效期7天Session CookieWeb端有状态依赖Redis存储AppKey AppSecret第三方开发者用于API调用可设置不同权限3.2 JWT认证实现javaComponent public class JwtAuthFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String token extractToken(request); if (token ! null) { try { Claims claims JwtUtils.parseToken(token); String userId claims.getSubject(); String tenantId claims.get(tenantId, String.class); // 注入上下文 UserContext.setUserId(userId); TenantContext.setTenantId(tenantId); } catch (ExpiredJwtException e) { response.setStatus(401); response.getWriter().write({\code\:3001,\msg\:\token已过期\}); return; } } chain.doFilter(request, response); } // Token生成 public String generateToken(String userId, String tenantId) { return Jwts.builder() .setSubject(userId) .claim(tenantId, tenantId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } }四、 开放平台API商业化4.1 开放API的使用场景┌─────────────────────────────────────────────────────────────┐ │ 外部开发者/第三方商家 │ │ ├─ ERP系统同步订单、商品、库存 │ │ ├─ 物流服务商获取发货信息、回传物流单号 │ │ ├─ 财务系统拉取对账单、交易流水 │ │ └─ 第三方应用扩展平台功能 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 开放平台网关 │ │ 认证 │ 限流 │ 计费 │ 监控 │ 日志 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 开放API服务层 │ │ 订单开放API │ 商品开放API │ 物流开放API │ └─────────────────────────────────────────────────────────────┘4.2 AppKey认证第三方开发者调用开放API需要持有AppKey AppSecret请求签名流程1. 开发者申请AppKey、AppSecret 2. 请求时携带appKey timestamp sign 3. sign MD5(appKey timestamp 请求参数 appSecret) 4. 服务端校验timestamp有效期、sign是否匹配 5. 通过后按AppKey关联的权限返回数据javaComponent public class OpenApiAuthFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, ...) { String appKey request.getHeader(X-App-Key); String timestamp request.getHeader(X-Timestamp); String sign request.getHeader(X-Sign); // 1. 校验timestamp防重放攻击 long now System.currentTimeMillis(); if (Math.abs(now - Long.parseLong(timestamp)) 60000) { throw new BusinessException(请求已过期); } // 2. 查询AppKey关联的AppSecret和权限 OpenApp app openAppService.getByAppKey(appKey); if (app null || !app.isEnabled()) { throw new BusinessException(AppKey无效或已禁用); } // 3. 计算签名并比对 String expectedSign calculateSign(request, app.getAppSecret()); if (!expectedSign.equals(sign)) { throw new BusinessException(签名错误); } // 4. 注入上下文 OpenApiContext.setAppKey(appKey); OpenApiContext.setPermissions(app.getPermissions()); OpenApiContext.setTenantId(app.getTenantId()); } }4.3 开放API限流第三方调用需要独立限流防止单个应用拖垮系统javaTarget(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OpenApiLimit { int value() default 100; // 每分钟最大请求数 } Aspect Component public class OpenApiLimitAspect { Around(annotation(openApiLimit)) public Object checkLimit(ProceedingJoinPoint joinPoint, OpenApiLimit openApiLimit) throws Throwable { String appKey OpenApiContext.getAppKey(); String apiName joinPoint.getSignature().getName(); String key openapi:limit: appKey : apiName; Long count redis.incr(key); if (count 1) { redis.expire(key, 60); } if (count openApiLimit.value()) { throw new RateLimitException(API调用次数超限请升级套餐); } return joinPoint.proceed(); } }4.4 开放API商业化定价套餐月调用次数同时在线价格免费版1000次1个应用¥0基础版1万次3个应用¥199/月专业版10万次10个应用¥999/月企业版100万次不限¥4999/月五、 接口版本管理API版本向后兼容是开放平台的底线能力——不能因为升级导致第三方应用挂掉。5.1 版本策略javaRestController RequestMapping(/api/v1/orders) public class OrderApiV1 { GetMapping(/{id}) public Result getOrder(PathVariable Long id) { // v1返回数据结构 OrderDTO order orderService.getById(id); return Result.success(order); } } RestController RequestMapping(/api/v2/orders) public class OrderApiV2 { GetMapping(/{id}) public Result getOrder(PathVariable Long id) { // v2返回扩展数据结构新增了售后状态 OrderDTOV2 order orderService.getByIdV2(id); return Result.success(order); } }5.2 版本过渡策略┌─────────────────────────────────────────────────────────────┐ │ 版本生命周期 │ │ │ │ v1.0 ──→ v1.1 ──→ v1.2 ──→ v2.0 ──→ v2.1 │ │ 发布 兼容 兼容 发布 │ │ ↓ ↓ │ │ 维护期内 开始迁移 │ │ │ │ 策略 │ │ 1. 新功能只在最新版本中提供 │ │ 2. 旧版本在最新版本发布后维护6个月 │ │ 3. 6个月后旧版本下线提前通知开发者迁移 │ └─────────────────────────────────────────────────────────────┘六、 API监控与SLA监控维度指标告警阈值请求量总QPS、各接口QPS环比增长 100%响应时间平均RT、TP99TP99 1000ms错误率HTTP 4xx/5xx占比错误率 1%限流触发限流拦截次数超过100次/分钟第三方调用各AppKey调用统计单应用流量异常对外SLA承诺商业化API需要明确的契约:SLA项承诺值可用性99.9%月度不可用不超过43分钟TP99响应时间 500ms错误率 0.5%赔偿标准月度费用 × 不可用时长比例七、 踩坑实录坑1版本升级导致第三方应用崩溃现象升级到v2版本后v1版本的响应体中删除一个废弃字段导致第三方应用解析失败。教训v1版本必须完全向后兼容不能删除字段、不能修改字段类型只能新增字段。修复恢复废弃字段标记为Deprecated在文档中说明将在v3版本移除。坑2签名算法被暴力枚举现象某恶意开发者用低强度签名算法的弱密钥暴力枚举AppSecret成功伪造了一个有效签名。修复升级签名算法增加时效性请求有效期为60秒且加入随机nonce防重放。javapublic String calculateSign(MapString, Object params, String appSecret) { // 排序 拼接参数 timestamp nonce appSecret // 使用SHA-256代替MD5 return DigestUtils.sha256Hex(sb.toString()); }坑3限流误伤正常请求现象某商家的ERP系统定时同步订单每分钟批量请求100次刚好触发限流阈值100次/分钟导致部分请求被拒绝。解决方案限流改为滑动窗口避免分钟边界时的瞬时高峰对批量操作接口单独设置更高的限流阈值提供获取配额接口批量操作前预先获取配额java// 滑动窗口限流Redis Lua String luaScript local current redis.call(get, KEYS[1]) or 0 if current 1 tonumber(ARGV[1]) then return 0 end redis.call(incr, KEYS[1]) redis.call(expire, KEYS[1], ARGV[2]) return 1;八、 总结设计维度核心原则实现方式统一规范所有端使用同一套API契约RESTful规范 统一响应格式统一鉴权多端共用一套认证体系JWT AppKey双层认证版本管理向后兼容平滑升级URL版本号 过渡期策略安全防护防重放、防枚举、防超限签名校验 滑动窗口限流商业化能力面向第三方开发者提供增值APIAppKey 套餐计费 SLA承诺可观测全链路可追踪、可计量统一requestId 监控大盘文末思考API是电商系统的外交界面。对内统一多端接入对外提供商业化能力。好的API设计应该让调用方感觉自然——命名直观、错误信息清晰、文档详尽、升级平滑。建议API设计遵循先有契约后有实现的原则用OpenAPI/Swagger定义好接口规范前后端、多方开发者在同一份契约下并行开发。