简介:面向Java后端开发者,这套代码包解决微信小程序二维码生成问题,覆盖裂变邀请、渠道推广等需要专属小程序码的场景。作者基于微信官方getUnlimitedQRCode接口,从前端-后端API-微信API的安全链路切入,总结出5种实现方式,避免secret和token明文暴露。资源共69个文件,压缩包仅53KB,以xml配置、java源码、properties配置及class与jar依赖为主,内含Maven工程结构与IDE配置文件,目录清晰,便于直接导入运行和二次开发。已有2644人学习,适合需要对比多种服务端方案的中级Java工程师。下载后可获得完整源码工程,配合五种方式的使用说明,能快速掌握接口对接、参数封装与安全调用细节,有效缩短排错周期。 先说个真实感受:小程序二维码这块,很多Java后端第一次做都会懵。你以为就是“调个接口、返回一张图片”的事,结果接口好几个、参数又一堆,还有一堆“码”的区别要弄清楚。我最早接到这个需求是在一个分销海报项目里,用户要生成带自己邀请码的小程序码,当时把官方文档翻了个遍,又踩了几个坑才把整套流程跑顺。这篇就把我在生产环境里实测过的5种Java实现方式全部拆开讲,每个方案适合什么场景、有什么坑、代码怎么写,一次说清楚,希望能帮你少走弯路。
1. 动手之前,先把微信二维码接口体系搞清楚
1.1 小程序码和普通二维码,别搞混了
微信生态里“二维码”其实分好几类,很多需求方自己都说不清楚到底要哪种。最常见的是“小程序码”,就是那个圆形的码,长得像一朵花,扫码后直接进指定小程序页面;另一种是方方正正的“小程序二维码”,外观接近普通二维码,扫码同样可以跳转小程序;还有一类是“普通二维码”,可以指向URL Scheme、URL Link,也能在微信里被扫后跳转小程序。
这三者对应的生成方式完全不同。圆形小程序码必须调用微信官方接口生成;方形小程序二维码也是官方接口输出;而普通二维码本身可以自己用ZXing等库生成,但它承载的内容(URL Scheme或URL Link)需要先通过微信HTTP接口换取。搞清楚业务方到底要哪种,再选方案,不然做出来很容易返工。
1.2 微信官方对外到底提供了几个HTTP接口
微信开放平台针对小程序码/链接,公开的HTTP接口主流就这几个:getwxacodeunlimit(获取不限制数量的小程序码)、getwxacode(获取小程序码,数量有限制)、createwxaqrcode(获取小程序二维码,数量有限制),另外还有generatescheme和generate_urllink这两个生成链接的接口。
前三个接口返回的都是图片二进制流,后两个返回的是JSON链接。区别很关键:带unlimit的接口适合数量大、参数动态的场景,比如每个用户一个邀请码;不带unlimit的两个接口适合数量少、相对固定的场景,因为官方有数量上限。如果业务量不大,倒是无所谓用哪个,但要是做分销、推广这种大规模场景,闭眼选getwxacodeunlimit就对了。
1.3 绕不开的前置步骤:access_token
不管调哪个生成接口,第一步都是拿access_token。它是小程序全局调用凭证,接口路径是:
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRETappid和secret在小程序后台的“开发管理-开发设置”里拿。access_token的有效期是7200秒,也就是2小时,但官方对获取频率有限制,每天有调用上限。所以生产环境绝不能每次都现拿,必须用Redis或者本地缓存存起来,提前几分钟刷新。
我见过太多新手直接每次请求都重新调token接口,结果上线第二天接口就报45009(调用超过限额)。正确姿势是:用一个定时任务提前刷新,或者拿token前先查缓存,缓存里没有再调。这个环节做好了,后面生成码的流程才稳。
2. 五种主流实现方式逐一拆解,附Java代码
2.1 方式一:getwxacodeunlimit,大规模带参码首选
这个接口我日常用得最多,特点是通过scene参数传入自定义信息,码的数量不限制,适合给每个用户、每个订单生成一个专属码。请求方式和Java示例代码如下:
POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN Content-Type: application/json { "scene": "id=1001&channel=poster", "page": "pages/index/index", "width": 430, "check_path": false, "env_version": "release" }Java端用最基础的HttpURLConnection就能搞定:
String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + token; HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json"); String body = "{\"scene\":\"id=1001&channel=poster\",\"page\":\"pages/index/index\",\"width\":430,\"check_path\":false,\"env_version\":\"release\"}"; conn.getOutputStream().write(body.getBytes(StandardCharsets.UTF_8)); int code = conn.getResponseCode(); if (code == 200) { InputStream in = conn.getInputStream(); byte[] imageBytes = readAllBytes(in); // 这里收到的是图片二进制 // 保存到本地、OSS或者返回Base64 } else { String error = new String(readAllBytes(conn.getErrorStream()), StandardCharsets.UTF_8); System.err.println("微信接口报错: " + error); }重点说几个参数:scene最大32个可见字符,不支持中文,官方文档明确说明只能包含数字、字母、下划线等可见字符,我习惯把业务ID和渠道参数按“id=1001&channel=poster”这种格式拼接;page是跳转的小程序页面路径,不能带query参数,所有动态参数都塞scene里;width默认430px,这个宽度扫码识别最稳,低于这个值图片在手机上容易糊;check_path如果设成true,微信会校验page是否存在,页面没发布就会报错,所以我建议做预生成时设成false。
2.2 方式二:createwxaqrcode与getwxacode,适合少量使用
这两个接口我放在一起说,因为它们的特点很像:生成的码永久有效,但总量有限。createwxaqrcode生成的是方形小程序二维码,getwxacode生成的是圆形小程序码,适合码数量不多、参数相对固定的场景,比如给线下门店配固定的点餐码、给每个商品做固定标签码。
这两个接口传参方式跟unlimit不一样,不是用scene,而是直接在path里带参数:
POST https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN Content-Type: application/json { "path": "pages/index/index?id=1001", "width": 430 }Java代码和方式一大同小异,就是URL和请求体变一下,这里就不重复贴了。需要注意的是,createwxaqrcode返回的图片格式是jpg,getwxacode返回的是jpg还是png具体看接口版本,保存时要根据Content-Type灵活处理。另外这两个接口生成时若page未发布,会直接报41030,所以测试阶段要么用体验版路径,要么先发布一个空页面兜底。
2.3 方式三:WxJava封装库,少写HTTP代码的选择
如果你的项目里不想维护这些HTTP调用细节,用WxJava(weixin-java-miniapp)是最快的路子。它是一个老牌微信开发Java SDK,把token获取、接口调用、错误处理都封装好了,加个依赖就能用:
<dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>4.5.0</version> </dependency>核心代码很清爽:
WxMaService wxMaService = WxMaConfiguration.getMaService(); WxMaCodeService codeService = wxMaService.getCodeService(); File qrCodeFile = codeService.createWxaCodeUnlimit( "id=1001&channel=poster", // scene "pages/index/index", // page 430, // width null, // autoColor false, // checkPath null, // lineColor false, // isHyaline false // isAutoColor );WxJava的好处是省心,错误码都帮你翻译成人话,比如token过期它会抛特定异常,捕获后刷新token重试即可。坏处是版本迭代快,不同小版本的API签名可能不一样,升级要谨慎看release note。如果团队里没人愿意手写HTTP调用,或者你们已经用了WxJava做登录、支付,那生成码也顺手用它,统一维护成本低。
2.4 方式四:URL Scheme / URL Link转普通二维码
这个方案思路不一样,不直接生成小程序码,而是先用HTTP接口生成一个链接,再把链接转成普通二维码。官方有两个接口:generatescheme生成URL Scheme,微信内有特定格式,适合微信内部识别;generate_urllink生成URL Link,适合短信、邮件、浏览器等微信外场景。
URL Scheme接口调用示例:
POST https://api.weixin.qq.com/wxa/generatescheme?access_token=ACCESS_TOKEN Content-Type: application/json { "jump_wxa": { "path": "/pages/index/index?id=1001", "query": "" }, "expire_type": 1, "expire_interval": 30 }接口返回的JSON里有个scheme字段,就是类似“weixin://dl/business/?t=xxxxx”的字符串。拿到链接后,再用ZXing把它编码成普通二维码图片,核心代码:
QRCodeWriter writer = new QRCodeWriter(); BitMatrix matrix = writer.encode(schemeUrl, BarcodeFormat.QR_CODE, 430, 430); BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix);这种方式最大的优势是灵活,二维码可以印在网页、海报、甚至线下物料上,用户扫完跳小程序。而且URL Link本身支持设置过期时间、单次打开限制等,做活动营销很合适。但要注意,URL Scheme和URL Link也有配额限制,需要在小程序后台申请,且链接有有效期,别做成永久码。
2.5 方式五:异步生成加对象存储,工程化兜底方案
前面几种都属于“同步生成、实时返回”,但真到大规模场景,比如一次性给10万个用户生成带参码,同步接口就会很吃力:HTTP调用慢、占用线程、容易超时、微信端也可能限流。这时候我建议把生成流程异步化、存储云端化,这也是我在生产环境里最终采用的架构。
整体思路是这样的:前端或上游服务请求时只提交一个“生成任务”,后端立刻返回任务ID;后端拿Redis做任务去重和状态维护,把生成请求丢进线程池或MQ队列;消费者线程分批调用getwxacodeunlimit接口拿到图片流,直接上传到阿里云OSS或腾讯云COS,然后把URL存数据库并更新任务状态;最后前端轮询任务状态,拿到图片URL后展示。
这个方案的好处是:把耗时操作从请求链路里摘出去,接口响应快;对微信接口限流也有天然缓冲,可以在消费者里控制并发速率。如果只是中小项目,直接用一个线程池加一张任务表就够了,不必上MQ。关键代码不复杂,核心就是把方式一的生成逻辑放进一个异步方法,再把写文件改成上传OSS:
public void asyncGenerate(String scene, String page) { byte[] imageBytes = wxCodeService.generateCode(scene, page, 430); String objectKey = "qr/" + scene + ".jpg"; String url = ossClient.putObject("bucket-name", objectKey, imageBytes); taskMapper.updateUrl(scene, url); }3. 高频报错与排查手册:错误码、token、图片模糊
3.1 最常见的5个报错及解决
我整理了平时遇到频率最高的几个错误码,做成速查表,建议收藏:
| 错误码 | 含义 | 解决方法 |
|---|---|---|
| 40001 | access_token无效或过期 | 检查缓存逻辑,重新拉取token |
| 40097 | 参数错误,通常是scene超长或含非法字符 | scene控制在32个可见字符内,不要放中文 |
| 41030 | page路径不正确或页面未发布 | 检查page参数,未发布的页面用check_path=false |
| 45009 | 接口调用超过限额 | 降低调用频率,或改用不限额接口/异步批量 |
| 48001 | 小程序未认证,api功能未授权 | 去小程序后台完成微信认证 |
45009这个错误最常见,最容易在压测或者活动大促时触发。解决思路有两个:一个是把同步生成改成批量异步生成,控制并发;另一个是如果数量实在庞大,升级到不限额接口的同时申请提高配额。我在做分销海报时就是靠异步队列才扛住大促的。
3.2 Content-Type的坑:成功是图片,失败才是JSON
这是新手最容易踩的坑,没有之一。微信这些生成码接口,调用成功时返回的是image/jpeg或image/png的二进制流,调用失败时返回的才是application/json错误信息。很多人拿到HTTP响应后习惯性先解析成JSON,结果成功时报JSON解析异常,抓瞎半天。
判断方法很简单:先看HTTP状态码。200基本就是图片流,直接读InputStream转字节数组;非200再去读ErrorStream里的JSON文本。如果用了WxJava这类SDK,它在内部已经处理好了,但自己手写HTTP调用时一定要区分。还有一点,读取图片字节流时要一次性读完再处理,别边读边往文件里写,容易因为流未关闭导致文件损坏。
3.3 二维码模糊与识别率问题
二维码图片生成出来模糊、扫码扫不出来,这类问题也特别多。我总结原因就三个:width参数太小、图片被二次压缩、码周围留白不够。
width低于300的话,在部分机型上识别率会明显下降,建议统一用430px作为基准,需要高清海报图可以按比例放大到600-800px。如果你生成的图片要贴在海报上,服务端返回后别用重压缩算法处理,尽量原图输出;前端展示时也别把图缩得太小,二维码区域至少要占屏幕宽度三分之一以上。另外微信小程序码自带一定边距,但如果是自己用ZXing生成的普通二维码,建议设置至少一个模块宽的留白区,否则印刷时会出问题。
4. 再聊几个工程化细节:存储、缓存与scene设计
4.1 scene参数的设计技巧:短码映射代替长参数
前面提到scene最长才32个字符,但业务上经常要传很多信息,比如用户ID加渠道加活动ID,一不小心就超了。我的做法是建一张码映射表,表里存自增ID或随机短字符串,scene里只放这个短码。比如数据库里存一条记录,短码是“A8F3K”,scene就传“A8F3K”,用户扫码进入小程序后,前端把这个短码带给后端,后端再反查出真实参数。
这个方法的好处非常明显:scene短,不容易触发参数校验问题;二维码内容简洁,图片上的码密度低,识别率更高;后续业务参数变了也不用重新生成二维码,只要改数据库映射关系就行。这套设计我强烈推荐。
4.2 token缓存与生成服务封装
访问token缓存别用本地Map,多实例部署时会互相踢掉,一定要用Redis这种共享存储。我的封装习惯是:key为“wx:access_token:{appid}”,value存token,expire设为6000秒(比7200秒短一些),每次获取时先用get,没有再调接口并set。另外再做一层逻辑:调用生成码接口如果遇到40001错误码,主动清掉Redis缓存并重试一次,这样即使token在边缘时间过期也能自动修复。
生成码的服务层也建议抽象一个接口,把“生成小程序码”“生成URL Link转码”“异步生成并上传OSS”分别封装成Strategy实现,后续业务方调用统一入口,想切换底层实现只动配置,不碰业务代码。这个改动不复杂,但线上维护会舒服很多。
4.3 线上经验:哪种方式最省心
直接给结论:普通业务同步获取、码量不大,用方式三(WxJava封装)最省心;要大规模生成、每用户一个码,走方式一加异步队列;要在短信、邮件、网页等微信外场景放码,优先方式四(URL Link转普通二维码)。方式二只适合极少量固定码,方式五本质是架构改造,适合有一定体量的项目。
我自己当前生产环境是这么组合的:用户生成专属海报码用getwxacodeunlimit,通过异步线程池生成后上传OSS,前端拿URL展示;短信营销场景用URL Link生成短链接,再用ZXing转码投放。两条链路都跑得比较稳,高峰期也没有再出现过限流或生成超时的问题。
最后分享一个细节:所有生成的码图片,在OSS上都要设置正确的Content-Type(image/jpeg或image/png)和Cache-Control,否则有些场景下图片会被当成下载附件,或者被浏览器强缓存导致旧码不更新。这个小问题当时排查了挺久,说出来省得你再去踩一遍。
本文还有配套的精品资源,点击获取