文章目录
- 前言
- 一、绑定相关数据
- 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微信小程序登录实现方式(我自己写的,感兴趣的可以去看一下)