判断用户是否关注微信订阅号 您所在的位置:网站首页 qq里怎么关注订阅号 判断用户是否关注微信订阅号

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

2024-07-06 02:04| 来源: 网络整理| 查看: 265

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

先回顾一下流程:

将订阅号绑定到服务号上。在一个服务号的不同应用,虽然openid不同,但unionid是一样的通过微信提供的接口,将订阅号的用户同步到本地,比如mysql中。同步过来的数据只有openid。通过调用接口查询出所有用户的unionid订阅号配置服务。如果有用户订阅,取消订阅将信息推送给你的服务用户通过服务号授权,可以获取到用户的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)

org.springframework.cloud spring-cloud-starter-openfeign

这里需要特别注意的是,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 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 allUsers = subscribeUserMapper.selectListByQuery(wrapper); for (List 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 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 data = req.getOpenids().stream().map(it -> new QueryUnionId.UserListDTO(it)).collect(Collectors.toList()); q.setUserList(data); Map 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之后,一直没有找到解决的问题,突然发现此篇文章而且能够解决你的问题。你可以打个赏吗?



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有