前端工程化(4):http-proxy-middleware在多环境下的代理应用

6,977 阅读5分钟

假设开发的时候存在以下几套环境:

  • 自己本地的MOCK环境(自己造一些随心所欲的数据)

  • RAP平台环境(与后端约定好数据格式)

  • 后端开发人员的本地环境(与后端联调)

  • 阿里云1测试环境(部署到测试环境,测试人员测试)

  • 阿里云2测试环境(环境紧张,需要部署到别的测试环境进行测试)

  • 阿里云3测试环境(公司不差钱,买了至少5个服务器)

  • 阿里云4测试环境

  • 阿里云5测试环境

    ......

是的,就是有这么多的环境等着你去访问它们。那么跑前端代码的时候如何才能与各个环境进行联调呢?最简单直接的办法就是修改proxy选项中的target,然后再重新执行开发脚本。这个方法在项目小的情况你还能忍受,如果项目大的话,过长的编译时间将直接消磨掉你的工作激情。

所以本文提出了一个能让你在联调时无缝切换不同环境的方案。如果你没用到代理或者后端设置了跨域资源共享,这篇文章看看也无妨。如果你用到了http-proxy-middleware来处理跨域的问题,那这篇文章你更得看看。

第一步、配置环境

定义一个变量来承载所需要的路由键值对:

测试环境

let PROXY_ROUTER = {
  'dev-aliyun1.test.com': 'https://m-aliyun1.test.com:10454',
  'dev-aliyun2.test.com': 'https://m-aliyun2.test.com:10454',
  'dev-aliyun3.test.com': 'https://m-aliyun3.test.com:10454',
  'dev-aliyun4.test.com': 'https://m-aliyun4.test.com:10454',
  'dev-aliyun5.test.com': 'https://m-aliyun5.test.com:10454'
}

每个开发域名dev-aliyunX.test.com域名都对应一个测试环境。

RAP环境

let PROXY_ROUTER = {
  ...
  'dev-rap.test.com': 'http://192.168.4.102:9999/mockjsdata/400',
}

用过RAP的同学都知道,不同模块挂着不同的id(就是👆url中的400),那这样是不是得把所需要的id都写上去?其实RAP提供了项目路由功能,能把不同模块的id路由到一个模块id上:

各个模块之间使用逗号隔开,这样只需要写一个主id就行。

后端开发本地环境

// 填写后端开发的ip地址和端口号
const devHostName = '172.16.9.xx'
const devServerPort = '8085'

let PROXY_ROUTER = {
  ...
  'dev-debug.test.com': `http://${devHostName}:${devServerPort}`
}

与后端开发联调就避免不了要重启项目了,因为后端开发的ip未知,需要手动填写。

本地mock环境

使用webpack-dev-server + mocker-api来启动本地mock-server

根目录下新建mock文件夹,编写index.js文件:

import Mock from 'mockjs' // 引入mock
import { mockMemberInfo } from './memberinfo'

const proxy = {
  'GET /cloud/ssc-server/model/authModels.do': {
    code: 200,
    msg: 'success',
    data: {
      userId: 5,
      userName: 'Option 5'
    }
  }
}
module.exports = proxy

vue.config.js中配置:

const apiMocker = require('mocker-api')

module.exports = {
  devServer: {
    ...
    before (app) {
      apiMocker(app, path.resolve('./mock/index.js'))
    },
    disableHostCheck: true
  },
  ...
}

只要接口匹配上了,就会映射本地mock-server环境,匹配不上再映射到别的环境,所以要切换环境,就得修改接口的baseUrl

let baseURL = ''
if (process.env.NODE_ENV === 'development') {
  if (window.location.hostname !== 'localhost') {
    baseURL = '/api'
  }
}

注:所有的开发域名都需要配置host,如下:127.0.0.1 dev-m-aliyun1.test.com ...

第二步、配置路由

统一前缀

统一对所有的接口进行代理处理,如果用了axios请求库,可以这么配置:

axios.defaults.baseURL = process.env.NODE_ENV === 'production' ? '' : '/api'

路由分发

http-proxy-middleware提供了一个router选项(接收一个对象或者函数):可以使用host或者path或者host+path匹配特定的请求来重写option.target,也就是说router命中的url优先级高于target配置的url

处理前缀为/api的接口:

let proxyTable = {
  '/api': ''
}

Object.entries(proxyTable).forEach(([key, value], index) => {
  proxyObj[key] = {
    target: value || 'http://localhost:8080',
    changeOrigin: true,
    pathRewrite (path, req) {
      return path.replace(/\/api/, '')
    },
    router (req) {
      let hostname = req.headers.host.split(':')[0]
      return value === '' ? PROXY_ROUTER[hostname] : value
    }
  }
})

通过接口请求的host,来匹配PROXY_ROUTER中对应的环境。如果匹配上了,router function返回匹配上的环境地址从而覆盖掉target。如果没匹配上,router function返回undefinedtarget生效,即代理本地mock环境。

在现实开发中还可能会出现一种奇葩的需求:你在a环境联调,但有的接口还在b环境上。要解决这种问题,你只需要在proxyTable中加上:

let proxyTable = {
  '/api/cms/renderData': 'https://m-aliyun2.test.com:10454',
  '/api': ''
}

这样无论你怎么切换域名,/api/cms/renderData始终将会被代理到https://m-aliyun2.test.com:10454环境上。

顺序很重要,第一个匹配上的将会生效。

总结

vue-cli3为脚手架,在根目录新建一个proxy.config.js文件:

let proxyObj = {}

// 与开发联调的时候由于未知 需手动填写
const devHostName = '172.16.9.xx'
const devServerPort = '8085'
// 代理路由表
let PROXY_ROUTER = {
  'dev-m-aliyun1.test.com': 'https://m-aliyun1.test.com:10454', // 代理到阿里云1测试环境
  'dev-m-aliyun2.test.com': 'https://m-aliyun2.test.com:10454', // 代理到阿里云2测试环境
  'dev-m-aliyun3.test.com': 'https://m-aliyun3.test.com:10454', // 代理到阿里云3测试环境
  'dev-m-aliyun4.test.com': 'https://m-aliyun4.test.com:10454', // 代理到阿里云4测试环境
  'dev-m-aliyun5.test.com': 'https://m-aliyun5.test.com:10454', // 代理到阿里云5测试环境
  'dev-rap.test.com': 'http://192.168.4.102:9999/mockjsdata/400', // 代理到rap环境
  'dev-debug.test.com': `http://${devHostName}:${devServerPort}` // 代理到后端开发人员的ip环境
}
// 代理接口
let proxyTable = {
  '/api/cms/renderData': 'https://m-aliyun2.test.com:10454',
  '/api': ''
}

Object.entries(proxyTable).forEach(([key, value], index) => {
  proxyObj[key] = {
    target: value || 'http://localhost:8080', // 代理到本地mock环境
    changeOrigin: true,
    pathRewrite (path, req) {
      return path.replace(/\/api/, '')
    },
    router (req) {
      let hostname = req.headers.host.split(':')[0]
      return value === '' ? PROXY_ROUTER[hostname] : value
    }
  }
})

module.exports = proxyObj

vue.config.js文件中修改:

const proxyBase = require('./proxy.config')

module.exports = {
  devServer: {
    proxy: proxyBase,
    disableHostCheck: true // 新版的webpack-dev-server出于安全考虑,默认检查hostname,如果hostname不是配置内的,将中断访问
  },
  ...
}

上述代理配置以后,只需要通过切换域名就可以无缝切换相应的测试环境,无需重启前端服务,这对所处多个开发环境的前端开发者来说大大提高了工作效率。

其实,代理只是个辅助我们开发的工具,将代理硬编码到项目代码中去的话,可维护性的确会差一点。可是,对于我们软件开发来说,工具只能成为我们的充分项,而不能成为我们的必要项。所以,我的这个方案各有利弊,大家可以依照自己项目中的实际需求来酌情使用。