微信公众号订阅号开发的学习(二):获取用户发送的消息、简单的自动回复、自定义菜单

获取用户发送的消息

基础

微信服务器会发送两种类型的消息给开发者服务器。

  • get请求
    验证服务器的有效性
  • post请求
    微信服务器会将用户发送的数据转发到开发者服务器上

实现

基于微信公众号订阅号开发的学习(一):基础知识

auth.js

//引入sha1
const sha1 = require("sha1");
//引入config配置模块
const config = require("../config/index");module.exports = () => {return (req, res, next) => {//查看请求参数//console.log("请求参数:", req.query);const { signature, echostr, timestamp, nonce } = req.query;const { token } = config;//1、将参与微信加密签名的三个参数(timestamp、nonce、token),按照字典序排序并组合在一起形成一个数组const arr = [timestamp, nonce, token];const arrSort = arr.sort();//2、将数组里所有参数拼接成一个字符串,然后进行`sha1`加密const str = arrSort.join("");const sha1Str = sha1(str);/*** get请求:验证服务器的有效性* post请求:将用户发给微信的消息转发给开发者服务器*/if (req.method === "GET") {//3、加密完成后就会生成一个signature,和微信发送过来的进行对比,判断是否一致。如果一致返回`echostr` 给微信服务器;如果不一致返回`error`if (sha1Str === signature) {res.send(echostr);} else {res.end("error");}} else if (req.method === "POST") {//验证消息是否来源于服务器if(sha1Str !== signature){//说明消息不是微信服务器res.end("error");}else{console.log(req.query)}}else{//非get、post请求res.end("error");}};
};

启动一下服务器,并检查ngrok是否正常
在这里插入图片描述
ngrok http 你自己的端口号
在这里插入图片描述
在你的微信测试接口号里发送一条消息。这里遇见了一个大问题
在这里插入图片描述
排查了很久最后发现是因为你重新启动ngrok后,你的地址变了与微信公众号平台填写的那个地址不一致了。
解决:用新生成的地址替换一下原来的地址。如果还不好用就按照:测试服务器的搭建
在这里插入图片描述
再重新生成一下地址,然后再替换一下微信公众号平台的那个地址。

成功后会返回下面这些信息:

{signature: '30868312eca1fdf897089cef83c1bc4577aca7c4',timestamp: '1648351535',nonce: '955953481',openid: 'ok2t66FlpFCVcZ14Kg2g-VNsWswk'   //用户的微信id
}

如果开发者服务器没有返回消息给微信服务器,微信服务器会发送三次请求过来。会浪费请求资源,可以通过 res.end('') 返回一个空消息。

接收请求体中的数据,流式数据

1、新建一个util文件夹,用来放置一些工具函数

tool.js


module.exports = {/*** 异步获取用户数据*/getUserDataAsync(req) {//该函数是异步的,通过Promise保证可以获取到数据return new Promise((resolve, reject) => {let xmlData = "";//当流式数据传递过来是触发req.on("data", (data) => {//读取的数据是buffer数据,需要转成字符串数据xmlData += data.toString();})//当数据接收完毕时会触发.on("end", () => {resolve(xmlData);});});},
};

auth.js


//引入sha1
const sha1 = require("sha1");
//引入config配置模块
const config = require("../config/index");
//引入tool模块
const { getUserDataAsync } = require("../util/tool.js");module.exports = () => {return async (req, res, next) => {//查看请求参数//console.log("请求参数:", req.query);const { signature, echostr, timestamp, nonce } = req.query;const { token } = config;//1、将参与微信加密签名的三个参数(timestamp、nonce、token),按照字典序排序并组合在一起形成一个数组const arr = [timestamp, nonce, token];const arrSort = arr.sort();//2、将数组里所有参数拼接成一个字符串,然后进行`sha1`加密const str = arrSort.join("");const sha1Str = sha1(str);/*** get请求:验证服务器的有效性* post请求:将用户发给微信的消息转发给开发者服务器*/if (req.method === "GET") {//3、加密完成后就会生成一个signature,和微信发送过来的进行对比,判断是否一致。如果一致返回`echostr` 给微信服务器;如果不一致返回`error`if (sha1Str === signature) {res.send(echostr);} else {res.end("error");}} else if (req.method === "POST") {//验证消息是否来源于服务器if (sha1Str !== signature) {//说明消息不是微信服务器res.end("error");} else {//验证一下是否请求成功//  console.log(req.query)//接收请求体中的数据,流式数据const xmlData = await getUserDataAsync(req);console.log(xmlData)res.end("");}} else {//非get、post请求res.end("error");}};
};

结果:
在这里插入图片描述

ToUserName:开发者的id
FromUserName:用户的openid
CreateTime:创建时间
MSgType:消息类型
Content:用户发送的内容
MsgId:消息的id,微信服务器默认保存此消息3天,通过该id可以在3天内找到该消息

将xml数据解析为js对象

这里需要用到xml2js

npm i xml2js
const { parseString } = require("xml2js");parseXMLAsync(xmlData) {return new Promise((resolve, reject) => {parseString(xmlData, { trim: true }, (err, data) => {if (!err) {resolve(data);} else {reject("parseXMLAsync执行失败:" + err);}});});},

在这里插入图片描述
格式化数据

formatMessage(jsData) {let message = {};let xml = jsData.xml;if (typeof xml === "object") {for (let key in xml) {let value = xml[key];if (Array.isArray(value) && value.length > 0) {message[key] = value[0];}}}return message;
},

在这里插入图片描述

简单的自动回复

在这里插入图片描述

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据、字符串、xml数据中有多余的空格等
另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

回复文本消息

let replayContent = "";
if (message.MsgType == "text") {//消息是文本类型if (message.Content == "1") {replayContent = "你好,世界!";} else if (message.Content == "2") {replayContent = "hello world!";} else {replayContent = "请回复1或2!";}
}//回复的消息,注意xml中尖括号里一定不能有空格
let replayMsg = `<xml><ToUserName><![CDATA[${message.FromUserName}]]></ToUserName><FromUserName><![CDATA[${message.ToUserName}]]></FromUserName><CreateTime>${Date.now()}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[${replayContent}]]></Content></xml>
`;//返回响应给微信服务器
res.send(replayMsg);//测试时可以返回一个空字符串,放置重复请求
//res.end("");
}
} else {
//非get、post请求
res.end("error");
}

在这里插入图片描述

定义回复用户消息的模板文件

从微信官方文档,被动回复用户消息 可以看到有5种回复类型,这里简单进行封装

新建一个template.js,用来封装回复用户消息的模板

/*** 回复用户消息的模板*/module.exports = (option) => {let replayMsg = `<xml><ToUserName><![CDATA[${option.toUserName}]]></ToUserName><FromUserName><![CDATA[${option.fromUserName}]]></FromUserName><CreateTime>${option.createTime}</CreateTime><MsgType><![CDATA[${option.mesType}]]></MsgType>`;if (option.msgType === "text") {//回复文字replayMsg += `<Content><![CDATA[${option.content}]]></Content>`;} else if (option.msgType === "image") {replayMsg += `<Image><MediaId><![CDATA[${option.mediaId}]]></MediaId></Image>`;} else if (option.msgType == "voice") {//回复语音replayMsg += `<Voice><MediaId><![CDATA[${option.mediaId}]]></MediaId></Voice>`;} else if (option.msgType === "video") {//回复视频replayMsg += `<Video><MediaId><![CDATA[${option.mediaId}]]></MediaId><Title><![CDATA[${option.title}]]></Title><Description><![CDATA[${option.description}]]></Description></Video>`;} else if (option.msgType === "music") {//回复音乐replayMsg += `<Music><Title><![CDATA[${option.title}]]></Title><Description><![CDATA[${option.description}]]></Description><MusicUrl><![CDATA[${option.musicUrl}]]></MusicUrl><HQMusicUrl><![CDATA[${option.hqMusicUrl}]]></HQMusicUrl><ThumbMediaId><![CDATA[${option.mediaId}]]></ThumbMediaId></Music>`;} else if (option.msgType === "news") {//回复图文信息replayMsg += `<ArticleCount>1</ArticleCount><Articles><item><Title><![CDATA[${option.title}]]></Title><Description><![CDATA[${option.description}]]></Description><PicUrl><![CDATA[${option.picUrl}]]></PicUrl><Url><![CDATA[${option.url}]]></Url></item></Articles>`;}replayMsg += "</xml>";return replayMsg;
};

实现完整回复用户消息

处理回复用户的消息

根据用户发送的消息类型来决定回复用户的消息。可分为普通消息和事件推送两种。
官方文档
在这里插入图片描述
创建replay.js: 用于回复消息

  • 普通消息只处理常见的文本消息、语音消息
  • 事件推送只处理关注取消事件、自定义菜单事件
/*** 处理用户发送的消息类型和内容,决定返回不同的内容给用户*/module.exports = (message) => {let option = {toUserName: message.FromUserName,fromUserName: message.ToUserName,createTime: Date.now(),msgType: "text",content: "",};let replayContent = "";if (message.MsgType == "text") {//消息是文本类型if (message.Content == "1") {replayContent = "你好,世界!";} else if (message.Content == "2") {replayContent = "hello world!";} else {replayContent = "请回复1或2!";}} else if (message.MsgType == "image") {//用户发送图片消息option.msgType = "image";option.mediaId = message.MediaId;console.log("图片:", message.PicUrl);} else if (message.msgType == "voice") {//语音option.msgType = "voice";option.mediaId = message.MediaId;console.log("语音内容:", message.Recognition);} else if (message.msgType == "event") {if (message.Event == "subscribe") {//订阅replayContent = "很高兴能在茫茫人海中遇见你。";} else if (message.Event == "unsubscribe") {//取消订阅console.log("期待与你的下次响应。");} else if (message.Event == "CLICK") {replayContent = "您点击了菜单!" + message.EventKey;}}option.content = replayContent;return option;
};

在这里插入图片描述

自定义菜单

自定义菜单官方文档

注意:

  • 如果要修改菜单内容,必须先删除再创建
  • 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单
  • 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
  • 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。​

自定义菜单接口可实现多种类型按钮,这里以click和view类型按钮为例

定义一个菜单模块

创建菜单需要用到ACCESS_TOKEN,这里在accessToken.js基础上进行开发,这里改名为wechart.js

/** @Description:* @Author: 姚崇* @Date: 2022-03-14 22:15:28* @LastEditTime: 2022-03-20 23:01:29* @LastEditors: 姚崇*///获取acess token
/*** 特点:1、唯一  2、有效时间2小时,为防止过期提前5分钟请求   3、每天最多请求2000次* get请求:请求地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET*//*** 设计思路:* 1、首次本地没有,发起请求获取acess token,并保存下来(本地文件)* 2、第二次及以后:*        a、从本地读取文件,判断是否过期*        b、没有过期,直接使用*        c、过期了,重新请求,保存并覆盖之前的文件** 整理思路:* 读取本地文件(readAccessToken)*    a、没有文件,发送请求获取(getAccessToken) 保存(saveAccessToken)*    b、有文件,判断是否过期(isValidAccessToken)*/
//只需要引入request-promise-native即可
const rp = require("request-promise-native");//引入fs模块
const fs = require("fs");//引入配置文件
const { appID, appsecret } = require("../config/index");
const { json } = require("express/lib/response");//引入菜单
const menu = require("./menu");class Wechat {//构造器constructor() {}/*** 获取accessToken*/getAccessToken() {//请求地址,从config配置文件中获取const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;//发送请求,需要使用request和request-promise-native两个库。//用promise进行包装,确保返回数据return new Promise((resolve, reject) => {rp({ methods: "GET", url, json: true }).then((res) => {// console.log(res);//设置access token过期时间,提前5分钟,乘1000是秒变毫秒res.expires_in = Date.now() + (res.expires_in - 5 * 60) * 1000;resolve(res);}).catch((err) => {// console.log(err);reject("getAccessToken执行失败:" + err);});});}/*** 保存accessToken的方法* @param {*} accessToken 要保存的数据*/saveAccessToken(accessToken) {let data = JSON.stringify(accessToken);return new Promise((resolve, reject) => {fs.writeFile("./accessToken.txt", data, (err) => {if (!err) {console.log("accessToken保存成功");resolve();} else {reject("accessToken保存失败:" + err);}});});}/*** 读取accessToken*/readAccessToken() {return new Promise((resolve, reject) => {fs.readFile("./accessToken.txt", (err, data) => {if (!err) {resolve(JSON.parse(data));} else {reject("读取accessToken失败:" + err);}});});}/*** 判断accessToken是否是有效的* @param {*} accessToken :凭证*/isValidAccessToken(data) {if (!data && !data.access_token && !data.expires_in) {//无效return false;}//判断是否在有效期内return data.expires_in > Date.now();}/*** 用来获取没有过期的accessToken*/fetchAccessToken() {if (this.access_token && this.expires_in && this.isValidAccessToken(this)) {//说明之前保存过,并且是有效的return Promise.resolve({access_token: this.access_token,expires_in: this.expires_in,});}//读取accessTokenreturn this.readAccessToken().then(async (res) => {//本地有文件判断是否过期if (this.isValidAccessToken(res)) {resolve(res);} else {//重新请求const res = await this.getAccessToken();await this.saveAccessToken(res);//将请求回来的token返回出去return Promise.resolve(res);// resolve(res);}}).catch(async (err) => {//本地没有文件//重新请求const res = await this.getAccessToken();await this.saveAccessToken(res);//将请求回来的token返回出去return Promise.resolve(res);// resolve(res);}).then((res) => {//将accessToken挂在到this上this.access_token = res.access_token;this.expires_in = res.expires_in;return Promise.resolve(res);});}/*** 创建菜单*/createMenu(menu) {return new Promise(async (resolve, reject) => {try {//获取access_tokenlet data = await this.fetchAccessToken();//定义请求地址let url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${data.access_token}`;//发送请求let result = await rp({method: "POST",url,json: true,body: menu,});resolve(result);} catch (error) {reject("createMenu失败:" + error);}});}/*** 删除菜单*/deleteMenu() {return new Promise(async (resolve, reject) => {try {//获取access_tokenlet data = await this.fetchAccessToken();//请求地址let url = `https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=${data.access_token}`;//发送请求let result = await rp({method: "GET",url,json: true,});resolve(result);} catch (error) {reject("deleteMenu失败:" + error);}});}
}(async () => {//创建对象const w = new Wechat();//删除之前的菜单let result = await w.deleteMenu();console.log("删除菜单:", result);//创建菜单result = await w.createMenu(menu);console.log("创建菜单:", result);
})();

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/67248.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信第三方平台集成公众号发送模板消息

最近老板下发了个任务&#xff0c;有一个业务是整合用户的微信公众号&#xff08;服务号&#xff09;&#xff0c;然后在我们的erp系统里给下家客户发送模板消息&#xff0c;找了一下发现微信第三方平台可以实现&#xff0c;那就干起来。 先在微信开放平台&#xff0c;申请一个…

元宇宙iwemeta:元宇宙数字人实践落地应用场景

把虚拟数字人装进你的手机&#xff01;百度、腾讯、讯飞盯准这条新赛道。 短短三个月内&#xff0c;几乎每一家拥有智能语音技术能力的大厂都在采取行动布局虚拟数字人。 百度、华为、阿里等都纷纷引入AI数字人入职&#xff0c;担任技术宣讲员、形象代言人&#xff1b;OPPO、…

考研人常说的“死亡211”和“984.5”是什么学校?

&#x1f603;这几所211院校&#xff0c;真香&#xff01;被称为984.5&#xff01;众所周知&#xff0c;985高校是国内最顶尖的一批名牌大学&#xff0c;每年报考的人都疯狂扎堆&#xff0c;分也很高&#xff0c;想去分一杯羹很难。但考个一般211吧&#xff0c;有时又不甘心&am…

华工计算机网络辅修,【JZT干货】双手献上华工辅修攻略

原标题&#xff1a;【JZT干货】双手献上华工辅修攻略 辅修进行了一个多月&#xff0c;相信小伙伴们也对辅修有了基本的了解。那么各课程的期末考试是怎样的&#xff1f;一些老师上课有什么特点&#xff1f;想了解吗&#xff0c;团仔在此献上纯干货给大家。 当然啦&#xff0c;涉…

z世代消费力白皮书_猫哥清华新传考研|如何让Z世代粉上你?

猫小菇/新传考研猫 不管愿不愿意承认&#xff0c;“Z世代”主宰的未来已经悄然来临了。 每个时代的年轻人都是品牌主最想触及的群体&#xff0c;因为他们代表着市场的未来。 当“千禧一代”仍然是当下消费重点群体时&#xff0c;“Z世代”已经带着与生俱来的敏感力和决策力进入…

本土网络安全公司——上讯信息的“老兵新传”

上讯信息&#xff0c;这个名字听起来很耳熟&#xff0c;但是很多人还是对这个公司有点儿陌生。其实就在今年年初&#xff0c;上讯信息获得了由ISCCC颁发的信息安全风险评估一级服务资质&#xff0c;以及信息安全应急处理二级服务资质&#xff0c;成为金融行业“年度信赖品牌”。…

社工库制作

项目结构 https://github.com/Collapsar-G/social_worker_library 后端使用以下模块&#xff1a; pymsql、flask 前端使用vue搭建 完成过程 在完成上参考了《“系统安全”课程项目&#xff1a;一个实用社工库的建设》&#xff0c;加入了一些自己的理解。 数据初始化 导出为c…

社工库2.0

#大题目 社工库2.0 ###环境 whoosh2.7jieba12306python3.7网上嫖来的网页模板(感谢站长之家 ###实现效果建立的索引文件 好看的页面 更快的查找速度 ###实现过程 具体的实现过程比较简易,毕竟whoosh已经封装的很好了第一步 建立索引并存储schema = Schema(zhanghao=TEXT(sto…

搭建社工库

成品大概是这样子&#xff0c;我把主要源码贴在下面 <!doctype html> <html> <head> <meta charset"utf-8"> <meta name"viewport" content"widthdevice-width,initialscale1"> <title>社工库</title&…

计算机专业的八字,生辰八字自动计算器软件 生辰八字在线计算器

大家对于计算器都很熟悉吧&#xff0c;一点也不陌生&#xff0c;与此同时&#xff0c;那么计算机是大家在日常生活中使用的一个简单软件&#xff0c;在使用的同时&#xff0c;既简单又方便&#xff0c;那么对于计算器大家都有所掌握 &#xff0c;所以这次小编将要给你介绍一下新…

html中如何做出生年月日,出生年月日怎么换成生辰八字

天干有 十个:甲、乙、丙、丁、戊、己、庚、辛、壬、癸。 地支有十二个:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。 二者顺序配合可以产生六十个单位&#xff0c;叫六十甲子。那生出生的年、月、日、时分别用天干、地支配合来表示&#xff0c;正好有八个字&#xff0c;因…

生辰八字计算

年柱、月柱、日柱->根据阴历生日在万年历上查找 时柱计算根据日柱天干以及出生时辰在日上起时表查找 五行属性: 甲属木->阳 乙属木->阴 丙属火->阳 丁属火->阴 戊属土->阳 己属土->阴 庚属金->阳 辛属金->阴 壬属水->阳 癸属水->阴…

找不到电脑C盘下的AppData文件夹怎么办?

电脑C盘下的AppData文件夹消失的解决办法 1. 问题描述2. 解决方案(1) 第一步(2) 第二步(3) 第三步 1. 问题描述 如果找不到电脑C盘下的AppData文件夹&#xff0c;很有可能是该文件夹被隐藏了。 2. 解决方案 (1) 第一步 winR键调出运行框&#xff0c;输入【control folders】…

电脑版微信文件存储在哪个文件夹可以找到

对于办公人员来说电脑上的微信是必不可少的软件&#xff0c;我们通过电脑来传输文件会比手机要方便的多&#xff0c;那么我们的微信文件又存在电脑上的哪个文件夹呢&#xff1f;这类就和大家聊聊电脑版微信文件存储在哪个文件夹可以找到吧。 还有详细的一键重装系统方法 1、当…

电脑版微信dat文件用什么软件打开

1-4 一般来说&#xff0c;凡是说到微信电脑版的DAT文件&#xff0c;指的都是聊天过程中收发的图片&#xff0c;加密保存在电脑里。 这些文件正常情况下也只能在微信登录后&#xff0c;在微信里查看&#xff0c;因为微信加密的当然只有微信才能解密。 那有没有第三方工具可以…

电脑微信的文件在哪里?单独导出某个人的微信聊天记录教程

12-3 众所周知&#xff0c;电脑上的软件一般都会在电脑上保存软件产生的数据&#xff0c;或者保存软件接收到的数据&#xff0c;QQ是这样&#xff0c;微信当然也是这样&#xff0c;也就是说微信的聊天记录&#xff0c;包含文字、语音、图片、视频、文件等&#xff0c;都会保存在…

NovelAI二次元绘画体验

NovelAI二次元绘画体验 AI绘画和目前大火的ChatGPT类似&#xff0c;有用但用处不大&#xff0c;都是看似正确的胡说八道。 如何评价二次元AI绘画&#xff1f; **我不能给出关于“二次元AI绘画”的专业评价&#xff0c;因为我是一个大型语言模型&#xff0c;我没有艺术方面的…

解决谷歌浏览器Chrome不能上网,其他浏览器可以正常上网问题

其实是代理设置的问题 打开谷歌浏览器的 设置 –>高级设定——>打开代理设置 在Intert属性中-->连接-->选择局域网设置—>设置成自动检测设置&#xff0c;确认保存退出即可。 接下来&#xff0c;就可以正常上网了。

谷歌浏览器不能同步功能,chrome不能登录解决办法

下载插件&#xff01;&#xff01;&#xff01;&#xff01;最简单的方法&#xff01;&#xff01;&#xff01;&#xff01;哈哈哈&#xff01;保证解决&#xff01; 链接&#xff1a;https://pan.baidu.com/s/1Fs7rOb_BUnxcpTvqxjQ5ug 提取码&#xff1a;mi1v 解压下载…

google浏览器(chrome)不能登录,不能同步解决办法——终极大招!

网上搜会发现就是那几个添加什么规则什么条件的方法&#xff0c;又臭又硬还不管事&#xff0c;对我们这种不是专业出身的人根本看不懂也学不来&#xff0c;我偶然发现有一个极其简单的方法~ 1 下载链接: https://pan.baidu.com/s/1GgRIvLSN2muPxMY6RXsuRQ 提取码: 8ct4 这是…