公众号开发-本地开发环境中将公众号授权域名使用内网穿透(frp+nginx)进行本地开发、调试

3,595 阅读12分钟

问题

在微信公众号开发中,必须要跟微信服务进行通讯、交互,包括但不限于如下内容:

  • 业务域名设置:设置业务域名后,在微信内访问该域名下页面时,不会被重新排版。用户在该域名上进行输入时,不出现安全提示。
  • JS接口安全域名:设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。
  • 网页授权域名:用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。
  • 服务器地址(URL):这个更不用解释了,公众号向我们发送的用户消息数据、用户事件数据等等,都需要通过该接口。

开发的时候真的是Ri了Dog了,每次完成某些功能都需要将代码打包提交到线上测试,不仅繁琐,更是增加了调试的难度和精力!

解决思路

针对以上问题,可以使用内网穿透,让公众号中绑定的域名最终指向我们开发环境本地。

通过搜索,发现natappsunny-ngrok这两款工具可以实现,具体大家可以自行搜索,已经有很完善的说明。

本文我们介绍使用开源的内网穿透工具fatedier/frp+Nginx,自己搭建一个内网穿透隧道,以达到我们的目的。

定义

  1. 本文中使用vue2.x开发公众号网页应用,npm run serve后,本地环境地址为: 041ade0ab9964a83a6668eab8218bee8.png
  2. 本文中使用Golang开发公众号应用服务端,运行端口为:50110,并通过/mpServe接收微信公众号服务器发来的请求。
  3. 本文中使用的域名test.666.com
  4. 文本中云服务器的公网Ip:111.112.113.114

定亿个小目标

目标:

  • (服务器地址(URL))使用test.666.com域名在公众号配置中设置服务器地址,并成功在本地开发环境中收到来自微信服务器发送的消息。
  • (JS接口安全域名)本地开发中可以使用test.666.com域名调用公众号开放的JS接口。
  • (网页授权域名)本地开发中可以使用test.666.com域名进行网页授权登录。
  • 访问test.666.com域名,可以访问到本地开发环境中的公众号网页应用,并且无需在本地打包,实时热更新。
  • 一句话概括:使我们的本地环境跟真正的线上环境一致。

准备

  1. 已备案的的域名,本文中我会将自己的域名打码,使用test.666.com作为代替。
  2. 域名备案的云服务器,公网IP:111.112.113.114
  3. Nginx下载:nginx.org/en/download…
  4. Frp下载:github.com/fatedier/fr…

说明:

  1. 本文的开发环境和云服务器都是Windows,因此下载的工具都是Windows版本,各位请根据自己的开发环境、云服务器环境下载对应的工具版本。
  2. 本文使用的是阿里云的域名,非阿里云的域名控制台可能不一样,请各位根据自己的域名控制台,做相应的配置即可。

难点:

达成这亿个小目标,都需要配置什么,以及存在哪些问题需要解决?

  • 【域名】将域名test.666.com解析到云服务器公网IP:111.112.113.114
  • 【云服务器】访问域名test.666.com后,将指向云服务器的80端口,frp服务端启用HTTP类型代理监听180端口,那么就需要使用Nginx,将域名为test.666.com的请求(80端口),转发到180端口。
  • 【本地环境】frp客户端收到来自frp服务端的数据,该如何处理:
    • 来自域名test.666.com的请求,需要用Nginx转发到网页开发环境的端口:http://192.168.2.222:50101
      • !!!注意!!!:不能转发到http://localhost:50101
    • 来自域名test.666.com/mpServe的请求,需要用Nginx转发到后端应用的端口:50110
  • 在公众号配置中,设置JS接口安全域名业务域名网页授权域名test.666.com时,微信会检测该域名根目录下,其指定的随机校验文本是否存在,但是我们test.666.com指向的是50101开发端口,那么就需要配置Nginx:收到来自test.666.com/MP_verify_****.txt的请求时,直接返回校验字符串。
  • 成功后,发现使用微信开发者工具发现循环报错:Invalid Host/Origin header该如何处理?关闭disableHostCheck配置即可。

开始

一、配置并运行frp

1、frp服务端

a. 打开服务端配置文件frps.ini

aac81c2c5f904e4e920f232d2ab33064.png

b. 配置如下:

[common]
# 服务端监听端口,接收 frpc 的连接,默认值:7000
bind_port = 7000

# 为 HTTP 类型代理监听的端口,启用后才支持 HTTP 类型的代理,未配置则默认不启用
vhost_http_port = 180

# 鉴权方式: token, oidc
authentication_method = token
# 鉴权使用的 token 值
token = token_123456789


# 启用 Dashboard 监听的本地地址
dashboard_addr = 0.0.0.0
# 启用 Dashboard 监听的本地端口
dashboard_port = 7500
# Dashboard 登录账号
dashboard_user = admin
# Dashboard 登录密码
dashboard_pwd = admin

c. 保存后将配置文件frps.ini,以及服务端可执行文件frps.exe 一并上传到云服务器。

8e9af0f6fb644eafbb450d64e6f541a1.png

d. 控制台运行命令:start frps -c frps.ini,启动成功后如下图所示: image.png

e. 检查是否成功:云服务器中使用浏览器打开链接http://localhost:7500,成功打开登录页面即代表成功。(您也可以使用配置文件中设置的账号密码进行登录)

image.png

f.!!!注意!!!:云服务器安区组入方向需要添加:端口7000以及端口180允许,如果需要外网访问Dashboard,还需要添加端口7500允许。

2、云服务器中配置Nginx反向代理

a. Nginx增加配置项,如下:

# 监听80端口的 test.666.com 域名请求,并转发到 180 端口
server {
	listen 80; # 监听80端口
	server_name test.666.com; # 监听的域名
	
	location / {
		# 设置header
		proxy_redirect http://$host/ http://$http_host/;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $host;

		# 转发到180端口
		proxy_pass http://127.0.0.1:180;
	}
}

b. 保存配置后,控制台运行:nginx -s reload让Nginx重新加载配置。

3、frp客户端

a. 打开客户端配置文件frpc.ini

image.png

b. 配置如下:

[common]
# 连接服务端的地址
server_addr = 111.112.113.114
# 连接服务端的端口
server_port = 7000

# 鉴权方式:token, oidc,需要和服务端一致
authentication_method = token
# 鉴权使用的 token 值,需要和服务端一致
token = token_123456789

[web01]
type = http
local_ip = 127.0.0.1
local_port = 80

# 域名
custom_domains = test.666.com

c. 保存后配置文件后,在本地环境中控制台运行命令:start frpc -c frpc.ini,成功后如下图所示:

image.png

二、本地启动Nginx

1. 在Nginx根目录,控制台运行命令:start nginx

2. 浏览器打开链接http://localhost/,成功后如下图所示:

image.png

三、设置域名解析

进入域名控制台,添加一条A记录,指向云服务器公网IP即可,不同域名商的后台都不一样,请各位自行设置。 本文中设置如下:

image.png

四、测试内网穿透效果

根据每个服务商的时间,等待域名解析成功,本文中阿里云大概在10分钟左右。 浏览器打开网址http://test.666.com,如果成功穿透,即可正常展示本地环境搭建的Nginx服务,如下:

image.png

五、喝杯咖啡,小憩一下

到这里,我们已经成功完成自己搭建一个支持HTTP协议的内网穿透隧道。 具体流程如下图:

image.png

看到这里,可能有人会提出疑问:

  • 在云服务器中为什么不让frp服务端直接监听80端口,取消Nginx?
  • 本地环境中同样也可以让frp客户端直接将数据转发到对应端口,为什么还需要加一层Nginx?

带着以上问题,我们继续完成目标,并且在本文最后给出解释。

六、完成目标

1. 完成公众号服务器URL配置:

本地开发环境中,本文的服务端程序,运行在50110端口,并通过/mpServe接收来自微信公众号服务的请求。 也就是说需要让微信公众号将请求发送到我们本地环境的http://localhost:50110/mpServe这个位置。

那通过前面的内网穿透,是不是让公众号将请求发送到http://test.666.com:50110/mpServe就行?

当然不可以,因为微信公众号配置服务器URL的时候不允许在链接中出现端口......Ri了Dog了 Again

那么我们就让本地环境中的Nginx 将/mpServe的数据转发到50110端口不就行了!

a. 本地环境中Nginx添加配置,如下:

server {
		listen 80; # 监听的端口
		server_name test.666.com; # 监听的域名
		
		# 将80端口中收到来自 http://test.666.com/mpServe 的数据,转发到50110端口
		location ^~/mpServe {
		  proxy_pass http://localhost:50110;
		}
	}

b. 保存配置后,命令行执行命令:nginx -s reload 让Nginx重新载入配置。

c. 在微信公众号中配置服务器URL,如图:

image.png

!!!注意!!!:本地环境中50110端口的公众号服务端,需要各位自己处理响应微信发送的Token验证噢,不然穿透是成功的,但是公众号不能验证Token,提交会提示失败!

到这里,我们已实现本地环境中的服务端能成功接收并回复微信公众的发来的消息请求。

2. 完成公众号JS接口安全域名、网页授权域名、业务域名设置时的随机文本校验:

image.png 这个图不陌生吧? 各位在配置公众号设置中JS接口安全域名、网页授权域名、业务域名保存的时候,微信服务器都会验证上图所述的内容。

这个问题很好解决,我们让Nginx在收到/MP_verify_xxxx.txt请求的时候,直接返回校验字符串不就好了,开始动手:

a. 下载文本MP_verify_xxxx.txt文本,打开查看文本中的字符串内容,文本中的字符串为:3wl6rZgobo3WEMaA,如图:

image.png

b.本地环境中Nginx添加配置,如下:

	server {
		listen 80; # 监听的端口
		server_name test.666.com; # 监听的域名
		
		# 将80端口中收到来自 http://test.666.com/mpServe 的数据,转发到50110端口
		location ^~/mpServe {
		  proxy_pass http://localhost:50110;
		}
		
		#  ======= 以下为新增的配置 =======

		# 在80端口中收到 http://test.666.com/MP_verify_3wl6rZgobo3WEMaA.txt 请求时,直接返回字符串 '3wl6rZgobo3WEMaA'
		location ~ ^/MP_verify_3wl6rZgobo3WEMaA.txt {
		  default_type text/html;
		  return 200 '3wl6rZgobo3WEMaA'; 
		}
	}

c.保存配置后,命令行执行命令:nginx -s reload 让Nginx重新载入配置。

d.公众号设置中提交JS接口安全域名、网页授权域名、业务域名设置即可。

成功后,我们就实现了可以在本地环境中调用JS接口、可以使用test.666.com进行网页授权登录等操作。

3. 完成访问test.666.com域名时,可以访问到本地开发环境中的公众号网页应用,并且无需在本地打包,实时热更新:

其实到这里,我们将本地的公众号网页应用打包后,放入Nginx中html目录,使用http://test.666.com域名就能成功运行本地的网页应用。

但是,咱的野心不止于此,凭什么每次更新代码后,还得打包一次?而且这也不好调试啊!

先看看本文中的网页开发环境:

本文中使用vue2.x开发公众号网页应用,npm run serve后,本地环境地址为: 041ade0ab9964a83a6668eab8218bee8.png

看到这里,大家应该知道了吧,让Nginx将http://test.666.com的请求全部反向代理到50101端口不就好了,开始动手:

a. 本地环境中Nginx添加配置,如下:

	server {
		listen 80; # 监听的端口
		server_name test.666.com; # 监听的域名
		
		# 将80端口中收到来自 http://test.666.com/mpServe 的数据,转发到50110端口
		location ^~/mpServe {
		  proxy_pass http://localhost:50110;
		}

		# 在80端口中收到 http://test.666.com/MP_verify_3wl6rZgobo3WEMaA.txt 请求时,直接返回字符串 '3wl6rZgobo3WEMaA'
		location ~ ^/MP_verify_3wl6rZgobo3WEMaA.txt {
		  default_type text/html;
		  return 200 '3wl6rZgobo3WEMaA'; 
		}
		
		#  ======= 以下为新增的配置 =======
		
		# 将80端口中收到来自 http://test.666.com 的数据,转发到50101端口
		location / {
		  proxy_pass http://192.168.2.222:50101; # 这里必须是 192.168.xxx.xxx,不可以是localhost
		}
	}

b.保存配置后,命令行执行命令:nginx -s reload 让Nginx重新载入配置。

c.打开微信开发者工具,打开链接http://test.666.com

c75b5f902f2c4094b7edfcec3d69c503.png

搞定!

七、报错:Invalid Host/Origin header

使用http://test.666.com打开我们本地环境中的网页应用后,无论是微信开发者工具,还是浏览器,控制台都会循环输出错误Invalid Host/Origin header

image.png

这是webpack本身出于安全考虑,检查header中的Host、Origin不一致,以为是受到攻击提示从错误信息。 咱这是内网穿透,当然会出现这个问题,所以,禁用掉disableHostCheck配置即可。

打开配置文件:

image.png

添加如下内容:

...
devServer: { // 在devServer配置中
    ...
    disableHostCheck: true, //  新增该配置项
    ...
}
...

保存配置,重启npm:npm run serve

刷新微信开发者工具 或 浏览器

搞定,烦人的错误无了

ca38f9befaf54a6e82265e1422ebb8b5.png

总结

到这我们已经完成全部目标,再也不用单独写代码检测开发环境和线上环境了,再也不用频繁打包上传到服务器了,再也不用在线上调试到抓狂了!!!

下面,来说说前面提到的疑问,为什么云服务器和本地环境中为什么不将Nginx这一层直接取消。

先看个图:

bfd3a69f603d4d87a8aacbcead45643c.png 先说本地环境: 上图所述,本地环境中需要针对/mpServe//MP_verify_xxx.txt 做出不同的转发,使用Nginx就没什么好说的了。

再说云服务器: 上图所述,如果服务器中运行了其他的应用,那么Nginx还是需要的。 如果服务器中没有其他应用,则完全可以将Nginx这层去掉,将frp服务端的端口配置为80即可。