08. 微信公众号生成带参数二维码

1.官网
  1. 为了满足用户聚到推广分析、用户账号绑定等场景需要,可以生成不同场景值的二维码

  2. 目前有两种类型二维码

    1. 临时二维码

      • 有过期时间,最长可以设置为在二维码生成后的30天(即2592000秒)后过期
      • 数量不限制
      • 临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
    2. 永久二维码

      • 无过期时间
      • 数量较少(目前为最多10万个)
      • 主要用于适用于帐号绑定、用户来源统计等场景
  3. 创建

    1. 接口

      https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
      
    2. 接口请求体参数示例

      {
          "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
    3. 返回

      1. 正确

        {
            "ticket": "gQHo8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyVWNqM3BtRmFjVEgxN0hmWTF4MUoAAgTrwhRhAwQAjScA",
            "expireSeconds": 2592000,
            "url": "http://weixin.qq.com/q/02Ucj3pmFacTH17HfY1x1J"
        }
        
        参数说明
        ticket获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。
        expire_seconds该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)。
        url二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片
        1. ticket,可以用来作为参数调用微信接口GET请求https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET获取该二维码(TICKET记得进行UrlEncode)
          • ticket正确情况下,http 返回码是200,是一张图片,可以直接展示或者下载
          • 错误情况下(如ticket非法)返回HTTP错误码404
        2. url,用这个 url 生成的二维码图片与 根据 ticket 调微信服务器接口获得二维码图片一致,扫码关注之后的事件推送一致。
      2. 错误

        {
            "errCode": null,
            "errMsg": null
        }
        
  4. 用户扫码关注公众号后的事件推送

    1. 如果用户未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。

      <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>
      
      • EventKeyqrscene_ + 创建二维码时的scene_id 或 scene_str
      • Ticket是创建二维码时返回的,可以用作参数获取二维码
    2. 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。

      <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>
      
      • EventSCAN
      • EventKey是创建二维码时的scene_id 或 scene_str
2. 实现
  1. 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;
            }
        }
    }    
    
  2. 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;
    }    
    
  3. 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("生成二维码关闭流失败!");
                }
            }
        }
    }    
    
  4. 源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值