Java微信小程序获取关联公众号用户是否已关注标识

小程序 0

文章目录

  • 前言
  • 一、绑定相关数据
    • 1.小程序绑定公众号
    • 2.小程序和公众号关联微信开放平台
  • 二、公众号操作回调
    • 1.服务器配置
    • 2.回调接口(URL)
  • 三、获取微信公众号用户是否已关注标识
    • 1.获取公众号accessToken
    • 2.feign远程调用微信公众号获取用户基本信息(UnionID机制)
    • 3.回调接口补充更新用户关注标识
  • 总结


前言

如果公众号不是认证的服务号就无需看这篇文章,需要先认证,可以参考这篇文章五分钟!手把手教你快速完成微信公众号认证!

你们是否经常在小程序看到这种关注标识
在这里插入图片描述
实现的方法很简单,后端只需要返回给前端给前端当前用户是否有关注公众号就行了,整个逻辑很简单,微信官方也有对应的API,但实际上整个流程就很复杂


一、绑定相关数据

微信官方有很多限制,咱们需要做好一些关联

1.小程序绑定公众号

根据微信官方文档,微信小程序需要关联对应的公众号才能够引导关注公众号
咱们先登录微信公众平台,扫码登录公众号
在这里插入图片描述
然后点击【广告与服务】->【小程序管理】添加关联小程序,这步很简单只需要后台管理员扫码一下就行了。
在这里插入图片描述

2.小程序和公众号关联微信开放平台

这一步比较重要,这是为了获取unionId,关于unionId介绍可以看一下官方说明,UnionID 机制说明,简单来说就是同一个微信开放平台用户的unionId都是唯一的

先注册微信开放平台,然后关联小程序和公众号,关联流程参考文章微信开放平台绑定公众号,小程序也同理,这个时候你们需要改造原来的微信登录接口,会发现登录接口返回的参数多了unionId,把对应用户的unionId存到数据库里,后面会用得上。

二、公众号操作回调

这个是公众号的相关操作会有回调,相当强大,咱们服务器能立马得知用户做了什么操作,这里咱们只需要配公众号关注取消关注的回调。

1.服务器配置

同样登录微信公众平台,选择公众号账号登录,进入【服务与配置】->【基本配置】,会看到有一个服务器配置
在这里插入图片描述
点击修改配置
在这里插入图片描述
这个URL实际上就是咱们要给微信的回调地址,需要外网可访问;
Token咱们随意填写,只要跟服务器对得上;
EncodingAESKey随机生成就好,然后消息加解密选择用明文模式,安全模式可以看看别人写的加解密方式;

2.回调接口(URL)

RequestMethodEnum枚举类是为了判断什么请求方式,如果是get请求实际上就是验证token合法性

public enum RequestMethodEnum {    /**     * get     */    GET(1,"GET"),    /**     * post     */    POST(2,"POST");    private final Integer code;    private final String desc;    RequestMethodEnum(Integer id, String name){        this.code = id;        this.desc = name;    }    public Integer getCode() {        return code;    }    public String getDesc() {        return desc;    }}

MsgTypeEnum枚举类是为了判断微信公众号执行了什么操作

public enum MsgTypeEnum {    /**     * 关注/取消关注事件     */    EVENT(1, "event");    private final Integer code;    private final String desc;    MsgTypeEnum(Integer id, String name){        this.code = id;        this.desc = name;    }    public Integer getCode() {        return code;    }    public String getDesc() {        return desc;    }}

checkSignature验证回调地址是否合法的工具方法

public static boolean checkSignature(String signature, String timestamp,String nonce, String token) {        // 1.将token、timestamp、nonce三个参数进行字典序排序        String[] arr = new String[]{token, timestamp, nonce};        Arrays.sort(arr);        // 2. 将三个参数字符串拼接成一个字符串进行sha1加密        StringBuilder content = new StringBuilder();        for (int i = 0; i < arr.length; i++) {            content.append(arr[i]);        }        MessageDigest md = null;        String tmpStr = null;        try {            md = MessageDigest.getInstance("SHA-1");            // 将三个参数字符串拼接成一个字符串进行sha1加密            byte[] digest = md.digest(content.toString().getBytes());            tmpStr = byteToStr(digest);        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        }        content = null;        // 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信        log.info("tmpStr:{},signature:{}",tmpStr,signature.toUpperCase());        return tmpStr != null && tmpStr.equals(signature.toUpperCase());    }

然后验证完整方法如下

public void callback(HttpServletRequest request, HttpServletResponse response) throws Exception {        String method = request.getMethod();        request.setCharacterEncoding("UTF-8");        response.setCharacterEncoding("UTF-8");        if (RequestMethodEnum.GET.getDesc().equals(method)) {            //验证回调地址            String signature = request.getParameter("signature");            String timestamp = request.getParameter("timestamp");            String nonce = request.getParameter("nonce");            String echostr = request.getParameter("echostr");            boolean checkSignature = WechatOffiAccountUtil.checkSignature(signature, timestamp, nonce, token); // 这个token要跟服务器配置的token一致            if (checkSignature) {                log.info("签名验证成功");                response.getWriter().write(echostr);            }        }}

写好接口后,回去填写服务器配置,就会验证成功。


三、获取微信公众号用户是否已关注标识

1.获取公众号accessToken

咱们要先清楚,小程序的accessToken和公众号的accessToken是不同的,所以公众号的accessToken要重新获取一下,先查阅一下微信公众号API获取 Access token
Java获取方法如下:

    public String getOfficialAccessToken() {        String accessToken = redisCache.getCacheObject("official_access_token"); // 这里会用到redisCache工具        if (Objects.isNull(accessToken)) {            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + OFFICIAL_APP_ID + "&secret=" + OFFICIAL_SECRET;            RestTemplate restTemplate = new RestTemplate();            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);            String responseBody = response.getBody();            // 解析返回结果,提取 access_token 字段的值            accessToken = parseAccessToken(responseBody);            redisCache.setCacheObject("official_access_token", accessToken, 1, TimeUnit.HOURS);        }        if(!isAccessTokenValid(accessToken)){ // 这是为了解析accessToken是否还有效,因为官方的过期时间会变动            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + OFFICIAL_APP_ID + "&secret=" + OFFICIAL_SECRET;            RestTemplate restTemplate = new RestTemplate();            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);            String responseBody = response.getBody();            // 解析返回结果,提取 access_token 字段的值            accessToken = parseAccessToken(responseBody);            redisCache.setCacheObject("official_access_token", accessToken, 1, TimeUnit.HOURS);        }        return accessToken;    }    	private String parseAccessToken(String responseBody) {	        JSONObject jsonObject = new JSONObject(responseBody);	        return jsonObject.getString("access_token");	    }	public boolean isAccessTokenValid(String accessToken) {	        String url = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=" + accessToken;		        RestTemplate restTemplate = new RestTemplate();	        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);	        String responseBody = response.getBody();		        // 解析返回结果,提取错误信息	        String errorCode = parseErrorCode(responseBody);		        // 如果错误码为空,则表示 access_token 有效;否则,表示无效	        return errorCode == null;	    }

2.feign远程调用微信公众号获取用户基本信息(UnionID机制)

远程调用我是用了feign,这样可以节省很多建远程调用步骤,然后查阅一下微信公众号API获取用户基本信息(UnionID机制)
请求结果实体类

@Datapublic class PublicUserInfo {    /**     * 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。     */    private int subscribe;    /**     * 用户的标识,对当前公众号唯一     */    private String openid;    /**     * 用户的语言,简体中文为zh_CN     */    private String language;    /**     * 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间     */    private long subscribe_time;    /**     * 只有在用户将公众号绑定到微信开放平台账号后,才会出现该字段。     */    private String unionid;    /**     * 公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注     */    private String remark;    /**     * 用户所在的分组ID(兼容旧的用户分组接口)     */    private int groupid;    /**     * 用户被打上的标签ID列表     */    private List<Integer> tagid_list;    /**     * 返回用户关注的渠道来源     */    private String subscribe_scene;    /**     * 二维码扫码场景(开发者自定义)     */    private int qr_scene;    /**     * 二维码图片     */    private String qr_scene_str;    private String qrSceneImage;    // 添加构造方法、getter和setter等}

编写Feign接口方法

@FeignClient(name = "officialAccount",url = "https://api.weixin.qq.com/cgi-bin")public interface OfficialAccountFeign {    @GetMapping("/user/info?access_token={accessToken}&openid={openId}&lang=zh_CN")    ResponseEntity<PublicUserInfo> getUserInfo(@PathVariable("accessToken") String accessToken, @PathVariable("openId") String openId);}

3.回调接口补充更新用户关注标识

补充上面写的callBack回调方法

    public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {        String method = request.getMethod();        request.setCharacterEncoding("UTF-8");        response.setCharacterEncoding("UTF-8");        if (RequestMethodEnum.GET.getDesc().equals(method)) {            //验证回调地址            String signature = request.getParameter("signature");            String timestamp = request.getParameter("timestamp");            String nonce = request.getParameter("nonce");            String echostr = request.getParameter("echostr");            boolean checkSignature = WechatOffiAccountUtil.checkSignature(signature, timestamp, nonce, token);            if (checkSignature) {                log.info("签名验证成功");                response.getWriter().write(echostr);            }        } else {            //接收回调xml;            Document document = XmlUtil.readXML(request.getReader());            String toUserName = (String) XmlUtil.getByXPath("//xml/ToUserName", document, XPathConstants.STRING);            String fromUserName = (String) XmlUtil.getByXPath("//xml/FromUserName", document, XPathConstants.STRING);            Long createTime = Long.valueOf((String) XmlUtil.getByXPath("//xml/CreateTime", document, XPathConstants.STRING));            String msgType = (String) XmlUtil.getByXPath("//xml/MsgType", document, XPathConstants.STRING);            log.info("FromUserName:{}", fromUserName);            log.info("CreateTime:{}", createTime);            log.info("MsgType:{}", msgType);            if (MsgTypeEnum.EVENT.getDesc().equals(msgType)) {                //是否事件为关注/取消关注事件                String event = (String) XmlUtil.getByXPath("//xml/Event", document, XPathConstants.STRING);                log.info("Event:{}", event);                if (EventEnum.SUBSCRIBE.getDesc().equals(event)) {                    //关注                    //新增用户                    Boolean insert = updateUserSubscribe(fromUserName, 1);                    if (insert) {                        log.info("新增关注用户成功");                        response.getWriter().write("success");                    }                } else {                    //取关                    //更新用户状态                    Boolean update = updateUserSubscribe(fromUserName, 0);                    if (update) {                        log.info("更新关注用户状态成功");                        response.getWriter().write("success");                    }                }            }        }    }        private Boolean updateUserSubscribe(String fromUserName, Integer subscribe) {        QueryWrapper<WechatUser> userQueryWrapper = new QueryWrapper<>(); // 这里用了mybatis-plus方法,匹配用户的公众号public_openid,匹配上就查出来,否则就根据unionId重新匹配        userQueryWrapper.eq("public_openid", fromUserName);        WechatUser user = this.getOne(userQueryWrapper);        if (Objects.nonNull(user)){            user.setSubscribe(subscribe);            this.updateById(user);            return true;        }else{            PublicUserInfo publicUserInfo = officialAccountFeign.getUserInfo(loginService.getOfficialAccessToken(), fromUserName).getBody();            userQueryWrapper.clear();            userQueryWrapper.eq("unionid", publicUserInfo.getUnionid());            user = this.getOne(userQueryWrapper);            if (Objects.nonNull(user)) {                user.setPublicOpenid(fromUserName);                this.updateById(user);                return true;            }        }        return false;    }

到这里就大功告成了,先注册小程序账号,然后在公众号上点击关注和取消关注操作,关注数据库显示的内容。


总结

很多功能看似很简单实现,但是如果在第三方平台上开发,需要遵从官方的要求,才能把整个环节实现。

参考博客:ruoyi的springboot微信小程序登录实现方式(我自己写的,感兴趣的可以去看一下)

也许您对下面的内容还感兴趣: