1.进入微信官方文档(公众号);html
2.接口测试号申请(微信公众号的注册有必定门槛) node
3.测试帐号管理 git
1.编写启动项文件github
// app-1.js
/** * @建立码农: pr * @建立日期: 2019-12-26 19:47:02 * @最近更新: pr * @更新时间: 2019-12-26 19:47:02 * @文件描述: 启动项文件 */
const Koa = require('koa');
const sha1 = require('sha1');
// 配置文件
const config = {
wechat: {
appID: 'wxb284d7a53fa2da16',
appSecret: '24af419d8f6c997b5582fd46eafb377c',
token: 'ruizhengyunpr840690384'
}
};
const PORT = 1989;
const app = new Koa();
// 中间件
app.use(function*(next) {
console.log('query', this.query);
const token = config.wechat.token;
const signature = this.query.signature;
const nonce = this.query.nonce;
const timestamp = this.query.timestamp;
const echostr = this.query.echostr;
const str = [token, timestamp, nonce].sort().join('');
const sha = sha1(str);
if (sha === signature) {
this.body = echostr + '';
} else {
this.body = 'wrong';
}
});
app.listen(PORT);
console.log(`正在监听:${PORT}`);
复制代码
2.执行启动项编程
node --harmony app-1.js
复制代码
3.ngrok 启动json
./ngrok http 1989
复制代码
4.拷贝 ngrok 产生的 https 地址到微信公众号 > 接口配置信息 > URL 中,而后点击 提交
,结果以下api
微信 - token、timestamp、nonce - 字典排序 - sha1 加密 - (r === signature) - echostrbash
1.编写启动项文件微信
// app-2.js
/** * @建立码农: pr * @建立日期: 2019-12-27 19:47:02 * @最近更新: pr * @更新时间: 2019-12-27 19:47:02 * @文件描述: 启动项文件,版本二 */
const Koa = require('koa');
const weChatMiddleWare = require('./app-2/weChat');
// 配置文件
const config = {
wechat: {
appID: 'wxb284d7a53fa2da16',
appSecret: '24af419d8f6c997b5582fd46eafb377c',
token: 'ruizhengyunpr840690384'
}
};
const PORT = 1989;
const app = new Koa();
// 中间件
app.use(weChatMiddleWare(config.wechat));
app.listen(PORT);
console.log(`正在监听:${PORT}`);
复制代码
2.编写中间件网络
// app-2/weChat.js
/** * @建立码农: pr * @建立日期: 2019-12-27 19:47:02 * @最近更新: pr * @更新时间: 2019-12-27 19:47:02 * @文件描述: 公众号校验中间件 */
const sha1 = require('sha1');
// 中间件
module.exports = function(opts) {
return function*(next) {
console.log('query', this.query);
const token = opts.token;
const signature = this.query.signature;
const nonce = this.query.nonce;
const timestamp = this.query.timestamp;
const echostr = this.query.echostr;
const str = [token, timestamp, nonce].sort().join('');
const sha = sha1(str);
if (sha === signature) {
this.body = echostr + '';
} else {
this.body = 'wrong';
}
};
};
复制代码
3.执行启动项、ngrok 启动、拷贝 ngrok 产生的 https 地址
1.启动项改写,这块要建个 /app-3/config/access-token.txt
// app-3.js
/** * @建立码农: pr * @建立日期: 2019-12-28 19:47:02 * @最近更新: pr * @更新时间: 2019-12-28 19:47:02 * @文件描述: 启动项文件,版本三 */
const Koa = require('koa');
const path = require('path');
const util = require('./app-3/util');
const weChatMiddleWare = require('./app-3/weChat');
const wechat_file = path.join(__dirname, './app-3/config/access-token.txt');
// 配置文件
const config = {
wechat: {
appID: 'wxb284d7a53fa2da16',
appSecret: '24af419d8f6c997b5582fd46eafb377c',
token: 'ruizhengyunpr840690384',
getAccessToken: () => {
return util.readFileAsync(wechat_file);
},
saveAccessToken: data => {
data = JSON.stringify(data);
return util.writeFileAsync(wechat_file, data);
}
}
};
const PORT = 1989;
const app = new Koa();
// 中间件
app.use(weChatMiddleWare(config.wechat));
app.listen(PORT);
console.log(`正在监听:${PORT}`);
复制代码
2.编写中间件
// app-3/weChat.js
/** * @建立码农: pr * @建立日期: 2019-12-28 19:47:02 * @最近更新: pr * @更新时间: 2019-12-28 19:47:02 * @文件描述: 公众号校验中间件 */
// 依赖包引入
const sha1 = require('sha1');
const Promise = require('bluebird');
const request = Promise.promisify(require('request'));
// 参数定义
const prefix = 'https://api.weixin.qq.com/cgi-bin/';
const api = {
accessToken: `${prefix}token?grant_type=client_credential`
};
// 判断 access_token 是否过时
function AccessTokenInfo(opts) {
const that = this;
this.appID = opts.appID;
this.appSecret = opts.appSecret;
this.getAccessToken = opts.getAccessToken;
this.saveAccessToken = opts.saveAccessToken;
that
.getAccessToken()
.then(function(data) {
try {
data = JSON.parse(data);
} catch (e) {
// 不合法就从新更新 access_token
return that.updateAccessToken();
}
if (that.isValidAccessToken(data)) {
// 判断是否有效
// 取到合法 access_token
that.access_token = data.access_token;
that.expires_in = data.expires_in;
that.saveAccessToken(data);
} else {
// 不合法就从新更新 access_token
return that.updateAccessToken();
}
})
}
// 验证 access_token 是否有效
AccessTokenInfo.prototype.isValidAccessToken = function(data) {
if (!data || !data.access_token || !data.expires_in) {
return false;
}
const now = new Date().getTime();
if (now < data.expires_in) {
return true;
}
return false;
};
// 更新 access_token
AccessTokenInfo.prototype.updateAccessToken = function() {
const url = `${api.accessToken}&appid=${this.appID}&secret=${this.appSecret}`;
console.log('url', url);
return new Promise((resolve, reject) => {
request({
url,
json: true
}).then(res => {
const data = res.body;
console.log('data', data);
const now = new Date().getTime();
// 缩短 20 秒(算上网络请求时间)
const expires_in = now + (data.expires_in - 20) * 1000;
data.expires_in = expires_in;
resolve(data);
});
});
};
// 中间件
module.exports = function(opts) {
const accessTokenInfo = new AccessTokenInfo(opts);
return function*(next) {
console.log('query', this.query);
const token = opts.token;
const signature = this.query.signature;
const nonce = this.query.nonce;
const timestamp = this.query.timestamp;
const echostr = this.query.echostr;
const str = [token, timestamp, nonce].sort().join('');
const sha = sha1(str);
if (sha === signature) {
this.body = echostr + '';
} else {
this.body = 'wrong';
}
};
};
复制代码
3.编写工具库
/** * @建立码农: 芮正云 16396@etransfar.com * @建立日期: 2019-12-28 16:58:04 * @最近更新: 芮正云 16396@etransfar.com * @更新时间: 2019-12-28 16:58:04 * @文件描述: 工具库 */
const fs = require('fs');
const Promise = require('bluebird');
// access_token 读操做
exports.readFileAsync = (fpath, encoding) => {
return new Promise((resolve, reject) => {
fs.readFile(fpath, encoding, function(err, content) {
if (err) {
reject(err);
} else {
resolve(content);
}
});
});
};
// access_token 写操做
exports.writeFileAsync = (fpath, content) => {
return new Promise((resolve, reject) => {
fs.writeFile(fpath, content, function(err, content) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
复制代码
4.执行启动项、ngrok 启动、拷贝 ngrok 产生的 https 地址
5.你会发现 /app-3/config/access-token.txt
文件生成 access_token 信息内容
关注公众号后
1.启动项
// app-4.js
/** * @建立码农: pr * @建立日期: 2019-12-29 19:47:02 * @最近更新: pr * @更新时间: 2019-12-29 19:47:02 * @文件描述: 启动项文件,版本四 */
const Koa = require('koa');
const path = require('path');
const fileAsync = require('./app-4/util/fileAsync');
const weChatMiddleWare = require('./app-4/middleWare/weChat');
const wechat_file = path.join(__dirname, './app-4/config/access-token.txt');
// 配置文件
const config = {
wechat: {
appID: 'wxb284d7a53fa2da16',
appSecret: '24af419d8f6c997b5582fd46eafb377c',
token: 'ruizhengyunpr840690384',
getAccessToken: () => {
// 读取文件
return fileAsync.readFileAsync(wechat_file);
},
saveAccessToken: data => {
// 写入文件
return fileAsync.writeFileAsync(wechat_file, JSON.stringify(data));
}
}
};
const PORT = 1989;
const app = new Koa();
// 中间件
app.use(weChatMiddleWare(config.wechat));
app.listen(PORT);
console.log(`正在监听:${PORT}`);
复制代码
2.编写中间件
// app-4/middleWare/weChat.js
/** * @建立码农: pr * @建立日期: 2019-12-29 19:47:02 * @最近更新: pr * @更新时间: 2019-12-29 19:47:02 * @文件描述: 公众号校验中间件 */
// 依赖包引入
const sha1 = require('sha1');
const rawBody = require('raw-body');
const fileAsync = require('../util/fileAsync');
const AccessTokenInfo = require('../util/AccessTokenInfo');
// 中间件
module.exports = function(opts) {
const accessTokenInfo = new AccessTokenInfo(opts);
return function*(next) {
console.log('query', this.query);
const token = opts.token;
const signature = this.query.signature;
const nonce = this.query.nonce;
const timestamp = this.query.timestamp;
const echostr = this.query.echostr;
const str = [token, timestamp, nonce].sort().join('');
const sha = sha1(str);
/** * GET 验证开发者身份 * POST */
if (sha !== signature) {
this.body = '❌';
return false;
}
if (this.method === 'GET') {
this.body = echostr + '';
} else if (this.method === 'POST') {
// 依赖包 raw-body 能够把 this 上的 request 对象(http 模块中的 request 对象),拼写它的数据,最终拿到一个 buffer 的 XML
const data = yield rawBody(this.req, {
length: this.length,
limit: '1mb',
encoding: this.charset
});
const content = yield fileAsync.parseXMLAsync(data);
const message = fileAsync.formatMessage(content.xml);
if (message.MsgType === 'event') {
if (message.Event === 'subscribe') {
const now = new Date().getTime();
this.status = 200;
this.type = 'application/xml';
// 文本模板,后面能够把这块业务抽离处理
this.body = `<xml> <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName> <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName> <CreateTime>${now}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[${`你好,${message.FromUserName},欢迎来到本公众号。`}]]></Content> <MsgId>1234567890123456</MsgId> </xml>`;
console.log('message', this.body);
return;
}
}
console.log('message', message);
}
};
};
复制代码
3.编写工具库
// app-4/uitl/fileAsync.js
/** * @建立码农: pr * @建立日期: 2019-12-29 16:58:04 * @最近更新: pr * @更新时间: 2019-12-29 16:58:04 * @文件描述: 工具库 */
const fs = require('fs');
const Promise = require('bluebird');
const xml2js = require('xml2js');
// access_token 读操做
exports.readFileAsync = (fpath, encoding) => {
return new Promise((resolve, reject) => {
fs.readFile(fpath, encoding, function(err, content) {
if (err) {
reject(err);
} else {
resolve(content);
}
});
});
};
// access_token 写操做
exports.writeFileAsync = (fpath, content) => {
return new Promise((resolve, reject) => {
fs.writeFile(fpath, content, function(err, content) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
// 格式化 xml 消息
function formatMessage(data) {
const message = {};
if (typeof data === 'object') {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const item = data[key];
const len = item.length;
if (!(item instanceof Array) || len === 0) {
continue;
}
if (len === 1) {
const value = item[0];
if (typeof value === 'object') {
message[key] = formatMessage(value);
} else {
message[key] = (value || '').trim();
}
} else {
message[key] = [];
for (let i = 0; i < len; i++) {
message[key].push(formatMessage(item[i]));
}
}
}
}
return message;
}
exports.formatMessage = formatMessage;
exports.parseXMLAsync = function(xml) {
return new Promise(function(resolve, reject) {
xml2js.parseString(xml, { trim: true }, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};
复制代码
// app-4/util/AccessTokenInfo.js
/** * @建立码农: pr * @建立日期: 2019-12-29 19:47:02 * @最近更新: pr * @更新时间: 2019-12-29 19:47:02 * @文件描述: 公众号校验中间件 */
// 依赖包引入
const Promise = require('bluebird');
const request = Promise.promisify(require('request'));
// 参数定义
const prefix = 'https://api.weixin.qq.com/cgi-bin/';
const api = {
accessToken: `${prefix}token?grant_type=client_credential`
};
// 判断 access_token 是否过时
function AccessTokenInfo(opts) {
const that = this;
this.appID = opts.appID;
this.appSecret = opts.appSecret;
this.getAccessToken = opts.getAccessToken;
this.saveAccessToken = opts.saveAccessToken;
that.getAccessToken().then(function(data) {
try {
data = JSON.parse(data);
} catch (e) {
// 不合法就从新更新 access_token
return that.updateAccessToken();
}
if (that.isValidAccessToken(data)) {
//判断是否有效
// Promise.resolve(data);
// 取到合法 access_token
that.access_token = data.access_token;
that.expires_in = data.expires_in;
that.saveAccessToken(data);
} else {
// 不合法就从新更新 access_token
return that.updateAccessToken();
}
});
// .then(function(data) {
// // 取到合法 access_token
// that.access_token = data.access_token;
// that.expires_in = data.expires_in;
// that.saveAccessToken(data);
// });
}
// 验证 access_token 是否有效
AccessTokenInfo.prototype.isValidAccessToken = function(data) {
if (!data || !data.access_token || !data.expires_in) {
return false;
}
const now = new Date().getTime();
if (now < data.expires_in) {
return true;
}
return false;
};
// 更新 access_token
AccessTokenInfo.prototype.updateAccessToken = function() {
const url = `${api.accessToken}&appid=${this.appID}&secret=${this.appSecret}`;
console.log('url', url);
return new Promise((resolve, reject) => {
request({
url,
json: true
}).then(res => {
const data = res.body;
const now = new Date().getTime();
// 缩短 20 秒(算上网络请求时间)
const expires_in = now + (data.expires_in - 20) * 1000;
data.expires_in = expires_in;
resolve(data);
});
});
};
module.exports = AccessTokenInfo;
复制代码
5.执行启动项、ngrok 启动、拷贝 ngrok 产生的 https 地址
6.扫描关注公众号