light-bridge
Version:
LightBridge ===========
220 lines (187 loc) • 5.54 kB
JavaScript
/**
* @file 微信企业号 回调模式封装
* @author r2space@gmail.com
*/
;
var light = require("light-framework")
, url = require("url")
, xml2js = light.util.xml2js
, conf = light.util.config.wechat
, helper = light.framework.helper
, log = light.framework.log
, Crypto = require("./crypto")
;
var crypto = conf ? new Crypto(conf.Token, conf.EncodingAESKey, conf.CorpID) : {};
/**
* GET方法请求
* 开启回调模式,验证企业服务器配置时使用
* 在配置窗口指定的Token及EncodingAESKey需要写到配置文件中
* @param req
* @param res
* @returns {*}
*/
exports.verify = function(req, res) {
// 获取由微信服务器发起的验证请求的参数
var query = url.parse(req.url, true).query;
var signature = query.msg_signature // 微信加密签名
, timestamp = query.timestamp // 时间戳
, nonce = query.nonce // 随机数
, echostr = query.echostr; // 加密的随机字符串
// 对请求进行校验,验证是否是来自微信服务器
var compute = crypto.getSignature(timestamp, nonce, echostr);
if (signature != compute) {
log.error("Invalid signature");
return sendError("Invalid signature", res);
}
// 返回解密后的消息本体
send(crypto.decrypt(echostr).message, res);
};
/**
* 接受微信企业号产生的事件,用户发送的消息
* @param req
* @param res
* @param callback
* 第一参数是从微信收到的消息本体
* 第二个参数是需要返回给微信的消息本体
* {
* fromUsername:
* toUsername:
* createTime:
* msgType:
* content: [
* title:
* description:
* picUrl:
* url:
* ]
* }
*/
exports.reply = function(req, res, callback) {
var signature = req.query.msg_signature // 微信加密签名
, timestamp = req.query.timestamp // 时间戳
, nonce = req.query.nonce; // 随机数
log.debug(req.url);
loadXml(req, function(err, xml) {
// 解析企业号发送过来的消息体
xml2js.parseString(xml, {trim: true}, function (err, result) {
if (err) {
log.error("BadMessage");
return sendError("BadMessage" + err.name, res);
}
var xml = formatMessage(result.xml);
// 验证signature
var encryptMessage = xml.Encrypt;
if (signature !== crypto.getSignature(timestamp, nonce, encryptMessage)) {
log.error("Invalid signature");
return sendError("Invalid signature", res);
}
// 解密消息本体
var decrypted = crypto.decrypt(encryptMessage);
if (decrypted.message == "") {
log.error("BadMessage");
return sendError("BadMessage", res);
}
// 解析消息内容
xml2js.parseString(decrypted.message, {trim: true}, function (err, result) {
if (err) {
log.error("BadMessage");
return sendError("BadMessage" + err.name, res);
}
var message = formatMessage(result.xml);
if (callback) {
callback(message, function(content) {
formatReplyMessage(res, content);
});
} else {
send(undefined, res);
}
});
});
});
};
/**
* TODO: 异步回复,当无法在指定的时间5秒内返回结果时,可以使用一部方式
*/
exports.replyAsync = function() {
};
/**
* 将xml2js解析出来的对象转换成直接可访问的对象
*/
function formatMessage(result) {
var message = {};
if (typeof result === "object") {
for (var key in result) {
if (result[key].length === 1) {
var val = result[key][0];
if (typeof val === "object") {
message[key] = formatMessage(val);
} else {
message[key] = (val || "").trim();
}
} else {
message = result[key].map(formatMessage);
}
}
}
return message;
}
/**
* 发送错误信息
* @param message
* @param res
* @returns {boolean}
*/
function sendError(message, res) {
res.writeHead(401);
res.end(message);
return false;
}
/**
* 发送正确的回复信息
* @param message
* @param res
*/
function send(message, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.end(message);
}
/**
* 从请求(req)中解析出XML字符串
* @param req
* @param callback
*/
function loadXml(req, callback) {
var buffers = [];
req.on("data", function (trunk) {
buffers.push(trunk);
});
req.on("end", function () {
callback(null, Buffer.concat(buffers).toString("utf-8"));
});
req.once("error", callback);
}
/**
* 格式化
* @param res
* @param content
* @returns {*}
*/
function formatReplyMessage(res, content) {
// 响应空字符串,用于响应慢的情况,避免微信重试
if (!content) {
return send(undefined, res);
}
var xml = helper.ejsParser(__dirname + "/callback_template.ejs", content), wrap = {};
wrap.encrypt = crypto.encrypt(xml);
wrap.nonce = parseInt((Math.random() * 100000000000), 10);
wrap.timestamp = new Date().getTime();
wrap.signature = crypto.getSignature(wrap.timestamp, wrap.nonce, wrap.encrypt);
var xmlTemplate = "" +
"<xml>" +
"<Encrypt><![CDATA[{{-encrypt}}]]></Encrypt>" +
"<MsgSignature><![CDATA[{{-signature}}]]></MsgSignature>" +
"<TimeStamp>{{-timestamp}}</TimeStamp>" +
"<Nonce><![CDATA[{{-nonce}}]]></Nonce>" +
"</xml>";
send(helper.ejsFormat(xmlTemplate, wrap), res);
}