前话

在前后端分离模式开发项目、部署时,总会不可避免的碰到跨域问题,前前后后也查了不少资料,用了不少解决方案,能解决,但总感觉不太适合。
在经过多番查询、实践,日积月累下也慢慢摸索出一套解决方案。再结合项目部署场景,统一做一次记录。

问题复现

我们在开发时,将前后端分离项目分为 两个前端(客户端、管理端)、一个后端(服务端),两个前端各尽其责,由后端进行统筹。
如此一来,一个项目就启动了三个端口服务,如下:

  • 开发环境:
  • 前端—客户端,端口:100
  • 前端—管理端,端口:200
  • 后端—服务端,端口:300

build后,在线上环境中,服务端依然需要启动一个端口进行服务,如下:

  • 生产环境:
  • 前端—客户端,端口:80
  • 前端—管理端,端口:80
  • 后端—服务端,端口:300

由上述可见,有下列问题需要解决:

  • 开发、生产环境中端口不同导致的跨域问题;
  • build后,本地运行;
  • 生产环境中客户端与服务端(包括其他项目)同时使用80端口;
  • 一台服务器部署多个项目;

统筹一番

考虑到多个项目,我们将每个项目进行了一番规范

项目 Api规范

每个项目,api 根路径与项目名关联,二级路径与本身应用相关联,举栗:

  • 例 1:摩托商城项目 motorcycle-mall:
  • 前端 — 管理端:请求路径:       /moto-mall/mgr/...
  • 前端 — 客户端:请求路径:       /moto-mall/c/...
  • 外部 — 微信公众号:请求路径:/moto-mall/wxmp/...
  • 外部 — Alipay:请求路径:        /moto-mall/alipay/...
  • 以上请求将统一发送给后端,由后端接收并处理:
  • 后端 — 服务端:
  • 接收的请求路径:/moto-mall/...
  • 收到请求后,再通过二级路径确定来自哪里的请求,如:
  • /moto-mall/mgr/... 来自管理端的请求
  • /moto-mall/c/... 来自客户端的请求
  • /moto-mall/wxmp/... 来自公众号的请求
  • 例 2:汽车商城项目 car-mall:
  • 前端 — 管理端:请求路径:       /car-mall/mgr/...
  • 前端 — 客户端:请求路径:       /car-mall/c/...
  • 外部 — 微信公众号:请求路径:/car-mall/wxmp/...
  • 外部 — Alipay:请求路径:        /car-mall/alipay/...
  • 以上请求将统一发送给后端,由后端接收并处理:
  • 后端 — 服务端:
  • 接收的请求路径:/car-mall/...
  • 收到请求后,再通过二级路径确定来自哪里的请求,如:
  • /car-mall/mgr/... 来自管理端的请求
  • /car-mall/c/... 来自客户端的请求
  • /car-mall/wxmp/... 来自公众号的请求

解决问题

一、开发环境中跨域

举例,如下项目:

  • 后端 — 服务端,端口:300,请求路径:/car-mall/c/...
  • 前端 — 客户端,端口:200,请求路径:/car-mall/mgr/...
  • 前端 — 管理端,端口:100接收请求/car-mall/...

前端(客户端和管理端)配置 vue.config.js 文件:

// 将请求路径代理发送到服务端端口

module.exports = {
  devServer: {
    open: true,
    port: 100 or 200, // 客户端或管理端的端口,两个前端应用都需要配置该代理
    proxy: {
      /*
	      process.env.VUE_APP_BASE_API:
	      .env.development 和 .env.production 以及 .env.test 文件中配置的路径
	      根据此处例子:
	      	管理端为:/car-mall/mgr
	      	客户端为:/car-mall/c
      */
      [process.env.VUE_APP_BASE_API]: {
        target: 'http://localhost:300' + process.env.VUE_APP_BASE_API, // 目标路径,管理端端口为300,这里将会把请求直接发到300端口
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: '' // 路径重写
        }
      }
    }
  }
}

proxy 代理后,后端无需配置CrossOrigin,能正常收到请求,且不存在跨域问题。

二、build 后,本地运行以及跨域

前端项目 build 后,默认向 80 端口请求数据,与服务端依然不在同一端口,还是存在跨域问题。

若需要在本地运行已 build 项目,我们使用Nginx代理,并将 80 端口的请求分发给服务端。
配置nginx\conf\nginx.conf文件:

http {
# .......
# 其他配置
# .......

	server {
		listen 80; # 监听80端口
		server_name localhost; # 当前服务的域名
		root E:\\project-dev\\dist-test; # http://localhost指向的目录,将build后的前端项目放入该目录


		# 汽车商城配置
		# 将80端口接收到的 /car-mall/... 路径请求,代理到对应的服务端
		location ^~/car-mall {
			rewrite ^/car-mall/(.*)$ /car-mall/$1 break; # 无特殊情况不需要重写,这里可以注释掉。
			proxy_pass http://localhost:300; # 汽车商城服务端端口为300
		}


		# 摩托商城配置
		# 将80端口接收到的 /moto-mall/... 路径请求,代理到对应的服务端
		location ^~/moto-mall {
			rewrite ^/moto-mall/(.*)$ /moto-mall/$1 break; # 无特殊情况不需要重写,这里可以注释掉。
			proxy_pass http://localhost:301; # 摩托商城服务端端口为301
		}

	}
}

配置完成后start nginx启动 nginx ,若已启动则nginx -s reload重载配置。

将 build 后的前端放入 Nginx 配置中的 root 目录,启动服务端。

最后浏览器中输入 http://localhost 即可完成本地运行。

三、生产环境中跨域以及多项目部署

原理与本地运行大致相同,区别在于需要让 客户端、管理端 分别使用不同的域名访问,并且都是通过默认 80 端口,然后再将请求发给对应的服务端。

  • 1、汽车商城:
  • 前端 — 客户端:
  • 域名:carmall.abc.com
  • 请求路径:/car-mall/c/...
  • 前端 — 管理端:
  • 域名:carmalladmin.abc.com
  • 请求路径:/car-mall/mgr/...
  • 后端 — 服务端:
  • 端口:300
  • 2、摩托商城:
  • 前端 — 客户端:
  • 域名:motomall.abc.com
  • 请求路径:/moto-mall/c/...
  • 前端 — 管理端:
  • 域名:motomalladmin.abc.com
  • 请求路径:/moto-mall/mgr/...
  • 后端 — 服务端:
  • 端口:301

配置nginx\conf\nginx.conf文件:

http {
# .......
# 其他配置
# .......

	# 汽车商城---客户端配置
	server {
		listen 80; # 监听80端口
		server_name carmall.abc.com; # 汽车商城---客户端域名
		root E:\\mall\\car\\client; # http://carmall.abc.com 指向的目录,客户端应用放入该目录
		index index.html;

		location ^~/car-mall { # 将 汽车商城---客户端 的请求代理到服务端
			proxy_pass   http://localhost:300;
		}
	}

	# 汽车商城---管理端配置
	server {
		listen 80; # 监听80端口
		server_name carmalladmin.abc.com; # 汽车商城---管理端域名
		root E:\\mall\\car\\admin; # http://carmalladmin.abc.com 指向的目录,管理端应用放入该目录
		index index.html;

		location ^~/car-mall { # 将 汽车商城---管理端 的请求代理到服务端
			proxy_pass   http://localhost:300;
		}
	}

	# 摩托商城---客户端配置
	server {
		listen 80; # 监听80端口
		server_name motomall.abc.com; # 摩托商城---客户端域名
		root E:\\mall\\moto\\client; # http://motomall.abc.com 指向的目录,客户端应用放入该目录
		index index.html;

		location ^~/moto-mall { # 将 摩托商城---客户端 的请求代理到服务端
			proxy_pass   http://localhost:301;
		}
	}

	# 摩托商城---管理端配置
	server {
		listen 80; # 监听80端口
		server_name motomalladmin.abc.com; # 摩托商城---管理端域名
		root E:\\mall\\moto\\admin; # http://motomalladmin.abc.com 指向的目录,管理端应用放入该目录
		index index.html;

		location ^~/moto-mall { # 将 摩托商城---管理端 的请求代理到服务端
			proxy_pass   http://localhost:301;
		}
	}
}

配置完成后start nginx启动 nginx ,若已启动则nginx -s reload重载配置。

启动服务端。

完成。