用户登录

技术文章

外部链接将在新标签页打开。

微信小程序与 H5 调用第三方 API 的正确姿势

一句话回答

正确姿势是先划清信任边界:密钥与写操作一律放服务端,由前端通过自建 BFF 代理调用;小程序配置合法域名并设定超时重试,H5 用同源代理规避 CORS 预检,同时统一错误处理规则与限流缓存。

核心要点 TL;DR
  • 密钥、计费、写操作类接口必须走 BFF,Key 只放服务端
  • 小程序需预先配置 HTTPS 合法域名,超时设 4~6 秒并限次重试
  • H5 带自定义请求头会触发 CORS 预检,应用同源代理而非前端绕过
  • 只对 429 与 5xx 做有限退避重试,非幂等 POST 不要自动重试
  • BFF 统一 {code,message,traceId} 错误结构,并加限流与短 TTL 缓存

为什么“能跑”不等于“能上线”#

很多团队第一次在微信小程序或 H5 里接第三方 API,做法都很直接:把文档里的 curl 示例改成一个 wx.request 或 fetch,本地看到 JSON 返回就认为完成了。演示阶段确实没问题,但真实流量一上来,问题会集中暴露:密钥被打进代码包、请求域名没进白名单、跨域预检失败、超时没有上限、失败重试把配额打爆。近期技术社区关于应用审核与客户端透明度的讨论,无论具体事实如何,都指向同一个工程常识:客户端交付物不是保险箱,写进小程序包或前端 bundle 的字符串,都要假设它可能被看到。本文按“边界划分 → 小程序 → H5 → BFF 收口 → 错误契约 → 检查清单”的顺序,给出一套可以直接落地的做法。

一、先划信任边界:哪些调用不能放在前端#

第三方 API 的调用可以粗暴分成两类:公开只读、低配额、无敏感副作用的,和带计费、带写操作、带密钥的。前者可以前端直连,后者必须经过自己的服务端。判断标准不是“接口复不复杂”,而是“密钥泄露后损失有多大”。

  • API Key、签名盐值、支付回调校验逻辑,一律只放服务端环境变量。
  • 需要 POST 写数据、扣减额度、发送通知的接口,走自建 BFF,前端只拿业务结果。
  • 需要在客户端做重试的接口,先确认它是否幂等;非幂等接口不要自动重试。
  • 小程序云函数、Serverless 函数可以作为轻量 BFF,但同样要配置密钥与超时。

以 IP 查询为例,IP 查询接口的调用形状是 https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8。在本地调试时直连看响应没问题,但生产环境更推荐让前端请求自己的 /api/ip,由服务端注入 Key 并做参数校验。

二、小程序侧:合法域名、超时预算与统一封装#

微信小程序的网络请求有硬性约束:请求域名必须是 HTTPS、已备案,并提前配置到小程序的 request 合法域名列表;开发者工具里的“不校验合法域名”只对本地调试有效,真机与线上不会放行。另一个常见坑是超时:wx.request 默认超时不一定符合你的页面体验,建议显式设置 4~6 秒,并给重试设置上限。

示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。

const API_BASE = 'https://www.api.plus/api/get';

function requestJSON(url, data, options) {
  const opts = options || {};
  const timeout = opts.timeout || 5000;
  const maxRetry = opts.maxRetry == null ? 2 : opts.maxRetry;

  return new Promise(function (resolve, reject) {
    function attempt(left) {
      wx.request({
        url: url,
        method: 'GET',
        data: data,
        timeout: timeout,
        success: function (res) {
          if (res.statusCode === 200) {
            resolve(res.data);
            return;
          }
          if ((res.statusCode === 429 || res.statusCode >= 500) && left > 0) {
            setTimeout(function () { attempt(left - 1); }, 600 * (maxRetry - left + 1));
            return;
          }
          reject(new Error('HTTP ' + res.statusCode));
        },
        fail: function (err) {
          if (left > 0) {
            setTimeout(function () { attempt(left - 1); }, 600 * (maxRetry - left + 1));
            return;
          }
          reject(err);
        }
      });
    }
    attempt(maxRetry);
  });
}

requestJSON(API_BASE, { apikey: 'YOUR_KEY', ip: '8.8.8.8' })
  .then(function (data) { console.log(data); })
  .catch(function (err) { console.error(err.message); });

这段封装刻意做了三件事:只对 429 和 5xx 做有限退避重试、把超时作为参数暴露、把错误统一成 Error。页面层只需要处理 loading、空态和失败提示,不需要关心底层是第几次重试。注意上面的 YOUR_KEY 仅用于演示接口形状,真实项目应改为请求自有 BFF。

三、H5 侧:CORS、预检与可取消请求#

H5 比小程序更自由,但也更容易踩跨域。浏览器把请求分成简单请求和需要预检的请求:一旦你带上 X-API-Key 这类自定义头,或者把 Content-Type 设成 application/json,浏览器就会先发 OPTIONS 预检。如果第三方接口没有返回对应的 Access-Control-Allow-Origin 和 Access-Control-Allow-Headers,响应会被浏览器拦下,Network 面板里看起来“请求成功”,代码里却拿不到数据。遇到这种情况,不要试图在前端绕,正确做法是同源 BFF 代理。

async function getJSON(url, timeoutMs) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs || 5000);
  try {
    const res = await fetch(url, {
      method: 'GET',
      signal: controller.signal,
      credentials: 'omit',
      headers: { 'Accept': 'application/json' }
    });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } finally {
    clearTimeout(timer);
  }
}

getJSON('https://www.api.plus/api/dongman?type=json', 4000)
  .then((data) => console.log(data))
  .catch((err) => console.warn(err.name === 'AbortError' ? '请求超时' : err.message));

图片类接口还有额外细节。以随机动漫壁纸接口为例,type=json 返回的是元数据,真正的图片由 <img> 加载。图片请求不受 CORS 限制,但如果后续要用 canvas 处理,就会遇到画布污染问题;需要处理图像时,应让图片走同源代理或在服务端完成裁剪。另外,随机接口不要在同一页面高频轮询,建议加本地缓存和节流,避免把配额消耗在用户反复切换上。

四、用 BFF 收口:代理、鉴权与限流#

BFF(Backend for Frontend)不需要很重,一个路由就能解决密钥和跨域两个问题。下面是一个 Node.js + Express 的最小示例,核心是:校验入参、从环境变量读取 Key、设置上游超时、透传状态码。

const express = require('express');
const app = express();

const API_KEY = process.env.API_PLUS_KEY;

app.get('/api/ip', async (req, res) => {
  const ip = String(req.query.ip || '').trim();
  if (!ip || ip.length > 45) {
    return res.status(400).json({ code: 'BAD_IP', message: 'ip 参数不合法' });
  }
  const target = 'https://www.api.plus/api/get?apikey=' +
    encodeURIComponent(API_KEY) + '&ip=' + encodeURIComponent(ip);
  try {
    const r = await fetch(target, { signal: AbortSignal.timeout(4000) });
    const body = await r.text();
    res.status(r.status).type('application/json').send(body);
  } catch (err) {
    res.status(504).json({ code: 'UPSTREAM_TIMEOUT', message: '上游超时' });
  }
});

app.listen(3000);
维度小程序直连H5 直连自建 BFF
密钥暴露面代码包可被解包bundle 可被查看仅服务端可见
跨域问题无 CORS,但有域名白名单受 CORS 与预检约束同源,无跨域
限流与缓存难统一难统一可集中做
适用场景公开低配额接口公开低配额接口计费、写操作、多端复用

代理层还应该做两件容易被忽略的事:按用户或 IP 做简单限流,避免单个前端把额度打满;对稳定的查询结果做短 TTL 缓存,例如同一 IP 的归属地在几分钟内复用。这样即使上游抖动,前端体验也可控。

五、错误契约与可观测性#

前端调用第三方 API 时,最忌讳把上游返回原样透传给用户。建议在 BFF 层统一错误结构,例如 { code, message, traceId },并区分“参数错误”“上游超时”“额度不足”“鉴权失败”几类。对于需要提交表单或 JSON 体的接口,可以参考站内POST 示例接口,重点检查 Content-Type、字段类型和重复提交保护。

  • 日志里记录上游状态码、耗时和 traceId,但不要打印完整 Key。
  • 非幂等 POST 请求不要自动重试;需要重试时由服务端生成幂等键。
  • 把 429 和 5xx 当作可恢复错误,把 4xx 参数错误当作不可恢复错误。
  • 前端对超时给出明确文案,不要一直转圈。

更多接口形态与响应约定,可以在API 文章中心按主题检索;遇到接入问题,也可以通过技术支持反馈。

六、上线前检查清单#

  1. 小程序后台已配置 request 合法域名,线上包没有依赖“不校验域名”开关。
  2. API Key 只存在于服务端环境变量,前端代码与构建产物中搜索不到。
  3. 所有请求都有显式超时,重试次数有上限,非幂等请求已排除自动重试。
  4. H5 已确认目标接口的 CORS 响应头,或已改为同源代理。
  5. 图片、随机类接口有缓存与节流,避免重复消耗配额。
  6. 错误码、日志、traceId 已串起来,能在分钟级定位上游故障。

总结#

微信小程序和 H5 调用第三方 API,真正要解决的不是“怎么发请求”,而是信任边界、域名与跨域约束、超时与重试预算、错误契约这四件事。公开低配额接口可以直连,但涉及密钥和计费的调用应统一收口到 BFF;这样既降低泄露风险,也让限流、缓存和排障有地方落地。如果你还没有可用的 Key,可以在 API.PLUS 注册账号获取免费 API Key,先用 IP 查询和随机壁纸接口把整条链路跑通,再按本文的清单逐步加固。

常见问题(FAQ)

小程序的 API Key 直接写在代码里安全吗?
不安全。小程序代码包可被解包、H5 的 bundle 可被查看,写进客户端的 Key 都要假设会泄露。正确做法是把 Key 放在服务端环境变量中,前端只请求自建 BFF,拿回业务结果。
H5 调用第三方接口遇到 CORS 跨域报错怎么办?
不要在客户端强行绕过。带上 X-API-Key 等自定义头或 application/json 会先发 OPTIONS 预检,若上游没返回对应的 Allow-Origin 与 Allow-Headers,浏览器会拦下响应。应改用同源 BFF 代理转发请求。
wx.request 需要设置超时和重试吗,怎么设合理?
需要。wx.request 默认超时不一定符合页面体验,建议显式设为 4~6 秒,并且只对 429 和 5xx 做有上限的退避重试;非幂等接口不要自动重试,重试应改由服务端用幂等键控制。
哪些第三方接口可以直接从前端调用?
公开只读、低配额、无敏感副作用的接口可以直连;带计费、带写操作、需要密钥或签名的接口必须经过自建服务端。判断标准是密钥泄露后损失有多大,而不是接口复不复杂。
第三方接口返回 429 或 5xx 时该怎么处理?
把 429 和 5xx 当作可恢复错误,做有限次退避重试并设上限;4xx 参数错误视为不可恢复,直接给用户明确提示。BFF 层统一成 {code,message,traceId} 结构,避免把上游响应原样透传给用户。

作者:沐小悠

本文约 14 分钟读完 · 关键词:微信小程序、H5开发、第三方API、API Key安全、CORS跨域

更多文章 GET 示例接口

「」标签下的其他文章