整体思维导图
详细步骤
1、申请公众号,完成微信公众平台接口测试申请。
2、填写服务器配置
3、验证服务器的有效性
3、模块化
4、获取access_token
5、获取用户消息
6、处理用户发送过来的信息
7、通过解析用户发送来的消息,进行相应的回复
在本地搭建好服务器,并利用ngrok输入指令ngrok http 3000(相应的端口号)生成对应的IP,将本地生成的服务器端口号生成对应的外网跨域访问的网址。在服务器中配置相应的信息。
配置信息的代码实现
const config = {
appID:"wx6077e5089a73e15d",
appsecret:"4408047fc4a1337366a94bdaf63cee57",
token:"lemonclass0412"
}
说明:appID 是第三方用户唯一凭证
appsecret 是 第三方用户唯一凭证密钥
- 首先将参数签名加密的三个参数timestamp、nonce、token按照字典序进行排序
- 将排序后的参数拼接成一个字符串,进行sha1加密
- 将加密后的字符串与signature进行对比
如果匹配,说明验证成功的,返回echostr给微信服务器. 如果不匹配,说明验证失败的,返回''给微信服务器
代码实现
const {signature,echostr,timestamp,nonce} =req.query;
//1、将timestamp、nonce、token排序、加密
const sha1Str = sha1([timestamp,nonce,token].sort().join(''))
//2、匹配,返回echostr,不匹配,返回''
if(sha1Str === signature)res.send(echostr)
else res.send("");
1、暴露模块--->module.exports
2、暴露的模块,要有返回值,利用return
3、异步的函数,要结合async...await使用
access_token特点:
accesstoken是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用accesstoken。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。accesstoken的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的accesstoken失效。
获取方法:
1、上来判断本地有没有access_token (readAccessToken) 如果有 判断access_token有无过期,(isValidAccessToken) 如果没有过期,直接使用 如果过期了,再次请求获取access_token(getAccessToken),保存下来(saveAccessToken) 如果没有 发送请求获取access_token(getAccessToken),保存下来(至少1小时55分钟)(saveAccessToken)
2、官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
思路实现
将四个方法定义成一个类Wechat,然后通过构造实例对象,调用Wechat的方法。
const wechatAPI = new Wechat;
代码实现
getAccessToken() {
//https请求方式: GET
//https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;
return new Promise((resolve, reject) => {
// rq的本质是一个promise对象
rq({method: "GET", url, json: true})
.then(res => {
//相应的数据
console.log(res);
//设置凭据的过期时间
res.expires_in = Date.now() + (res.expires_in - 300) * 1000;
resolve(res);
})
.catch(err =>reject("getAccessToken方法出了错误:"+err));
})
}
const {writeFile,readFile} = require("fs");
saveAccessToken(data){
return new Promise((resolve, reject) => {
//微信会返回下述JSON数据包给公众号,要转一下js格式
data = JSON.stringify(data);
//为了方便读取,将获取到的数据保存到accessToken.txt文件中
writeFile("accessToken.txt",data,err=>{
if(!err){
resolve(data)
}else{
reject("saveAccessToken方法出错了"+err)
}
})
})
}
readAccessToken函数
const {readFile} = require("fs");
fs.readFile语法:
fs.readFile('accessToken.txt', (err, data) => { if (!err) console.log(data); else throw err; });
readAccessToken(){
return new Promise((resolve, reject) => {
readFile("accessToken.txt",(err,data)=>{
if (!err){
//返回时将读取到的数据再转化为json数据
resolve(JSON.parse(data.toString()))
} else{
reject("readAccessToken方法出错了"+err)
}
})
})
}
isValidAccessToken函数
isValidAccessToken(data){
//优化:如果没有以上的属性,直接返回,不用再走下面逻辑
if(!data || !data.access_token || !data.expires_in) return false;
//如果没有过期,返回true,过期了,返回false
return Date.now()< data.expires_in;
}
fetchAccessToken函数:获取accessToken,定义再wechat类中
fetchAccessToken(){
//性能优化
if(this.access_token && this.expires_in && this.isValidAccessToken(data)){
//如果没有过期,且有这几个属性,直接返回出去,不用重新获取
return Promise.resolve({access_token:this.access_token,expires_in:this.expires_in});
}
return this.readAccessToken()
.then(async res=>{ //如果有
if(this.isValidAccessToken(res)){
//没有过期
return Promise.resolve(res)
}else{ //过期了
const data = await this.getAccessToken();
await this.saveAccessToken(data);
return Promise.resolve(data)
}
})
.catch(async ()=>{ //如果 没有
const data = this.getAccessToken();
await this.saveAccessToken(data);
return Promise.resolve(data)
})
/*以上返回的结果为一个promise对象,但是返回的结果都是成功的回调。这样还可以利用.then方法,
这一步调用.then方法是为了优化----将两个属性挂载再this的这个类上,外界就可以直接进行判断,见上面 性能优化 部分*/
.then(res=>{
//将access_token和expires_in挂载到this上
this.access_token = res.access_token;
this.expires_in = res.expires_in;
//给函数一个整体的返回值
return Promise.resolve(res);
})
}
调用
(async ()=>{
const wechatAPI = new Wechat;
const data = await wechatAPI.fetchAccessToken();
})();
说明:
1、微信会发送两个消息:
GET:验证服务器的有消息(我们在服务器验证模块中已经验证过了) POST:接受用户发送过来的消息(以下代码处理POST请求): else if(req.method === "POST"){ console.log(req.query);}
2、req.query返回出来的数据是一个xml格式的:
1532582999
6582393859576522806
3、所以要将其转化为一个js格式的对象。转化步骤:
先利用xml2js将 xml ---带有xml属性的对象
xml2js转化语法:
var parseString = require('xml2js').parseString;
var xml = "Hello xml2js! "
parseString(xml, function (err, result) {
console.dir(result);
});
const {parseString} = require("xml2js");
将xml属性的对象遍历,转化为js一般对象
4、定义三个专门用来转化的方法:
图示:
代码实现
1、getuserDataAsyc()
getuserDataAsyc(req) {
return new Promise((resolve, reject) => {
let userData = '';
req
//传送数据,不断累加
.on('data', data => {
userData += data;
})
//数据传送完毕后,将结果返回出去
.on('end', () => {
resolve(userData);
})
})
}
说明:此时userData的返回值形式为:xml格式,见上。
2、 parserXMLDataAsyc()
前提:引入const {parseString} = require("xml2js")
parserXMLDataAsyc(xmlData) {
return new Promise((resolve, reject) => {
parseString(xmlData, {trim: true}, function (err, result) {
if (!err) {
resolve(result);
} else {
reject("parserXMLData方法出错了" + err);
}
});
})
}
说明:此时result返回的数据格式:
{ xml:
{ ToUserName: [ 'gh_406d257c2cc2' ],
FromUserName: [ 'owOA61F8J8xzZv1EpmUcsAcalXPI' ],
CreateTime: [ '1532519920' ],
MsgType: [ 'text' ],
Content: [ '2222' ],
MsgId: [ '6582122937334454364' ] } }
3、formatData()
formatData(newData) {
const jsData = newData.xml;
for (let item in jsData) {
let value = jsData[item];
//防止不是数组获取非法数据
if(Array.isArray(value) && value.length)
jsData[item] = value[0];
}
return jsData;
}
说明:jsData数据格式:
{ ToUserName: 'gh_406d257c2cc2',
FromUserName: 'owOA61F8J8xzZv1EpmUcsAcalXPI',
CreateTime: '1532583006',
MsgType: 'text',
Content: '2',
MsgId: '6582393889641293880' }
说明:
1、一旦遇到以下情况,微信都会在公众号会话中,
向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”: 1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
2、通过if判断MsgType类型,利用Content进行内容回复
const replyMessage = '
' + ' '; //返回相应给服务器 res.send(replyMessage);' + ' ' + ' '+Date.now()+' ' + '' + ' ' + '