一.申请微信公众号账号;官网网址
二.开发文档;网址
三.接口测试号申请并测试连接
url:要被外网访问,本地服务器,搭建ngrok 即可。当然有钱的话,可以买一个;ngrok每次运行都不一样。指令 ngrok http 端口号。内网穿透,有时效性。
token:微信消息加密的参数
测试是否连接,有什么参数
配置本地服务器
index.js
// 引入express模块
const express = require('express')
// app应用对象
const app = express();
app.use((req,res,next) => {
// 微信服务器自动开发者服务器是哪个
//测试号
console.log(req.query)
})
//监听端口号
app.listen(4000,()=>console.log("服务器启用"))
确保本地服务器打开, 点击微信公众号刚刚配置的地方–提交按钮
// { signature: '35eab388c24c9d0deafa3bd12513d6f0cac7ac03', //微信加密签名
// echostr: '4361760528703928317', // 微信的随机字符串
// timestamp: '1555216285', // 微信的发生时间
// nonce: '316998338' } // 随机数
四.计算出signature微信加密签名和微信传递过来的参数值是否一样。一样就说明是微信服务器传来的信息
a.将参与微信加密的三个参数(timestamp,nonce,token),组合在一起,安装字典排序并组合在一起形成一个数组;
b.将数组所有参数拼接一个字符串,进行sha1加密;
c.如果一样,就消息来自微信服务器,返回echostr给微信服务器;如果不一样,就报个错
index.js
// 引入express模块
const express = require('express')
// app应用对象
const app = express();
const sha1= require('sha1')
const config = {
token:'sd*********dssda22345',
appID:'wxf*******0cf5a8b',
appsecret:'9d1fa6c8eee80c5**********cf8b53'
}
app.use((req,res,next) => {
// console.log(req.query)
// { signature: '35eab388c24c9d0deafa3bd12513d6f0cac7ac03', //微信加密签名
// echostr: '4361760528703928317', // 微信的随机字符串
// timestamp: '1555216285', // 微信的发生时间
// nonce: '316998338' } // 随机数
const {signature,echostr,timestamp,nonce} = req.query;
const {token} =config;
const arr = [timestamp , nonce , token]
const arrSort = arr.sort();
console.log(arrSort)
const str = arrSort.join('')
const sha1Str = sha1(str)
console.log(sha1Str)
if(sha1Str === signature){
res.send(echostr)
}else{
res.send('error')
}
})
//监听端口号
app.listen(4000,()=>console.log("服务器启用"))
微信公众号提示配置成功即可
五.获取access_token
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效,还有请求次数
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
设计思路:
1.首次本地没有,发送请求获取access_token,保存(本地)
2.第二次,先去本地读取,判断是否过期。没有直接使用;过期的话,重新请求。过期时间判断。当前时间+7200,之前就行
//获取access_token 微信调用接口的唯一凭据
// 需要的url参数
const {appID , appsecret} = require('../config')
const rp = require('request-promise-native')
const {readFile , writeFile} = require('fs')
class Wechat {
constructor(){
}
//获取access_token
getAccessToken(){
const url =`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;
// 发送请求
// 服务器端不能ajax 使用 request request-promise-native,返回的是promise对象
return new Promise((resolve,reject) => {
rp({method:'get',url,json:true})
.then(res =>{
console.log(res)
res.expires_in = Date() + (res.expires_in -300)*1000;
resolve(res)
// { access_token:
// '20_ZCrQnrFmz20x21BwfYy9b2fvJvqQPvQZ5NQMeiy5LZwRfKXgtgArpNFl-WnGDT0QvvTI94CjJ459Fy8IfFn4g00tskZ_Z76xgEDe8fXYnGXd9PwkZhQJ0_mN8FE_sVPVPz1x7OFy0RRPbyfMVXGaAJAIKT',
// expires_in: 7200 }
})
.catch(err =>{
console.log(err);
reject('getAccessToken errer')
})
})
}
//保存access_token
/**
* @param accessToken
*/
saveAccessToken(accessToken){
//将对象转化成json字符串
accessToken = JSON.stringify(accessToken);
return new Promise((resolve,reject)=>{
writeFile('./accessToken.txt',accessToken,err => {
if(!err){
console.log("file write success");
resolve();
}else{
console.log(err);
reject();
}
})
})
}
// 读取access_token
readAccessToken(){
return new Promise((resolve,reject)=>{
readFile('./accessToken.txt',(err,data) => {
if(!err){
console.log("file read success");
data = JSON.parse(data)
resolve(data);
}else{
console.log(err);
reject();
}
})
})
}
// 是否有效
isValidAccessToken(data){
if(!data&&data.access_token&&!data.expires_in){
return false;
}
return data.expires_in > data.now()
}
// 获取没有过期的access_token
/**
* @param
*/
fetchAccessToken(){
// console.log(this.access_token)
if(this.access_token && this.expires_in && this.isValidAccessToken(this)) {
// 保存过access_token
return Promise.resolve({
access_token:this.access_token,
expires_in:this.expires_in,
})
}
return this.readAccessToken()
.then( async res => {
// 本地有文件
if(this.isValidAccessToken(res)){
resolve(res)
}else{
// 过期
const res= await this.getAccessToken();
await this.saveAccessToken(res);
return Promise.resolve(res)
}
})
.catch( async err => {
const res= await this.getAccessToken();
await this.saveAccessToken(res);
return Promise.resolve(res)
})
.then(res => {
this.access_token = res.access_token;
this.expires_in = res.expires_in;
return Promise.resolve(res);
})
}
}
// 测试文件是否成功新建类,调用fetchAccessToken()方法
六.接受回复用户信息
数据信息流式数据 xml格式文本
自动回复
注意点
1.5秒内自动回复
2.开发者数据格式
until.js
const { parseString } = require('xml2js')
module.exports = {
// 流式信息拼接
getUserDataAsync(req) {
return new Promise((resolve, reject) => {
let xmlData = '';
req.on('data', data => {
console.log(data)
xmlData += data.toString();
})
.on('end', () => {
resolve(xmlData)
})
})
},
//xml to js
parseXMLAsync(xmlData){
return new Promise((resolve,reject)=>{
parseString(xmlData,{trim:true},(err,data)=>{
if(!err){
resolve(data)
}else{
reject("parseXMLAsync errer:" +err)
}
})
})
},
// 字段将数组改成对应值
formatMessage(jsData){
let message = {};
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;
}
}
auth.js
// 类似中间件,封装
const config = require('../config')
const sha1 = require('sha1')
const { getUserDataAsync , parseXMLAsync ,formatMessage} = require('../utils/tool.js')
module.exports = () => {
return async (req, res, next) => {
// console.log(req.query)
// { signature: '35eab388**************6f0cac7ac03', //微信加密签名
// echostr: '436176052*******8317', // 微信的随机字符串
// timestamp: '155****16285', // 微信的发生时间
// nonce: '316998338' } // 随机数
const { signature, echostr, timestamp, nonce } = req.query;
const { token } = config;
const arr = [timestamp, nonce, token]
const arrSort = arr.sort();
const str = arrSort.join('')
const sha1Str = sha1(str)
// const sha1Str = sha1([timestamp,nonce,token].sort().join(''))
// console.log(sha1Str)
/**
* 微信服务器发送get post两种类型的开发者给服务器
*/
// { signature: 'd25a9573617f**********7e87d4062475a',
// timestamp: '1558****176',
// nonce: '8018***15',
// openid: 'o*************w3nObg' }
if (req.method === "GET") {
if (sha1Str === signature) {
res.send(echostr)
} else {
res.send('wechat server error')
}
} else if (req.method === "POST") {
if (sha1Str !== signature) {
res.send("errer")
} else {
console.log(req.query)
}
const xmlData = await getUserDataAsync(req);
// buffer数据
console.log(xmlData)
// < xml >
// <ToUserName><![CDATA[gh_d772f38c5ba2]]></ToUserName> //开发者id
// <FromUserName><![CDATA[o1Q3x03Pn0eLpM8wlwFHn7w3nObg]]></FromUserName> //用户openid
// <CreateTime>1555231639</CreateTime> //发送的时间
// <MsgType><![CDATA[text]]></MsgType> //信息类型
// <Content><![CDATA[123]]></Content> //信息内容
// <MsgId>22265565114514992</MsgId> //消息id 微信服务器保存三天
// </xml >
// xml对象转为js对象
const jsData = await parseXMLAsync (xmlData)
console.log(jsData)
/**
* 格式化
*/
var message = formatMessage(jsData)
// 用户信息是否是文本信息
let content = '您在说什么,我听不清';
if(message.MsgType == 'text'){
if(message.Content === '0'){
content = '对不起,我是个警察';
}else if (message.Content === '1'){
content = '以前没得选';
}else if (message.Content === '2'){
content = '现在我想做个好人';
}else if (message.Content.match('讨厌')){
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>`
//返回给服务器
console.log(replyMessage)
res.send(replyMessage);
//请求没响应的话是三次
// res.end('')
} else {
res.send("errer")
}
}
}