项目作者: KDODIUNCNWERU

项目描述 :
微信公众号签名接口,微信公众号消息管理接口
高级语言: JavaScript
项目地址: git://github.com/KDODIUNCNWERU/node-weixin.git
创建时间: 2018-07-19T07:11:46Z
项目社区:https://github.com/KDODIUNCNWERU/node-weixin

开源协议:

下载


一、准备工作

1.在微信公众平台申请测试账号,并设置好好 JS 接口安全域名

2.验证服务器信息

1531734590_1_
微信截图_20180716122946

  1. router.use("/wx", (req, res) => {
  2. let { signature, timestamp, nonce, echostr } = url.parse(req.url, true).query;
  3. //将Token,timestamp,nonce按字典排序,排序后链接成一个字符串
  4. let str = [TOKEN, timestamp, nonce].sort().join("");
  5. //使用sha1模块进行sha1加密
  6. let jsSHA = require('jssha');
  7. let shaObj = new jsSHA(str, 'TEXT');
  8. let sha1Str = shaObj.getHash('SHA-1', 'HEX');
  9. if (sha1Str === signature) {
  10. //将echostr返回给微信服务器
  11. res.send(echostr)
  12. }
  13. else {
  14. res.send("wrong")
  15. }
  16. })

二、微信的签名算法

使用微信 sdk 必须自己实现微信的签名算法,大概需要4个步骤:

1.获取 access_token;

  1. function getAccessToken() {
  2. return request(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`)
  3. .then(function (body) {
  4. accessToken = JSON.parse(body).access_token;
  5. })

2.根据 access_token 获取 jsapi_ticket

  1. function getAccessToken() {
  2. return request(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`)
  3. .then(function (body) {
  4. accessToken = JSON.parse(body).access_token;
  5. return request(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${accessToken}&type=jsapi`);
  6. })
  7. .then(function (body) {
  8. jsapiTicket = JSON.parse(body).ticket;
  9. });
  10. }

3.根据 appId(公众号唯一id)、noncestr(随机字符串)、timestamp(时间戳)、url(当前页面完整url,不包括#aaa=bbb) 通过sha1算法签名

  1. let createNonceStr = function () {
  2. return Math.random().toString(36).substr(2, 15);
  3. };
  4. let createTimestamp = function () {
  5. return parseInt(new Date().getTime() / 1000) + '';
  6. };
  7. //对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1 = value1 & key2=value2…)拼接成字符串string
  8. let raw = function (args) {
  9. let keys = Object.keys(args);
  10. keys = keys.sort()
  11. let string = '';
  12. keys.forEach(k => {
  13. string += '&' + k + '=' + args[k];
  14. })
  15. string = string.substr(1);
  16. return string;
  17. };
  18. //生成签名
  19. const sign = function (jsapi_ticket, url) {
  20. let ret = {
  21. jsapi_ticket: jsapi_ticket,
  22. noncestr: createNonceStr(),
  23. timestamp: createTimestamp(),
  24. url: url
  25. };
  26. const string = raw(ret);
  27. const jsSHA = require('jssha');
  28. const shaObj = new jsSHA(string, 'TEXT');
  29. ret.signature = shaObj.getHash('SHA-1', 'HEX');
  30. return ret;
  31. };

4.将信息返回给前端 , 设置wx.config。

由于获取access_token 和 jsapi_ticket的接口都有访问限制,所以明确指出需要第三方做缓存处理。

  1. //增加定时任务,刷新accessToken和jsapiTicket
  2. setInterval(function () {
  3. getAccessToken();
  4. }, 7200000);

三、消息管理
    当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应。
    消息推送也是微信公众号开发更为有趣的功能,涉及到文本消息、图片消息、语音消息、视频消息、音乐消息以及图文消息。
    
1.捕获消息信息
    微信消息产生的请求方式为 POST,然后监听这个接口,得到的数据是xml的数据,解析XML,这里使用了 第三方的包 xml2js(npm install xml2js ):

  1. let xml = '' //存储将要回复给公众号的文字
  2. //接收post内容
  3. req.on('data', chunk => {
  4. xml += chunk
  5. })
  6. //接收结束
  7. req.on('end', () => {
  8. //将接受到的xml数据转化为json
  9. if (xml) {
  10. xml2js.parseString(xml, { explicitArray: false }, function (err, json) {
  11. let backTime = parseInt(new Date().valueOf() / 1000) //创建发送时间,整数
  12. console.log(json)
  13. })
  14. }
  15. })

2.根据得到的消息类型(MsgType),可以进行不同的回复

  1. //event表示事件,
  2. if (json.xml.MsgType == 'event') {
  3. // EventKey是在自定义菜单的时候定义的事件名称
  4. if (json.xml.EventKey == 'clickEvent') {
  5. res.send(msg.getTextXml(json, backTime, '你戳我干啥...'))
  6. }
  7. //text表示文字信息
  8. } else if (json.xml.MsgType == 'text') {
  9. //回复公众号的文字信息
  10. if (json.xml.Content == 1) {
  11. var contentArr = [{
  12. Title: "Node.js 微信自定义菜单",
  13. Description: "使用Node.js实现自定义微信菜单",
  14. PicUrl: "http://www.fpfh5.site/images/recording/record.png",
  15. Url: "http://www.baidu.com"
  16. }]
  17. res.send(msg.graphicMsg(json, backTime, contentArr))
  18. } else {
  19. res.send(msg.getTextXml(json, backTime, `你发"${json.xml.Content}"过来干啥?`))
  20. }
  21. } else if (json.xml.MsgType == 'image') {
  22. //回复公众号的图片信息
  23. res.send(msg.getImageXml(json, backTime))
  24. } else if (json.xml.MsgType == 'voice') {
  25. //回复公众号的语音信息
  26. res.send(msg.getVoiceXml(json, backTime))
  27. }

其中ToUserName是接受者的openid,FromUserName是发送者的openid,CreateTime就是一个整型的时间戳。MsgType就是消息类型,一般有文本(text),图片(image),语音(voice),视频(video),小视频(shortvideo),地理位置(location)以及链接消息(link)。

3.消息模板,消息接口收到消息时,与回复消息时,ToUserName和FromUserName的位置互换。     

  1. //回复图文消息
  2. exports.graphicMsg = function (json, backTime, contentArr) {
  3. let content = contentArr.map(item => `<item>
  4. <Title><![CDATA[${item.Title}]]></Title>
  5. <Description><![CDATA[${item.Description}]]></Description>
  6. <PicUrl><![CDATA[${item.PicUrl}]]></PicUrl>
  7. <Url><![CDATA[${item.Url}]]></Url>
  8. </item>`)
  9. content = content.join('')
  10. let backXML = `<xml>
  11. <ToUserName><![CDATA[${json.xml.FromUserName}]]></ToUserName>
  12. <FromUserName><![CDATA[${json.xml.ToUserName}]]></FromUserName>
  13. <CreateTime>${backTime}</CreateTime>
  14. <MsgType><![CDATA[news]]></MsgType>
  15. <ArticleCount>${contentArr.length}</ArticleCount>
  16. <Articles>${content}</Articles>
  17. </xml>`
  18. return backXML;
  19. }
  20. //回复文本消息
  21. exports.getTextXml = function(json, backTime, word) {
  22. let backXML = `<xml>
  23. <ToUserName><![CDATA[${json.xml.FromUserName}]]></ToUserName>
  24. <FromUserName><![CDATA[${json.xml.ToUserName}]]></FromUserName>
  25. <CreateTime>${backTime}</CreateTime>
  26. <MsgType><![CDATA[text]]></MsgType>
  27. <Content><![CDATA[${word}]]></Content>
  28. </xml>`
  29. return backXML;
  30. };
  31. //回复图片消息
  32. exports.getImageXml = function(json, backTime) {
  33. let backXML = `<xml>
  34. <ToUserName><![CDATA[${json.xml.FromUserName}]]></ToUserName>
  35. <FromUserName><![CDATA[${json.xml.ToUserName}]]></FromUserName>
  36. <CreateTime>${backTime}</CreateTime>
  37. <MsgType><![CDATA[image]]></MsgType>
  38. <Image><MediaId><![CDATA[${json.xml.MediaId}]]></MediaId></Image>
  39. </xml>`
  40. return backXML;
  41. };
  42. // /回复语音消息
  43. exports.getVoiceXml = function(json, backTime) {
  44. let backXML = `<xml>
  45. <ToUserName><![CDATA[${json.xml.FromUserName}]]></ToUserName>
  46. <FromUserName><![CDATA[${json.xml.ToUserName}]]></FromUserName>
  47. <CreateTime>${backTime}</CreateTime>
  48. <MsgType><![CDATA[voice]]></MsgType>
  49. <Voice><MediaId><![CDATA[${json.xml.MediaId}]]></MediaId></Voice>
  50. </xml>`
  51. return backXML;
  52. };

4.客服回复消息

  1. request({
  2. method: 'POST',
  3. uri: `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`,
  4. json: true,
  5. body:{
  6. "touser": json.xml.FromUserName,
  7. "msgtype": "text",
  8. "text": {
  9. "content": "Hello World"
  10. }
  11. }
  12. })

github地址奉上:https://github.com/KDODIUNCNWERU/node-weixin
请帮我start一下