文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

微信公众号配置 Token 认证以及消息推送功能

2023-10-04 11:12

关注

前言

基于后端服务,操作微信公众号,定制化菜单、消息推送、消息回复等功能,都需要通过服务端进行统一的 Token 认证,这样双方才能形成绑定的关系,从而实现相互认知后进行通信.

公众号服务配置 Token认证

接入服务器配置的目的:接入微信第三方开发平台,在微信公众平台提供的基础服务上开发微网站、微商城、活动应用zd、娱乐插件等扩展功能

如何配置

服务器基本配置
在这里插入图片描述
URL必须是以 HTTP 或 HTTPS 开头,必须是在线上可访问的域名地址,部署在本地不可以,/wx/auth 是后台的接口地址,Token 是在后端服务配置的一个固定值,必须要一致相互才能 ping 通,其他如上图所示,点击提交后进行启用在这里插入图片描述

Token 认证接口

/wx/auth 接口源码如下:

@Slf4j@RestController("/wx")@Api(value = "微信服务器 token 认证", tags = "微信服务器 token 认证 接口API")public class WxPortalController {    @GetMapping("/auth")    public void authPost(HttpServletRequest request, HttpServletResponse response) throws IOException {        if (StringUtils.isNotBlank(request.getParameter("signature"))) {            String signature = request.getParameter("signature");            String timestamp = request.getParameter("timestamp");            String nonce = request.getParameter("nonce");            String echostr = request.getParameter("echostr");            log.info("signature[{}], timestamp[{}], nonce[{}], echostr[{}]", signature, timestamp, nonce, echostr);            if (SignUtil.checkSignature(signature, timestamp, nonce)) {                log.info("数据源为微信后台,将echostr[{}]返回!", echostr);                response.getOutputStream().println(echostr);            }        }    }}

Token 认证类源码如下:

public class SignUtil {        private static final String TOKEN = "familySchool";        public static boolean checkSignature(String signature, String timestamp, String nonce) {        String checktext = null;        if (null != signature) {            // 对ToKen,timestamp,nonce 按字典排序            String[] paramArr = new String[]{TOKEN, timestamp, nonce};            Arrays.sort(paramArr);            // 将排序后的结果拼成一个字符串            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);            try {                MessageDigest md = MessageDigest.getInstance("SHA-1");                // 对接后的字符串进行sha1加密                byte[] digest = md.digest(content.getBytes());                checktext = byteToStr(digest);            } catch (NoSuchAlgorithmException e) {                e.printStackTrace();            }        }        // 将加密后的字符串与signature进行对比        return checktext != null && checktext.equals(signature.toUpperCase());    }        private static String byteToStr(byte[] byteArrays) {        String str = "";        for (int i = 0; i < byteArrays.length; i++) {            str += byteToHexStr(byteArrays[i]);        }        return str;    }        private static String byteToHexStr(byte myByte) {        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};        char[] tampArr = new char[2];        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];        tampArr[1] = Digit[myByte & 0X0F];        String str = new String(tampArr);        return str;    }}

公众号获取网页授权及用户信息

导向

获取网页授权前提是我们需要在公众号上配置好网页授权的域名,配置好以后,我们需要一个触发点去触发这个网页(H5)以便用户点击我们设置好的触发按钮或事件时能拿到其对应的用户信息,本篇文章我们采用被关注自动回复消息,点击消息触发

此处介绍公众号UNION_ID和OPEN_ID区别:

同一个微信开放平台下的相同主体的 App、公众号、小程序,如果用户已经关注公众号,或者曾经登录过 App 或公众号,则用户打开小程序时,开发者可以直接通过 wx.login 获取到该用户 UNION_ID,无须用户再次授权。如果用户未关注公众号,可以通过 wx.getUserInfo 获取到该用户 UNION_ID

在这里插入图片描述

网页授权

首先要拿到网页授权的接口权限
在这里插入图片描述
修改网页授权接口时,要修改 JS 接口安全域名以及网页授权域名

上图需要下载对应的文件,前提是域名/文件名需要访问到你对应的文件内容才能配好网页授权域名,代码如下:

@RestControllerpublic class DomainAuthController {        @GetMapping("MP_verify_zxgx6O368tVx5sDK.txt")    private String returnConfigFile() {        //把 MP_verify_xxxxxx.txt 中的内容返回        return "zxgx6O368tVx5sDK";    }}

关注后消息触发授权

在导向中提及到的 被关注后自动回复消息点击触发
在这里插入图片描述
URL 地址:点击绑定你的个人信息

域名是我们配置好网页授权域名,接口地址是我们后台处理的接口
官方介绍:微信网页授权认证
utf-
redirect_uri 回调的链接地址是需要通过 utf-8 编码过后的,如下:

java.net.URLEncoder.encode(source, "utf-8");

WxConstant 类是自定义微信常量类:存放请求 TOKEN Address、APPID、SECRET

public class WxConstant {        public static final String AUTHORIZATION_CODE = "023VMFlQ0fMEp72wK7kQ0FIqlQ0VMFlu";        public final static String PUNCH_TEMPLATE_ID = "0pMwyHxbobrotPdcbkIUt3fY03pnVm9Ril4KEqLmeNE";        public final static String COLLECTION_TEMPLATE_ID = "7vdNCysQL64KBQtHG4xy2wsTQWyvQo4P66HPDvXbIHE";        public final static String MSG_TEMPLATE_ID = "DVBa7LpPssglj5DYgNtenxDhZ-8DBuxnctVgiwnhGxs";        public final static String GZH_APP_ID = "公众号APP_ID";        public final static String GZH_SECRET = "公众号SECRET";        public final static String GZH_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";        public final static String APPLET_APP_ID = "小程序APP_ID";        public final static String APPLET_SECRET = "小程序APP_SECRET";        public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +            "APPID&secret=SECRET";        public final static String QRCODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESSTOKEN";        public final static String MSG_API = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESSTOKEN";        public final static String AUTH_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";}
@Datapublic class Oauth2Token {        private String accessToken;        private int expiresIn;        private String refreshToken;        private String openId;        private String scope;}

/wx/login 接口源码如下:

@Slf4j@RestController@RequestMapping("/wx/wxLogin")public class WxLoginController {    @GetMapping    public void weixinLogin(HttpServletRequest request, HttpServletResponse response) throws Exception {        // 用户同意授权后,能获取到code        String code = request.getParameter("code");//拿到code的值        logger.info("****************code:" + code);        // 用户同意授权        if (!"authdeny".equals(code)) {            // 获取网页授权access_token            Oauth2Token oauth2Token = getOauth2AccessToken(WxConstant.GZH_APP_ID, WxConstant.GZH_SECRET, code);            logger.info("***********************************oauth2Token信息:" + oauth2Token.toString());            // 网页授权接口访问凭证            String accessToken = oauth2Token.getAccessToken();            // 用户标识            String openId = oauth2Token.getOpenId();            // 获取用户信息            WxUserInfo wxUserInfo = getWxUserInfo(accessToken, openId);            logger.info("***********************************用户信息unionId:" + wxUserInfo.getUnionid() + "***:" + wxUserInfo.getNickname());            //到数据库查询 该 用户信息是否存在            int result = userService.queryIsExists(wxUserInfo.getUnionid());            if (result < 1) {//添加一条新的记录                User communityUser = new User();                communityUser.setNickName(wxUserInfo.getNickname());                communityUser.setOpenId(wxUserInfo.getOpenId());                communityUser.setUnionId(wxUserInfo.getUnionid());                result = userService.addUser(communityUser);                if (result > 0) {                    logger.info("绑定用户信息成功");                } else {                    logger.info("绑定用户信息失败");                }            } else {                // 这种情况出现:已关注->取关->再次关注(数据库肯定存在记录,此时重新删除,再次添加)                userService.delInfo(wxUserInfo.getUnionid());                User communityUser = new User();                communityUser.setNickName(wxUserInfo.getNickname());                communityUser.setOpenId(wxUserInfo.getOpenId());                communityUser.setUnionId(wxUserInfo.getUnionid());                result = userService.addUser(communityUser);                if (result > 0) {                    logger.info("绑定用户信息成功");                } else {                    logger.info("绑定用户信息失败");                }            }            // 设置要传递的参数            //具体业务start            logger.info("openId:" + openId);            //具体业务end            logger.info(wxUserInfo.toString());            //重定向到我们需要跳转的地址 由前端编写的h5页面 必须是部署到服务器上的            response.sendRedirect("跳转的网页地址");            return;        }        return;    }        public static Oauth2Token getOauth2AccessToken(String appId, String appSecret, String code) {        Oauth2Token wat = null;        // 拼接请求地址        String requestUrl = WxConstant.AUTH_URL.replace("APPID", appId).replace("SECRET", appSecret).replace("CODE", code);        // 获取网页授权凭证        JSONObject jsonObject = JSON.parseObject(HttpUtil.get(requestUrl));        if (null != jsonObject) {            try {                wat = new Oauth2Token();                wat.setAccessToken(jsonObject.getString("access_token"));                wat.setExpiresIn(jsonObject.getInteger("expires_in"));                wat.setRefreshToken(jsonObject.getString("refresh_token"));                wat.setOpenId(jsonObject.getString("openid"));                wat.setScope(jsonObject.getString("scope"));            } catch (Exception e) {                wat = null;                int errorCode = jsonObject.getInteger("errcode");                String errorMsg = jsonObject.getString("errmsg");                log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg);            }        }        return wat;    }        public static WxUserInfo getWxUserInfo(String accessToken, String openId) {        WxUserInfo wxUserInfo = null;        // 拼接请求地址        String requestUrl = WxConstant.GZH_USER_INFO.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);        // 通过网页授权获取用户信息        JSONObject jsonObject = JSON.parseObject(HttpUtil.get(requestUrl));        if (null != jsonObject) {            try {                wxUserInfo = new WxUserInfo();                // 用户的标识                wxUserInfo.setOpenId(jsonObject.getString("openid"));                // 昵称                wxUserInfo.setNickname(jsonObject.getString("nickname"));                // 性别(1是男性,2是女性,0是未知)                wxUserInfo.setSex(jsonObject.getInteger("sex"));                // 用户所在国家                wxUserInfo.setCountry(jsonObject.getString("country"));                // 用户所在省份                wxUserInfo.setProvince(jsonObject.getString("province"));                // 用户所在城市                wxUserInfo.setCity(jsonObject.getString("city"));                // 用户头像                wxUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));                // 用户特权信息                List<String> list = JSON.parseArray(jsonObject.getString("privilege"), String.class);                wxUserInfo.setPrivilegeList(list);                // 与开放平台共用的唯一标识,只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。                wxUserInfo.setUnionid(jsonObject.getString("unionid"));                // 此处需要进入数据库  需要将 UnionId、openId 存入数据库            } catch (Exception e) {                wxUserInfo = null;                int errorCode = jsonObject.getInteger("errcode");                String errorMsg = jsonObject.getString("errmsg");                log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg);            }        }        return wxUserInfo;    }        public static String urlEncodeUTF8(String source) {        String result = source;        try {            result = java.net.URLEncoder.encode(source, "utf-8");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        return result;    }}

微信网页授权成功并拿到用户信息以后跳转的 H5 网页是一个简单的显示跳转成功页面,让公司的前端大佬做一下就 OK
在这里插入图片描述
关注后会显示出来,点击绑定你的个人信息
在这里插入图片描述
点击后跳转成功,完成测试 ,注意:全部操作需要部署到线上操作,或者通过内网穿透的方式来部署,否则无法进行测试

公众号推送模板消息

模板消息功能插件

在这里插入图片描述
开通成功以后会在功能栏下出现模板消息,右侧是我们从模板库引入的一个模板,稍后推送会用到
在这里插入图片描述

推送模版消息接口介绍

官方文档:模版消息接口
按照官方文档操作步骤如下:

POST 请求 data 数据需要传递的参数介绍:
在这里插入图片描述
注:url、miniprogram 都是非必填字段,若都不传则模板无跳转;若都传,会优先跳转至小程序。开发者可根据实际需要选择其中一种跳转方式即可;当用户的微信客户端版本不支持跳小程序时,将会跳转至 url

获取 Token 接口凭证

获取 token 接口凭证,获取到的 token 有效时间为 2 小时,在我们程序正常运行的时候,不可能每次都重启,我们可以使用定时器去定时更新我们 token 接口凭证

token 接口凭证类源码如下:

@Datapublic class AccessToken {        private Integer noticeType;    private Integer schoolId;    private String accessToken;    private int expiresIn;}
public class TokenUtil {    public static AccessToken accessToken = null;    private final static Logger logger = LoggerFactory.getLogger(TokenUtil.class);        @Scheduled(initialDelay = 1000, fixedDelay = 60 * 1000 * 110)    private static AccessToken getAccessToken() {        String requestUrl = WxConstant.TOKEN_URL.replace("APPID", WxConstant.GZH_APP_ID)                    .replace("SECRET", WxConstant.GZH_SECRET);        // 发起GET请求获取凭证        JSONObject jsonObject = JSONObject.parseObject(HttpUtil.sendPost(requestUrl, "GET", null));        if (null != jsonObject) {            try {                TokenUtil.accessToken = new AccessToken();                TokenUtil.accessToken.setAccessToken(jsonObject.getString("access_token"));                TokenUtil.accessToken.setExpiresIn(jsonObject.getIntValue("expires_in"));                logger.info("AccessToken>>>>>>>>>>>>>" + jsonObject.toString());            } catch (Exception e) {                TokenUtil.accessToken = null;                // 获取token失败                logger.error(e.getMessage());            }        }        return TokenUtil.accessToken;    }}

模版消息 Send 工具类

@Data@NoArgsConstructor@AllArgsConstructorpublic class TemplateData {        private String value;    private String color;}
public class SendTemplateMsgUtil {    private final static Logger logger = LoggerFactory.getLogger(SendTemplateMsgUtil.class);    private final static String TO_URL = "http://weixin.qq.com/download";        public static Map<String, String> getMiniProgramMap() {        //点击消息跳转相关参数map        Map<String, String> miniProgramMap = new HashMap<String, String>();        miniProgramMap.put("appid", WxConstant.APPLET_APP_ID);        miniProgramMap.put("path", "pages/me/me");        return miniProgramMap;    }        public static void SendWeChatPunchClockMsg(String openId, String studentName, String category, String content) {        logger.info(TokenUtil.accessToken.toString());        AccessToken accessToken = TokenUtil.accessToken;        // 接口地址        String sendMsgApi = WxConstant.MSG_API.replace("ACCESSTOKEN", accessToken.getAccessToken());        // 消息模板ID        String template_id = WxConstant.PUNCH_TEMPLATE_ID;        // 整体参数map        Map<String, Object> paramMap = new HashMap<String, Object>();        paramMap.put("touser", openId);        paramMap.put("template_id", template_id);        paramMap.put("url", TO_URL);        paramMap.put("miniprogram", getMiniProgramMap());        paramMap.put("data", getDataMap(studentName, category, content, 1));        logger.info(HttpUtil.sendPost(sendMsgApi, "POST", paramMap));    }        public static Map<String, Object> getDataMap(String param1, String param2,                     String param3, Integer msgType) {        Map<String, Object> dataMap = new HashMap<>();        if (msgType.equals(1)) {//打卡通知            dataMap.put("first", new TemplateData("亲爱的" + param1 + "家长," + param1 + "今天的作业如下:", "#173177"));            dataMap.put("name", new TemplateData(param1, "#173177"));            dataMap.put("subject", new TemplateData(param2, "#173177"));            dataMap.put("content", new TemplateData(param3, "#173177"));        } else if (msgType.equals(2)) {//收款通知            dataMap.put("first", new TemplateData(param1 + "家长你好,你的孩子需要收取费用:", "#173177"));            dataMap.put("keyword1", new TemplateData(param1, "#173177"));//param1 为学生姓名            dataMap.put("keyword2", new TemplateData(param2, "#173177"));//缴费类型            dataMap.put("keyword3", new TemplateData(param3, "#173177"));//缴费金额            dataMap.put("remark", new TemplateData("为有助于学校各项工作的顺利开展,请您务必在缴费截止日期前缴费", "#173177"));        } else if (msgType.equals(3)) {//消息通知            dataMap.put("first", new TemplateData("您好,为方便对学生进行管理,特通知如下:", "#173177"));            dataMap.put("keyword1", new TemplateData(param1, "#173177"));//keyword1  param1 为学校            dataMap.put("keyword2", new TemplateData(param2, "#173177"));//keyword2  param2 为通知人            dataMap.put("keyword3", new TemplateData(DateUtil.dateToString(new Date()), "#173177"));//keyword3   为通知时间            dataMap.put("keyword4", new TemplateData(param3, "#173177"));//keyword4  param3  为通知内容            dataMap.put("remark", new TemplateData("为有助于学校各项工作的顺利开展", "#173177"));        }        return dataMap;    }}

推送模板消息接口测试

传入刚刚网页授权所拿到的 openId、模板 id 进行测试,在获取 Token 接口凭证时报错:invalid IP

因为该 IP 未设置白名单,进入公众号后台设置 IP 白名单
在这里插入图片描述
重新启动以后,已拿到 token 接口凭证,之后我们可以通过 postman 进行发送模板消息请求

@GetMapping("/wx/sendMsg")public void sendMsg(@RequestParam String openId,@RequestParam String studentName,@RequestParam String category,@RequestParam String content){    SendMsgUtil.SendWeChatPunchClockMsg(openId,studentName,category,content);}

Post man 测试推送消息接口:
在这里插入图片描述
手机上正常接收到消息提醒
在这里插入图片描述

总结

该文章介绍在使用公众号开发功能时,涉及到的菜单、消息回复、推送等功能时,前面需要的一些准备工作,以及如何配置后端服务进行交互,同时提供了 Token 认证配置、网页授权、模版消息推送功能相关的源码给到大家

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

来源地址:https://blog.csdn.net/vnjohn/article/details/129110518

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯