小程序与 H5 调用第三方 API:同一条请求,两套规则
小程序与 H5 的请求被不同机制拦截:H5 主要受浏览器同源策略与 CORS 预检约束,小程序主要受平台侧请求域名校验约束。因此一端的联调通过不能推出另一端可用。较稳妥的默认做法是前端只请求自有后端,由服务端持有第三方密钥并统一处理超时、重试与错误映射;平台具体限制以官方当前文档为准。
先分清规则来自哪一层#
小程序发出的请求和浏览器发出的请求,表面都是“发一个 HTTPS 请求”,但拦住它们的东西并不是同一个。H5 的失败多来自浏览器同源策略与服务端 CORS 响应头;小程序的失败多来自平台侧对请求域名、协议与端口的校验。报错出现的位置不同,排查顺序也不同。
本次写作没有可引用的官方文档链接,涉及平台具体限制的条目(域名数量、协议、端口、审核要求)请以对应平台的当前文档为准,本文标注为待核实;下面只讨论可以自行验证的结构性差异。
三个不共享的假设#
预检请求只存在于浏览器
浏览器在跨域且请求属于“非简单请求”时——例如带 Content-Type: application/json 或自定义 header——会先发一个 OPTIONS 预检,服务端正确响应后才轮到真实请求。小程序的请求不经过这套机制。所以“H5 联调通过”推不出“小程序也能通”:前者可能被预检卡住,后者的失败点常在别处,比如域名未登记或证书链不完整。
凭据的携带方式不同
H5 跨域要带 Cookie,需要前端 credentials 与服务端 Access-Control-Allow-Credentials 同时成立,且 Access-Control-Allow-Origin 不能写成通配符。小程序端更常见的做法是把令牌放进请求 header,由业务自己管理有效期。小程序运行时是否自动维护 Cookie,取决于实现与版本,属待核实项——不要把 H5 的 Cookie 方案原样搬过去。
域名清单是部署产物,不是代码产物
H5 换域名只涉及 DNS 与证书;小程序侧通常还要在后台维护请求域名清单,改域名是一次配置变更,要跟着发布流程走。把第三方域名直接写进前端,等于把对方的可用性并入了自己的发版节奏。
建议的默认姿势:前端只调自己的后端#
让 H5 与小程序都请求同一个自有入口,由服务端持有第三方密钥、做签名与限流,前端只处理自己的会话。这样跨域退化为“自己的域名对自己的域名”,第三方换域名或改鉴权方式时不必重新发版。
服务端契约至少要写清四件事:超时由谁先放弃、重试条件(只对幂等请求重试)、错误映射(把第三方的错误码收敛成自己的少数几类)、响应体大小上限。第三方返回 200 但响应体是错误对象,是很常见的形态,最好在入口处统一判定。
失败归因顺序#
| 层 | 典型表现 | 先核对什么 |
|---|---|---|
| 传输层 | 连不上、证书错误、超时 | 证书链、DNS、出口网络 |
| 平台层 | 请求未发出即被拒,提示域名不合法 | 后台域名清单、协议与端口 |
| HTTP 层 | 4xx/5xx、浏览器报 CORS 错误 | 预检响应头、鉴权 header、状态码 |
| 业务层 | 200 但响应体是错误对象 | 响应结构约定、错误码映射 |
按这个顺序核对,可以避免把平台层的拒绝误判成服务端 bug,也避免在业务代码里反复加重试去掩盖传输层问题。
一个未执行的示例#
下面代码未实际运行,仅示意参数位置。环境为浏览器 H5;小程序端业务逻辑相同,只是把调用换成平台提供的请求 API。
// 未执行的示例:H5 调用自有后端,第三方密钥不下发到前端
async function fetchItems(signal) {
const res = await fetch("https://app.example.com/api/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", // 仅当确实使用 Cookie 会话
body: JSON.stringify({ page: 1 }),
signal // 超时由调用方控制
});
if (!res.ok) throw new Error(`HTTP ${res.status}`); // 传输层判断
return res.json(); // 业务层判定放在调用方
}
超时可用 AbortController 一类机制控制,目标环境的具体支持范围需按项目基线自行核对。示例中的域名与路径均为占位,不指向真实服务。
结论的边界#
“前端只调自有后端”是默认建议,不是唯一解。若第三方提供按来源限制、可公开的凭据,前端直连在工程上可行;但只要凭据需要长期保密,就不应放进前端代码或小程序包内——这一点与平台版本无关。