接入设计基础:接口规范与通信协议

接口规范,说白了就是你和外部系统之间定下的「沟通规矩」。规矩定得好,后面十几年都省心;规矩定得烂,每天都是填坑日。我刚开始带SDK设计时,就吃过「没规矩」的亏——接口字段命名随心所欲,有的驼峰有的下划线,签名格式也混乱,结果对接方每次联调都要打十几通电话确认。

嗯,这一章我们就来把这些「规矩」说清楚。

通信协议选型:不是越快越好

SDK接入最常见的通信协议有三个:HTTP/HTTPS、gRPC、WebSocket。怎么选?我先泼盆冷水——别一上来就追求「高性能」。你看那些初创团队,动不动就上gRPC,结果连TLS证书配置都搞不明白,性能没榨出来,兼容性问题倒是一堆。

协议 适用场景 优缺点 我个人推荐指数
HTTP/HTTPS 云API、移动端SDK 成熟、调试方便、不被防火墙拦;但头部偏大、无状态 ⭐⭐⭐⭐⭐
gRPC 微服务、内部高频调用 二进制编码快、双向流、强类型;但浏览器端受限、调试工具少 ⭐⭐⭐⭐
WebSocket 实时推送、长连接 全双工、延迟低;但状态维护复杂、断线重连麻烦 ⭐⭐⭐
小建议:大多数外部SDK对接,选 HTTPS + JSON 就够了。我在项目中遇到过,某大厂非要在移动SDK里强推gRPC,结果客户端包体积暴涨、SSL握手失败率飙升,最后还是偷偷切回了HTTP/2。

接口规范:签名、格式与版本

接口规范包含三个核心部分:认证签名、数据格式、版本管理。这三样不全,后面一定出问题。

3.1 认证签名

最简单的方案是 AppKey + AppSecret + HMAC-SHA256。客户端用AppKey标识身份,服务端用AppSecret验签。注意一点:AppSecret不能明文存客户端,我曾经见过有人把密钥硬编码在iOS二进制里,结果被逆向得干干净净。

// 签名生成示例(Python)
import hmac, hashlib, time

def sign_request(app_secret: str, method: str, path: str, body: str) -> str:
    message = f"{method}\n{path}\n{int(time.time())}\n{body}"
    return hmac.new(app_secret.encode(), message.encode(), hashlib.sha256).hexdigest()

3.2 数据格式

我统一推荐 JSON over UTF-8。XML太重,Protobuf对移动端调试不友好,JSON是「下限最高」的选择。但要注意几个雷区:

  • 时间格式:一律用 ISO 8601,如 2025-03-15T08:30:00Z。不要用时间戳,因为毫秒/秒单位经常会搞混。
  • 枚举值:用字符串,不要用数字。我在项目中遇到过,某次对接方把枚举数字和含义的对应表忘了发给客户端,结果客户端解析全错位。
  • 空值:明确区分 null 和 ""。有一个统一约定就可以,不要让调用方去猜。

3.3 版本管理

版本号放在 URL路径 或 请求头 里。我个人习惯放路径,因为可见、方便日志排查。版本号用 vMajor 语义,只在大版本变更时升级:

GET /v3/payments/123
Accept: application/json
警告:永远不要做「隐式版本升级」。比如在请求体里加一个字段就认为version已经变了——不行的。你必须显式声明版本号,否则客户端和服务端会「各自以为对方懂自己」,最终出现数据错乱。

序列化与编码:那些容易被忽略的细节

表面上看序列化就是JSON.stringify和JSON.parse,但里面坑不少。

  • BigInteger 精度丢失:JavaScript 的 Number 只能精确表示 2^53 以内的整数,超过这个范围必须用字符串传输。我记得有一次,一个支付订单号传过来直接被截断了,排查了整整两天才发现是序列化精度问题。
  • 浮点数序列化:避免传递浮点数做比较,比如金额尽量用「分」单位的长整型。
  • 二进制数据:如果必须传图片或文件,用 Base64 编码后放在 JSON 的某个字段里。但要注意编码膨胀——Base64 会让数据体积增大约 33%。
我的经验:在接口定义阶段,就统一出一个「数据类型转 JSON 映射表」,比如 Long->String、Decimal->String、DateTime->String。这看起来笨,但对接效率最高。

连接与重试机制:稳定性的最后防线

协议选好了,签名也对了,序列化也搞定了——但网络是不可靠的。你想想看,移动端用户随时可能从电梯切换到地铁,信号来回跳,没有重试机制等于等着崩溃。

重试策略我推荐 指数退避 + 抖动 (Exponential Backoff with Jitter):

// 重试算法伪代码
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
    try {
        return await makeRequest();
    } catch (e) {
        if (isRetryable(e)) {
            const delay = Math.min(1000 * Math.pow(2, i), 10000);
            // 加上随机抖动,防止雪崩
            const jitter = Math.random() * 200;
            await sleep(delay + jitter);
        } else {
            throw e; // 非重试性错误直接抛出
        }
    }
}

什么错误可以重试?你记住:只有 5xx(服务器错误)和 429(限流)才重试;4xx(客户端错误)绝不重试,因为重试一万次也是白费。

流程图:SDK接入的通信架构

下面这张图展示了SDK从发起请求到收到响应的完整通信链路。每个节点都对应了我们上面提到的设计要点。

SDK 接入通信架构图 客户端 SDK 序列化/反序列化 签名 & 加密 连接 & 重试 传输协议 服务端 SDK 关键设计原则 • 序列化层统一处理数据格式转化,避免重复劳动 • 签名层独立出来,可单独替换加密算法 • 连接管理器负责重试、超时、熔断,隔离网络不确定性

接口定义文档:一个容易踩的坑

接口定义文档(API Spec)其实是SDK接入的「地面砖」——看起来不起眼,少了它寸步难行。我曾经接手过一个项目,对接方说「代码即文档,你看我接口实现就知道了」,结果我反编译了他的jar包,花了一周才把接口字段摸清楚。

好的做法是:

  • 用 OpenAPI 3.0 (Swagger) 或 GraphQL Schema 做接口定义。这些格式有标准工具链,可以自动生成客户端代码。
  • 每个字段都要标注:是否必填、取值范围、默认值、示例值。
  • 接口文档必须和代码一起 版本管理。我见过最离谱的事——文档更新了,代码没改,结果生产环境直接报错。
总结一句话:接口规范不是给别人看的,是给你自己未来的「坑位」留的救命绳。协议选型要务实,签名要防篡改,序列化要防精度丢失,重试要防雪崩。嗯,把这些都做到位,SDK接入基本上就稳了。