API 集成
用类型化客户端、重试和限流处理调用外部 REST/GraphQL API。
为什么
每个外部 API 调用都是潜在故障:网络断、提供方限流、或字段改名在凌晨 2 点破你的解析器。从 OpenAPI spec 生成的类型化客户端把'字段改名'变成编译错误而非运行时 undefined。带指数退避的重试无需人工处理瞬态故障,限流 handler 防你被提供方 429 封禁。没有这些层,单个提供方抖动级联成用户可见 500,因为调用是发后不管无降级。
何时使用
调用非平凡外部 API 时。类型化客户端 + 重试包装约 1-2 小时。一次性 curl 脚本可跳过。
如何做
1. 从提供方 OpenAPI spec 生成类型化客户端 - npx openapi-typescript-codegen --input spec.yaml --output src/api-client - 若无 spec,手写 zod schema 并解析每次调用的响应 2. 用重试 + 超时层包装客户端 - 用 p-retry 或 async-retry,3 次尝试,指数退避(1s、2s、4s) - 每次调用设 10s 超时,挂起连接不阻塞请求 3. 读 Retry-After 处理限流并排队 - 429 时:读 Retry-After 头,sleep,重试——不要立即重试 - 高量:用令牌桶限流器(p-limit)保持在限额下 4. 加熔断器处理连续失败 - 连续 5 次失败后,60s 内停止调用(半开探测) - 提供方宕时防级联故障 5. 每次调用记 request id、延迟、状态——但脱敏 auth 头 - 提供方说"我们没收到你的请求"时这是调试生命线
示例
import { retry } from "p-retry";
import { client } from "./api-client"; // 从 OpenAPI 生成
export async function getUser(id: string) {
return retry(
() => client.GET("/users/{id}", { params: { path: { id } } }),
{ retries: 3, minTimeout: 1000, maxTimeout: 4000, onFailedAttempt: (e) => {
if (e.response?.status === 429) throw e; // 不重试 429
}}
);
}
// 限流感知调用
import pLimit from "p-limit";
const limit = pLimit(5); // 最多 5 并发
const results = await Promise.all(ids.map(id => limit(() => getUser(id))));常见问题
- ⚠立即重试 429 被提供方 IP 封禁 24 小时。
- ⚠HTTP 调用无超时,提供方连接停滞挂住服务器 worker 池。
- ⚠用 'as any' 而非 schema 解析响应,字段改名静默返回 undefined。
- ⚠每次请求都调 API 而非缓存幂等结果——API 配额成本 10 倍。
最佳实践
- ✓spec 变更时 CI 重新生成客户端,破坏性提供方更新在到 prod 前失败构建。
- ✓每环境用独立 API key,泄漏的 staging key 碰不到 prod 数据。
- ✓用短 TTL(60s)stale-while-revalidate 缓存幂等 GET——API 调用减 80% 无过期风险。
- ✓发自定义指标(api.provider.latency),用户察觉前告警降级。