Nginx 反向代理实现线上测试环境(微信开发类项目)

环境说明

  • Ubuntu 16.04 LTS
  • Nginx version: nginx/1.10.3 (Ubuntu)
  • PHP 7.1.18
  • Laravel 5.5

需求说明

微信开发类项目,需要要调试微信接口,本地开发上可以采用微信开发者工具和微信测试公众号模拟运行环境和接口。但有部分微信商户号的接口例如微信支付,目前需要用沙盒模拟的方式开发,不够方便。
因此,我们想要构建一个能用于微信开发项目的线上测试环境,能调取到真实微信公众号的接口方便线上测试。

思路

要构建线上测试环境最直接的想法就是额外购买一台硬件参数、环境配置与生产环境服务器完全一致的服务器。对接微信平台方面,还需额外注册一个开通微信认证的微信公众号。
这么做虽然能解决问题,但增加了额外成本,而且增加了服务器、微信账号等等的额外维护工作,对于我们这种初创技术团队来讲不是上策。

有没有更低成本和更便于系统维护迭代的方案呢?
经过一番折腾,我们摸索出如下方案,分享出来欢迎交流。

解决方案分享

问题的关键在于如何解决解决微信接入问题

做过微信开发的朋友都知道,接入微信,需要在微信公众号后台填写一个绑定服务器的备案域名

未分类

问题是 URL 这一项,可填值是唯一的且区分子域名。
例如:

http://www.project.com/login 指向生产环境
http://dev.project.com/login 指向线上测试环境
那么微信公众号只能选择接入一个域名。

我们采取Nginx反向代理的功能去解决,即当请求访问 http://www.project.com 这一域名,Nginx服务器通过路径规则匹配,实现请求转抛,以指向不同的项目目录。
例如:

http://www.project.com/login 指向生产环境
http://www.project.com/dev/login 指向线上测试环境

我们通过配置 Nginx 服务器,使得请求匹配到 /dev/ 则将请求转抛给测试环境下的项目去处理

下面是 Nginx 配置的代码实现

代理管理配置文件 /etc/nginx/sites-available/project.proxy.conf

server {
    listen 80;

    server_name 127.0.0.1 www.project.com;

    index index.html index.php;

    charset utf-8;
    access_log /var/log/nginx/project.proxy.access.log;
    error_log /var/log/nginx/project.proxy.error.log;

    # 生产环境
    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $host:$server_port;
    }

    # 线上测试环境
    location ^~ /dev/ {
        proxy_pass http://127.0.0.1:9002;
        proxy_set_header Host $host:$server_port;
    }
}

除了 Nginx 服务器的配置,还需要对项目的配置环境做设置。
主要是进入项目的请求加上统一前缀,这个不同框架有不同的实现,下面仅以 php 框架 Laravel 为例:
Laravel 项目下,
先在 .env 配置环境中增加变量 PREFIX

PREFIX=/dev  // 前面加 / 是为了解决后续静态资源处理的问题,算是一个小坑

然后修改 app/Providers/RouterServiceProvider.php 文件中的 mapWebRouters() :

protected function mapWebRoutes()
    {
        // 从配置文件中获取前缀
        $prefix = env("PREFIX") === "" ? "" : explode('/',env("PREFIX"))[1];
        Route::prefix($prefix) // 给路由添加统一前缀 
             ->middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

实际部署中生产环境和线上测试环境的 .env 文件中的 PREFIX 分别配置为 “” 和 “/dev” 即可。

到这里,本以为万事俱备。

一访问却发现静态文件404。

排查后发现,问题在于静态文件引用路径,例如:

<script src="/dev/test/test.js"></script>

实际页面访问时,这个资源请求路径会被拼接上域名,即最终变为:

http://www.project.com/dev/test/test.js

而 Laravel 的静态资源全部放置在 public 目录下,由于添加了统一路由前缀,所以上面的 URL 并不会指向 public 目录所在的资源目录,而是被当作路由请求处理了…

最终我的处理方案是,修改 Nginx 配置,对请求的 URI 路径做规则校验,匹配到 .js 或 .css 结尾的请求,去除 URL 中的 /dev/ 字符串。

修改后的 /etc/nginx/sites-available/project.proxy.conf 配置代码如下:

server {
    listen 80;

    server_name 127.0.0.1 www.project.com;

    index index.html index.php;

    charset utf-8;
    access_log /var/log/nginx/project.proxy.access.log;
    error_log /var/log/nginx/project.proxy.error.log;

    # 生产环境
    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $host:$server_port;
    }

    # 线上测试环境
    location ^~ /dev/ {
        proxy_pass http://127.0.0.1:9002;
        # 静态资源过滤 /dev/
        if ($request_uri ~* .(?:js|css)$) {
                rewrite /dev/(.+)$ /$1 break;
        }
        proxy_set_header Host $host:$server_port;
    }
}

现在当访问

http://www.project.com/dev/test/test.js

请求转抛后,会被处理成

http://www.project.com/test/test.js

因此能正常访问到 Laravel 项目中的 public 资源目录

坑记录

过程中还是踩了不少的坑:

1.Nginx 服务器代理配置时

proxy_set_header Host $host:$server_port;

这行代码必不可少,否则初次访问 http://www.project.com/dev/login 能去到正确目录,但后续项目内所有请求都会被设置为 http://127.0.0.1:9002 开头…

2.项目中的静态资源引用路径也要改变,如:

<script src="/dev/test/test.js"></script>

上面的是编译后的运行代码,实际开发时的代码应该是( Laravel 框架的 blade 模板语法):

<script src="{{ env('PREFIX') }}/test/test.js"></script>

3.静态资源方面还存在的隐患,如果资源是异步引用的,那就凉凉。这种情况多出现在引用工具库,另外在引用图片等静态资源方面也会比较麻烦…

待优化问题

静态资源引用方面存在的隐患促使需要更优解决方案,目前想到的优化方案是:

  1. 静态资源全部采用 CDN 方式引用,避免路径问题。
  2. 样式 icon 全部采用 font-icon 方式,避免图标元素的路径问题。

总结

本文主要分享了单一服务器构建微信项目线上测试环境的方式(同一个公众号),主要有以下要点:

  1. 通过 Nginx 反向代理机制,实现请求分发
  2. 通过 项目环境配置,实现请求在项目内添加统一路由前缀
  3. 项目中静态资源路径问题导致的坑,对此的处理方法及优化思路