上一篇文章介绍过 微信小程序配置消息推送,没有看过的可以先去查看一下,这里就直接去把那个客服消息接口去解密那个消息了。
在这里我选择的还是json格式的加密。
也就是给小程序客服消息发送的消息都会被微信转发到这里配置的地址和接口上面。
在页面中使用客服消息也就是如下这个效果,是需要用到微信提供的button标签上面的open-type的
点击进入客服消息也就是如下这个效果。
然后你发送的消息就会被转发到上面你配置的那个服务器的端口和那个接口上面,也就是例子中的
只不过配置确认的时候的接口是get请求方式,而微信转发消息到这里的接口这里是post请求方式。
在聊天窗口中发送消息,微信就会把加密后的消息转发到我们的接口 也就是 /checkPushMsg 上面,post请求方式。
在node中的代码接收,看看微信给我们转发了什么东西呢
1 // 配置前后端的推送消息 2 router.get('/checkPushMsg', wx_msg.check_push); 3 4 // 那个函数就是如下的处理 5 exports.handle_customer_sevice = (req, res) => { 6 console.log('接收到了消息,请求体中'); 7 console.log(req.body); 8 console.log('接收到了消息,请求url中'); 9 console.log(req.query); 10 }
我们来查看一下发送过来的什么,我再用文档来比对一下哈。
现在我在手机上面那个页面里面发送一条消息,任意内容。
这里服务器的图片太小了,可以 按住 ctrl + 鼠标滚轮把这里放大看一下,我相信你肯定知道的。
请求url中的内容如下
请求体中的内容一个是发送者的 openid,另外一个加密的就是消息内容,它的构成是由 16个字节的随机字符串,4个字节的消息长度,发送的消息,appId由基于AES加解密算法加密而成的。
这里咱们先不着急解密发送的内容,我们要在解密之前先判断一下是不是微信发送过来的呢,如果不是你在这里解密半天然后发出去那不就难受了。
上面的图片中也详细的介绍了如何去判断是不是微信发送过来的消息。
我什么也不想解释了,直接上代码啦。
1 exports.handle_customer_sevice = (req, res) => { 2 console.log('接收到了消息,请求体中'); 3 console.log(req.body); 4 console.log('接收到了消息,请求url中'); 5 console.log(req.query); 6 let signature = req.query.signature, 7 timestamp = req.query.timestamp, 8 nonce = req.query.nonce, 9 openid = req.query.openid, 10 encrypt_type = req.query.encrypt_type, 11 msg_signature = req.query.msg_signature, 12 msg_encrypt = req.body.Encrypt; // 密文体 13 14 // 开发者计算签名 15 let devMsgSignature = sha1(pushToken, timestamp, nonce, msg_encrypt); // 这里的pushToken是 上面那个 配置消息推送 里面设置的Token令牌 16 17 if(devMsgSignature == msg_signature){ 18 console.log('验证成功,是从微信服务器转发过来的消息'); 19 20 res.send('success'); 21 }else{ 22 console.log('error'); 23 res.send('error'); 24 } 25 };
上面的那个sha1函数是这样的
1 /* 2 @explain sh1加密 3 @version 1.0.1 4 5 @author : Z 6 @data : 2019-2-13 7 8 @params : a,b,c…… 9 @return : String 加密完成后的字符串 10 */ 11 exports.sha1 = function (...arr) { 12 return crypto.createHash('sha1').update(arr.sort().join('')).digest('hex'); 13 };
上面就可以验证出来如果那个加密的东西确实是那样的就可以验证出来消息确实是由微信发送过来的。
然后就要开始解密了,这里我遇到了好多坑,说实话我也不知道下面的代码是谁第一个写出来的,因为官网上面根本就没有Node.js的代码,所以我也是看到了加密消息的构成部分,然后使用下面封装的函数解密。
1 /* 2 @explain: 微信的消息密文解密方法 3 @version 1.0.1 4 修复部分消息解析失败的情况 5 @author: Z 6 @data :2019-02-14 7 @params: 8 obj.AESKey:解密的aesKey值 这里的key就是配置消息推送的那部分 9 obj.text: 需要解密的密文 10 obj.corpid: 企业的id / 微信小程序的appid 11 12 @return 13 obj.noncestr 随机数 14 obj.msg_len 微信密文的len 15 obj.msg 解密后的明文 16 */ 17 18 exports.decrypt = function (obj) { 19 let aesKey = Buffer.from(obj.AESKey + '=', 'base64'); 20 const cipherEncoding = 'base64'; 21 const clearEncoding = 'utf8'; 22 const cipher = crypto.createDecipheriv('aes-256-cbc',aesKey,aesKey.slice(0, 16)); 23 cipher.setAutoPadding(false); // 是否取消自动填充 不取消 24 let this_text = cipher.update(obj.text, cipherEncoding, clearEncoding) + cipher.final(clearEncoding); 25 /* 26 密文的构成 27 Base64_Encode(AES_Encrypt[random(16B) + msg_len(4B) + msg + $appId]) 28 但是由于部分消息是不满足那个 32 位的,所以导致上面那个 cipher.final() 函数报错,所以修改为了自动填充,所以 appId后面还跟着一些字符 29 就无法正常解析了,所以就不返回 corpid 了,然后返回我们想要的东西。 30 */ 31 return { 32 noncestr:this_text.substring(0,16), 33 msg_len:this_text.substring(16,20), 34 msg:this_text.substring(20,this_text.lastIndexOf("}")+1) 35 } 36 };
其中的坑就是我遇到了那个 cipher.setAutoPadding(false) 这里填充的问题,但是有的消息是可以解密出来的,但是有的消息是不可以解密出来的,会报错,所以我就让所有的消息都填充了。
let this_tex = cipher.update(obj.text, cipherEncoding, clearEncoding) + cipher.final(clearEncoding);
解密消息这里的问题是,这是一个加法,前面的那个更新函数会改变 cipher的值,然后导致后面的那个值发生改变,当时找这个错误出现了很多问题。这个问题就是当字体的内容的长度不是32个字节还是什么的倍数的时候,这个函数会报一个500的错误码,然后不知道是哪里的问题,所以我就取消自动填充设置为了false,我就让它一直填充,然后处理那个消息,最后那个明文我再把最后的一个 } 后面截取掉。就可以展示出来了,但是这个里面就无法获取到小程序 appId 了,但是这又有什么必要呢?你后台肯定是知道appId的。
我把消息处理的完整代码发出来。
1 /* 2 消息体验证和解密 3 客服接收到的消息 4 handle_customer_sevice 5 6 */ 7 8 exports.handle_customer_sevice = (req, res) => { 9 console.log('接收到了消息,请求体中'); 10 console.log(req.body); 11 console.log('接收到了消息,请求url中'); 12 console.log(req.query); 13 let signature = req.query.signature, 14 timestamp = req.query.timestamp, 15 nonce = req.query.nonce, 16 openid = req.query.openid, 17 encrypt_type = req.query.encrypt_type, 18 msg_signature = req.query.msg_signature, 19 msg_encrypt = req.body.Encrypt; // 密文体 20 21 // 开发者计算签名 22 let devMsgSignature = sha1(pushToken, timestamp, nonce, msg_encrypt); 23 24 if(devMsgSignature == msg_signature){ 25 console.log('验证成功,是从微信服务器转发过来的消息'); 26 27 let returnObj = decrypt({ 28 AESKey: config.server.EncodingAESKey, 29 text: msg_encrypt, 30 corpid: config.app.appId 31 }); 32 console.log('解密后的消息'); 33 console.log(returnObj); 34 console.log('解密后的消息内容'); 35 const decryptMessage = JSON.parse(returnObj.msg); 36 console.log(decryptMessage); 37 38 /* 39 详细参数请查看官网 消息 https://developers.weixin.qq.com/miniprogram/dev/api/sendCustomerMessage.html 40 @params 41 access_token 调用接口凭证 42 touser 用户的openid 43 msgtype 消息类型 44 */ 45 46 if(JSON.parse(returnObj.msg).Content == '值班'){ 47 axios.post(config.url.ip + config.url.P_CustomSend + '?access_token='+config.access_token, { 48 touser: decryptMessage.FromUserName, 49 msgtype: "text", 50 text: { 51 content: "发送消息" 52 } 53 }) 54 .then(res => { 55 console.log('消息接口发送成功'); 56 57 console.log(res.data); 58 if(res.data.errcode == 0){ 59 console.log('消息发送成功'); 60 }else if(res.data.errcode == 40001){ 61 console.log('access_token过期'); 62 }else{ 63 console.log('其他错误信息') 64 } 65 console.log(res.data); 66 }) 67 .catch(err => { 68 console.log('错误消息'); 69 console.log(err); 70 }) 71 } 72 res.send('success'); 73 }else{ 74 console.log('error'); 75 res.send('error'); 76 } 77 };
上面用到的两个函数 一个 sha1,另外一个就是 解密函数。
现在发送一下消息,看一看服务器上面的处理过程。
如此是可以解密出来那个消息的内容的,然后我设置了一个发送值班的话会给我发送一个消息。
如此 就大功告成了。
看了网上的许多文章大多都是Java,php的代码,很少有Node的代码,而且看了之后也不知道能不能弄出来,如果你遇到这个问题, 仔细看了我的文章之后,还有地方不知道,欢迎打扰我,告诉我哪里不清楚,我也会和你一起把你的问题解决掉。
如果解决了你的问题我会非常高兴的,其实上面的文章中我也感觉到了有一些地方描述的不是那么清晰,比如说消息加密的那一块,说实话,我也不是那么了解他的加密,只是微信 这里是有讲述的 点击查看
它的加密我会在最近几天研究一下,把他的加密的细节也给发出来,下一篇文章你可以查看到它的加密。
如果解决了你的问题我真的会非常高兴,因为我开发了这么久感觉开发出来的东西都没有去影响一些人,甚至我想先给自己做一些程序,并且我已经开始着手去做了,先去方便一下自己再去做一些去对一些人有好处的影响的东西。