微信公众号
一、消息自动回复
A、公众号平台自带的功能模块(平台编辑模式)
1. 用户在公众号 输入信息 发送到微信的服务器
2. 微信服务器内部验证匹配规则
3. 将匹配到的结果发送给用户
B、有开发者参与,开发者服务器(开发模式,可以使用公众平台测试账号)
1. 用户在公众号 输入信息 发送到微信的服务器
2. 微信服务器将信息转发到开发者服务器
3. 开发者服务器内部做逻辑处理
4. 将结果发送给wx服务器
5. wx服务器将结果发送给客户端
区别:微信平台官方提供的消息回复功能 是固定写死的 无法添加一些业务逻辑,需要自己开发
二、确定接收消息的开发者服务器,并验证安全性
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
1. 填写服务器信息
2. 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数:signature,echostr,timestamp,nonce
* 创建本地服务器
* 进行内网穿透 在本地服务器接受微信发送的消息
3. 第三方服务器进行配置验证
* 将token、timestamp、nonce三个参数进行字典序排序
let string =[token,timestamp,nonce].sort().join(\’\’)
* 将三个参数字符串拼接成一个字符串进行sha1加密,使用node内置模块crypto
let result =crypto.createHash(\’sha1\’).update(string).digest(\’hex\’)
* 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
if(result === signature){
// 验证通过
res.send(echostr)
}else{
res.send(\’heheda\’)
}
三、服务器接收公众号发送的消息
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上(用户发送消息后,如果公众号不回复,会默认发送三次,最后显示公众号故障)
1. 接受普通的消息:文本 语音 图片 视频 地理位置 …
2. 事件消息:关注事件 取消关注事件
3. 菜单栏的点击事件
接收到的数据包:let {xml} = req.body
四、服务器回复消息给公众号
接收完消息,进行逻辑处理之后,回复xml格式的数据包:文本 图片 语音 视频 …
例如文本:
responseText(from,to,text){
return `<xml>
<ToUserName><![CDATA[${to}]]></ToUserName>
<FromUserName><![CDATA[${from}]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[${text}]]></Content>
</xml>`
},
let resStr = responseText(ToUserName,FromUserName,result)
res.send(resStr)
例如音乐:
responseMusic(from,to,name,desc,media_id,url){
return `<xml>
<ToUserName><![CDATA[${to}]]></ToUserName>
<FromUserName><![CDATA[${from}]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[music]]></MsgType>
<Music>
<Title><![CDATA[${name}]]></Title>
<Description><![CDATA[${desc}]]></Description>
<MusicUrl><![CDATA[${url}]]></MusicUrl>
<HQMusicUrl><![CDATA[${url}]]></HQMusicUrl>
<ThumbMediaId><![CDATA[${media_id}]]></ThumbMediaId>
</Music>
</xml>`
}
if(content === \’音乐\’){
let name=\’放学别走\’
let desc =\’周杰伦的超好痛\’
let media_id=\’pKzI4ueALEkWL6KCx8AFadP4eB2at5nEPgLmNmJKHAhP7FkhOP4UZWJYucTbgy2t\’
let url = \’https://m10.music.126.net/20200414100848/84491a02c41e1b8400bb8538594f1fae/yyaac/obj/wonDkMOGw6XDiTHCmMOi/2076368740/69be/ae40/c121/7f87342bca9f56e6a0acae8f415df0c9.m4a\’
let resStr = responseMusic(ToUserName,FromUserName,name,desc,media_id,url)
console.log(resStr)
res.send(resStr)
}
五、调用微信公众号提供的功能接口(自定义菜单)
1、获取调用接口的唯一凭证 access_token
https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
* 网络请求 get方法
* 参数 APPID、APPSECRET
// 获取token
function getAccessToken(){
let url =`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
return axios.get(url)
}
2、access_token 缓存处理
* access_token 有调用限制 1天2000次
* access_token 有效期2小时/7200秒
* 将access_token 保存到本地 2小时 使用的时候如果本地有 直接用本地的 如果本地没有再去请求网络的 存入本地
* 解决办法
用缓存库:radius memchache …
或者存一个本地json文件
const {getAccessToken} = require(path.join(__dirname,\’../../api/index\’))
const File = path.join(__dirname,\’./access_token.json\’)
/*
writeToken
从网络请求token
保存到本文件里
*/
async function writeToken(){
let {data} = await getAccessToken()
// 数据存入本地文件json
// 将token 以及自己维护的过期时间存入本地
let content = JSON.stringify({data,expires:(new Date()).getTime()})
let wReuslt =fs.writeFileSync(File,content)
// 返回token
return data.access_token
}
/*
getToken
用户获取本地保存的token
*/
async function getToken(){
// 捕获第一次文件没有的错误
try {
let rResult = fs.readFileSync(File,\’utf8\’)
let {data,expires} = JSON.parse(rResult)
//判断过期时间
if((new Date().getTime())-expires<=700000){
console.log(\’没过期\’)
return data.access_token
}else{
console.log(\’过期了\’)
let token = await writeToken()
return token
}
} catch (error) {
let token = await writeToken()
return token
}
}
module.exports={getToken}
3、创建自定义菜单
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
* 服务器端请求 post方法
* 需要一个参数叫 ACCESS_TOKEN
* 自定义菜单的数据包
function createMenu(access_token){
let url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${access_token}`
// 自定义菜单的数据结构
let menuData ={
button:[
// 一级菜单
{
type:\’click\’,
name:\’点击事件\’,
key:\’click_01\’
},
{
name:\’二级导航菜单\’,
sub_button:[
{
“type”: “scancode_push”,
“name”: “扫码直接跳转事件”,
“key”: “rselfmenu_0_1”
},
{
“type”: “scancode_waitmsg”,
“name”: “扫码等待事件”,
“key”: “rselfmenu_0_2”
},{
“type”: “pic_sysphoto”,
“name”: “拍照上传”,
“key”: “rselfmenu_0_3”
}
]
},
{
“type”:”view”,
“name”:”跳转到其他网页”,
“url”:”http://qstest.natapp1.cc/iwantsay”
}
]
}
return axios.post(url,menuData)
}
app.get(\’/createmenu\’,async (req,res)=>{
let token = await getToken()
let reuslt = await createMenu(token)
res.send(reuslt.data)
})
4、删除自定义菜单
function deleteMenu(access_token){
let url =`https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=${access_token}`
return axios.get(url)
}
app.get(\’/deletemenu\’,async (req,res)=>{
let token = await getToken()
let reuslt = await deleteMenu(token)
res.send(reuslt.data)
})
5、查询自定义菜单
function searchMenu(access_token){
let url = `https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=${access_token}`
return axios.get(url)
}
app.get(\’/searchmenu\’,async (req,res)=>{
let token = await getToken()
let result = await searchMenu(token)
res.send(result.data)
})