Java 生成微信小程序二维码:5 种 HTTP 调用方式与避坑指南 简介面向Java后端开发者的小程序二维码生成示例工程基于微信官方服务端能力实现覆盖裂变分享、渠道推广等常见营销场景。针对每个账号生成专属邀请二维码的诉求系统梳理了5种实现方式并设计前端-后端API-微信API的安全调用链路规避secret与token泄露风险核心使用永久有效、无数量限制的getUnlimitedQRCode接口。资源包共69个文件压缩后约53KB按Maven标准拆分主代码、测试与资源配置包括java源码、xml配置、properties参数文件及少量jar依赖可直接导入IDE运行验证。已有2647人学习下载整体目录结构清晰便于对照不同实现方式在参数构造、异常处理、返回码校验上的差异理解各方案的适用边界源码中保留了完整应用配置与接口调用骨架替换appid等参数即可快速集成到实际裂变活动中对中小团队及个人开发者均很友好能有效降低二次开发与联调成本。1. 生成微信小程序二维码本质上是一次带 access_token 的 HTTP 请求做 Java 后端久了你会发现“生成微信小程序二维码”这件事说穿了就是一次带 access_token 的 HTTP 请求。运营要的是每个用户一张专属码微信官方不会提供可视化按钮能靠的只有那几个接口和一把 HTTP 客户端。很多人第一反应是找封装好的 SDK但 SDK 封装一多token 过期、接口报错反而更难排查。这篇笔记不绑定具体业务框架从 JDK 原生 HttpURLConnection 到常见的 HttpClient、OkHttp、RestTemplate、Hutool整理 5 种可落地的实现方式把接口选型、参数设置和踩过的坑一起说清楚。后面涉及的代码都保持同一个方法签名方便你直接抄进自己的 Service 里。2. 微信小程序二维码接口与 AccessToken先选对接口再动手2.1 三个官方接口的差异getwxacodeunlimit 才是主力微信官方一共给了三个生成二维码/小程序码的接口名字接近但限制天差地别。我见过不少新人把三个接口混着用照着 createwxaqrcode 写了一版上线第一天就撞数量限制。所以动手写代码前务必先确认你用的是哪个接口。第一个是 getwxacodeunlimit全称“获取不限制数量的小程序码”。它支持通过 scene 传入自定义参数生成数量不限制是做带参数推广的首选。page 参数可以指向已发布的小程序页面也能配合 env_version 参数在开发版、体验版里调试。缺点是 scene 有长度和字符限制最长 32 个可见字符只支持数字、大小写英文和!#$()*,/:;?-._~这些特殊字符中文、空格、换行都不行。这个限制会在后面避坑部分重点展开。第二个是 getwxacode获取小程序码但不支持 scene 参数直接用 page 作为路径。它生成的也是方形带 logo 的小程序码数量有限制适合某些固定页面的低频场景。第三个是 createwxaqrcode获取小程序二维码返回的是传统二维码样式扫码后跳转指定页面同样有限制。这里还有一个小误区用户嘴上说“要二维码”实际要的往往是扫完能直接打开小程序的码。getwxacodeunlimit 返回的是小程序码为了让用户识别建议在需求阶段就确认清楚。选型上我的习惯是只要业务里需要按用户或按活动生成不同参数就统一用 getwxacodeunlimit。它虽然没有传统二维码样式但把参数放 scene后端拿到场景值自己解析是扩展性和稳定性最好的组合。下面是三接口对比建议直接收藏接口是否支持 scene数量限制返回样式推荐场景getwxacodeunlimit支持不限小程序码带参推广、按用户生成getwxacode不支持有限小程序码固定页面、低频createwxaqrcode不支持有限传统二维码需要旧版样式接口调用频率也得留意。getwxacode 和 createwxaqrcode 有累计数量限制而 getwxacodeunlimit 虽然不限总量但接口本身的 QPS 仍受官方限制不建议在循环里批量生成几百张。如果业务要预生成大量码需要分批执行控制每秒最多几次请求。2.2 先用 curl 走通链路再写 Java 封装在写 Java 代码之前强烈建议先用 curl 把链路跑通。这样能排掉大部分网络和参数问题也能对返回结果有一个直观认识。第一步是获取 access_token微信接口文档要求使用你的小程序 appid 和 secretGET 请求就行curl -s https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidAPPIDsecretAPPSECRET返回 JSON 包含 access_token 与 expires_inexpires_in 固定 7200 秒。这里有个容易忽略的点access_token 是 appid 维度的全局唯一值频繁获取会互相挤掉而且旧 token 失效前新 token 并不会立刻生效所以生产环境必须做缓存。后面第 5 章会专门讲。拿到 access_token 后再调生成接口这一步就是 POST JSONscene、page、width 是必填参数curl -s https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_tokenTOKEN \ -H Content-Type: application/json; charsetutf-8 \ -d {scene:uid_1001,page:pages/index/index,width:430} \ -o wxacode.png如果生成的 wxacode.png 能正常打开说明参数链路没问题接下来换成 Java 只是换了 HTTP 客户端。这里要强调微信接口虽然文档里叫 HTTP 请求实际全部走 HTTPSJava 的 HTTP 请求库会帮我们处理 SSL 握手不需要自己写证书逻辑。curl 跑通之后建议再用file wxacode.png看一眼文件类型确认是 PNG 图片。如果返回的是 JSON大概率是参数错误或者 token 失效。常见错误码可以先记住40001 表示 access_token 无效41030 表示 page 路径无效41031 表示 scene 无效。这些错误码在 Java 里要按照 errcode 落到日志而不是只打印 HTTP 状态码。第 3 章的代码都假设你已经有有效 access_token并把生成方法统一为byte[] createWxACode(String accessToken, String scene, String page, int width)这样每种客户端只需要关注请求体、响应体和异常处理。3. 5 种 Java HTTP 调用方式从原生到第三方工具库下面的代码示例都聚焦在“生成微信小程序码”这一步方法签名统一为byte[] createWxACode(String accessToken, String scene, String page, int width)。入参已经拿到了 access_token所以代码块里不会再出现获取 token 的逻辑。每种方式的依赖、超时配置、异常处理各不一样我会在每个小节后面说明。3.1 方式一JDK 原生 HttpURLConnection这是零依赖的实现适合不引入第三方库的项目。JDK 8 的用户需要把readAllBytes()换成手动读取流下面代码以 JDK 9 为例逻辑大家都能看明白。public byte[] createWxACode(String accessToken, String scene, String page, int width) throws IOException { // 把 access_token 拼在 query 参数里这是微信接口的要求 URL url new URL(https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token accessToken); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setDoOutput(true); conn.setConnectTimeout(3000); // 连接超时单位毫秒 conn.setReadTimeout(5000); // 读超时图片生成一般很快5000 足够 conn.setRequestProperty(Content-Type, application/json; charsetutf-8); // 拼 JSON 请求体这里没有做 JSON 转义实际生产建议用 JSON 库 String body String.format({\scene\:\%s\,\page\:\%s\,\width\:%d}, scene, page, width); try (OutputStream os conn.getOutputStream()) { os.write(body.getBytes(StandardCharsets.UTF_8)); } int responseCode conn.getResponseCode(); if (responseCode 200) { // 微信返回的是图片二进制流不是 JSON try (InputStream is conn.getInputStream()) { return is.readAllBytes(); } } // 非 200 时错误流里一般是 JSON 错误码读出来方便排查 try (InputStream es conn.getErrorStream()) { String errorBody new String(es.readAllBytes(), StandardCharsets.UTF_8); throw new IOException(微信接口返回失败HTTP responseCode , body errorBody); } }逻辑说明HttpURLConnection 打开连接后通过setDoOutput(true)声明要写请求体然后用getOutputStream()写入 JSON 字符串。微信接口成功时返回图片二进制失败时返回 JSON 错误信息需要分别处理。参数说明connectTimeout控制 TCP 连接建立时间readTimeout控制等待响应的最长时间如果你用的网络环境较差可以把 5000 调到 8000但没必要更长接口通常会秒回。3.2 方式二Apache HttpClient 5Apache HttpClient 是老牌 HTTP 客户端适合项目里已经用了 httpclient 依赖的团队。HttpClient 5 的 API 比 4.x 更清晰连接池管理也更成熟。public byte[] createWxACode(String accessToken, String scene, String page, int width) throws Exception { // 默认会使用连接池每次调用结束后要 close否则连接无法释放 try (CloseableHttpClient client HttpClients.createDefault()) { HttpPost post new HttpPost(https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token accessToken); post.setHeader(Content-Type, application/json; charsetutf-8); String json String.format({\scene\:\%s\,\page\:\%s\,\width\:%d}, scene, page, width); post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); try (CloseableHttpResponse resp client.execute(post)) { byte[] bytes EntityUtils.toByteArray(resp.getEntity()); int status resp.getStatusLine().getStatusCode(); if (status 200) { return bytes; } throw new IOException(微信接口返回失败HTTP status , body new String(bytes, StandardCharsets.UTF_8)); } } }这里用try-with-resources确保 HttpClient 和响应都关闭。EntityUtils.toByteArray会把整个响应读进内存微信返回的图片一般在几十到几百 KB不会有内存压力。如果想要更稳健可以通过RequestConfig设置连接超时和读取超时但示例从简生产环境要补上。对于并发较高的服务用同一个 HttpClient 实例比每次 new 一个更好连接池复用是 Apache HttpClient 的核心优势。3.3 方式三OkHttp 4OkHttp 是 Android 和 Java 后端都很常用的 HTTP 客户端API 简洁内部对连接复用和超时处理做了大量优化。项目里如果用 OkHttp推荐优先用它的 Builder 配置。public byte[] createWxACode(String accessToken, String scene, String page, int width) throws IOException { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.SECONDS) .build(); String url https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token accessToken; String json String.format({\scene\:\%s\,\page\:\%s\,\width\:%d}, scene, page, width); Request request new Request.Builder() .url(url) .post(RequestBody.create(json, MediaType.parse(application/json; charsetutf-8))) .build(); try (Response response client.newCall(request).execute()) { if (!response.isSuccessful()) { // body() 只能调用一次先取字符串再抛异常 throw new IOException(微信接口返回失败: response.body().string()); } return response.body().bytes(); // bytes() 会把响应体全部读入内存 } }OkHttp 的线程模型是异步可中断的但这里用的是同步execute()返回后就是最终结果。注意response.body()只能调用一次所以先判断成功再取 bytes。超时配置在 Builder 里完成connectTimeout和readTimeout与方式一的含义一致。如果你担心 OkHttpClient 被反复创建可以在类里声明为static final因为 OkHttpClient 本身就是线程安全的。3.4 方式四Spring RestTemplate如果项目已经是 Spring BootRestTemplate 是很多人最早接触的客户端。它最大的坑是默认消息转换器可能把图片二进制当字符串处理所以接收类型必须声明为byte[]。public byte[] createWxACode(String accessToken, String scene, String page, int width) { // 生产环境建议把 RestTemplate 声明为单例避免每次 new 浪费性能 RestTemplate restTemplate new RestTemplate(); // 添加 ByteArrayHttpMessageConverter并放到第一位优先处理二进制响应 restTemplate.getMessageConverters().add(0, new ByteArrayHttpMessageConverter()); String url https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token accessToken; String json String.format({\scene\:\%s\,\page\:\%s\,\width\:%d}, scene, page, width); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityString entity new HttpEntity(json, headers); ResponseEntitybyte[] response restTemplate.exchange( url, HttpMethod.POST, entity, byte[].class); if (response.getStatusCode().is2xxSuccessful()) { return response.getBody(); } throw new RuntimeException(微信接口返回失败: HTTP response.getStatusCode()); }这里最关键的是new ByteArrayHttpMessageConverter()否则 RestTemplate 会根据响应头中的Content-Type选择转换器图片的响应头往往不是标准的 application/jsonSpring 可能用 StringHttpMessageConverter 把它转成乱码字符串。把ByteArrayHttpMessageConverter放到 List 第一位能保证它优先处理。如果你在 Spring Boot 项目里使用 RestTemplate最好用Bean统一配置而不是在方法里 new这样也能避免多个调用点重复设置转换器。3.5 方式五Hutool HttpUtilHutool 是工具集合很多项目已经有它的依赖。用 HttpUtil 发请求非常简洁特别适合在内部工具、定时任务里快速实现。public byte[] createWxACode(String accessToken, String scene, String page, int width) { String url https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token accessToken; // 这里用 Hutool 的 JSONObject 拼参数避免手写 String.format 的转义问题 JSONObject body new JSONObject() .set(scene, scene) .set(page, page) .set(width, width); HttpResponse response HttpRequest.post(url) .body(body.toString()) .timeout(5000) .execute(); if (response.isOk()) { // bodyBytes() 返回原始字节流不会做字符串解码 return response.bodyBytes(); } throw new RuntimeException(微信接口返回失败: response.body()); }Hutool 的HttpRequest.post支持链式调用.body()可以传 JSON 字符串.timeout()统一设置连接和读取超时。response.isOk()判断 HTTP 状态码是否为 2xx。注意这里返回的是HttpResponse需要用bodyBytes()而不是body()因为body()会默认按文本解码图片会被弄乱。如果你已经依赖 Hutool这种方式是最省事的。3.6 五种方式怎么选如果你已经把上面的代码都看懂了选型其实就看你项目里的依赖现状。我给一个常见选择参考方式依赖适用场景我的建议HttpURLConnection无零依赖、学习或简单工具小流量可用但连接管理要自己做Apache HttpClient需要引入已用 httpclient 的团队连接池成熟推荐长期服务OkHttp需要引入已用 OkHttp 或追求 API 简洁综合体验好推荐RestTemplateSpring 项目自带已在 Spring 生态注意 ByteArray 转换器Hutool HttpUtil需要引入工具脚本、定时任务上手最快但依赖会绑 Hutool我的做法是内部定时任务用 Hutool业务接口用 OkHttp 或 RestTemplate少一个依赖就少一分排查成本。选哪一种不重要核心是把第 4 章的参数和第 5 章的坑处理好。4. 参数与返回图片处理scene、page、width 以及二进制流4.1 scene、page、width 的填写规则和常见误解先给参数一个明确的表然后逐个拆。这是我在生产环境执行了无数次的参数组合下面是关键参数清单参数类型必填说明sceneString是unlimit 接口最大 32 个可见字符只能数字字母和部分符号pageString否默认首页页面路径如 pages/index/index不带 / 和后缀widthInteger否默认 430图片宽度单位 px范围 280-1280auto_colorBoolean否自动配色true 时忽略 line_colorline_colorObject否线条颜色默认 black如 {r:0,g:0,b:0}env_versionString否release、trial、develop默认 releasescene 最容易踩坑。很多运营把订单号、渠道码、用户ID 全塞进去比如order202401010001userabcchannelxxx一不小心就超过 32 字符而且中文直接报错。正确的做法是scene 里只放一个短的业务标识比如数据库自增 ID或者uid_12345由后端拿到 scene 后去查真正的业务数据。如果确实要组合多个维度可以用_或-分隔但要控制在字符集和长度内。page 参数必须是小程序里真实存在的路径不能带.html或前导/也不能带?参数。如果你在开发阶段调试需要配合env_version: develop并确保这个页面在开发者工具里已经配置为可访问。width 控制图片宽高430 在小程序码里视觉效果最好如果嵌入到海报或长图中需要大图可以调到 500 以上但不要超过 1280否则微信也会拒绝。在线下联调时如果小程序页面还没发布getwxacodeunlimit 默认会校验 page 是否属于 release 版本此时生成失败。你需要在请求体里加env_versiondevelop并且保证后端使用的 appid 和小程序开发工具登录的是同一个账号。trial 则对应体验版release 对应正式版。这个参数容易被忽略我第一次联调时就是被这个参数卡了一天。还可以用 line_color 指定小程序码的颜色例如生成红色码。这里给一个带颜色参数的请求体示例后续接口逻辑不变只是 JSON 多两个字段String json {\scene\:\uid_12345\,\page\:\pages/index/index\,\width\:430, \auto_color\:false,\line_color\:{\r\:255,\g\:0,\b\:0}};4.2 拿到 byte[] 之后保存文件、转 Base64、直接输出图片流微信接口正常返回时body 是一张 PNG 图片的二进制流完全不是 JSON。不同业务场景对这份二进制的消费方式不同我拆成常见的三种。第一种是保存到服务器磁盘适用于定时生成、预生成文件后上传 CDN 或 OSS 的场景// 保存前确保目录存在否则会抛 NoSuchFileException byte[] bytes createWxACode(token, uid_12345, pages/index/index, 430); Path file Paths.get(/data/qr/uid_12345.png); Files.write(file, bytes);第二种是返回 Base64 字符串给前端前端可以直接放进img srcdata:image/png;base64,...。注意要拼接data:前缀否则浏览器不会识别byte[] bytes createWxACode(token, uid_12345, pages/index/index, 430); String base64 Base64.getEncoder().encodeToString(bytes); // 返回给前端的就是 data:image/png;base64,xxxxxx String dataUrl data:image/png;base64, base64;第三种是后端接口直接输出图片流前端用img src/qrcode?scenexxx来加载。这种方式适合低频调用每次请求实时生成// 伪代码Controller 层 GetMapping(/qrcode) public void qrcode(HttpServletResponse response, RequestParam String scene) throws IOException { byte[] bytes createWxACode(token, scene, pages/index/index, 430); response.setContentType(image/png); response.getOutputStream().write(bytes); }微信接口成功时 Content-Type 通常是 image/png失败时则是 application/json。在 Java 中不要只判断 HTTP 状态码因为接口失败也可能返回 200 但 body 是 JSON微信接口在部分错误场景下会用 HTTP 200 返回 errcode。所以更稳妥的做法是先读取 byte[]然后判断图片魔数。PNG 文件的前四字节固定是0x89 0x50 0x4E 0x47也就是\x89PNG可以用下面的方式判断byte[] bytes createWxACode(token, scene, page, width); if (bytes.length 4 (bytes[0] 0xFF) 0x89 bytes[1] P bytes[2] N bytes[3] G) { // 正常图片继续处理 } else { // 错误 JSON转字符串查看 errcode String err new String(bytes, StandardCharsets.UTF_8); log.error(微信接口错误: {}, err); }三种返回方式各有取舍保存文件适合预生成Base64 适合后端接口直接返回 JSON 给前端不需要额外请求直接输出流最简单但每次都要调微信流量一大就会触发频率限制。我建议把 Base64 和直接输出流都作为上层选择底层统一暴露byte[]这样切起来非常快。5. 避坑指南微信二维码接口最常见的 4 个坑以下都是真实场景里反复出现的问题我按“现象 → 原因 → 解决”的顺序写每条都可以直接对照排查。5.1 坑一access_token 在服务里频繁失效接口报 40001现象生成二维码的接口偶尔报错日志里出现errcode:40001, errmsg: invalid access_token而且不是每次失败是隔一段时间就集中报一批。原因access_token 有效期 7200 秒但你的服务可能部署了多个节点每个节点各获取各的 token。后拿到的 token 会把先前的挤掉但微信对 token 的生效有延迟多节点之间会出现短暂的空窗期。另一个常见原因是没有做缓存每次生成二维码都重新调 token 接口也会引发 token 被刷新。解决用一个全局缓存比如 Rediskey 按 appid 区分value 存 access_token过期时间设为 7000 秒比有效期提前 200 秒刷新。如果请求期间发现 token 过期加锁重新获取避免多个线程同时刷 token。下面是一个典型的封装示意public String getAccessToken() { // 第一步查缓存命中就直接返回 String token redis.opsForValue().get(WX_TOKEN_KEY appid); if (token ! null) { return token; } // 第二步加锁防止并发重复刷新 synchronized (this) { token redis.opsForValue().get(WX_TOKEN_KEY appid); if (token ! null) { return token; } // 调用微信 token 接口刷新 token fetchTokenFromWeixin(appid, secret); redis.opsForValue().set(WX_TOKEN_KEY appid, token, 7000, TimeUnit.SECONDS); } return token; }如果项目没用 Redis也可以用本地内存缓存但要注意多实例部署时每个实例的 token 可能不一致最好还是引入一个共享存储。如果已经用了 Redis 还出现 40001检查多个服务实例的时钟是否一致以及 Redis 的 key 是否被手动删除。这个坑不解决第 3 章的 5 种实现方式谁写都会炸。5.2 坑二scene 内容超长或带中文接口返回 41031现象curl 或 Java 调用返回errcode:41031, errmsg: invalid scene。有时甚至生成成功但扫码后小程序拿不到预期的参数。原因scene 参数被拼接得又长又复杂或者里面带了中文、空格。微信对 scene 有严格限制最多 32 个可见字符只允许数字、字母以及!#$()*,/:;?-._~这些特殊字符。很多后端会下意识把userId123activityId456channelabcd整个塞进去长度超限。解决scene 里只放一个短 ID比如用户表主键。需要传多维度信息时可以在服务端维护一个超短随机码例如s 9 位随机字符串把它和真实业务参数存到 Redis并设置过期时间。这样 scene 永远只有 10 个字符既满足限制又能让扫码后的页面一次性拿到完整参数。// 生成短标识作为 scene避免直接拼业务参数 String scene s RandomStringUtils.randomAlphanumeric(9); redis.set(scene: scene, userId123activityId456, 300, TimeUnit.SECONDS);如果只是中文可以尝试用英文字母替代但不建议对中文做 URL 编码因为 scene 的总长度限制按字符数算编码后的 % 会占多个字符更容易超限。另外注意和是允许的但传参前要对拼接后的字符串做一次长度校验超过 32 字符直接拒绝并告警别让它走到微信接口才报错。5.3 坑三page 路径写错或带了 .html生成失败现象调用接口返回errcode:41030, errmsg: invalid page或者生成了一张码但是扫码后提示页面不存在。原因page 参数必须是小程序内已存在的页面路径而且不能带前导/不能带.html不能带参数。常见错误是把pages/index/index.html写进去或者写成了/pages/index/index。还有一个小程序端大小写敏感的问题路径大小写不匹配也会报 41030。解决先在小程序开发工具的“页面”列表里确认路径复制不带后缀的路径。如果接口已经报 41030先检查路径是否匹配。开发阶段记得给请求体加env_version:develop否则默认 release 模式会直接校验线上已发布页面本地未发布的页面会失败。下面是一个修复后的请求体示例{ scene: uid_12345, page: pages/index/index, width: 430, env_version: develop }还有一个细节page 如果不传默认跳小程序首页如果你的首页路径不是pages/index/index最好显式传递。如果想把这个参数配置化放到application.yml里不同环境用不同 path可以避免联调时反复改代码。5.4 坑四用 String 接收图片响应得到一堆乱码现象代码里用response.body().string()或ResponseEntityString接收微信返回结果打印出来是一串乱码或者保存后的文件不是图片打开报错。原因微信接口返回的是二进制图片流但你用了文本解析器。比如 Hutool 的body()会按默认编码解码RestTemplate 的 String 转换器也会把字节流转成乱码字符串。更隐蔽的是某些 HTTP 客户端会自动把响应头里的Content-Type当作文本类型导致内部转码。解决统一用byte[]或字节数组接收响应不要用 String。上面第 3 章代码里我们用的readAllBytes、EntityUtils.toByteArray、response.body().bytes()、response.bodyBytes()都是正确的。如果使用 RestTemplate必须保证消息转换器列表里ByteArrayHttpMessageConverter在最前面否则 Spring 会选 String 转换器。排查时可以先打印响应头看Content-Type是不是image/png如果变成了application/json说明接口返回了错误信息而不是图片这时候再去解码错误 JSON。6. 进阶玩法把二维码生成做成稳定生产线6.1 用 Redis 做图片缓存按场景分层第 5 章的 token 缓存只是保底真正的生产环境我会把“二维码图片本身”也缓存起来。比如同一个活动码如果 scene 是 activityId所有用户共用一张码缓存 7 天都没问题。按用户维度生成的码内容不同不能复用但也可以把生成结果存下来用户再次请求时直接返回图片字节。我在 service 层一般做两层查询byte[] getQrCode(String scene) { // 第一层Redis 缓存图片字节避免重复调微信 byte[] cached redis.get(qr: scene); if (cached ! null) { return cached; } // 第二层调微信实时生成然后写回缓存 byte[] fresh createWxACode(token, scene, pages/index/index, 430); redis.set(qr: scene, fresh, 24, TimeUnit.HOURS); return fresh; }缓存时间不要太长因为小程序页面内容迭代后旧码扫码可能进到一个报错页面。一般 24 小时或按活动周期设置。高频活动码建议异步预生成而不是用户点要求时才去请求微信。常见做法是在活动创建时把需要的 scene 列表丢进消息队列消费者拉取后统一调用微信接口生成图片上传到 OSS 或 CDN再把 URL 写入数据库。前端拿到的是一张 CDN 图片不直接依赖微信接口。6.2 重试、监控和最后留个习惯异步预生成也别忘了重试和监控。微信接口失败后延迟 1 分钟重试并记录错误码如果连续失败超过 3 次邮件告警。这样把实时调用变成了异步任务微信接口频率限制的影响就小很多。小程序二维码接口本身不复杂真正复杂的是 token 有效期的网络问题、参数格式的严格限制以及图片流的正确消费方式。以前我在一个业务系统里图省事直接每次实时生成结果活动流量一大微信接口报了很多超时和限流又被运营追着问为什么图片加载不出来。后来改成“Redis 缓存 异步预生成 图片上传 OSS”这套组合接口基本没有打满过微信频率限制。每当你准备在业务代码里直接调微信接口时多想想能不能缓存、能不能提前生成这比选哪个 HTTP 客户端更关键。希望帮到你。本文还有配套的精品资源点击获取