判断用户是否关注微信订阅号-订阅号

前一篇阐述了,判断用户是否关注服务号的逻辑实现及部分代码。今天说一下判断是否关注订阅号如何实现。

先回顾一下流程:

  1. 将订阅号绑定到服务号上。在一个服务号的不同应用,虽然openid不同,但unionid是一样的
  2. 通过微信提供的接口,将订阅号的用户同步到本地,比如mysql中。同步过来的数据只有openid。
  3. 通过调用接口查询出所有用户的unionid
  4. 订阅号配置服务。如果有用户订阅,取消订阅将信息推送给你的服务
  5. 用户通过服务号授权,可以获取到用户的unionid,拿关这个unionid去mysql查询,是否能查到。如果能查到说明已关注过了;如果查询不到,说明没有关注。

准备

首先要有服务号订阅号,且将订阅号与服务号关联起来

然后建表用来存关注用户信息,我们简单一点(一看就明白)

CREATE TABLE `t_subscribe_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `open_id` varchar(555) NOT NULL,
  `union_id` varchar(255) DEFAULT '',
  `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_x` (`union_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;;

获取订阅号用户信息

获取access_token

准备好订阅号的appid和secret,调用微信接口

    /**
     * access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。
     * 开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。
     * access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
     * https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/Nontax_Bill/API_list.html#1.1
     * @param grantType
     * @param appid
     * @return
     */
    @RequestMapping(value = "/cgi-bin/token", method = RequestMethod.GET)
    String getToken(@RequestParam("grant_type") String grantType,
                    @RequestParam("appid") String appid, @RequestParam("secret") String secret);

这里使用feign client, 依赖如下(springboot 2.7.15)

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

这里需要特别注意的是,access_token这个需要缓存到本地的,比如内存,redis或MySql里,都可以。因access_token有效期是7200秒,且这个接口每天访问次数是有限制的。没必要用一次请求一次。记录下申请的时间,可以用job在快到期时刷新一下,或使用access_token时,快过期了(比如7100秒的时候),再申请一下。如果单实例的,更新时锁一下,避免多线程同时刷新(浪费次数);多实例的可以考虑使用全局锁。如果访问者不多,不锁也没啥,最多,多申请一次。

查询订阅用户信息

这是另一个接口了,官方提供的接口,一次可以获取到10000个用户和next_openid信息,这个next_openid是为了下次一请求使用,所以查询时,可以写一个递归一次性拉完数据并且在上面创建的表中。参考代码如下:

查询接口

    /**
     * 获取用户列表
     * 公众号可通过本接口来获取账号的关注者列表,关注者列表由一串OpenID(加密后的微信号,
     * 每个用户对每个公众号的OpenID是唯一的)组成。一次拉取调用最多拉取10000个关注者的OpenID,
     * 可以通过多次拉取的方式来满足需求。
     * https://developers.weixin.qq.com/doc/offiaccount/User_Management/Getting_a_User_List.html
     * @param token
     * @param openId
     * @return
     */
    @RequestMapping(value = "/cgi-bin/user/get", method = RequestMethod.GET)
    String getSubscribe(@RequestParam("access_token") String token,
                        @RequestParam("next_openid") String openId);

递归实现


    /**
     * 查询用户
     * @param nextOpenid
     */
    private void getNext(String nextOpenid) {
        //这里缓存了,如果过期会自动更新,否则继续使用之前申请的
        final AccessToken ac = weiXinService.getGlobalToken(subAppid, subSecret);
        final String res = feignClient.getSubscribe(ac.getAccessToken(), nextOpenid);
        final WeChatUser weChatUser = JSON.parseObject(res, WeChatUser.class);
        if (weChatUser == null) {
            return;
        }
        log.info("next openid {} ", weChatUser.getNext_openid());
        final List<SubscribeUser> uses = weChatUser.getData().getOpenid().stream().map(openid -> {
            SubscribeUser u = new SubscribeUser();
            u.setOpenId(openid);
            u.setCreatedDate(LocalDateTime.now());
            return u;
        }).collect(Collectors.toList());
        this.subscribeUserMapper.insertBatch(uses, 0);
        String next = weChatUser.getNext_openid();
        if (StringUtils.isNotEmpty(next)) {
            getNext(next);
        }
    }

2点说明:

  • 代码中access_token是存在数据库里的
  • 查询到用户创建DO后,使用insertBatch保存(并非一个一个保存,如果用户非常多话非常慢)。我使用的是Mybatis-flex。默认是1000条/次保存。

更新union_id

有了open id后,我们还要查询相应的union id。官方提供2个接口:单个查询和批量(100/次)查询使用的接口如下:

    /**
     * 批量查询
     * @param accessToken
     * @param json
     * @return
     */
    @RequestMapping(value = "/cgi-bin/user/info/batchget?access_token={token}", method = RequestMethod.POST)
    String userInfoExtBatch(@PathVariable("token") String accessToken, @RequestBody String json);

依然要用access_token.

用户数据有了,接口也有了,我们可以查询DB中union id为空的,全查出来之后,分个组,然后调用这个接口,参考代码如下:

    @Async
    public void fillUnion() {
        QueryWrapper wrapper = QueryWrapper.create()
                .select()
                .from("t_subscribe_user")
                .where("union_id is null");
        final List<SubscribeUser> allUsers = subscribeUserMapper.selectListByQuery(wrapper);
        for (List<PhotoSubscribeUser> sub : Lists.partition(allUsers, 100)) {
            QueryUnionIdBatch req = new QueryUnionIdBatch();
            req.setOpenids(sub.stream().map(it -> it.getOpenId()).collect(Collectors.toList()));
            req.setAccessToken("");
            final QueryUnionIdRes unionId = getUnionId(req);
            final Map<String, String> map = unionId.getUserInfoList().stream().collect(Collectors.toMap(QueryUnionIdRes.UserInfoListDTO::getOpenid, QueryUnionIdRes.UserInfoListDTO::getUnionid, (v1, v2) -> v1));
            sub.forEach(u -> {
                u.setUnionId(map.getOrDefault(u.getOpenId(), ""));
                subscribeUserMapper.update(u);
            });

        }

    }
  • QueryUnionId类结构是按接口要求创建的。使用了异步方法。
  • 将List切分成100个一块使用提guava提供的方法。

getUnionId代码如下:

    /**
     * 批量获取union id
     * @param req
     * @return
     */
    public QueryUnionIdRes getUnionId(QueryUnionIdBatch req) {
        final AccessToken ac = weiXinService.getGlobalToken(subAppid, subSecret);
        String accessToken = ac.getAccessToken();
        QueryUnionId q = new QueryUnionId();
        final List<QueryUnionId.UserListDTO> data = req.getOpenids().stream().map(it -> new QueryUnionId.UserListDTO(it)).collect(Collectors.toList());
        q.setUserList(data);
        Map<String,String> header = new HashMap<>();
        header.put("Content-Type","application/json");
        header.put("Host","api.weixin.qq.com");
        final String res = feignClient.userInfoExtBatch(accessToken, JSON.toJSONString(q));
        log.info("union {}", res);
        return JSON.parseObject(res, QueryUnionIdRes.class);
    }

至此,用户信息与unionid已经准备完毕!

判断用户是否关注订阅号

核心代码是,构建授权请求(准备好正确的redirect)

redirect处理

参考代码如下:

   /**
     * 微信授权回调接口
     *
     * @param request
     * @return
     * @throws IOException
     */
    @GetMapping("/auth")
    @ResponseBody
    public ApiResponse authCallBack(HttpServletRequest request) {
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        if (StringUtils.isEmpty(code)) {
            return new ApiResponse().ofFailure(11, "code为空");
        }
        log.info("微信授权回调, code = {}  ,state(race id) = {}", code, state);
        try {
            //获取微信授权access_token
            final LocalAccessToken localAccessToken = weiXinService.getLocalAccessToken(code);
            if (Objects.isNull(localAccessToken)) {
                return new ApiResponse().ofFailure(11, "微信授权失败:");
            }
            log.info("微信授权access_token返回参数:{}", JSON.toJSONString(localAccessToken));
            String accessToken = localAccessToken.getAccessToken();
            String openId = localAccessToken.getOpenid();
            String unionId = localAccessToken.getUnionId();
            log.info("微信授权: " + "获取授权元数据:access token: {}, openid: {}, unionid: {}", accessToken, openId, unionId);
            //如果微信的返回access_token为空
            if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)) {
                return new ApiResponse().ofFailure(11, "微信授权失败");
            }
            //这里需要改,使用上面unionid去库里查询,用户是否关注过“订阅号”
            boolean hasSub = true;
            //这里就是使用union id查询DB中是否有记录
            hasSub = subscribeService.checkSubscribeByUnionid(unionId);
            //业务处理
            
            if (hasSub) {
                log.info("用户授权登录成功跳转地址:{}", dest);
                return new ApiResponse().ofSuccess("成功", race.getThirdUrl());
            } else {
                return new ApiResponse().ofFailure(10, "需要订阅公众号");
            }
        } catch (Exception e) {
            log.info("#微信授权# 失败", e);
            return new ApiResponse().ofFailure(11, "微信授权失败");
        }
    }

基本就是这么多。

订阅与取消订单

这时你可能会想,数据同步之后,如果有新用户关注了,DB中没有呀,不就出错了吗?确实如此,这时就用到订阅号事件推送的功能。 

配置好,启用之后,只有事件都会通过/token这个入口推送过来,我们可以根据事件来增加或删除DB中的记录。推送过来的数据,会有用户的openid,如果是关注事件,就用这个openid再去查一下unionid,现将数据存到DB中。

获取单个union id的接口:

    /**
     * 用户信息
     * @param token
     * @param openId
     * @return
     */
    @RequestMapping(value = "/cgi-bin/user/info", method = RequestMethod.GET)
    String userInfoExt(@RequestParam("access_token") String token,
                    @RequestParam("openid") String openId,
                    @RequestParam("lang") String lang);

写在最后

其实整个过程很简单,基本没有难点。按部就班即可。初之接触的人可能主要卡在,订阅号与服务号区别。还有什么时候用哪种类型的号。

最后的最后,如果你在google或baidu之后,一直没有找到解决的问题,突然发现此篇文章而且能够解决你的问题。你可以打个赏吗?

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Taro开发微信小程序中,如果你需要获取用户的手机码,可以按照以下步骤进行操作: 1. 首先,确保在微信公众平台上已经设置了小程序获取用户手机的权限。你可以在小程序管理后台的"开发-开发设置-接口设置"中找到相关设置。 2. 在Taro的页面或组件中,引入微信小程序的API:通过`import Taro from '@tarojs/taro'`引入微信小程序的API。 3. 调用`Taro.login()`方法获取用户登录凭证code,用于后续的手机授权验证。 4. 在获取到code后,调用`Taro.getUserInfo()`方法获取用户信息,包括手机码。示例代码如下: ```javascript Taro.login().then((loginRes) => { if (loginRes.code) { Taro.getUserInfo().then((userRes) => { const { encryptedData, iv } = userRes.userInfo // 在这里可以将encryptedData和iv发送到后端解密获取手机码 // 也可以直接在前端解密获取手机码 }).catch((err) => { console.log(err) }) } else { console.log('登录失败') } }).catch((err) => { console.log(err) }) ``` 5. 在上述代码中,`encryptedData`和`iv`是用户信息的加密数据,你可以将它们发送到后端进行解密,或者在前端使用相应的解密算法解密获取手机。 需要注意的是,获取用户手机的过程需要用户授权,并且用户必须在微信设置中允许小程序获取手机的权限。如果用户未授权或未设置权限,将无法获取手机。 希望这些信息对你有所帮助!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值