微信小程序与 H5 调用第三方 API:密钥放哪、跨域怎么过、失败怎么兜
小程序与 H5 调第三方 API 的可行结构是:前端只请求自己的域名,由服务端中转并持有密钥。小程序受合法域名白名单约束,H5 受同源策略与 CORS 约束,两者都指向同一套 BFF 方案;超时、重试、错误分层与日志脱敏需在服务端先设计好。微信域名规则与具体接口服务的鉴权、配额本次无材料,待核实。
- 密钥不放前端:小程序包可被解包,H5 代码可被读取,任何前端加密都挡不住直接取用。
- 小程序靠合法域名白名单、H5 靠 CORS,两条约束都指向同一结论:前端只调自己的域名,由服务端转发。
- 重试只应对幂等请求,并配合超时与错误分层;上游原始错误不要原样透给前端。
结论先给:把第三方 API 的调用统一转发到自己的服务端,前端只请求自己的域名。小程序受“合法域名白名单”约束,H5 受同源策略与 CORS(Cross-Origin Resource Sharing,跨域资源共享)约束,这两条约束都指向同一个结构——小程序或浏览器 → 你的 BFF(Backend for Frontend,面向前端的后端)→ 第三方 API。密钥、重试、日志都放在中间这一层。
一、为什么不能让前端直连#
小程序代码包可以被解包,H5 的 JavaScript 可以被直接阅读。任何写在前端的密钥、签名算法或“加密”逻辑,都只能提高一点提取成本,不能构成安全边界。因此第三方 API 的 key、secret、签名私钥必须只存在于服务端,前端最多持有一个你自己签发的、可撤销、有作用域和有效期的会话凭证。
直连还会把第三方的不稳定性直接传递给端上:对方超时、限流、返回结构变更,都会变成前端要处理的分支。经过中转后,这些差异可以由服务端统一处理成稳定的接口约定。
二、小程序侧:合法域名是硬性约束#
- 小程序的网络请求只能发往后台配置过的 HTTPS 域名;域名需满足平台的备案与格式要求,具体规则以微信官方文档为准(本次材料未提供,待核实)。
- 开发者工具里可以临时关闭域名校验,但真机与线上环境不行;不要用开发工具的宽松行为推断线上可用性。
- 第三方域名往往不在你的控制范围内,能否加入白名单、是否长期稳定,都不由你决定。用自己的域名做一层转发,可以把“白名单变更”变成一次可控的后端配置。
三、H5 侧:CORS 与预检#
浏览器按同源策略限制跨源请求。如果第三方响应里没有允许你来源的 Access-Control-Allow-Origin,前端拿不到响应体;带自定义请求头、非简单方法或 JSON 请求体时,还会先发一次 OPTIONS 预检。这些都不是前端能绕过的。
可行做法有两条:一是同源反向代理,把 /api/* 转发到第三方,浏览器看到的是同源请求;二是由服务端发起请求、前端只调自己的接口。后者与前文的小程序方案可以共用同一套代码。
四、服务端中转的最小骨架#
下面的示例用 Node 18+ 的内置 fetch,只示意结构与失败处理,未实际执行;端点、请求头字段均为占位,需按真实接口替换。
// 未执行示例:仅示意结构
import express from "express";
const app = express();
const UPSTREAM = process.env.UPSTREAM_BASE; // 第三方基址,占位
const KEY = process.env.UPSTREAM_KEY; // 只存在服务端,占位
app.get("/api/items", async (req, res) => {
try {
const page = encodeURIComponent(req.query.page ?? 1);
const r = await fetch(`${UPSTREAM}/items?page=${page}`, {
headers: { Authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(3000),
});
if (!r.ok) return res.status(502).json({ error: "upstream_error" });
res.json(await r.json());
} catch (e) {
const timeout = e.name === "TimeoutError";
res.status(timeout ? 504 : 502)
.json({ error: timeout ? "upstream_timeout" : "upstream_unreachable" });
}
});
要点是:密钥从环境变量读取;设置明确超时;把上游状态码映射成自己的错误码,而不是把对方原始报错原样传给前端。
五、失败路径要先设计#
- 重试:只对幂等的 GET 类请求重试,并限制次数与总耗时;写操作重试可能造成重复提交。
- 错误分层:参数校验失败(4xx,前端可提示用户)、上游不可用(5xx,前端只需展示降级态)、超时(单独区分,便于排查网络)。
- 日志脱敏:不要记录完整密钥和含个人信息的响应体;记录请求 ID、耗时、上游状态码即可。
- 限流:你自己的接口也需要限流,避免端上异常重试把配额打满。
| 对比项 | 前端直连第三方 | 服务端中转 |
|---|---|---|
| 密钥暴露面 | 在客户端,无法保护 | 仅服务端 |
| 小程序域名白名单 | 需加入第三方域名 | 只需自己的域名 |
| H5 跨域 | 依赖第三方返回 CORS 头 | 同源,无跨域问题 |
| 上游变更影响 | 直接传导到端上 | 可在中间层统一适配 |
| 成本 | 低,但风险高 | 多一跳与一层运维 |
如果使用 API.PLUS 这类接口服务,其端点、鉴权方式、配额与计费规则请以官方文档为准;本次没有可核验的材料,不作断言(待核实)。
最后一条边界:中转层不负责“让第三方变快”,只负责让失败可控。是否需要缓存、缓存键怎么设计,是另一个独立问题,要按响应是否与用户相关、是否含私有数据分别判断。