jingzhiMo.github.io
jingzhiMo.github.io copied to clipboard
使用egg搭建微信公众号开发,转发流量到本地服务
最近在折腾一下微信公众号的开发,在这个过程中还是遇到挺多麻烦的事,当然,每一件麻烦的事情,在 goole 下都能找到对应的解决方案,这次是把整个过程记录一下,从开始到放弃。
这次文章的目标是,在本地能够启动 node.js 服务,并能够接收用户发送给公众号的消息,进行响应处理。
1. 申请公众号
(废话,下一个)
2. 搭建本地 node.js 开发
最开始的时候,是选中 koa 来作为本地开发的,因为研发手写的代码简单,就那么几行,就可以在一个完全空白的项目跑起来了。但是后面发现,koa虽简单,但是功能也比较简单,如果要进行完整的服务端开发,还需自己搭建非常多的内容。于是选中 egg 作为开发的框架。
# 下面命令是初始化 egg 相关项目,并启动
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
$ npm run dev
运行完上面的命令之后,打开本地链接 http://localhost:7001
即可看到 egg 启动的界面了。
要让微信公众号的信息正确发到本地服务,这些东西还不够,要暴露一个外部的 ip,让微信把公众号接收到的消息发送到这个 外部 ip。这个时候需要一个代理工具:localtunnel
3. 使用 localtunnel 代理请求
localtunnel 可以把本地的接口,生成一个暴露对外的域名地址;当外部进行发送消息的时候,把发送到该域名的地址,转发到本地的服务。简单如图:
下面是安装的简单过程
# 也可以使用 npx localtunnel --port 7001,不过通常后续会多次执行,还是全局安装之后,后面执行起来比较快
$ npm install -g localtunnel
# 7001 这个端口就是使用 egg 启动占用的端口
$ lt --port 7001
# 执行完命令,会返回url,例如下面的地址。如果处理出错,重复多两次一般就可以
your url is: https://rude-robin-7.loca.lt
拿到这个url之后,就需要复制到浏览器打开,通常会出现下面的界面:
然后点击按钮,就能看到访问http://localhost:7001
内容一致。后续刷新,就不会出现以上界面,而是直接返回本地服务的内容。
4. 公众号后台配置localtunnel域名
执行完上一步之后,本地开发的服务,就可以通过域名访问了。然后去到微信公众号后台修改配置,没有开启,则需要开启:开发 => 基本配置 => 服务器配置。
URL 那一栏就填写通过 localtunnel 获取的域名;Token 那一栏现在可以随便填一下,具体后面的使用待会说到。然后点击,提交,会发现,出错。因为我们的服务还没有按照微信的要求,对请求进行验证。
5. 对公众号后台配置的服务进行验证
微信要求设定的URL需要进行验证,否则不能作为公众号接收的服务,简要的验证逻辑如下。官方对于验证的文档在这里。
知道验证的方法之后,就可以着手进行处理,就按照初始化的 egg 文件内容来处理,修改 app/controller/home.js
的 index()
方法:
// 需要依赖该加密工具
const sha1 = require('sha1')
class HomeController extends Controller {
async index() {
const { ctx } = this;
const { query } = ctx
// 在微信公众号后台的配置
const token = 'testtoken'
const { signature, echostr, timestamp, nonce } = query
if (!echostr) return
const newSignature = sha1([token, timestamp, nonce].sort().join(''))
ctx.body = newSignature === signature
? echostr
: 'error'
}
}
确认服务重启之后,回去到公众号后台配置的地方,再次点击“提交”。什么?还是出错,那大概率是 localtunnel 的问题,因为这个服务不太稳定,导致微信那边访问也会有波动。点多两次提交就可以了。
6. 接收微信公众号的信息
当我们发送消息到公众号的时候,公众号后台会对信息进行处理,然后以一个 post 方法的 http 请求,发送到刚才在公众号后台配置的链接中,请求的内容是以 xml 为格式的内容。官方关于接收消息文档如下。
上面我们的 egg 应用只是对 GET
请求进行处理,还没有对 POST
请求进行响应,所以做了以下两步处理:
// app/router.js 新增处理
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
// 下面这一行为新增
router.post('/', controller.home.post);
};
在官方文档中可以知道,需要对 XML 的文件格式进行处理,而 POST
请求是基于事件,类似流式接收二进制数据;且针对 XML,需要 xml2js
的工具,进行转换为常用的 js 对象。
const xml2js = require('xml2js');
class HomeController extends Controller {
// 为之前处理 GET 请求的方法
async index() {}
async post() {
let data = '';
const { req } = this.ctx;
// 马上对响应进行返回,让公众号后台能够收到请求已收到,避免公众号后台进行重试
this.ctx.body = 'success';
req.setEncoding('utf8');
req.on('data', function (chunk) {
data += chunk;
});
req.on('end', function () {
xml2js.parseString(data, { explicitArray: false }, function (err, json) {
if (err) {
// TODO
return
}
// 拿到后台发送过来的数据
console.log(json.xml)
});
});
}
}
以上的两个文件更改,即可接收 POST
请求了;但是 egg 默认对 POST
请求做了 csrf 防御的处理,如果需要快速解决这个问题,可以先把配置关掉:
// /config/config.default.js
const config = exports = {
// ...
security: {
csrf: false
},
}
完成以上步骤之后,用户发送到微信公众号的数据,本地服务基本就可以接收到了。
优化版
为什么会有优化版?因为 localtunnel
实在太不稳定了,经常会掉线;掉线之后,重新获取域名之后,域名再次发生更改,还需要在公众号后台更改对应的域名。
所以优化版是针对有远端服务器进行处理,代理部分,从 localtunnel
改为 云服务器
。
接下来,需要对原来 localtunnel
实现转发这一步,改为我们手动来实现。而有一个需要注意的是,微信后台配置的链接,只能配置 http/https
,分别对应 80/443
端口。因为没有证书,所以只能够在 80 端口下手了。而 80 端口,第一时间想到 nginx。通常来说,使用 nginx 进行反向代理,通过一定的规则,把访问 80 端口的一部分流量代理到其他端口。
另外,为了把线上的流量代理到本地,可以使用 ssh
的功能进行转发。ssh
命令为:
$ ssh -NfR 8080:localhost:7001 [email protected] -i ./my.pem
上面这句命令的意思是,把服务器端口为 8080
的流量代理到本地 7001
端口,i- ./my.pem
是使用对应的密钥进行登录;如果没有,则使用密码进行处理。
为什么是 8080
端口,而不是 80
端口呢。因为 80
端口已经被 nginx 占用了。会提示出错。所以,还需要配合ngxin,把针对微信的流量进行转发,更改配置如下:
# /etc/nginx/sites-enabled/default
server {
# 其他配置...
# 把微信的请求,代理到 8080 端口
location /wxapi/ {
proxy_pass http://127.0.0.1:8080/;
}
}
这个时候,就需要更改公众号后台的开发者接口配置
上面填写的 URL 的ip地址就是云服务器的地址,/wxapi/
就是 ngxin 设置的地址。
所以总体来说,自己搭建代理的方式如下:
通过这个优化之后,后续只需要本地启动 egg 服务,然后至少上述 ssh
的命令即可;ssh
代理命令也可以设置为开机启动,那样子就可以再继续减少一步。
结尾
整体流程其实并不复杂,但是需要把各个知识点都串联起来,各种工具、配置的简要知识都需要了解一下。完。