1.官网
-
为了满足用户聚到推广分析、用户账号绑定等场景需要,可以生成不同场景值的二维码
-
目前有两种类型二维码
-
临时二维码
- 有过期时间,最长可以设置为在二维码生成后的30天(即2592000秒)后过期
- 数量不限制
- 临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
-
永久二维码
- 无过期时间
- 数量较少(目前为最多10万个)
- 主要用于适用于帐号绑定、用户来源统计等场景
-
-
创建
-
接口
https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
-
接口请求体参数示例
{ "expire_seconds": 2592000, "action_name": "QR_STR_SCENE", "action_info": { "scene": { "scene_str": "zxcvbnm" } } }
参数 说明 expire_seconds 该二维码有效时间, 以秒为单位
。最大不超过2592000(即30天)
,此字段如果不填,则默认有效期为30秒。永久二维码为 null
action_name 二维码类型, QR_SCENE为临时的整型参数值
,QR_STR_SCENE为临时的字符串参数值
,QR_LIMIT_SCENE为永久的整型参数值
,QR_LIMIT_STR_SCENE为永久的字符串参数值
action_info 二维码详细信息 scene_id 场景值ID,临时二维码时为32位非0整型, 永久二维码时最大值为100000(目前参数只支持1--100000)
scene_str 场景值ID(字符串形式的ID), 字符串
类型,长度限制为1到64
-
返回
-
正确
{ "ticket": "gQHo8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyVWNqM3BtRmFjVEgxN0hmWTF4MUoAAgTrwhRhAwQAjScA", "expireSeconds": 2592000, "url": "http://weixin.qq.com/q/02Ucj3pmFacTH17HfY1x1J" }
参数 说明 ticket 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。 expire_seconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)。 url 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片 ticket
,可以用来作为参数调用微信接口GET
请求https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
获取该二维码(TICKET
记得进行UrlEncode
)ticket
正确情况下,http
返回码是200
,是一张图片
,可以直接展示或者下载- 错误情况下(如
ticket
非法)返回HTTP错误码404
。
url
,用这个 url 生成的二维码图片
与 根据ticket
调微信服务器接口获得二维码图片一致,扫码关注之后的事件推送一致。
-
错误
{ "errCode": null, "errMsg": null }
-
-
-
用户扫码关注公众号后的
事件推送
-
如果用户
未关注
公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。<xml><ToUserName><![CDATA[gh_51a674c7e13c]]></ToUserName> <FromUserName><![CDATA[oqUo360zkIEFzJRcrxCn6MfZhah0]]></FromUserName> <CreateTime>1628733211</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[qrscene_ceshi]]></EventKey> <Ticket><![CDATA[gQFc8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyM0xpZ29qRmFjVEgxMkI2WTF4Y3gAAgSleRRhAwQAjScA]]></Ticket> </xml>
EventKey
是qrscene_
+ 创建二维码时的scene_id 或 scene_str
Ticket
是创建二维码时返回的,可以用作参数获取二维码
-
如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
<xml><ToUserName><![CDATA[gh_51a674c7e13c]]></ToUserName> <FromUserName><![CDATA[oqUo36wMcs4NW1Tpjl_LcUKPmauY]]></FromUserName> <CreateTime>1628732049</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[SCAN]]></Event> <EventKey><![CDATA[ceshi]]></EventKey> <Ticket><![CDATA[gQFc8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyM0xpZ29qRmFjVEgxMkI2WTF4Y3gAAgSleRRhAwQAjScA]]></Ticket> </xml>
Event
是SCAN
EventKey
是创建二维码时的scene_id 或 scene_str
-
2. 实现
-
WxQrCodeBean.java
import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * 生成 二维码 所需参数 * * @author sheng_zs@126.com * @date 2021-08-11 16:13 */ @Data public class WxQrCodeBean implements Serializable { /** * 该二维码有效时间,以秒为单位。 最大不超过 2592000(即30天)<br> * 此字段如果不填,则默认有效期为30秒。 */ @JsonProperty("expire_seconds") private Integer expireSeconds; /** * 二维码类型 * <ol> * <li>QR_SCENE为临时的整型参数值</li> * <li>QR_STR_SCENE为临时的字符串参数值</li> * <li>QR_LIMIT_SCENE为永久的整型参数值</li> * <li>QR_LIMIT_STR_SCENE为永久的字符串参数值</li> * </ol> */ @JsonProperty("action_name") private String actionName; /** * 二维码详细信息 */ @JsonProperty("action_info") private ActionInfo actionInfo; /** * 初始化,获取 scene_str 对象 * * @param isTemporary 是否是临时二维码 * @param sceneId 场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000) * @param expireSeconds 过期时间,最大不超过 2592000,如果不是临时二维码,则要求为 null * @return {@link WxQrCodeBean} */ public static WxQrCodeBean getSceneIdInstance(boolean isTemporary, Integer sceneId, Integer expireSeconds) { // 参数判断 final boolean flag = (isTemporary && ((Objects.isNull(expireSeconds) || expireSeconds > 2592000 || expireSeconds <= 0))) || (!isTemporary && (Objects.nonNull(expireSeconds) || sceneId > 100000 || sceneId < 1)); if (flag) { throw new IllegalArgumentException("生成二维码参数有误!"); } WxQrCodeBean codeBean = new WxQrCodeBean(); codeBean.setExpireSeconds(expireSeconds); codeBean.setActionName(isTemporary ? "QR_SCENE" : "QR_LIMIT_SCENE"); codeBean.setActionInfo(ActionInfo.getSceneId(sceneId)); return codeBean; } /** * 初始化,获取 scene_str 对象 * * @param isTemporary 是否是临时二维码 * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64 * @param expireSeconds 过期时间,最大不超过 2592000,如果不是临时二维码,则要求为 null * @return {@link WxQrCodeBean} */ public static WxQrCodeBean getSceneStrInstance(boolean isTemporary, String sceneStr, Integer expireSeconds) { // 参数判断 final boolean flag = (isTemporary && (Objects.isNull(expireSeconds) || expireSeconds > 2592000 || expireSeconds <= 0)) || (!isTemporary && Objects.nonNull(expireSeconds)) || StrUtil.isBlank(sceneStr); if (flag) { throw new IllegalArgumentException("生成二维码参数有误!"); } WxQrCodeBean codeBean = new WxQrCodeBean(); codeBean.setExpireSeconds(expireSeconds); codeBean.setActionName(isTemporary ? "QR_STR_SCENE" : "QR_LIMIT_STR_SCENE"); codeBean.setActionInfo(ActionInfo.getSceneStr(sceneStr)); return codeBean; } @Data static class ActionInfo { /** * scene */ private Map<String, Object> scene; /** * 初始化,获取 scene_id 对象 * * @param sceneId scene_id * @return {@link ActionInfo} */ static ActionInfo getSceneId(Integer sceneId) { ActionInfo info = new ActionInfo(); Map<String, Object> map = new HashMap<>(1); map.put("scene_id", sceneId); info.setScene(map); return info; } /** * 初始化,获取 scene_str 对象 * * @param sceneStr scene_str * @return {@link ActionInfo} */ static ActionInfo getSceneStr(String sceneStr) { ActionInfo info = new ActionInfo(); Map<String, Object> map = new HashMap<>(1); map.put("scene_str", sceneStr); info.setScene(map); return info; } } }
-
WxTicketBean.java
import com.fasterxml.jackson.annotation.JsonAlias; import com.small.nine.wxmp.domain.bean.wx.WxErrorMsgBean; import lombok.Data; import lombok.EqualsAndHashCode; /** * 微信生成永久、临时二维码返回的 ticket 对象 * * @author sheng_zs@126.com * @date 2021-08-11 16:07 */ @Data @EqualsAndHashCode(callSuper = true) public class WxTicketBean extends WxErrorMsgBean { private static final long serialVersionUID = 1L; /** * 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。 */ private String ticket; /** * 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)。 */ @JsonAlias("expire_seconds") private Integer expireSeconds; /** * 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片<br> * 就是用这个地址生成 二维码 的二维码图片与 根据 ticket 调微信服务器接口获得二维码图片一致 */ private String url; }
-
serviceImpl
@Override public WxTicketBean createTemporaryQrCode() { final String url = String.format(WxConstant.URL_QR_CODE_CREATE_POST, getAccessToken()); // 生成最大失效时间 临时二维码 String post = HttpUtil.post(url, JsonUtils.writeValueAsString(WxQrCodeBean.getSceneStrInstance(true, UUID.randomUUID().toString(), 2592000))); return JsonUtils.readValue(post, WxTicketBean.class); } @Override public void getQrCode(HttpServletResponse response) { /*final String url = String.format(WxConstant.URL_SHOW_QR_CODE_GET, "gQFc8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyM0xpZ29qRmFjVEgxMkI2WTF4Y3gAAgSleRRhAwQAjScA"); try { response.reset(); // 需要指定文件名的编码方式为 ISO-8859-1,否则会出现文件名中的中文字符丢失的问题 response.addHeader("Content-Disposition", "attachment;filename=" + new String("测试临时二维码.png".getBytes("gb2312"), StandardCharsets.ISO_8859_1)); // 设置响应内容类型为图片 response.setContentType("image/png"); long download = HttpUtil.download(url, new BufferedOutputStream(response.getOutputStream()), true); } catch (IOException e) { log.error("生成二维码失败:{}", e.getMessage()); log.error("", e); }*/ BufferedOutputStream stream = null; try { response.reset(); // 需要指定文件名的编码方式为 ISO-8859-1,否则会出现文件名中的中文字符丢失的问题 response.addHeader("Content-Disposition", "attachment;filename=" + new String("测试临时二维码.png".getBytes(), StandardCharsets.ISO_8859_1)); // 设置响应内容类型为图片 response.setContentType("image/png"); // "http://weixin.qq.com/q/023LigojFacTH12B6Y1xcx" 是生成二维码时返回的 url BufferedImage image = QrCodeUtil.generate("http://weixin.qq.com/q/023LigojFacTH12B6Y1xcx", 300, 300); ImageIO.write(image, "png", stream = new BufferedOutputStream(response.getOutputStream())); } catch (IOException e) { log.error("生成二维码失败:{}", e.getMessage()); log.error("", e); } finally { if (Objects.nonNull(stream)) { try { stream.flush(); stream.close(); } catch (IOException e) { log.error("生成二维码关闭流失败!"); } } } }