本教程为看微信公众号视频做的笔记,原视频链接:尚硅谷公众号开发,微信公众号开发实战_哔哩哔哩_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 这个路径时,将会渲染出此页面