一、验证服务器的有效性
1、将参与微信加密签名的三个参数(timestamp,nonce,token)按照字典排序并组合在一起形成一个数组
2、 将数组里所有参数拼接成一个字符串,进行sha1加密
3、加密后的字符串sha1Str与微信加密签名signature对比
相等的话说明消息来自于微信服务器,否则返回error
注意: 微信可以发起两种请求(GET和POST)
auth.js
/**
* 验证服务器有效性模块
*/
const sha1 = require('sha1');
// 引入config模块
const config = require('../config')
module.exports = () => {
return (req, res, next) => {
//结构赋值
const { signature, echostr, timestamp, nonce } = req.query;
const { token } = config;
/**
* 1、将参与微信加密签名的三个参数(timestamp,nonce,token)按照字典排序并组合在一起形成一个数组
* 2、 将数组里所有参数拼接成一个字符串,进行sha1加密
*/
const sha1Str = sha1([timestamp, nonce, token].sort().join(''));
if (req.method === 'GET') {
// 验证服务器的有效性
// 加密完成就生成了一个signature,和微信发送过来的进行对比
if (sha1Str === signature) {
res.send(echostr);
} else {
res.end('error');
}
} else if (req.method === 'POST') {
// 微信服务器将用户发送的数据以POST请求发送给开发者服务器
// 验证消息来自于微信服务器
if (sha1Str !== signature) {
// 说明消息不是来自于微信服务器
res.end('error');
}
console.log(req.query)
/**
* {
* signature: '38cb293666bf04190c91334fc68200209fe02d22',
* timestamp: '1642384586',
* nonce: '1077908299',
* openid: 'orqa66UaDU5mY3UqQTJxdpxaiUT0'
* }
*/
// 接受请求体的数据,流式数据
// 如果开发者服务器没有返回响应给微信服务器的话,微信服务器会发送三次请求过来
res.end('');
} else {
res.end('error');
}
}
}
config文件夹下的index.js
这里要填自己的配置信息,不要照抄
/**
* 配置对象模块
*/
module.exports = {
token: 'token',
appID: 'appID',
appsecret: 'appsecret'
}
二、获取accessToken
accessToken是什么?
微信调用接口全局唯一凭证
-
特点:
1.唯一性
2.有效期为2个小时
3.接口权限:2000次/天 -
请求地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
-
请求方式:GET
-
设计思路:
1.首次本地没有,发送请求获取access_token,保存下来(本地文件)
2.第二次或以后-
先去本地读取文件,判断它是否过去
- 过期了
- 重新请求获取access_token,保存下来覆盖之前的文件(保证文件是唯一性的)
- 没有过期
- 直接使用
- 过期了
-
整理思路:
读取本地文件(getAccessToken)
- 本地有文件
- 判断它是否过期(isValidAccessToken)
- 过期了
- 重新请求获取access_token(getAccessToken),保存下来覆盖之前的文件(保证文件是唯一性的)(saveAccessToken)
- 过期了
- 判断它是否过期(isValidAccessToken)
- 本地没有文件
- 发送请求获取access_token(getAccessToken),保存下来(本地文件)(saveAccessToken),直接使用。
wechat.js
// 引入request-promise-native
const rp = require('request-promise-native');
// 引入fs模块
const { writeFile, readFile } = require('fs')
// 引入config
const { appID, appsecret } = require('../config');
const { resolve } = require('path');
class Wechat {
// 类的属性
constructor() {
}
/**
* 获取access_token
*/
getAccessToken() {
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({
method: 'GET',
url,
json: true
}).then(res => {
console.log(res);
/**
* {
* access_token: '53_-1Hb7Jp1kyUhnxE4LWaT7bic3SB4pfVo3wJmzfe4biAgHCdqxpRL9KmOInaS_I227rPjKKD5LN9WQ3vCEtKv8gkUGejVmQz6buOLm0lAPt_QrnPKfxlzWoPlrLvAzVB9-DGjq98lVzl5Q6abTTMeADAZZS',
* expires_in: 7200 //过期时间
* }
*/
// 设置access_token的过期时间
res.expires_in = Date.now() + (res.expires_in - 300) * 1000;
// 将promise对象状态改成成功的状态
resolve(res)
}).catch(err => {
console.log(err);
// 将promise对象状态改成失败的状态
reject('getAccessToken方法出了问题:' + err);
})
})
}
/**
* 保存access_token
* @param accessToken 要保存的凭据
*/
saveAccessToken(accessToken) {
// 将对象转化为json字符串
accessToken = JSON.stringify(accessToken);
// 将access_token保存为一个文件,异步存储
return new Promise((resolve, reject) => {
writeFile('./accessToken.txt', accessToken, err => {
if (!err) {
console.log('文件保存成功~');
resolve();
} else {
reject('saveAccessToken方法出了问题:' + err)
}
})
})
}
/**
* 读取access_token
*/
readAccessToken() {
// 读取本地文件中的access_token
return new Promise((resolve, reject) => {
readFile('./accessToken.txt', (err, data) => {
if (!err) {
// 将json字符串转换成js对象
data = JSON.parse(data)
console.log('文件读取成功~');
resolve(data);
} else {
reject('readAccessToken方法出了问题:' + err)
}
})
})
}
/**
* 检测access_token是否有效
* @param data
*/
isValidAccessToken(data) {
// 检测传入的参数是否有效
if (!data && !data.access_token && !data.expires_in) {
// 代表access_token无效的
return false;
}
return data.expires_in > Date.now();
}
/**
* 获取有效的access_token
* @returns access_token
*/
fetchAccessToken() {
if (this.accessToken && this.expires_in && this.isValidAccessToken(this)) {
// 说明之前保存过access_token,并且它是有效的,直接使用
return Promise.resolve({
access_token: this.accessToken,
expires_in: this.expires_in
})
}
// 是fetchAccessToken函数的返回值
return this.readAccessToken()
.then(async res => {
// 本地有文件,就判断是否在有效期
if (this.isValidAccessToken(res)) {
// 有效的
return Promise.resolve(res)
// resolve(res);
} else {
// 无效的
const res = await this.getAccessToken();
// 获取成功,就保存到本地文件,直接使用
await this.saveAccessToken(res)
// 将请求回来的access_token返回出去
return Promise.resolve(res)
// resolve(res);
}
})
.catch(async err => {
// 没有文件,需获取access_token
const res = await this.getAccessToken();
// 获取成功,就保存到本地文件,直接使用
await this.saveAccessToken(res)
// 将请求回来的access_token返回出去
return Promise.resolve(res)
// resolve(res);
})
.then(res => {
// 将access_token的内容挂载到this上
this.access_token = res.access_token;
this.expires_in = res.expires_in;
// 返回res包装了一个promise对象(成功的状态)
// 是this.readAccessToken()最终的返回值
return Promise.resolve(res);
})
}
}
// 模拟测试
const w = new Wechat();
w.fetchAccessToken()
三、基础消息功能
1、接收用户消息(文本消息)
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
/*auth.js*/
// 引入tool模块
const { getUserDataAsync, parseXMLAsync, formatMessage } = require('../utils/tool');
// 接受请求体的数据,流式数据
const xmlData = await getUserDataAsync(req);
// 将xml数据解析为js对象
const jsData = await parseXMLAsync(xmlData);
// 格式化数据
const message = formatMessage(jsData);
// 如果开发者服务器没有返回响应给微信服务器的话,微信服务器会发送三次请求过来
res.end('');
} else {
res.end('error');
}
格式化后的message
/*tool.js*/
// 引入xml2js,将XML数据转换成JS对象
const { parseString } = require('xml2js')
module.exports = {
// 接收用户发过来的数据,流式数据
getUserDataAsync(req) {
return new Promise((resolve, reject) => {
let xmlData = '';
req.on('data', data => {
// 当流式数据传递过来时,会触发当前事件,会将数据注入到回调函数中
// 读取的数据是buffer,需要将其转换成字符串才能拼串
xmlData += data.toString();
})
.on('end', () => {
// 当数据接收完毕时会触发当前事件
resolve(xmlData);
})
})
},
// 将XML数据转换成JS对象
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 = {};
// 获取xml对象
jsData = jsData.xml;
// 判断数据是否是个对象
if (typeof jsData === 'object') {
for (let key in jsData) {
// 获取属性值
let value = jsData[key];
// 过滤掉空的数据
if (Array.isArray(value) && value.length > 0) {
message[key] = value[0]
}
}
}
return message;
}
}
2、被动回复用户消息
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
- 1、开发者在5秒内未回复任何内容
- 2、开发者回复了异常数据,比如JSON数据、字符串、xml数据中有多余的空格等
/*auth.js*/
// 简单的自动回复,回复文本内容
let content = '您在说什么?我听不懂~';
// 判断用户发送的是否是文本消息
if (message.MsgType = 'text') {
if (message.Content === '1') {
content = '大吉大利,今晚吃鸡!!!';
} else if (message.Content === '2') {
content = '落地成盒~'
} else if (message.Content.match('爱')) {
content = '我也爱你~'
}
}
let replyMessage = `<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(replyMessage);
3、接收普通消息和事件推送
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
根据接收的消息类型,回复不同的内容
/*reply.js*/
/**
* 处理用户发送的消息类型和内容,决定返回不同的内容给用户
*/
module.exports = (message) => {
let options = {
toUserName: message.FromUserName,
fromUserName: message.ToUserName,
createTime: Date.now(),
msgType: message.MsgType
};
let content = '您在说什么?我听不懂~';
// 判断用户发送的是否是文本消息
if (message.MsgType = 'text') {
// 文本消息
if (message.Content === '1') {
content = '大吉大利,今晚吃鸡';
} else if (message.Content === '2') {
content = '落地成盒~';
} else if (message.Content.match('爱')) {
content = '我也爱你~';
}
} else if (message.MsgType === 'image') {
// 图片消息
options.msgType = 'image';
options.mediaId - message.MediaId;
console.log(message.PicUrl);
} else if (message.MsgType === 'voice') {
// 语音消息
options.msgType = 'voice';
options.mediaId - message.MediaId;
console.log(message.Recognition);
} else if (message.MsgType === 'location') {
// 位置消息
content = `纬度:${message.Location_X} 经度:${message.Location_Y} 缩放大小:${message.Scale} 位置信息:${message.Label}`;
} else if (message.MsgType === 'subscribe') {
if (message.Event === 'subscribe') {
// 用户订阅事件
content = '感谢您的关注~~';
if (message.Eventkey) {
console.log('新用户是扫描了带参数的二维码关注事件');
}
} else if (message.Event === 'unsubscribe') {
// 取消订阅
console.log("无情取关!");
} else if (message.Event === 'SCAN') {
console.log('老用户用户是扫描了带参数的二维码关注事件');
} else if (message.Event === 'LOCATION') {
// 上报位置信息
content = `纬度:${message.Location_X} 经度:${message.Location_Y} 精度:${message.Precision}`;
} else if (message.Event === 'CLICK') {
// 点击菜单事件
content = `您点击了按钮:${message.Eventkey}`;
}
}
options.content = content;
console.log(options);
return options;
}
回复不同类型的消息的模板
/*template.js*/
/**
* 加工处理最终回复用户消息的模板(xml数据)
*/
module.exports = (options) => {
let replyMessage = `<xml>
<ToUserName><![CDATA[${options.fromUserName}]]></ToUserName>
<FromUserName><![CDATA[${options.toUserName}]]></FromUserName>
<CreateTime>${options.createTime}</CreateTime>
<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
if (options.msgType === 'text') {
replyMessage += `<Content><![CDATA[${options.content}]]></Content>`
} else if (options.msgType === 'image') {
replyMessage += `<Image>
<MediaId><![CDATA[${options.mediaId}]]></MediaId>
</Image>`
} else if (options.msgType === 'voice') {
replyMessage += `<Voice>
<MediaId><![CDATA[${options.mediaId}]]></MediaId>
</Voice>`
} else if (options.msgType === 'video') {
replyMessage += `<Video>
<MediaId><![CDATA[${options.mediaId}]]></MediaId>
<Title><![CDATA[${options.title}]]></Title>
<Description><![CDATA[${options.description}]]></Description>
</Video>`
} else if (options.msgType === 'music') {
replyMessage += `<Music>
<Title><![CDATA[${options.title}]]></Title>
<Description><![${options.description}]]></Description>
<MusicUrl><![CDATA[${options.musicUrl}]]></MusicUrl>
<HQMusicUrl><![CDATA[${options.hqMusicUrl}]]></HQMusicUrl>
<ThumbMediaId><![CDATA[${options.mediaId}]]></ThumbMediaId>
</Music>`
} else if (options.msgType === 'news') {
replyMessage += `<ArticleCount>${options.content.length}</ArticleCount><Articles>`
options.content.forEach(item => {
replyMessage += `
<item>
<Title><![CDATA[${item.title}]]></Title>
<Description><![CDATA[${item.description}]]></Description>
<PicUrl><![CDATA[${item.picUrl}]]></PicUrl>
<Url><![CDATA[${item.url}]]></Url>
</item>
`
});
replyMessage += '</Articles>';
}
replyMessage += `</xml>`;
return replyMessage;
}
/*app.js*/
// 格式化数据
const message = formatMessage(jsData);
const options = reply(message);
// 最终回复用户的消息
const replyMessage = template(options);
res.send(replyMessage);
四、自定义菜单
自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。开启自定义菜单后,公众号界面如图所示:
- 请注意:
1、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
2、一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
3、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
- 自定义菜单接口可实现多种类型按钮,如下:
1、click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
2、view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
3、scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5、pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6、pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
7、pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8、location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
9、media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频 、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
10、view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
11、article_id:用户点击 article_id 类型按钮后,微信客户端将会以卡片形式,下发开发者在按钮中填写的图文消息
12、article_view_limited:类似 view_limited,但不使用 media_id 而使用 article_id
注意: 草稿接口灰度完成后,将不再支持图文信息类型的 media_id 和 view_limited,有需要的,请使用 article_id 和 ==article_view_limited == 代替
/*menu.js*/
/**
* 菜单模板
*/
module.exports = {
"button": [{
"type": "click",
"name": "今日歌曲",
"key": "V1001_TODAY_MUSIC"
},
{
"name": "搜索",
"sub_button": [{
"type": "view",
"name": "百度搜索",
"url": "https://www.baidu.com/"
},
// {
// "type": "miniprogram",
// "name": "wxa",
// "url": "http://mp.weixin.qq.com",
// "appid": "wx286b93c14bbf93aa",
// "pagepath": "pages/lunar/index"
// },
{
"type": "view",
"name": "谷歌搜索",
"url": "http://www.goole.com/"
}
]
}
]
}
/*wechat.js*/
/**
* 创建自定义菜单
*/
createMenu(menu) {
return new Promise(async(resolve, reject) => {
try {
// 获取accessToken
const data = await this.fetchAccessToken();
// 定义请求地址
const url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${data.access_token}`;
// 发送请求
const result = await rp({ method: 'POST', url, json: true, body: menu })
resolve(result);
} catch (error) {
reject("createMenu出了问题:" + error);
}
})
}
/**
* 删除自定义菜单
*/
delMenu() {
return new Promise(async(resolve, reject) => {
try {
// 获取accesstoken
const data = this.fetchAccessToken();
//定义请求
const url = `https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=${data.access_token}`;
const result = await rp({ method: 'GET', url, json: true });
resolve(result);
} catch (error) {
reject("delMenu方法出了问题" + error);
}
})
}
}
(async() => {
// 模拟测试
const w = new Wechat();
// 删除之前的菜单
let result = await w.delMenu();
console.log(result);
// 创建自定义菜单
result = await w.createMenu(menu);
console.log(result)
})()
五、微信网页开发
1、获取jsapi_ticket
api_ticket 是用于调用微信卡券JS API的临时票据,有效期为7200 秒,通过access_token 来获取。,并保存到本地
/*wechat.js*/
/**
* 获取jsapi_ticket
*/
getTicket() {
return new Promise(async(resolve, reject) => {
// 获取access_token
const data = await this.fetchAccessToken();
const url = `${api.ticket}&access_token=${data.access_token}`;
rp({
method: 'GET',
url,
json: true
}).then(res => {
// 将promise对象状态改成成功的状态
resolve({ ticket: res.ticket, ticket_expires_in: Date.now() + (res.expires_in - 300) * 1000 })
}).catch(err => {
console.log(err);
// 将promise对象状态改成失败的状态
reject('getAccessToken方法出了问题:' + err);
})
})
}
/**
* 保存access_token
* @param ticket 要保存的凭据
*/
saveTicket(ticket) {
return writeFileAsync(ticket, 'ticket.txt');
}
/**
* 读取ticket
*/
readTicket() {
return readFileSync('ticket.txt')
}
/**
* 检测ticket是否有效
* @param data
*/
isValidTicket(data) {
// 检测传入的参数是否有效
if (!data && !data.ticket && !data.ticket_expires_in) {
// 代表ticket无效的
return false;
}
return data.ticket_expires_in > Date.now();
}
/**
* 获取有效的ticket
* @returns ticket
*/
fetchTicket() {
if (this.accessToken && this.ticket_expires_in && this.isValidTicket(this)) {
// 说明之前保存过ticket,并且它是有效的,直接使用
return Promise.resolve({
ticket: this.ticket,
expires_in: this.expires_in
})
}
// 是fetchTicket函数的返回值
return this.readTicket()
.then(async res => {
// 本地有文件,就判断是否在有效期
if (this.isValidTicket(res)) {
// 有效的
return Promise.resolve(res)
// resolve(res);
} else {
// 无效的
const res = await this.getTicket();
// 获取成功,就保存到本地文件,直接使用
await this.saveTicket(res)
// 将请求回来的ticket返回出去
return Promise.resolve(res)
// resolve(res);
}
})
.catch(async err => {
// 没有文件,需获取ticket
const res = await this.getTicket();
// 获取成功,就保存到本地文件,直接使用
await this.saveTicket(res)
// 将请求回来的ticket返回出去
return Promise.resolve(res)
// resolve(res);
})
.then(res => {
// 将ticket的内容挂载到this上
this.ticket = res.ticket;
this.ticket_expires_in = res.expires_in;
// 返回res包装了一个promise对象(成功的状态)
// 是this.readTicket()最终的返回值
return Promise.resolve(res);
})
}
/*api.js*/
// 地址前缀
const prefix = 'https://api.weixin.qq.com/cgi-bin/';
...
ticket: `${prefix}ticket/getticket?type=jsapi`
}
/**tool.js**/
// 以文件的形式保存数据到本地
writeFileAsync(data, fileName) {
// 将对象转化为json字符串
data = JSON.stringify(data);
const filePath = resolve(__dirname, fileName);
return new Promise((resolve, reject) => {
writeFile(filePath, data, err => {
if (!err) {
console.log('文件保存成功~');
resolve();
} else {
reject(err)
}
})
})
},
// 读取本地文件
readFileSync(fileName) {
const filePath = resolve(__dirname, fileName);
// 读取本地文件中的access_token
return new Promise((resolve, reject) => {
readFile(filePath, (err, data) => {
if (!err) {
// 将json字符串转换成js对象
data = JSON.parse(data)
console.log('文件读取成功~');
resolve(data);
} else {
reject('readFileSync方法出了问题:' + err)
}
})
})
}
2、验证微信JS-SDK
生成js-sdk使用的签名:
- 1.组合参与签名的四个参数:jsapi_ticket(临时票据)、noncestr(随机字符串)、timestamp(时间戳)、url(当前服务器地址)
- 2.将其进行字典序排序,以‘&’拼接在一起
- 3.进行sha1加密,最终生成signature
/**app.js**/
/**
* 验证服务器的有效性
*/
// 引入express模块
const express = require('express');
//引入sha1模块
const sha1 = require('sha1');
// 引入auth模块
const auth = require('./wechat/auth');
// 引入wechat模块
const Wechat = require('./wechat/wechat');
// 引入config模块
const { url } = require('./config/index');
// 创建app应用对象
const app = express();
// 配置模板资源目录
app.set('views', './views');
// 配置模板引擎
app.set('view engine', "ejs");
// 创建实例对象
const wechatApi = new Wechat();
// 页面路由
app.get('/search', async(req, res) => {
// 生成随机字符串
const noncestr = Math.random().split('.')[1];
// 获取时间戳
const timestamp = Date.now();
// 获取票据
const { ticket } = await wechatApi.fetchTicket();
// 组合参与签名的四个参数
const arr = [`jsapi_ticket=${ticket}`,
`noncestr=${noncestr}`,
`timestamp=${timestamp}`,
`url=${url}/search`
];
// 字典序排序
const str = arr.sort().join('&');
// sha1加密
const signature = sha1(str);
// 渲染页面,将渲染好的页面返回给用户
res.render('search', { signature, noncestr, timestamp });
})
// 能接受处理所有消息
app.use(auth())
// 监听端口号
app.listen(3000, () => console.log('服务器启动成功了~'));
JSSDK使用步骤
-
步骤一:绑定域名
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 -
步骤二:引入JS文件
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.6.0.js
-
步骤三:通过config接口注入权限验证配置
* wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
- 步骤四:通过ready接口处理成功验证
config信息验证成功会执行ready函数
wx.ready(function(){
});
- 步骤五:通过error接口处理失败验证
config信息验证失败会执行error函数
wx.error(function(res){
});
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>Search</title>
</head>
<body>
<h1>这是一个搜索页面</h1>
<script type="text/javaScript" src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>
/**
* 1.绑定域名
* -在接口测试号页面上填写js安全域名接口
* 2.引入JS文件
* -http://res2.wx.qq.com/open/js/jweixin-1.6.0.js
* 3.通过config接口注入权限验证配置
**/
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wx3495be16b0046818', // 必填,公众号的唯一标识
timestamp: '<%= timestamp %>', // 必填,生成签名的时间戳
nonceStr: '<%= noncestr %>', // 必填,生成签名的随机串
signature: '<%= signature %>', // 必填,签名
jsApiList: ["onMenuShareQQ", "onMenuShareQZone", "startRecord", "stopRecord", "translateVoice"] // 必填,需要使用的JS接口列表
});
wx.ready(function() {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
wx.error(function(res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
</script>
</body>
</html>