本文主要描述了判断小程序用户是否关注公众号的逻辑实现及部分代码
首先阐述一下大致流程:
1、在将小程序和公众号绑定至同一个微信开发平台下;
2、后端拉取公众号已关注用户列表,并获取其中每一个用户的unionID, 建立已关注用户表;
3、后端可做定时任务更新该表;
4、用户在小程序中登录注册时后端用code拿到用户的unionID并保存;
5、前端请求查询时,后端根据发起请求用户的unionID查表,判断该用户是否已关注;
一、数据库表和Mapper层
这里简单给出了建表语句和实体类, 具体mapper/service层可自行实现CREATE TABLE `public_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`open_id` varchar(50) DEFAULT NULL COMMENT 'openId',`union_id` varchar(50) DEFAULT NULL COMMENT 'union',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8mb4;@Table(name = "public_user")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class PublicUser implements Serializable {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "open_id")private String openId;@Column(name = "union_id")private String unionId;}public interface PublicUserMapper {}
二、微信公众号对接接口相关逻辑代码
1.三个基础接收对象类
@Data
public class WeixinUserInfoVo {private String openid;private String unionid;/*** 该值为0值 则没有openId和unionId*/private Integer subscribe;private Integer errcode;
}@Data
public class WeixinUserListVo {private Integer errcode;@ApiModelProperty("关注该公众账号的总用户数")private Integer total;@ApiModelProperty("拉取的OPENID个数,最大值为10000")private Integer count;@ApiModelProperty("列表数据,OPENID的列表")private WxOpenidInfo data;@ApiModelProperty("拉取列表的最后一个用户的OPENID")private String next_openid;
}@Data
public class WxOpenidInfo {private List<String> openid;
}
2.调用微信接口相关
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.mzlion.easyokhttp.HttpClient;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import tk.mybatis.mapper.entity.Example;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;@Slf4j
@Service
public class WeixinPublicService {@Autowiredprivate RedisTemplate redisTemplate;//公众号appId@Value("${weixin.mp.account:xxxxxxxx}")private String miniappAppId;//公众号secret@Value("${weixin.mp.secret:xxxxxxxxxxxxxxxxx}")private String miniappSecret;@Resourceprivate PublicUserMapper publicUserMapper;public static final String ACCESS_TOKEN="weixin:access_token";/*** 判断小程序用户是否否关注的主要方法-根据unionId来查询用户观众公众号信息* @param unionId* @return*/public WeixinUserInfoVo selectWeixinPublicUserInfoByUnionId(String unionId) {//现查询数据库-根据unionid获取到openid-逻辑可自行实现PublicUser dbPublicUser = publicUserMapper.selectPublicUserByUnionId(unionId);WeixinUserInfoVo weixinUserInfoVo = null;if (Objects.isNull(dbPublicUser)) {//查询数据库-逻辑可自行实现List<PublicUser> existPublicUsers = publicUserMapper.selectPublicUserList();// 查询所有 openidHashSet<String> openidSet = getUserOpenIdList();if (!CollectionUtil.isEmpty(openidSet)) {if (!openidSet.isEmpty()) {// 差集for (PublicUser user : existPublicUsers) {openidSet.remove(user.getOpenId());}}}// 更新 未入库的 公众号信息String openid = null;RestTemplate restTemplate = new RestTemplate();List<PublicUser> publicUserList = new ArrayList<>();for (String id : openidSet) {// 根据openid查询unionIdString requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+ getAccessToken()+ "&openid=" + id + "&lang=zh_CN";weixinUserInfoVo = restTemplate.getForObject(requestUrl, WeixinUserInfoVo.class);if (!ObjectUtil.isNull(weixinUserInfoVo) && ObjectUtil.isNull(weixinUserInfoVo.getErrcode())) {if (!StrUtil.isEmpty(weixinUserInfoVo.getUnionid())) {publicUserList.add(PublicUser.builder().openId(weixinUserInfoVo.getOpenid()).unionId(weixinUserInfoVo.getUnionid()).build());if (unionId.equals(weixinUserInfoVo.getUnionid())) {openid = id;}}}}if (!CollectionUtil.isEmpty(publicUserList)) {this.publicUserMapper.insertList(publicUserList);}}else {RestTemplate restTemplate = new RestTemplate();String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+ getAccessToken()+ "&openid=" + dbPublicUsers.get(0).getOpenId() + "&lang=zh_CN";weixinUserInfoVo = restTemplate.getForObject(requestUrl, WeixinUserInfoVo.class);}return weixinUserInfoVo;}/*** 获取请求token* @return*/private String getAccessToken() {String accessToken = (String) this.redisTemplate.opsForValue().get(ACCESS_TOKEN);if (StrUtil.isEmpty(accessToken)) {String result = HttpClient.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET".replace("APPID", this.miniappAppId).replace("APPSECRET", this.miniappSecret)).asString();log.info("getAccessToken---result===>"+result);if (!Strings.isNullOrEmpty(result)) {Integer expiresIn = null;JSONObject jsonObject = JSON.parseObject(result);if (ObjectUtil.isNotNull(jsonObject)) {expiresIn = (Integer) jsonObject.get("expires_in");accessToken = (String) jsonObject.get("access_token");}log.info("getAccessToken---accessToken===>"+accessToken);if (StrUtil.isNotEmpty(accessToken) && ObjectUtil.isNotNull(expiresIn)) {this.redisTemplate.opsForValue().set(ACCESS_TOKEN, accessToken, expiresIn - 20);}}}return accessToken;}/*** 初始化所有公众号用户数据*/public void init() {//查询数据库-逻辑可自行实现List<PublicUser> dbPublicUsers = publicUserMapper.selectPublicUserList();List<String> existOpenIds = new ArrayList<>();if (dbPublicUsers != null && !dbPublicUsers.isEmpty()) {existOpenIds.addAll(dbPublicUsers.stream().map(PublicUser::getOpenId).collect(Collectors.toList()));}// 查询所有 openidHashSet<String> openidSet = getUserOpenIdList();//去除已经存在的openidSet.removeIf(existOpenIds::contains);// 更新 未入库的 公众号信息RestTemplate restTemplate = new RestTemplate();List<PublicUser> publicUserList = new ArrayList<>();for (String openId : openidSet) {// 根据openid查询unionIdString requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+ getAccessToken()+ "&openid=" + openId + "&lang=zh_CN";WeixinUserInfoVo weixinUserInfoVo = restTemplate.getForObject(requestUrl, WeixinUserInfoVo.class);if (!ObjectUtil.isNull(weixinUserInfoVo) && ObjectUtil.isNull(weixinUserInfoVo.getErrcode())) {if (!StrUtil.isEmpty(weixinUserInfoVo.getUnionid())) {publicUserList.add(PublicUser.builder().openId(weixinUserInfoVo.getOpenid()).unionId(weixinUserInfoVo.getUnionid()).build());}}//一次性插入一百条 防止服务断掉 导致全没拉下来if (!CollectionUtil.isEmpty(publicUserList) && publicUserList.size() == 100) {this.publicUserMapper.insertList(publicUserList);publicUserList.clear();}}if (!CollectionUtil.isEmpty(publicUserList)) {this.publicUserMapper.insertList(publicUserList);}}/*** 获取公众号关注用户列表的openid* @return*/public HashSet<String> getUserOpenIdList() {//获取最新的access_tokenString accessToken = getAccessToken();log.info("accessToken===>"+new String(accessToken));RestTemplate restTemplate = new RestTemplate();WeixinUserListVo openIdList = null;HashSet<String> openidSet = new HashSet<String>();synchronized (this) {try {//循环获取用户openid列表--一次获取10000String nextOpenid = null;do {//微信公众号获取用户列表信息接口地址String requestUrl = null;if (StringUtil.isBlank(nextOpenid)) {requestUrl = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="+ accessToken;} else {requestUrl = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="+ accessToken + "&next_openid=" + nextOpenid;}openIdList = restTemplate.getForObject(requestUrl, WeixinUserListVo.class);if (openIdList != null && Objects.nonNull(openIdList.getData())) {//获取用户关注列表对象WxOpenidInfo data = openIdList.getData();//获取当前循环的openid--10000条openidSet.addAll(data.getOpenid());//拉取列表的最后一个用户的OPENIDnextOpenid = openIdList.getNext_openid();}} while (Objects.nonNull(openIdList.getData()));} catch (Exception e) {log.debug("获取用户列表失败:{}", openIdList);return null;}}return openidSet;}}
3.判断是否关注的逻辑
根据调用selectWeixinPublicUserInfoByUnionId的返回判断是否关注
①已关注(subscribe字段判断 1:已关注)