前言
之前讲过如何利用公众号针对指定用户完成业务操作之后实时发送消息.就好比在线医院公众号中看病挂号,挂号预约成功之后微信列表中会新增一条关注的公众号预约成功消息.具体实现步骤可以看下文章如何实现:手把手教你微信公众号如何给指定用户发送消息提醒,有兴趣的可以看下。现在要从小程序中要加入引导关注公众号并授权的功能,用于使用公众号发送业务消息提醒,注意这里不是订阅消息。
参考过很多其他同学的实现方案,总感觉有些繁琐,并且接口有调用次数的限制。结合业务场景梳理了一下流程,并对具体的实现做了详细说明,希望对有同样需求的同学有所帮助,下面详细说下整个流程。
业务流程说明
用户登录小程序进入到消息模块之后,对于没有关注公众号的用户或是已经关注公众号但是没有授权的用户显示引导关注公众号提示信息,已经关注公众号并授权处理的不显示(授权逻辑主要是指将用户关注公众号的openID与用户信息进行绑定,下文会详细讲)业务截图如下:
点击进入关注公众号页面,这里进入的是公众号发布的一篇公众号关注文章,业务截图如下:
没有关注公众号的进入到关注页面。
对于已关注公众号的用户,直接进入公众号聊天页面,设置默认回复为授权链接(进入到此页面的为已关注公众号但是没有授权的用户)。截图如下:
点击开通消息之后进入到授权页面进行授权即可,授权成功之后跳转到小程序完成授权操作。
以上是对小程序中引导关注公众号的流程梳理,下面说下如何进行实现,主要侧重于服务端讲解实现原理!
实现方案说明
这里需要用户进入到小程序之后获取一下用户的unionId,unionId可以说是同一个用户在微信开放平台下各个产品中的唯一标识.openId在微信平台下每个产品是唯一的.两个都需要后端进行入表操作.unionId可以放到用户注册登录中实现.openId入表逻辑可以放到授权操作中(这里使用的是公众号的openID,因为官方提供的公众号发送消息接口中需要获取关注公众号用户的唯一标识openID).主要说下本文重点:如何判断用户是否关注过公众号.梳理逻辑如下:
判断的主要逻辑是看用户表信息中是否有维护过公众号的openId,如果有则说明之前关注过,但考虑到关注的用户可能会取消关注但是表中还保存着之前的公众号openId,所以这里调用官方的获取用户基本信息接口根据subscribe进行判断现在用户是否处于关注状态.subscribe表示用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。官方接口截图如下:
这里再贴一下公众号网页授权流程:
需要在授权操作中维护一下用户在公众号下面的唯一标识:openId.
代码以及表结构实现
用户表结构设计如下:
CREATE TABLE `user` ( `user_id` bigint(20) NOT NULL AUTO_INCREMENT, `login` varchar(20) NOT NULL COMMENT '用户唯一标识', `union_id` varchar(30) NOT NULL COMMENT '用户unionId', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `gzh_open_id` varchar(60) DEFAULT '' COMMENT '用户关注的公众号openId', PRIMARY KEY (`user_id`), UNIQUE KEY `unique` (`login`), UNIQUE KEY `unionId_unique` (`union_id`) ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
主要提供两个接口,一个是判断用户数是否关注公众号并授权,另一个是用户授权接口(主要逻辑是引导用户关注公众号后将公众号openid绑定用户信息入表)。现提供两个接口的实现逻辑。
1.判断用户是否关注公众号并授权
@ApiOperation(value = "判断用户是否关注公众号并授权:true表示关注且授权,false表示未关注或已关注未授权",notes = "返回true表示已关注公众号并已进行授权(用户信息中维护openId),页面不做引导处理.返回false表示没有关注公众号或是关注公众号之后没有授权,可引导关注并授权") @ApiImplicitParams({ @ApiImplicitParam(name = "unionId", value = "unionId", required = true, dataType = "Boolean", paramType = "query",example = "1")}) @GetMapping("/checkIsFollowGzg") public ResultVo checkIsFollowGzg(@NotBlank(message = "unionId不允许为空!") String unionId){ boolean returnFlag = userService.checkIsFollowGzg(unionId); return ResultVoUtil.success(returnFlag); }
service层实现具体逻辑:
@Override public boolean checkIsFollowGzg(String unionId) { boolean returnFlag=false; // 根据unionId查询用户关注公众号的openId User userBasicInfo = userMapper.findUserBasicInfo(unionId,null); if(ObjectUtil.isNull(userBasicInfo)) throw new BussinessExcption(ApiCode.SYSTEM_EXCEPTION.getMessage()); String gzhOpenId = userBasicInfo.getGzhOpenId(); if(StrUtil.isBlank(gzhOpenId)) return returnFlag; // 根据公众号的openId和accessToken判断用户是否关注(subscribe为1表示关注) // 获取微信认证信息 String wxgAccessToken = getWxgAccessToken(); if (checkIsGzhUer(gzhOpenId, wxgAccessToken)) return true; return false; } /** * @Author: txm * @Description: 获取公众号认证AccessToken * @Param: [] * @return: java.lang.String * @Date: 2022/10/5 10:47 **/ public String getWxgAccessToken() { String returnMsg=""; JSONObject accessTokenObject = null; String gzhAppId =jobConfig.getGzhAppId(); String gzhSecret=jobConfig.getGzhSecrect(); String requestUrl = StrUtil.format(Constants.GZH_ACCESS_TOKEN_URL, gzhAppId, gzhSecret); try { returnMsg=HttpUtil.get(requestUrl); accessTokenObject = JSON.parseObject(returnMsg); log.info("错误信息:{}",accessTokenObject); } catch (Exception e) { log.error("获取用户AccessToken认证信息失败:{}",e.getMessage()); } String accessToken = accessTokenObject.getString("access_token"); if(StrUtil.isBlank(accessToken)) throw new BussinessExcption("响应异常:获取accessToken信息为空!"); return accessToken; } /** * @Author: txm * @Description: 校验是否是关注过公众号用户,返回false表示没有关注公众号;返回true表示关注过公众号 * @Param: [gzhOpenId, wxgAccessToken] * @return: boolean * @Date: 2022/10/8 16:06 **/ private boolean checkIsGzhUer(String gzhOpenId, String wxgAccessToken) { // 根据获取公众号用户基本信息 String responBasicUserInfo=""; JSONObject basicUserInfo = null; String reqUrl = StrUtil.format(Constants.USER_BASIC_INFO_URL, wxgAccessToken, gzhOpenId); try { responBasicUserInfo= HttpUtil.get(reqUrl); basicUserInfo = JSON.parseObject(responBasicUserInfo); } catch (Exception e) { log.error("获取用户公众号基本信息失败:{}",e.getMessage()); } if(ObjectUtil.isNull(basicUserInfo)) throw new BussinessExcption(ApiCode.SYSTEM_EXCEPTION.getMessage()); // 用户是否订阅该公众号标识,1表示关注,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。 if(StrUtil.isBlank(basicUserInfo.getString("subscribe")) || StrUtil.equals("0",basicUserInfo.getString("subscribe"),true)) return false; return true; }
2.用户授权操作
@ApiOperation("公众号授权操作:判断是否关注公众号:返回true表示已经关注公众号用户并绑定公众号openId,返回false表示没有关注公众号") @PostMapping("/getWxgUserInfo") public ResultVo getWxgUserInfo(@RequestBody @Validated WxgUserInfoDto wxgUserInfoDto){ userService.getWxgUserInfo(wxgUserInfoDto); return ResultVoUtil.success(); }
service层实现逻辑:
/** * @Author: txm * @Description: 授权处理 * @Param: [wxgUserInfoDto] * @return: void * @Date: 2022/10/5 9:08 **/ @Override public void getWxgUserInfo(WxgUserInfoDto wxgUserInfoDto) { // 网页授权根据code获取access_token JSONObject accessTokenInfo = getWxgAccessTokenInfo(wxgUserInfoDto.getCode()); String accessToken = accessTokenInfo.getString("access_token"); String openid = accessTokenInfo.getString("openid"); if(StrUtil.isBlank(accessToken)|| StrUtil.isBlank(openid)) throw new BussinessExcption("获取用户信息失败:accessToken或openId获取为空!"); // 根据公众号的openId和accessToken判断用户是否关注(subscribe为1表示关注) // 获取微信认证信息access_token String wxgAccessToken = getWxgAccessToken(); // 校验用户是否已经关注公众号,已经关注公众号需要查询是否维护过公众号的openId if (checkIsGzhUer(openid, wxgAccessToken)){ // 根据unionId查询用户是否已经维护过公众号openId,没有的话需要更新用户关注公众号的openId String login = wxgUserInfoDto.getLogin(); User userBasicInfo = userMapper.findUserBasicInfo(null,login); if(ObjectUtil.isNull(userBasicInfo)) throw new BussinessExcption(ApiCode.SYSTEM_EXCEPTION.getMessage()); // 更新用户公众号openId int i = userMapper.updateUserOpenId(openid, login); if(i == 0) throw new BussinessExcption("操作失败:更新信息为空!"); } } /** * @Author: txm * @Description: 获取AccessToken * @Param: [code] * @return: com.alibaba.fastjson.JSONObject * @Date: 2022/10/5 9:08 **/ public JSONObject getWxgAccessTokenInfo(String code) { String accessTokenInfo = ""; JSONObject accessTokenObject = null; String gzhAppId =jobConfig.getGzhAppId(); String gzhSecret=jobConfig.getGzhSecrect(); String reqUrl = StrUtil.format(Constants.ACCESS_TOKEN_URL, gzhAppId, gzhSecret, code); try { accessTokenInfo=HttpUtil.get(reqUrl); log.info("错误信息:{}",accessTokenInfo); accessTokenObject = JSON.parseObject(accessTokenInfo); } catch (Exception e) { log.error("获取用户AccessToken认证信息失败:{}",e.getMessage()); } return accessTokenObject; }
用户信息操作接口:
public interface UserMapper { // 根据unionId查询用户基本信息 User findUserBasicInfo(String unionId); }
用户信息持久层配置文件:
<!-- 获取用户基本信息,实体类暂不提供可自行自定义--> <select id="findUserBasicInfo" resultType="com.kawa.job.api.wanted.user.entity.User"> select user_id,login,gzh_open_id from job_user where union_id=#{unionId} limit 1 </select>
以上是处理小程序中引导关注公众号的处理方案,如果看完感觉有所帮助,欢迎评论区留言或点赞,每个赞美都是对自己最大的鼓励和支持!