Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine #39

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
language: node_js
node_js:
- "8"
- "9"
script: make test-coveralls
227 changes: 128 additions & 99 deletions lib/wechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,20 @@ var tpl = ['<xml>',
*/
var compiled = ejs.compile(tpl);

var wrapTpl = '<xml>' +
'<Encrypt><![CDATA[<%-encrypt%>]]></Encrypt>' +
'<MsgSignature><![CDATA[<%-signature%>]]></MsgSignature>' +
'<TimeStamp><%-timestamp%></TimeStamp>' +
'<Nonce><![CDATA[<%-nonce%>]]></Nonce>' +
'</xml>';

var encryptWrap = ejs.compile(wrapTpl);
var encryptWrap = (function () {
var tpl = '<xml>' +
'<Encrypt><![CDATA[${locals.encrypt}]]></Encrypt>' +
'<MsgSignature><![CDATA[${locals.signature}]]></MsgSignature>' +
'<TimeStamp>${locals.timestamp}</TimeStamp>' +
'<Nonce><![CDATA[${locals.nonce}]]></Nonce>' +
'</xml>';
return new Function ('locals', 'return `' + tpl + '`;');
}());

function reply2CustomerService (fromUsername, toUsername, kfAccount) {
var info = {};
info.msgType = 'transfer_customer_service';
info.createTime = new Date().getTime();
info.createTime = Date.now();
info.toUsername = toUsername;
info.fromUsername = fromUsername;
info.content = {};
Expand Down Expand Up @@ -155,7 +156,7 @@ function reply (content, fromUsername, toUsername) {
}
}
info.msgType = type;
info.createTime = new Date().getTime();
info.createTime = Date.now();
info.toUsername = toUsername;
info.fromUsername = fromUsername;
return compiled(info);
Expand All @@ -169,124 +170,152 @@ class Wechat {
this.token = config.token;
this.appid = config.appid || '';
this.encodingAESKey = config.encodingAESKey || '';
this.cryptor = new WXBizMsgCrypt(this.token, this.encodingAESKey, this.appid);
} else {
throw new TypeError('please check your config');
}
}

middleware(handle) {

if (this.encodingAESKey) {
this.cryptor = new WXBizMsgCrypt(this.token, this.encodingAESKey, this.appid);
}

get() {
return async (ctx, next) => {
const method = ctx.method;
if (method !== 'GET') {
throw new Error('The wechat.get handle only support GET method');
}

const query = ctx.query;
const {timestamp, nonce, echostr} = query;

// 加密模式
const encrypted = !!(query.encrypt_type && query.encrypt_type === 'aes' && query.msg_signature);
const timestamp = query.timestamp;
const nonce = query.nonce;
const echostr = query.echostr;
const method = ctx.method;

const TOKEN = ctx.wx_token || this.token;
const CRYPTOR = ctx.wx_cryptor || this.cryptor;

if (method === 'GET') {
var valid = false;
var valid = false;
if (encrypted) {
if (!CRYPTOR) {
throw new Error('In encrypt mode, must configure \'encodingAESKey\' and \'appid\'');
}
var signature = query.msg_signature;
valid = signature === CRYPTOR.getSignature(timestamp, nonce, echostr);
} else {
// 校验
valid = query.signature === getSignature(timestamp, nonce, TOKEN);
}

if (!valid) {
ctx.status = 401;
ctx.body = 'Invalid signature';
} else {
if (encrypted) {
var signature = query.msg_signature;
valid = signature === CRYPTOR.getSignature(timestamp, nonce, echostr);
var decrypted = CRYPTOR.decrypt(echostr);
// TODO 检查appId的正确性
ctx.body = decrypted.message;
} else {
// 校验
valid = query.signature === getSignature(timestamp, nonce, TOKEN);
ctx.body = echostr;
}
}
};
}

post(handle) {
return async (ctx, next) => {
const method = ctx.method;
if (method !== 'POST') {
throw new Error('The wechat.post handle only support POST method');
}

const query = ctx.query;
const {timestamp, nonce} = query;

const TOKEN = ctx.wx_token || this.token;
const CRYPTOR = ctx.wx_cryptor || this.cryptor;

// 加密模式
const encrypted = !!(query.encrypt_type && query.encrypt_type === 'aes' && query.msg_signature);

if (!valid) {
if (!encrypted) {
// 校验
if (query.signature !== getSignature(timestamp, nonce, TOKEN)) {
ctx.status = 401;
ctx.body = 'Invalid signature';
} else {
if (encrypted) {
var decrypted = CRYPTOR.decrypt(echostr);
// TODO 检查appId的正确性
ctx.body = decrypted.message;
} else {
ctx.body = echostr;
}
}
} else if (method === 'POST') {
if (!encrypted) {
// 校验
if (query.signature !== getSignature(timestamp, nonce, TOKEN)) {
ctx.status = 401;
ctx.body = 'Invalid signature';
return;
}
return;
}
}

var xml;
if (ctx.request.body && typeof ctx.request.body === 'string') {
xml = ctx.request.body;
} else {
// 取原始数据
xml = await getRawBody(ctx.req, {
length: ctx.request.length,
limit: '1mb',
encoding: ctx.request.charset || 'utf-8'
});
}
var xml;
if (ctx.request.body && typeof ctx.request.body === 'string') {
xml = ctx.request.body;
} else {
// 取原始数据
xml = await getRawBody(ctx.req, {
length: ctx.request.length,
limit: '1mb',
encoding: ctx.request.charset || 'utf-8'
});
}

// 保存原始xml
ctx.weixin_xml = xml;
// 解析xml
var result = await parseXML(xml);
var formatted = formatMessage(result.xml);
if (encrypted) {
var encryptMessage = formatted.Encrypt;
if (query.msg_signature !== CRYPTOR.getSignature(timestamp, nonce, encryptMessage)) {
ctx.status = 401;
ctx.body = 'Invalid signature';
return;
}
var decryptedXML = CRYPTOR.decrypt(encryptMessage);
var messageWrapXml = decryptedXML.message;
if (messageWrapXml === '') {
ctx.status = 401;
ctx.body = 'Invalid signature';
return;
}
var decodedXML = await parseXML(messageWrapXml);
formatted = formatMessage(decodedXML.xml);
// 保存原始xml
ctx.weixin_xml = xml;
// 解析xml
var result = await parseXML(xml);
var formatted = formatMessage(result.xml);
if (encrypted) {
var encryptMessage = formatted.Encrypt;
if (query.msg_signature !== CRYPTOR.getSignature(timestamp, nonce, encryptMessage)) {
ctx.status = 401;
ctx.body = 'Invalid signature';
return;
}

// 业务逻辑处理
// 注意不要在业务逻辑中操作 body、type
const body = await handle(formatted, ctx);

/*
* 假如服务器无法保证在五秒内处理并回复,可以直接回复空串。
* 微信服务器不会对此作任何处理,并且不会发起重试。
*/
if (body === '') {
ctx.body = '';
var decryptedXML = CRYPTOR.decrypt(encryptMessage);
var messageWrapXml = decryptedXML.message;
if (messageWrapXml === '') {
ctx.status = 401;
ctx.body = 'Invalid signature';
return;
}
var decodedXML = await parseXML(messageWrapXml);
formatted = formatMessage(decodedXML.xml);
}

var replyMessageXml = reply(body, formatted.ToUserName, formatted.FromUserName);
// 业务逻辑处理
// 注意不要在业务逻辑中操作 body、type
const body = await handle(formatted, ctx);
/*
* 假如服务器无法保证在五秒内处理并回复,可以直接回复空串。
* 微信服务器不会对此作任何处理,并且不会发起重试。
*/
if (body === '') {
ctx.body = '';
return;
}

if (!query.encrypt_type || query.encrypt_type === 'raw') {
ctx.body = replyMessageXml;
} else {
var wrap = {};
wrap.encrypt = CRYPTOR.encrypt(replyMessageXml);
wrap.nonce = parseInt((Math.random() * 100000000000), 10);
wrap.timestamp = new Date().getTime();
wrap.signature = CRYPTOR.getSignature(wrap.timestamp, wrap.nonce, wrap.encrypt);
ctx.body = encryptWrap(wrap);
}
var replyMessageXml = reply(body, formatted.ToUserName, formatted.FromUserName);

if (!query.encrypt_type || query.encrypt_type === 'raw') {
ctx.body = replyMessageXml;
} else {
var wrap = {};
wrap.encrypt = CRYPTOR.encrypt(replyMessageXml);
wrap.nonce = parseInt((Math.random() * 100000000000), 10);
wrap.timestamp = Date.now();
wrap.signature = CRYPTOR.getSignature(wrap.timestamp, wrap.nonce, wrap.encrypt);
ctx.body = encryptWrap(wrap);
}

ctx.type = 'application/xml';
ctx.type = 'application/xml';
};
}

middleware(handle) {
return async (ctx, next) => {
const method = ctx.method;

if (method === 'GET') {
await this.get()(ctx, next);
} else if (method === 'POST') {
await this.post(handle)(ctx, next);
} else {
ctx.status = 501;
ctx.body = 'Not Implemented';
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@
"coveralls": "*",
"expect.js": "*",
"koa": "^2.0.0",
"koa-generic-session": "*",
"mocha": "*",
"mocha-lcov-reporter": "*",
"muk": "*",
"nyc": "^10.2.0",
"rewire": "*",
"should": "~3.0.0",
"supertest": "*",
"travis-cov": "*"
},
Expand Down
Loading