假期一直在下雨,抽空整理了下小伙伴们问的比较多的前后端分离的部署问题。
1.问题焦点
问题焦点主要在于跨域,开发环境和生产环境是两种完全不懂的配置思路。
如果你直接在项目中引入 Vue,像用 jQuery 那样用 Vue,那没什么问题,你应该也不会有跨域的疑问。但是如果你做的是单页面应用(SPA),那么必然会有这样的疑问,跨域问题怎么搞!
因为在单页面应用中,前端项目可以单独通过 node 启动,它单独占用一个端口,后端项目启动后也是另外一个端口,此时从前端发送请求到后端,由于两者处于不同的端口之上,因此必然存在一个跨域问题。
但是大家想想,这个跨域有可能只是在开发环境下存在,生产环境下有可能不存在。因为当项目开发完成之后,我们对前端项目进行打包,打包后部署在 Nginx 上或者直接拷贝到后端项目中运行都可以(一般使用前者):
- 如果是前者,后端接口也通过 Nginx 进行映射,这个时候就不会存在跨域问题了
- 如果是后者,那就更简单了,部署的时候前后端代码放在一起,更不会有跨域问题了
因此,解决这个所谓的 “跨域” 问题,我们不能按照传统的思路来(传统思路主要是 JSONP 或者 CORS),因为在项目真正上线后,所谓的跨域问题可能就会消失。
那么这个问题怎么解决呢?我们可以在前端 nodejs 中配置请求转发。
配置请求转发其实不难,不过 vue-cli2 和 vue-cli3 之后的写法稍有不同,我们来分别看下。
先来看开发环境配置。
2.开发环境配置
2.1 vue-cli2 方案
如果我们使用的 vue-cli2 来创建的 SPA 应用,创建成功之后,在项目的 config 目录下有一个 index.js 文件,在这个文件中,我们可以进行请求转发配置,如下图:
配置内容如下:
1 | module.exports = { |
proxyTable 就是我们配置的转发路由表。这个里边我们一共配置了两个规则:
- 第一个是拦截所有 HTTP 请求,将之转发到后端服务器上(前端默认端口是 8080),后端的端口是 8082。至于拦截规则
/
,大家可以自定义,根据实际情况来写,例如所有的 HTTP 请求都有一个统一的前缀 api,那么这里就可以写/api
。 - 第二个是拦截所有的 websocket 请求进行转发,我这里给所有的 websocket 请求取了一个统一的前缀
/ws
如果你有更多的拦截规则,继续在这里配置就可以了,这些配置只会在开发环境下生效,当项目编译打包时,这些配置是不会打包进去的,也就是说,项目发布的时候,这些配置是失效的,这个时候我们通过 Nginx 或者将前端代码拷贝到后端,就可以解决生产环境下的跨域问题了(相当于开发时候的跨域在生产环境下不存在)。
相对来说,vue-cli2 在这里的配置还比较容易。
2.2 vue-cli3 方案
vue-cli3 和 vue-cli4 的思路基本一致,以 vue-cli3 为例:
使用 vue-cli3 创建的 SPA 应用,没有 config 目录了,因此请求转发的配置我们要在 vue.config.js 这个配置文件中来配置。
1 | let proxyObj = {}; |
首先我们创建一个 proxyObj 用来放各种代理对象,至于代理的内容这里的则和 vue-cli2 中的没有太多差异。要注意的是,HTTP 请求代理中,多了一个属性 ws: false
,用过 vue-cli3 同学可能发现了,如果不加这个属性,浏览器控制台会一直报连不上 socket 的错,加上就没事了。
最后在 devServer 中指定项目的 host 和 port ,然后再配置一下 proxy 对象就可以啦。
这就是我们在 vue-cli3 中请求的配置。
不过这里的配置老实说没有什么难度,做过一次就会啦,要是没做过,头一次可能得折腾半天。
3. 生产环境配置
很多人对前后端分离部署感到困惑,其实主要是困惑跨域问题怎么解决。因为前后端分离项目在开发的时候,前端通过 nodejs 来运行,需要一个单独的端口,后端通过 Tomcat 或者 Jetty 来运行,也需要端口,两个不同的端口,就造成了跨域。
但是开发完成后,我们会对前端项目编译打包,编译打包完成之后,就只剩下一堆 js、css 以及 html 文件了,我们把这些编译打包后的文件拷贝到后端项目中,这样再去运行就不存在跨域问题了。
整体上来看,生产环境的部署,我们有两种思路:
- 一种就是将前端项目打包编译之后,放到后端项目中(例如 Spring Boot 项目的
src/main/resources/static
目录下) - 另外一种则是将前端打包之后的静态资源用 Nginx 来部署,后端单独部署只需要单纯的提供接口即可(推荐)。
分别来看。
3.1 前后端一起部署
这种方式比较简单,大家作为了解即可,生产环境一般采用第二种方案。
首先在前端 Vue 项目中执行 npm run build
命令,将前端文件进行打包。
编译打包成功后,将打包好的 js、css、html、image、fonts 等文件和文件夹拷贝到 Spring Boot 项目的 src/main/resources/static
目录下,然后在 Spring Security 中对前端静态资源配置放行,如下:
1 | @Override |
然后将 Spring Boot 项目打包成 jar,执行命令启动即可:
1 | nohup java -jar vhr.jar > vhr.log & |
3.2 前后端分开部署
分开部署就离不开 Nginx。
3.2.1 Nginx 介绍
3.2.1.1 基本介绍
Nginx
是一个高性能的 HTTP
和反向代理 web
服务器,同时也提供了 IMAP/POP3/SMTP
服务。
Nginx
是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru
站点开发的,第一个公开版本 0.1.0
发布于 2004
年 10
月 4
日。
Nginx
特点是占有内存少,并发能力强。
事实上 nginx
的并发能力确实在同类型的网页服务器中表现较好,一般来说,如果我们在项目中引入了 Nginx
,我们的项目架构可能是这样:
在这样的架构中 , Nginx
所代表的角色叫做负载均衡服务器或者反向代理服务器,所有请求首先到达 Nginx
上,再由 Nginx
根据提前配置好的转发规则,将客户端发来的请求转发到某一个 Tomcat
上去。
那么这里涉及到两个概念:
- 负载均衡服务器
就是进行请求转发,降低某一个服务器的压力。负载均衡策略很多,也有很多层,对于一些大型网站基本上从 DNS
就开始负载均衡,负载均衡有硬件和软件之分,各自代表分别是 F5
和 Nginx
(目前 Nginx
已经被 F5
收购),早些年,也可以使用 Apache
来做负载均衡,但是效率不如 Nginx
,所以现在主流方案是 Nginx
。
- 反向代理服务器:
另一个概念是反向代理服务器,得先说正向代理,看下面一张图:
在这个过程中,Google 并不知道真正访问它的客户端是谁,它只知道这个中间服务器在访问它。因此,这里的代理,实际上是中间服务器代理了客户端,这种代理叫做正向代理。
那么什么是反向代理呢?看下面一张图:
在这个过程中,10086 这个号码相当于是一个代理,真正提供服务的,是话务员,但是对于客户来说,他不关心到底是哪一个话务员提供的服务,他只需要记得 10086 这个号码就行了。
所有的请求打到 10086 上,再由 10086 将请求转发给某一个话务员去处理。因此,在这里,10086 就相当于是一个代理,只不过它代理的是话务员而不是客户端,这种代理称之为反向代理。
3.2.1.2 Nginx 的优势
在 Java 开发中,Nginx 有着非常广泛的使用,随便举几点:
- 使用 Nginx 做静态资源服务器:Java 中的资源可以分为动态和静态,动态需要经过 Tomcat 解析之后,才能返回给浏览器,例如 JSP 页面、Freemarker 页面、控制器返回的 JSON 数据等,都算作动态资源,动态资源经过了 Tomcat 处理,速度必然降低。对于静态资源,例如图片、HTML、JS、CSS 等资源,这种资源可以不必经过 Tomcat 解析,当客户端请求这些资源时,之间将资源返回给客户端就行了。此时,可以使用 Nginx 搭建静态资源服务器,将静态资源直接返回给客户端。
- 使用 Nginx 做负载均衡服务器,无论是使用 Dubbo 还是 Spirng Cloud ,除了使用各自自带的负载均衡策略之外,也都可以使用 Nginx 做负载均衡服务器。
- 支持高并发、内存消耗少、成本低廉、配置简单、运行稳定等。
3.2.1.3 Nginx 安装
由于基本上都是在 Linux 上使用 Nginx,因此松哥这里主要向大家展示 CentOS 7 安装 Nginx:
- 首先下载 Nginx
1 | wget http://nginx.org/download/nginx-1.17.0.tar.gz |
然后解压下载的目录,进入解压目录中,在编译安装之前,需要安装两个依赖:
1 | yum -y install pcre-devel |
然后开始编译安装:
1 | ./configure |
装好之后,默认安装位置在 :
1 | /usr/local/nginx/sbin/nginx |
进入到该目录的 sbin
目录下,执行 nginx
即可启动 Nginx
:
Nginx 启动成功之后,在浏览器中直接访问 Nginx 地址:
看到如上页面,表示 Nginx 已经安装成功了。
如果修改了 Nginx 配置,则可以通过如下命令重新加载 Nginx 配置文件:
1 | ./nginx -s reload |
3.2.1.4 为什么用 Nginx
结合 Nginx 来部署前后端分离项目算是目前的主流方案。一来部署方便,二来通过动静分离也可以有效提高项目的运行效率。
大家知道我们项目中的资源包含动态资源和静态资源两种,其中:
- 动态资源就是那些需要经过容器处理的资源,例如 jsp、freemarker、各种接口等。
- 静态资源则是那些不需要经过容器处理,收到客户端请求就可以直接返回的资源,像 js、css、html 以及各种格式的图片,都属于静态资源。
将动静资源分开部署,可以有效提高静态资源的加载速度以及整个系统的运行效率。
在前后端分离项目部署中,我们用 Nginx 来做一个反向代理服务器,它既可以代理动态请求,也可以直接提供静态资源访问。
无论是动态请求还是静态请求,都是首先到达 Nginx,再从 Nginx 上去查找页面或接口,这样,原本存在的所谓跨域问题现在就不存在了。
3.2.2 部署步骤
3.2.2.1 后端部署
后端接口的部署,主要看项目的形式,如果就是普通的 SSM 项目,那就提前准备好 Tomcat ,在 Tomcat 中部署项目,如果是 Spring Boot 项目,可以通过命令直接启动 jar,如果是微服务项目,存在多个 jar 的话,可以结合 Docker 来部署(参考一键部署 Spring Boot 到远程 Docker 容器),无论是那种形式,对于我们 Java 工程师来说,这都不是问题,我相信这一步大家都能搞定。
后端项目可以在一个非 80 端口上部署,部署成功之后,因为这个后端项目只是提供接口,所以我们并不会直接去访问他。而是通过 Nginx 请求转发来访问这个后端接口。
松哥这里以我 2018 年为一个律所的小程序为例,后端是一个 Spring Boot 工程,那么我可以通过 Docker 部署,也可以直接通过命令来启动,这里简单点,直接通过命令来启动 jar ,如下:
1 | nohup java -jar jinlu.jar > vhr.log & |
后端启动成功之后,我并不急着直接去访问后端,而是安装并且去配置一个 Nginx,通过 Nginx 来转发请求。
这里我们在 nginx.conf 中做出如下配置:
首先配置上游服务器:
1 | upstream zqq.com{ |
在这里主要是配置服务端的地址,如果服务端是集群化部署,那么这里就会有多个服务端地址,然后可以通过权重或者 ip hash 等方式进行请求分发。
然后我们在 server 中配置转发规则:
1 | location /jinlu/ { |
这样配置完成后,假设我目前的域名是 javaboy.org,那么用户通过 http://www.javaboy.org/jinlu/**
格式的地址就可以访问到我服务端的接口。
3.2.2.2 前端部署
以 Vue 为例,如果是 SPA 应用,项目打包之后,就是一个 index.html 还有几个 js、css、images 以及 fonts ,这些都是静态文件,我们将静态文件首先上传到服务器,然后在 nginx.conf 中配置静态资源访问,具体配置如下:
1 | location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff|html|txt|pdf|) { |
当然我这里是按照资源类型来拦截的,即后缀为 js、css、ico 等的文件,统统都不进行请求分发,直接从本地的 /usr/local/nginx/html/ 目录下读取并返回到前端(我们需要将静态资源文件上传到 /usr/local/nginx/html/
目录下)。
如果我们的服务器上部署了多个项目,这种写法就不太合适,因为多个项目的前端静态文件肯定要分门别类,各自放好的,这个时候我们一样可以通过路径来拦截,配置如下:
1 | location /jinlu-admin/ { |
这样,请求路径是 /jinlu-admin/ 格式的请求,则不会进行请求分发,而是直接从本机的 /usr/local/nginx/html/jinlu-admin/
目录下返回相关资源。采用这方方式配置静态资源,我们就可以部署多个项目了,多个项目的部署方式和上面的一样。
这样部署完成之后,假设我的域名是 javaboy.org ,那么用户通过 http://www.javaboy.org/jinlu-admin/**
格式的请求就可以访问到前端资源了。
此时大家发现,前端的静态资源和后端的接口现在处于同一个域之中了(请求协议、域名、端口现在都一致),这样就不存在跨域问题,所以我一开始基说不必用 JSONP 或者 CORS 去解决跨域。当然可能还有一些非常特殊情况需要在 nginx 中配置跨域,这个松哥以后再和大家细聊~
4. Docker 部署
上面的部署方案实际上可以解决大部分的项目部署,如果项目的模块比较多,也可以使用 Docker 部署,公众号后台回复 Docker,可以查看松哥的自己纯手敲的 Docker 教程。
容器化部署的话,如果模块比较多的话,我们可能还需要结合 Jenkins ,这里我就单纯的从 Docker 容器的角度来说说。
Docker 部署的话,也是两种思路,直接部署或者使用 Jib。这个松哥之前发过相关文章,这里就不再赘述。
5.相关视频
松哥之前也录制过一下部署的相关视频,大家可以作为参考。
好了,看些资料看完,相信小伙伴们对于前后端分离的部署已经有一个基本的认知了。觉得有收获,记得点个在看哦~