微信公众号(服务号)用户扫码关注并推送消息

5,021 阅读6分钟

使用前提

有一个认证过的微信公众号(服务号)

解决思路

使用生成带参数的二维码,微信事件通知来解决这个问题

微信生成带参二维码官方文档

微信事件通知官方文档

具体要实现的流程,类似于Tower的扫码关注公众号并接收消息通知

image-20210817210752699.png 点击绑定二维码,弹出对用公众号二维码图片 image-20210817210953163.png 使用微信扫码 image-20210817211105793.png 点击关注后,成功绑定微信公众号。并能接收公众号推送的消息。

以上就是需要实现的功能。

具体实现

其实微信挺坑的

配置公众号

开发->基本配置

获取AppID AppSecret ,配置白名单 image-20210817212220627.png

设置与开发->公众号设置->功能设置

配置网页授权域名 image-20210817212513886.png

开发->基本配置->服务器配置

注:提交该配置服务器必须要有验证此token的接口(接收事件通知的接口->就是你配置的服务器地址(URL),token是自定义的填啥都行)

目前使用的是明文模式,后续有机会更新为加密模式。

配置事件通知接口(类似于微信支付回调地址一样,接收微信官方给推送的事件消息) image-20210817212708397.png

服务->模版消息

如果没看到模版消息,就把左侧菜单栏拉到底,找见模版消息 加到菜单里就行了

根据也无需求增加一个你要用的模版 image-20210817215127946.png

Java代码编写

导入一个微信开发工具包SpringBoot版(神器)

WxJava码云地址

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>wx-java-mp-spring-boot-starter</artifactId>
  <version>4.1.0</version>
</dependency>

yaml文件配置

公众号配置

 mp:
 app-id: #AppId
 secret: #AppSecret
 token: #自定义的token
 aes-key: #消息加解密秘钥(EncodingAESKey)

获取服务器的二维码

// 参数userId可视为附加值,因为我需要通过userId来将用户openId关联
// 注入
private final WxMpService wxMpService;

 /**
    *  @Description 获取服务号二维码(带参)
    *  @author Rick Jen
    *  @Date   2021/8/17 21:47
    */
    @RequestMapping(value = "/getMpQrCode",method = RequestMethod.GET)
    public ApiResult getMpQrCode(String userId) throws WxErrorException {
        // 先获取ticket
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateLastTicket(userId);
        // 通过ticket获取微信生成的二维码链接
        return ok(wxMpService.getQrcodeService().qrCodePictureUrl(wxMpQrCodeTicket.getTicket()));
    }

服务器地址(URL)接口编写

参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,subscribe
EventKey事件KEY值,qrscene_为前缀,后面为二维码的参数值
Ticket二维码的ticket,可用来换取二维码图片
// 此接口的uri必须和你配置的那个服务器地址(URL)对应上  注意放行此API
// 注入
private final WxMpService wxMpService;

/**
     * @Description 验证token  获取时间通知信息
     * @author Rick Jen
     * @Date 2021/8/17 17:42
     */
    @RequestMapping(value = "/redirectUri")
    public String redirectUri(HttpServletRequest request, HttpServletResponse response) throws WxErrorException, IOException {
        ServletInputStream inputStream = request.getInputStream();
        ServletOutputStream outputStream = response.getOutputStream();
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        String token = wxMpService.getWxMpConfigStorage().getToken();
        String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
        WxMpXmlMessage wxMpXmlMessage = WxMpXmlMessage.fromXml(xmlResult);
        log.info("接收到微信回调消息{{}}" , wxMpXmlMessage.toString());
        if (StringUtils.isNotBlank(wxMpXmlMessage.getEventKey())){
            // 附加值
            String[] eventKeyStr = wxMpXmlMessage.getEventKey().split("_");
            // 用户ID
            String userId = eventKeyStr[1];
            // 关注公众号
            if (wxMpXmlMessage.getEvent().equals("subscribe")){
                //TODO 执行你的操作
            }else {
                //TODO 取消关注
            }
        }
        //排序
        String[] arr = {token, timestamp, nonce};
        Arrays.sort(arr);

        StringBuilder content = new StringBuilder();
        for (String s : arr) {
            content.append(s);
        }
        //sha1Hex 加密
        MessageDigest md = null;
        String temp = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            temp = byteToStr(digest);
            log.info("加密后的token:" + temp);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        assert temp != null;
        if ((temp.toLowerCase()).equals(signature)) {
            return echostr;
        }
        return null;
    }

 private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    private static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        String s = new String(tempArr);
        return s;
    }

发送消息

// openId为用户在此微信公众号的唯一标识(同一用户在不同场景下的openId都不一样)
// 注入
private final WxMpService wxMpService;

/**
    *  @Description 发送消息
    *  @author Rick Jen
    *  @Date   2021/8/17 21:52
    */
    @RequestMapping(value = "/sendTemplateMessage", method = RequestMethod.POST)
    public ApiResult sendTemplateMessage(String openId) {
        if (StringUtils.isBlank(openId)){
            return fail("openID为空!");
        }
        log.info("获取到参数openID{{}}" , openId);
        // 需要跳转的小程序及页面
        WxMpTemplateMessage.MiniProgram miniProgram = new WxMpTemplateMessage.MiniProgram();
        miniProgram.setAppid("小程序的openId");
        // 注意如果小程序对应页面要token 这个必须设置为true 否则报错{“errcode“:40165,“errmsg“:“invalid weapp pagepath}
        miniProgram.setUsePath(true);
        miniProgram.setPagePath("要跳转的小程序页面-可以携带参数");
        // 创建消息模板对象
        WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                .toUser(openId)
                .templateId("模版ID")
                .miniProgram(miniProgram)
                .build();
        // 填写模板消息及内容  若有多个就再往下加就行了
        templateMessage.addData(new WxMpTemplateData("first", "值"));
        templateMessage.addData(new WxMpTemplateData("keyword1", DateUtil.today()));
        try {
            // 发送消息 并返回消息ID
            String messageId = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
            log.info("发送模版消息后的消息id:{{}}" , messageId);
            return ok("消息成功!");
        } catch (WxErrorException e) {
            e.printStackTrace();
        }
        return fail("消息发送失败!");
    }

至此结束。完成了从用户扫码到发送消息的一个功能。

在此之前还走出了一条实现了功能,但是没有这个完善的路。有兴趣可以继续往下看

具体实现

思路:通过微信的静默授权来获取用户openId。将授权接口地址生成一个二维码,告诉微信用户走了这个地址后回调到我们服务器的那个接口,用户微信扫描二维码进行重定向并获取用户的openId

公众号配置

这个只需要配置一个白名单和一个安全域名即可。

代码实现

maven依赖导入

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>wx-java-mp-spring-boot-starter</artifactId>
  <version>4.1.0</version>
</dependency>
<!--HuTool工具类依赖 https://hutool.cn/docs/#/ -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.9</version>
</dependency>
<!--生成二维码依赖-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.3</version>
</dependency>

获取授权二维码

// 注入
private final WxMpService wxMpService;

/**
    *  @Description 静默重定向获取用户openId地址
    *  @author Rick Jen
    *  @Date   2021/8/17 22:08
    */
@RequestMapping(value = "/getMpQrCode",method = RequestMethod.GET)
    public ApiResult getMpQrCode(){
        // 确定自己的回调地址
        String redirectUri = "回调通知地址";
        String url = wxMpService.getOAuth2Service().buildAuthorizationUrl(redirectUri,"snsapi_base","附加值");
        // 将这个url生成一个二维码
        QrConfig qrConfig = new QrConfig();
        // 想弄好看点自己设置
        qrConfig.setWidth(300);
        qrConfig.setHeight(300);
        String base64Qrcode = QrCodeUtil.generateAsBase64(url, qrConfig, "jpg");
        return ok(base64Qrcode);
    }

用户扫码后微信回调通知接口

/**
    *  @Description 静默授权重定向接收通知
    *  @author Rick Jen
    *  @Date   2021/8/17 20:52
    */
    @Deprecated
    @RequestMapping(value = "/notifyUrl")
    public void notifyUrl(HttpServletRequest request) throws WxErrorException {
        // 微信返回code
        String code = request.getParameter("code");
        log.info("返回参数code{{}}", code);
        // 获取附加值
        String state = request.getParameter("state");
        log.info("附带参数{{}}", state);
        // 通过code获取微信access_token
        WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
        // 获取该code对应的用户openId
        String openId = wxOAuth2AccessToken.getOpenId();
        log.info("用户openId{{}}", openId);
        //TODO 执行你自己的操作
    }

发送模版消息和上面一样

这种操作也可以实现此功能,唯一不好的一点就是通过用户扫码之后白屏了。后面尝试生成一个此微信公众号的二维码,然后解析该二维码得到一个地址,让跳转到得到的那个地址,结果一样,也是白屏。达不到想要的效果,所以尝试使用接收微信消息通知这个功能来实现了。

至此就全部结束了。

微信虽然很坑,但是功能很全。你想要的逻辑他基本都有,就看你怎么去理解文档了。