开发与部署前后端分离项目(主要解决跨域问题)

ChangeLog

[2020-03-03-update]:项目使用的是 Vue CLI 3(Vue CLI 2 的配置也有提及),现在已经更新到 Vue CLI 4,部分内容可能已过时。

Spring Boot + Gradle 打包为 Docker 可以查看: Spring Boot 项目打包为 Docker 镜像

Tomcat 配置 HTTPS 可以看:Tomcat 配置 HTTPS 证书

说明

后端采用 Spring Boot 开发,部署在 Tomcat;前端采用 Vue CLI3 开发,部署在 nginx;

在开发过程中,前端访问后端提供的 API 会遇到跨域的问题,Vue CLI 里可以直接配置代理解决

但是当项目部署到服务器端,比如把后端放在 Tomcat,前端放在 nginx,又会遇到跨域问题。而此时,开发环境的配置就不起作用了。

该如何解决部署时的跨域问题呢?

可以通过配置 nginx 的代理,使前端正常访问后端提供的 api。

所以问题大致分为三块:Tomcat 部署项目、nginx 部署项目、服务器端配置解决前端访问后端 api 出现的跨域问题。

当然,项目部署前还需要构建。本文不涉及 Vue CLI 如何 build 项目、但是提到了 Spring Boot 构建包以及部署,包括 Spring Boot 直接打 jar 包执行,Spring Boot + Gradle 如何打 war 包,部署 war 包至 Tomcat,以及通过 Docker 构建部署。

其它一些细节方面,本文没有提及 Tomcat 的安装、但是提到了 Vue CLI 在开发环境配置代理解决跨域问题。

还有腾讯云的一些问题,遇到的端口、防火墙等问题,项目如何从本地上传到服务器……

为什么会跨域

前后端分别开发的时候,前端使用了 Vue CLI,用 Node.js 模拟了一个服务器环境。而后端开发使用 Spring Boot,用的 Tomcat 做服务器。

这样导致一个问题 —— 前后端不在同一个服务器环境中。所以前端访问后端的 api 就会产生跨域问题

在开发环境解决跨域问题,Vue CLI 已经提供了解决方案,进行相应配置即可。

[Vue CLI3 与 Vue CLI 2 在开发环境解决跨域问题](# Vue CLI3 与 Vue CLI 2 在开发环境解决跨域问题)

而我在部署前后端分离项目时,将后端项目放在 Tomcat,前端项目部署在 nginx……又会遇到跨域问题。而此时,之前开发环境的配置就无效了。

但也可以在 nginx 配置代理。

nginx 配置代理解决跨域问题

所以解决跨域问题的大概原理就是做个代理,假装前端是在自己的服务器环境下访问后端的 API……

Spring Boot (后端)项目的部署

Spring Boot 自带了 Tomcat 容器,所以想要部署到外部容器,需要做点改动。

改动见下文:[去 Spring Boot 自带容器](#去 Spring Boot 自带容器)

不改动就部署的话,访问会报错 404。

直接 jar 包部署

Idea 使用 Spring Boot 默认方式初始化项目时,在 Gradle 中的配置是默认打包为 jar 包。

也可以把 jar 包放服务器上直接运行,执行 java -jar [project_name].jar 就阔以了。

不过这样运行的程序,关闭终端,程序就停止了。

我们可以通过 Docker 来打包部署,也可以打 war 包部署到外部容器(通常是 Tomcat)。

去 Spring Boot 自带容器

在 Gradle 中把默认的 Tomcat 设置为仅仅在生产环境使用。

1
2
3
dependencies {
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
}

修改 Spring Boot 的入口类,实现 SpringBootServletInitializer 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class ChatroomApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ChatroomApplication.class, args);
}
/**
* 实现SpringBootServletInitializer可以让spring-boot项目在web容器中运行
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(this.getClass());
}
}

Spring Boot + Gradle 打包为 Docker

Spring Boot + Gradle 打包为 Docker 可以查看: Spring Boot 项目打包为 Docker 镜像

Spring Boot + Gradle 打 war 包部署到外部容器

设置打包为 war,在 Gradle 里配置:

apply plugin: 'war'

再构建包就行啦!

war 包部署至 Tomcat 见:[Tomcat 部署(后端)项目](#Tomcat 部署(后端)项目)

Tomcat 部署(后端)项目

Tomcat 的运行没什么说的,将项目打包为 war,放在目录 /webapps 下,启动 Tomcat 后,会自动解压 war 包。

Tomcat 的安装不提。

Spring Boot 项目打 war 包部署到外部容器见文章末。

比如有个包 etob.war ,把它放在 /webapps 下,运行 Tomcat 后,/webapps 下会有一个 war 包解包后的 etob/ 文件夹。

此时如果没有修改配置文件,我们就已经可以通过 公网ip:8080/etob 来访问我们的项目了。

项目默认运行在 8080 端口,要用别的端口可以在 conf/server.xml 里修改。

当然,想要通过 ip:8080 直接访问项目,而不是通过 id:port/project_name 来访问的话,也可以进行配置。

上传项目

可以通过 ftp 上传,也可以 ssh 直接上传。

终端下 ssh 上传本地文件服务器。

安装 scp。

执行 scp 本地文件地址 用户名@ip:/服务器存放文件的文件夹

scp /home/etob.war hqweay@192.xxx.26.21:/src/local/tomcat/webapps

【可能过时】配置

方式(一)

把 war 包放在 /webapp 下,执行 ./start.sh 即可。

Tomcat 会自动解压 war 包。不过这样,Tomcat 只能部署一个项目。

方式(二)

删除原 webapps/ROOT 目录下的所有文件,修改文件 conf/server.xml,在 Host 节点下增加如下 Context 的内容配置:

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
…………
<Context path="" docBase="C:/apache-tomcat-6.0.32/myapps/bc.war"></Context>
</Host>

注意

[复制于网络,忘记来源了。]

  • 1)path 的值设置为空。
  • 2)应用不要放到 Tomcat 的 webapps 目录下(如上述配置是放到自定义的文件夹 myapps 内的),否则访问时路径会有问题。
  • 3)docBase 指定到绝对路径。

这样配置后重启 Tomcat,如果 docBase 指向的是 war 文件,则会自动将 war 解压到 webapps/ROOT 目录。

如果 docBase 指向的是应用已解压好的目录,如 docBase="C:/apache-tomcat-6.0.32/myapps/bc",则 Tomcat 不会在 webapps/ROOT 目录下继续生成目录。(这种情况下,这样就可以不用删除 webapps/ROOT 目录,但 webapps/ROOT 目录内的内容是无法访问的),访问时将直接使用docBase指定的目录。

【建议使用】方式(三)

与方法二类似,但不是修改全局配置文件 conf/server.xml,而是在 conf/Catalina/localhost 目录下增加新的文件 ROOT.xml (注意大小写),文件内容如下:

1
2
<?xml version='1.0' encoding='utf-8'?>
<Context path="/" docBase="/usr/local/src/java/tomcat/apache-tomcat-8.0.48/test" debug="0" privileged="true" reloadable="true"/>

nginx 部署(前端)项目

安装与使用不提。

配置解决跨域问题

使用前需要配置防火墙,以开放端口。

nginx 进行相关配置,需要修改 /nginx/conf/nginx.conf 文件(版本:nginx-1.15.2,不同版本配置文件路径不同 )。

我这样配置的,先看配置,再解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
## (省略其他)
http{
server {
listen 80;
location / {
root /home/hqweay/etob/dist;
index index.html;
}
location /api {
add_header 'Access-Control-Allow-Origin' '*';
proxy_pass http://localhost:89/etob/api;
}
}

注意下面的 location /api 这一项是配置代理的后端接口。

配置说明

路径重写

我的后端项目为 api.war ,项目内的 api 又是 api/getXXX 这样的格式。所以后端项目部署后,访问的项目根目录是 ip:port/api。(当然可以用 ip 直接当根,不过我没配置……)

而访问 api 则需要用 ip:端口/api/api/xxxx ……

所以在 nginx 配置中有这么一行:

1
proxy_pass http://localhost:89/api/api;

就是用 /api 替换了 http://localhost:89/api/api 。(部署在 tomcat 的后端项目)

请求头

至于上面的 add_header 'Access-Control-Allow-Origin' '*'; ,是据说 Vue 项目必须的配置。我按照别人说的加了,没有测试过不加会是什么情况。(也许不影响)

项目路径

其他没什么说的了,再解释下下面这个。

1
2
3
4
location / {
root /home/hqweay/etob/dist;
index index.html;
}

root 后面是前端代码所在的文件夹(随便放在服务器的哪里都可以,因为是绝对路径嘛……)。index 就是根文件(访问时默认打开的文件)。

Vue CLI3 与 Vue CLI 2 在开发环境解决跨域问题

后端环境:http://localhost:8089

后端接口:http://localhost:8089/api/xxxx

前端环境:http://localhost:80

vue.config.js 中配置:

PS:Vue CLI 2 是修改 config/index.js 的配置项 dev(开发环境)中的 ProxyTable(代理)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
devServer: {
proxyTable: {
'/api': { //使用"/api"来代替"http://localhost:8089"
target: 'http://localhost:8089', //源地址
changeOrigin: true, //改变源
pathRewrite: {
'^/api': '/api'
// 路径重写。因为原来的 api 路径含有 /api
// 如 http://localhost:8089/api/chatRoom/addMessage
// 若不重写,因为使用了 "/api" 来代替 "http://localhost:8089"
// 前端使用的 api 就类似:/api/api/chatRoom/addMEssage
// 重写后,路径就类似这样:/api/chatRoom/addMEssage
}
}
}
}
}

配置后,前端就可以直接用 api/xxxxx 作为接口访问。

但是,这样只是解决了开发环境的问题。

前端项目部署到服务器环境(线上),开发环境的配置就失效了嘛。

所以需要在 nginx 上配置一下代理!而配置,就是上面那样啦!

阔不阔以把前后端都放在 Tomcat ?

【过时】关于端口的一些沙雕问题

0x01 腾讯云

腾讯云要开启端口,首先需要到官网控制台修改权限组!!(没注意到这个问题折腾了很久)

然后设置开启端口就好了。

0x02 防火墙

处理中遇到一个特别诡异的问题,只有 8080 端口可以在公网访问……

但是其他端口可以在内网访问,比如设置 nginx 在 89 端口,在服务器内用curl localhost:89,可以收到返回信息。

扫描公网 ip,发现只开放了 8080、22(ssh)端口。

因为安全组再三确认已经搞了,怀疑是防火墙问题,但是自认为已经把防火墙关了,还是不行。

防火墙,安全组……翻来覆去地确认,不知问题在哪。

最后执行 iptables -F (清空防火墙规则),其他端口就能用了……现在还是不知所以然。

PS: firewalld 服务与 iptables 服务都不是真正的防火墙,它们都只是用来定义防火墙规则功能的防火墙管理工具。将定义好的规则交由内核中的 netfilter网络过滤器来读取,从而真正实现防火墙功能,所以其实在配置规则的思路上是完全一致的。

来源: Centos 7禁用firewalld安装iptables防火墙

猜测原因

我发现自己上装了 firewalldiptables 两个服务,猜测是不是这个原因造成了上面的问题,总之先关闭一个。决定关闭 iptables

怎么关闭和卸载iptables?小心了!!

不要用 remove 命令,用停用。

停用 chkconfig iptables off

开启 chkconfig iptables on

**我怀疑是百度 「端口操作」 时,网上的文章没有区分 iptables,firewalld 两种不同环境,直接给出命令,然后我就面向搜索引擎编程(copy,paste),把两个服务都搞上了……**然后关闭了这个防火墙那边仍开着,关闭了那个,这边又开启了,辗转反复,所以一直没能成功……

firewalld 与 iptables

注意区分 firewalld 与 iptables。