微信小程序与 H5 调用第三方 API 的正确姿势:把平台差异收敛到一个请求层
一句话回答
业务代码应只依赖统一的内部请求契约,由薄适配层吸收小程序请求域名配置与 H5 CORS 等平台差异;长期密钥不进入前端,超时、错误映射和幂等重试在上线前统一定义。具体平台规则与 API.PLUS 接入参数需以官方文档为准。
正确姿势可以概括为一句话:业务代码只依赖一个内部请求契约,平台差异由薄适配层吸收,长期密钥不进入小程序包或 H5 静态资源。小程序侧的请求域名配置与 H5 侧的 CORS(跨源资源共享)属于两类不同约束,材料标题层面已点出这两个概念,但具体配置口径需以平台官方文档为准;本次材料未提供可核验正文与链接,因此不展开具体规则,也不声称已联网核验。
先定三个边界,再写请求代码#
- 密钥边界。前端可见的内容都应视为可被提取。建议服务端持有长期密钥,前端只拿短期令牌,或改为调用自家代理。示例中的
YOUR_KEY仅作占位,不是真实密钥。 - 请求边界。小程序与 H5 的底层请求 API 不同,但入参、出参和错误结构可以统一,避免业务层出现两套分支。
- 失败边界。网络失败、超时、HTTP 4xx/5xx 与业务错误码要分层表达。否则页面只能显示“请求失败”,无法决定是否重试或提示用户。
内部请求契约#
适配层对业务层暴露一个函数,入参至少包含以下字段:
| 字段 | 含义 | 注意 |
|---|---|---|
path | 相对路径 | 不要由调用方拼接完整域名 |
method | HTTP 方法 | 默认 GET,写操作显式声明 |
timeoutMs | 超时时间 | 应小于页面或交互可等待的时长 |
idempotencyKey | 幂等键 | 仅对可安全重试的写操作使用 |
requestId | 请求标识 | 用于日志关联,不要记录密钥 |
统一返回结构建议固定为 { ok, data, error, requestId },其中 error.kind 取有限枚举,例如 network、timeout、auth、rate_limit、server、business。这样重试策略可以按 kind 分支,而不是散落在页面代码里。
适配层示意:同一个入口,两种实现#
下面的代码是结构示意,未在真实小程序或浏览器环境执行;域名、响应结构和状态码映射均为占位,接入时应按实际接口契约替换。
const API_BASE = 'https://api.example.com'; // 占位域名
function normalize(status, body) {
if (status >= 200 && status < 300) {
return { ok: true, data: body, error: null };
}
const kind = status === 401 || status === 403 ? 'auth'
: status === 429 ? 'rate_limit'
: status >= 500 ? 'server'
: 'business';
return { ok: false, data: null, error: { kind, status, body } };
}
// H5:受同源策略约束,跨域需要服务端 CORS 配合
async function requestH5(opts) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
try {
const res = await fetch(API_BASE + opts.path, {
method: opts.method,
headers: opts.headers,
body: opts.body,
signal: controller.signal
});
return normalize(res.status, await res.json());
} catch (e) {
return { ok: false, data: null, error: { kind: 'network', message: String(e) } };
} finally {
clearTimeout(timer);
}
}
// 小程序:使用平台请求 API,域名需按官方文档配置
function requestMP(opts) {
return new Promise((resolve) => {
wx.request({
url: API_BASE + opts.path,
method: opts.method,
data: opts.body,
header: opts.headers,
timeout: opts.timeoutMs,
success: (res) => resolve(normalize(res.statusCode, res.data)),
fail: (err) => resolve({
ok: false, data: null,
error: { kind: 'network', message: err.errMsg }
})
});
});
}
错误映射与重试的边界#
- 网络错误与超时:只对幂等请求做有限次退避重试;非幂等写操作需要幂等键才能重试。
- 401/403:不要自动循环重试。若需要刷新令牌,应做单飞控制,避免并发请求同时刷新。
- 429:若响应提供
Retry-After,可按其指示等待;否则采用有上限的指数退避并加入抖动。 - 5xx:可做少量退避重试,但要把最终失败明确交给页面。
- 业务错误:通常不重试,按契约提示用户或修正入参。
上线前检查清单#
- 长期密钥是否只存在于服务端,前端仅持有短期凭证或代理地址。
- 小程序请求域名与 H5 允许来源是否按各自官方文档配置;具体位置与最新规则待核实。
- 超时时间是否小于页面可等待时长,取消请求后是否清理定时器。
- 重试是否只覆盖幂等请求,是否设置最大次数和总时长。
- 日志是否记录
requestId,是否避免写入密钥、令牌和完整敏感响应。 - 若使用代理,是否限制可转发的目标域,并只透传必要的请求头。
本文给出的是结构建议,不是对某个平台最新规则的复述。微信小程序当前的域名配置要求、H5 的 CORS 细节,以及 API.PLUS 的实际端点、配额与鉴权参数,本次材料均未提供可核验正文,需查阅对应官方文档后再落地。