部署前后端分离项目

ChangeLog

[2019-10-09-update]

[2020-02-10-update]

[2020-03-03-update]:文章没讲清楚的地方有点多。且使用的是 Vue CLI 3,现在已经更新到 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 + Gradle 如何打 war 包。

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

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

导航

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

nginx 配置代理解决跨域问题

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 root@192.365.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"/>

为什么会跨域

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

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

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 这一项是配置代理的后端接口。

配置说明

路径重写

nginx 上的配置也有点奇怪。

因为我把后端部署上去后,访问的根目录是 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 ?

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

有一点没说,因为 Spring Boot 自带了 Tomcat 容器,所以想要部署到外部容器,需要做点改动。

好像不改动也可以,之前我在波波老师的服务器上部署时,就是直接打包 war 放上去…不知道有没有啥副作用…

Ps:这一步是必须的,2018-11-20 遇到了不改动部署,访问 404 的问题。

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

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

不过这样运行的程序,关闭终端,程序就停止了。打 war 包并部署在容器就是为了保持运行的稳定。

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

打 war 包

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

apply plugin: 'war'

在 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());
}
}

再构建包就行啦!

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

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。