港饮港食 發表於 2021-5-19 18:22:00

使用NodeJs作为微信公众号后台服务器 使用node回复公众号消息 node验证公众号tonken node微信公众号开发

<h1 id="使用nodejs作为微信公众号后台服务器">使用NodeJs作为微信公众号后台服务器</h1>
<p>下面演示代码的源码地址 https://gitee.com/szxio/weChatServer</p>
<h2 id="申请测试公众号">申请测试公众号</h2>
<p>首先登录微信公众平台,选择自己的公众号登录。登录成功后点击开发者工具,选择公众平台测试账号</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/1.png" alt="" loading="lazy"></p>
<p>点击进去后我们可以申请一个测试用的公众号,可以体验所有高级接口,这里我们要配置一个线上的接口地址,在验证 Tonken,和收发消息时微信都会请求我们配置的地址,这里推荐一个好用的内网穿透工具,可以把我们本地的项目地址映射到外网上,方便我们调试</p>
<p>小米球内网穿透工具</p>
<p>这里我生成的线上地址是 http://songzx.ngrok2.xiaomiqiu.cn/,下面我们会用这个地址作为我们的公众号的接口配置地址</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/2.png" alt="image-20210518191538886" loading="lazy"></p>
<h2 id="实现tonken验证">实现Tonken验证</h2>
<p>首先新建一个空白的 node 项目</p>
<pre><code class="language-shell">npm init -y
</code></pre>
<p>接着安装一些常用的依赖</p>
<pre><code class="language-she">npm install express
</code></pre>
<p>接在在项目根路径下新建<code> index.js</code>,初始代码如下</p>
<pre><code class="language-js">const express = require("express")
const app = express()

app.get("/",(req,res)=&gt;{
    res.send('Hello World')
})

app.listen(8088,()=&gt;{
    console.log("running 127.0.0.1:8088");
})
</code></pre>
<p>然后启动项目并用浏览器访问 <code>127.0.0.1:8088</code>可以看到如下结果,表示服务启动成功</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/3.png" alt="" loading="lazy"></p>
<p>现在我们实现验证 <code>tonken</code> 的逻辑</p>
<p>首先安装如下依赖,用作加密处理</p>
<pre><code class="language-shell">npm install crypto
</code></pre>
<p>然后新建 <code>util</code> 和 <code>router</code> 两个文件夹,分别放置我们的统一的方法和普通请求方法</p>
<p>然后新建 <code>util -&gt; validateToken.js</code> 文件,代码如下,这个方法专门用来验证微信传递过来的 <code>Tonken</code></p>
<pre><code class="language-js">var crypto = require("crypto");

// 加密方法
function sha1(str) {
var md5sum = crypto.createHash("sha1");
md5sum.update(str);
str = md5sum.digest("hex");
return str;
}

// 验证tonken
function validateToken(req) {
return new Promise((resolve, reject) =&gt; {
    let query = req.query;
    let signature = query.signature;
    let echostr = query["echostr"];
    let timestamp = query["timestamp"];
    let nonce = query["nonce"];
    let oriArray = new Array();
    oriArray = nonce;
    oriArray = timestamp;
    oriArray = "admin123"; // 这里是在公众号接口配置信息里面填写的Token
    oriArray.sort();
    let original = oriArray.join("");
    let scyptoString = sha1(original);
    if (signature == scyptoString) {
      // 验证通过,返回 echostr
      resolve(echostr);
    } else {
      reject(false);
    }
});
}
// 导出验证 Tonken 的发放
module.exports = validateToken;
</code></pre>
<p>然后新建 <code>router -&gt; weChat.js </code>文件,这个文件专门用来处理微信发送过来的请求,在这个文件中编写如下代码</p>
<pre><code class="language-js">const express = require("express");
const router = express.Router(); // 配置路由模块
const validateToken = require("../util/validateToken");

// get请求验证tonken有效性
router.get("/", (req, res) =&gt; {
validateToken(req).then((t) =&gt; {
    res.send(t);
});
});
// 导出 router
module.exports = router;
</code></pre>
<p>最后修改一下<code>index.js</code>文件,引入我们新建的 <code> router.js</code> 文件</p>
<pre><code class="language-js">const express = require("express");
const app = express();
const path = require("path");
const weChat = require(path.resolve(__dirname, "./router/weChat"));

app.use(weChat);

app.listen(8088, () =&gt; {
console.log("running 127.0.0.1:8088");
});
</code></pre>
<p>现在我们去微信公众号配置页面中测试一下</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/5.png" alt="image-20210519162630934" loading="lazy"></p>
<p>页面中弹出 配置成功 就表示我们验证 <code>Tonken</code> 的业务已经完成了</p>
<h2 id="获取tonken并定时刷新">获取Tonken并定时刷新</h2>
<p>微信中获取 <code>Tonken</code> 要发送一个 get 请求来获取,并且这个 <code>Tonken</code> 有过期时间,我们需要自己保存这个 <code>Tonken</code> 并定时刷新,以保证 <code>Tonken</code> 有效性</p>
<p>微信官方对于获取 Tonken 的描述</p>
<p>接口调用说明</p>
<ul>
<li>请求方式: GET</li>
<li>请求地址:<code>https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=APPID&amp;secret=APPSECRET</code></li>
</ul>
<p>既然要用到请求,我们安装一个 <code>axios</code> 用来发送请求</p>
<pre><code class="language-shell">npm install axios
</code></pre>
<p>然后在根目录新建 <code>public -&gt; tonken.json</code> ,用来存放我们获取到的 <code>tonken</code>,也是对<code> tonken</code> 的一种持久化存储方式,<code>json</code>文件内容为空即可</p>
<p>接着新建 <code>util -&gt; tonkenConfig.js</code> 文件,代码如下</p>
<pre><code class="language-js">const fs = require("fs");
const path = require("path");
const http = require("axios");
const fileUrl = path.resolve(__dirname, "../public/tonken.json");
const APPID = "wx2188729b190d357d"; // 测试号的 APPID
const APPSECRET = "d976b0e6262b829ba003e9a24032447c"; // 测试号的 APPSECRET
let INTERTIME = (7200 - 60) * 1000; // 设置一个默认的定期获取tonken的时间

// 保存Tonken
function setTonken() {
return new Promise((resolve, reject) =&gt; {
    http
      .get(
      `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=${APPID}&amp;secret=${APPSECRET}`
      )
      .then((res) =&gt; {
      // 更新tonken的过期时间,每隔这个时间重新获取一次tonken
      INTERTIME = (res.data.expires_in - 60) * 1000;
      // 获取到Tonken后保存到json文件中
      fs.writeFile(
          fileUrl,
          JSON.stringify({
            tonken: res.data.access_token,
          }),
          () =&gt; {
            // 通知外界Tonken获取成功
            resolve();
          }
      );
      });
});
}

// 定时获取Tonken
function timingSetTonken() {
// 定时刷新tonken
setInterval(() =&gt; {
    setTonken();
}, INTERTIME);
}

// 获取Tonken
function getTonken() {
return new Promise((resolve, reject) =&gt; {
    // 从json中读取保存的Tonken
    fs.readFile(fileUrl, (err, data) =&gt; {
      // 返回获取到的tonken
      resolve(JSON.parse(data).tonken);
    });
});
}

// 导出封装好的方法
module.exports = {
setTonken, // 更新tonken
getTonken, // 返回获取到的tonken
timingSetTonken, // 定时更新tonken
};
</code></pre>
<p>然后在 <code>router -&gt; weChat.js</code> 中引入 <code>tonkenConfig.js</code></p>
<pre><code class="language-js">const express = require("express");
const router = express.Router(); // 配置路由模块
const validateToken = require("../util/validateToken");
const { setTonken, timingSetTonken } = require("../util/tonkenConfig");
// 项目启动后自动执行获取tonken的方法
setTonken().then(() =&gt; {
// tonken 获取成功后开始定时刷新tonken操作
timingSetTonken();
});

// get请求验证tonken有效性
router.get("/", (req, res) =&gt; {
validateToken(req).then((t) =&gt; {
    res.send(t);
});
});
// 导出 router
module.exports = router;
</code></pre>
<p>此时我们在启动项目后会自动调用一下获取 <code>tonken</code> 的接口,然后从接口中获取到一个过期时间,微信返回的过期时间是以秒为单位,减去60秒是为了下一次<code>tonken</code>时与这次<code>tonken</code>之间的平滑过渡,之后每隔这个时间会重新获取一次<code>tonken</code></p>
<p>我们将这个<code>tonken</code>写入到了一个<code>json</code>文件中,我们可以在任何文件中通过如下方法获取<code>tonken</code></p>
<pre><code class="language-js">const { getTonken } = require("./util/tonkenConfig");
// 调用封装好的获取token方法
getTonken().then((tonken) =&gt; {
console.log(tonken); // 45_7k55HHRaYxM4MkD4aREraHZpgdjmT......
});
</code></pre>
<h2 id="接收微信消息并回复">接收微信消息并回复</h2>
<p>官方对于接收消息的描述</p>
<p>简单说就是:我们在微信公众号中发送消息后,微信会发送一个 <code>post</code> 请求给我们上面配置的地址,参数时一段 <code>xml</code> 文本,我们需要解析这个 <code>xml</code>,并按照微信指定的格式回复一个 <code>xml</code> 格式的字符串,注意是回复 <strong>xml 格式的字符串</strong></p>
<p>首先安装依赖,用来解析<code>post</code>请求中的<code>xml</code>参数</p>
<pre><code class="language-shell">npm install express-xml-bodyparser
</code></pre>
<p>然后在 <code>index.js</code> 文件中引用并配置中间件</p>
<pre><code class="language-js">const express = require("express");
const app = express();
const path = require("path");
const weChat = require(path.resolve(__dirname, "./router/weChat"));
const xmlparser = require('express-xml-bodyparser'); // 解析 xml
app.use(express.json());
app.use(express.urlencoded());
app.use(xmlparser());

app.use(weChat);

app.listen(8088, () =&gt; {
console.log("running 127.0.0.1:8088");
});
</code></pre>
<p>然后在 <code>weChat.js</code> 中添加一个 <code>post</code> 请求,打印一下看看微信给我们发过来的是什么东西</p>
<pre><code class="language-js">// post请求处理微信发送过来的消息
router.post("/", (req, res) =&gt; {
console.log(req.body);
res.send("");
});
</code></pre>
<p>重启项目,我们往微信公众号中随便发送一个消息</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/4.png" alt="" loading="lazy"></p>
<p>解析后的参数如下</p>
<pre><code class="language-js">{
xml: {
    tousername: [ 'gh_a0f004c20d2b' ],
    fromusername: [ 'olttN6WJOYe-lTysV8_tsnZ7-HMQ' ],
    createtime: [ '1621416487' ],
    msgtype: [ 'text' ],
    content: [ 'hello' ],
    msgid: [ '23213103466653274' ]
}
}
</code></pre>
<p>拿到参数后我们可以根据参数中的 <code>msgtype</code> 判断传递过来的消息类型,以及 <code>content</code> 是消息内容,获取到了参数,接下要做的就是根据消息回复内容了</p>
<p>官方被动回复用户消息文档</p>
<p>下面是一个回复消息的模板代码,可以很方便的帮助我们生成指定的 <code>xml</code> 格式的字符串</p>
<pre><code class="language-js">// 回复文本消息
exports.textMessage = function (message) {
var createTime = new Date().getTime();
return `&lt;xml&gt;
    &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
    &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
    &lt;CreateTime&gt;${createTime}&lt;/CreateTime&gt;
    &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
    &lt;Content&gt;&lt;!]&gt;&lt;/Content&gt;
    &lt;/xml&gt;`;
};
// 回复图片消息
exports.imageMessage = function (message) {
var createTime = new Date().getTime();
return `&lt;xml&gt;
    &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
    &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
    &lt;CreateTime&gt;${createTime}&lt;/CreateTime&gt;
    &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
    &lt;Image&gt;
      &lt;MediaId&gt;&lt;!]&gt;&lt;/MediaId&gt;
    &lt;/Image&gt;
    &lt;/xml&gt;`;
};
// 回复语音消息
exports.voiceMessage = function (message) {
var createTime = new Date().getTime();
return `&lt;xml&gt;
    &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
    &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
    &lt;CreateTime&gt;${createTime}&lt;/CreateTime&gt;
    &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
    &lt;Voice&gt;
      &lt;MediaId&gt;&lt;!]&gt;&lt;/MediaId&gt;
    &lt;/Voice&gt;
    &lt;/xml&gt;`;
};
// 回复视频消息
exports.videoMessage = function (message) {
var createTime = new Date().getTime();
return `&lt;xml&gt;
    &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
    &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
    &lt;CreateTime&gt;${createTime}&lt;/CreateTime&gt;
    &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
    &lt;Video&gt;
      &lt;MediaId&gt;&lt;!]&gt;&lt;/MediaId&gt;
      &lt;Title&gt;&lt;!]&gt;&lt;/Title&gt;
      &lt;Description&gt;&lt;!]&gt;&lt;/Description&gt;
    &lt;/Video&gt;
    &lt;/xml&gt;`;
};
// 回复图文消息
exports.articleMessage = function (message) {
var createTime = new Date().getTime();
return `&lt;xml&gt;
    &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
    &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
    &lt;CreateTime&gt;${createTime}&lt;/CreateTime&gt;
    &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
    &lt;ArticleCount&gt;${message.articles.length}&lt;/ArticleCount&gt;
    &lt;Articles&gt;
      ${message.articles
          .map(
            (article) =&gt;
            `&lt;item&gt;&lt;Title&gt;&lt;!]&gt;&lt;/Title&gt;
                &lt;Description&gt;&lt;!]&gt;&lt;/Description&gt;
                &lt;PicUrl&gt;&lt;!]&gt;&lt;/PicUrl&gt;
                &lt;Url&gt;&lt;!]&gt;&lt;/Url&gt;&lt;/item&gt;`
          )
          .join("")}
    &lt;/Articles&gt;
    &lt;/xml&gt;`;
};
</code></pre>
<p>在 <code>weChat.js</code> 中引入上面的模板,这里我把模板代码放到了 <code>util -&gt; template.js</code> 中,然后修改刚刚新建的 post 方法</p>
<pre><code class="language-js">// 引入消息模板
const template = require("../util/template");

// post请求处理微信发送过来的消息
router.post("/", (req, res) =&gt; {
let xml = req.body.xml;
let msgtype = xml.msgtype;
switch (msgtype) {
    case "text":
      // 封装要回复的消息参数
      let message = {
      FromUserName: xml.fromusername,
      ToUserName: xml.tousername,
      reply: "你好呀,我是通过代码回复你的",
      };
      res.send(template.textMessage(message));
      break;

    default:
      res.send(""); // 不是文本消息是默认响应一个空
      break;
}
});
</code></pre>
<p>我们现在在发送消息试一试</p>
<p><img src="https://gitee.com/szxio/weChatServer/raw/master/public/image/6.png" alt="" loading="lazy"></p>
<p>我们看到公众号已经可以回答我们了。</p><br><br>
来源:https://www.cnblogs.com/songzxblog/p/14786318.html
頁: [1]
查看完整版本: 使用NodeJs作为微信公众号后台服务器 使用node回复公众号消息 node验证公众号tonken node微信公众号开发