赞
踩
微信公众号管理:公众号 (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 是微信调用接口的唯一凭证,访问微信的接口必须都携带此凭证。
特点:
唯一的
有效期为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 这个路径时,将会渲染出此页面
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。