本教程为看微信公众号视频做的笔记,原视频链接:尚硅谷公众号开发,微信公众号开发实战_哔哩哔哩_bilibili
平台
微信公众号管理:公众号 (qq.com)
微信公众测试号平台:微信公众平台 (qq.com)
微信公众号开发文档:微信公众平台开发概述 | 微信开放文档 (qq.com)
目录说明:
|_ config 存放配置文件
|_ index.js 配置文件,存放了需要用到的内容
|_ utils 工具
|_ tool.js 存放写的工具类
|_ wechat 存放微信接口
|_ auth.js 微信服务器的有效性验证
|_ accessToken.js 获取微信 access_token(调用微信接口必须携带的参数,是全局的唯一凭证,2小时失效)
|_ app.js 程序入口
接口配置信息
进入微信公众平台 (qq.com),看到如下页面
首先配置接口(接口配置信息),用 js 使用 express 框架编写如下代码:
// 引入express
const express = require('express');
const app = express();
app.use((req, res, next) => {console.log(111);res.send(111);
});
app.listen(3000, () => {console.log('Server is running at http://localhost:3000');
});
也就是创建一个运行在 3000 端口的服务器,这里我们 localhost:3000 这个端口并不能用于微信这个配置接口。
URL
打开 ngrok.exe ,目标路径D:\A-study\software\cpolar.exe
,进行内网穿透,将本地端口号开启的服务映射外网跨域访问一个网址,输入如下命令:
// 此处 3000 为服务器所在端口号 cpolar http 3000
页面如下:
此时访问:http://7ed2c829.r3.cpolar.top
即可实现和 localhost:3000 一样的效果。
这个地址可用于微信这个配置接口。复制此路径,粘贴到配置接口中。
ngrok 这个程序不要关闭,关闭此接口无法响应。
Token
这里的 Token 是一个微信签名的一个参数,在这里可以随意填写,越长越好。
配置完如下
验证消息来源
这里我们之所以使用 app.use()
,原因是可以接受处理所有消息,配置好上两项,点击提交,在app.use()
中查看req.query
,得到一下结果:
{// 微信的加密签名signature: '0393d4e16dc0cf2c907c6d7d9c6bfdfce4c2efce',// 微信的随机字符串echostr: '4359501947458813682',// 微信的发送请求时间戳timestamp: '1661430480',// 微信的随机数字nonce: '1215363190' }
我们的目的是要验证消息是否是来自微信服务器的,原理:
计算得出 signature 微信加密签名,和微信传递过来的 signature 进行对比,如果二者相同,则说明消息来自于微信服务器,反之,不是微信服务器发送的消息。
那么怎么进行计算呢,如下是步骤:
-
将参与微信加密签名的三个参数(传来的:timestamp、nonce、自己设置的:token),组合到一起,按照字典排序,并组合在一起形成一个数组
-
将数组拼接所有项拼接成一个字符串,进行 sha1 加密
-
加密完成就生成了一个 signatrue ,和微信发送过来的进行对比
如果一样,说明消息来自于微信服务器,返回 echostr 给微信服务器
如果不一样,说明不是微信发送的消息,返回 error
设置一个 config 变量用于储存我们所有需要用到的值:
const config = {token: "VikeyAlvinForever",appID: "wx56e0df28b365f4d2",appsevret: "5b7d5c835c65e984ecb21e0c63864103"
}
安装 sha1 :cnpm i sha1
验证代码如下:
// 引入express
const express = require('express');
const app = express();
// 引入 sha1 模块将计算出的结果加密
const sha1 = require('sha1');
// 配置对象,存我们需要的内容
const config = {token: 'VikeyAlvinForever',appID: 'wx56e0df28b365f4d2',appsevret: '5b7d5c835c65e984ecb21e0c63864103',
};
app.use((req, res, next) => {console.log(req.query);// 解构赋值const { signature, echostr, timestamp, nonce } = req.query;const { token } = config;console.log('1232' + echostr);//将参与微信加密签名的三个参数(传来的:timestamp、nonce、自己设置的:token),组合到一起,按照字典排序,并组合在一起形成一个数组const arr = [timestamp, nonce, token];// 排序const arrSort = arr.sort();console.log('arrSort:' + arrSort);// 将数组所有参数拼接const str = arr.join('');console.log('str:' + str);// 加密const sha1Str = sha1(str);console.log('加密后:' + sha1Str);// 判断是否相同if (sha1Str === signature) {res.json(echostr);} else {res.end('error');console.log('error');}
});
app.listen(3000, () => {console.log('Server is running at http://localhost:3000');
});
然后在微信公众平台上点击提交,这里我遇到了一个问题,各种代码都没有问题,然而一直配置失败,是那个 ngrok 不行,换成 cpolar 就好使,气死我了。
如图即为成功。
cploar官网:cpolar - secure introspectable tunnels to localhost
获取access_token
access_token 是微信调用接口的唯一凭证,访问微信的接口必须都携带此凭证。
特点:
-
唯一的
-
有效期为2小时,最好留出时间来请求,我们一般提前5分钟请求
-
接口权限 每天最多 2000 次
请求地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
请求方式:GET
携带参数:
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,即appsecret |
设计思路:
-
首次本地没有,发送请求获取 access_token,保存下来(本地文件)
-
第二次及以后:
-
先去本地读取文件,判断他是否过期
-
过期了 -> 重新请求获取 access_token ,保存下来覆盖之前的文件(保证文件唯一)
-
没有过期 -> 直接使用
-
整体思路:
读取本地文件(readAccessToken)
-
本地有文件
-
判断是否过期(isValidAccessToken)
-
过期 -> 重新请求获取 access_token(getAccessToken) ,保存下来覆盖之前的文件(保证文件唯一)(saveAccessToken)
-
没过期 -> 直接使用
-
-
本地没有文件
-
发送请求获取 access_token(getAccessToken),保存下来(本地文件)(saveAccessToken),直接使用
-
自动回复
接收用户发来的消息
微信服务器会发送两种类型的消息给开发者服务器:
-
GET 请求 -> 验证服务器的有效性
-
POST 请求 -> 微信服务器会将用户发送的数据以POST请求的方式转发到开发者服务器上
再 auth.js 中验证微信服务器发送的是 GET请求 还是 POST请求 获取。
使用手机像测试号发送消息,得到如下
{signature: 'bd1ffd2a36fdc44ca5b6b0d45ac3980b78489346',timestamp: '1661772245',nonce: '1886903686',openid: 'o1cZB6SJhrmbTWVCfo6l_rzwrkgY'
}
这个 openid 为用户的微信id。
需要接收请求体中的数据,这是一个流式数据。
请求后获取数据:
<xml>
<ToUserName><![CDATA[gh_5cc210c9b162]]></ToUserName> // 开发者id
<FromUserName><![CDATA[o1cZB6SJhrmbTWVCfo6l_rzwrkgY]]></FromUserName> // 用户的openid
<CreateTime>1661773339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
<MsgId>23790873740096595</MsgId>
</xml>
将xml数据解析为 js 对象
这里我们需要使用一个库:xml2js
,使用 xml2js 所提供的 parseString 方法来转换:
parseString(要转换的xml数据,{trim:是否留白},(err,data)=>{})
上文转换成如下:
{xml: {ToUserName: [ 'gh_5cc210c9b162' ],FromUserName: [ 'o1cZB6SJhrmbTWVCfo6l_rzwrkgY' ],CreateTime: [ '1661774019' ],MsgType: [ 'text' ],Content: [ '哈喽' ],MsgId: [ '23790883295887531' ]}
}
回复用户发来的消息
判断用户发送的消息:
全匹配: ===
半匹配:message.Content.match('爱')
const content = '您在说什么,我听不懂'
if(message.MsgType === 'text'){// 全匹配 用户必须发送指定消息才匹配到if(message.Content === "1"){content = "大吉大利,今晚吃鸡"// 半匹配 只要用户发送消息包含 爱 就会匹配}else if(message.Content.match("爱")){content = '我爱你~'}
}
// 这里ju
let reply = `<xml><ToUserName><![CDATA[${message.FromUserName}]]></ToUserName><FromUserName><![CDATA[${message.ToUserName}]]></FromUserName><CreateTime>${Date.now()}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[${content}]]></Content></xml>`;
res.send(reply);
详细请看文档,这里不多赘述。
自定义菜单创建接口
// 自定义菜单
module.exports = {button: [{type: 'click',name: '戳我啊~',key: 'CLICK',},{name: '菜单',sub_button: [{type: 'view',name: '跳转链接',url: 'http://www.baidu.com/',},// {// // 小程序// type: 'miniprogram',// name: 'wxa',// url: 'http://mp.weixin.qq.com',// appid: 'wx286b93c14bbf93aa',// pagepath: 'pages/lunar/index',// },{type: 'click',name: '赞一下我们',key: 'V1001_GOOD',},{type: 'scancode_waitmsg',name: '扫码带提示',key: '扫码带提示',},{type: 'scancode_push',name: '扫码推事件',key: '扫码推事件',},// {// type: 'media_id',// name: '点击按钮发送图片',// media_id: 'MEDIA_ID1',// },// {// type: 'view_limited',// name: '图文消息',// media_id: 'MEDIA_ID2',// },],},{name: '发图',sub_button: [{type: 'pic_sysphoto',// 弹出相机name: '系统拍照发图',key: '系统拍照发图',},{type: 'pic_photo_or_album',// 相册或者拍照name: '拍照或者相册发图',key: '拍照或者相册发图',},{type: 'pic_weixin',// 打开相册name: '微信相册发图',key: '微信相册发图',},{name: '发送位置',type: 'location_select',key: 'rselfmenu_2_0',},],},],
};
开发页面
首先安装 ejs,这个ejs,相当于 html 页面,直接可以在 node 中指定跳转
在 app.js 中配置 ejs,配置完才能使用:
// 配置模板资源目录
app.set('views','./views')
// 配置模板引擎
app.set('view engine','ejs')
配置完模板资源以及模板引擎,在day01下创建 views 目录,在此文件夹下创建 search.ejs 文件,编写代码:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><h1>这是一个搜索页面</h1></body>
</html>
在 app.js 中编写代码:
app.get('/search', (req, res) => {// 渲染页面,把渲染好的页面传给用户res.render('search');
});
这样当访问search 这个路径时,将会渲染出此页面