Java微信小程序获取关联公众号用户是否已关注标识
文章目录
前言
如果公众号不是认证的服务号就无需看这篇文章,需要先认证,可以参考这篇文章五分钟!手把手教你快速完成微信公众号认证!
你们是否经常在小程序看到这种关注标识

实现的方法很简单,后端只需要返回给前端给前端当前用户是否有关注公众号就行了,整个逻辑很简单,微信官方也有对应的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机制)
请求结果实体类
@Data
public 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微信小程序登录实现方式(我自己写的,感兴趣的可以去看一下)