项目部署和上线

多环境

本地开发:localhost(127.0.0.1)

多环境:指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上。

为什么需要多环境?

  1. 每个环境互不影响
  2. 区分不同的阶段:开发 / 测试 / 生产
  3. 对项目进行优化:
    1. 本地日志级别
    2. 精简依赖,节省项目体积
    3. 项目的环境 / 参数可以调整,比如 JVM 参数

针对不同环境做不同的事情。

多环境分类:

  1. 本地环境(自己的电脑)localhost
  2. 开发环境(远程开发)大家连同一台机器,为了大家开发方便
  3. 测试环境(测试)开发 / 测试 / 产品,单元测试 / 性能测试 / 功能测试 / 系统集成测试,独立的数据库、独立的服务器
  4. 预发布环境(体验服):和正式环境一致,正式数据库,更严谨,查出更多问题
  5. 正式环境(线上,公开对外访问的项目):尽量不要改动,保证上线前的代码是 “完美” 运行
  6. 沙箱环境(实验环境):为了做实验

前端多环境

开发和上线请求的地址不同,开发我们可以请求localhost,但上线正式环境不可能让它来请求我们的本地电脑,一般

情况,我们是把项目放在云服务器上,那么如何让前端知道什么时候请求localhost,什么时候请求远程地址?

Sky-User-Center项目 举例

  • 请求地址

Sky-User-Center项目
项目用了 umi 框架,

build 时会自动传入 NODE_ENV == production 参数,start NODE_ENV 参数为 development

我们在启动项目时,根据我们的启动命令,npm run build 和 num start ,umi根据我们的启动命令的不

同传入不同的 NODE_ENV 参数,以此区分开发环境和生产环境。

appp.tsx文件:

image-20230311161221192

​ 因此,我们要找到全局请求类,Sky-User-Center项目 自定义了全局请求类,名为globalRequest

image-20230312142151424

  • 如何验证?

    ​ 首先我们将项目打包,在 pacage.json中执行build脚本

image-20230311160154803

​ 然后我们就能看到项目目录多了一个dist文件,使用终端命令行的方式,进到dist文件,输入命令serve(没有的话装一下

1
npm i -g serve

​ 可以看到请求的地址发生了改变

  • 项目的配置

    不同的项目(框架)都有不同的配置文件,umi 的配置文件是 config,可以在配置文件后添加对应的环境名称后缀来区分开发环境和生产环境。参考文档:https://umijs.org/zh-CN/docs/deployment

    • 开发环境:config.dev.ts
    • 生产环境:config.prod.ts
    • 公共配置:config.ts 不带后缀

后端多环境

SpringBoot 项目,通过 application.yml 添加不同的后缀来区分配置文件

可以在启动项目时传入环境变量:

1
java -jar .\sky-user-center-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

主要是改:

  • 依赖的环境地址

    • 数据库地址

    • 缓存地址

    • 消息队列地址

    • 项目端口号

  • 服务器配置

项目部署

需要一台Linux服务器

原始部署

  • 前端:

    主要安装nginx服务器,去官网下载安装

    nginx: download

    (注意nginx的权限)

  • 后端:

    主要是项目依赖的服务:java、maven、mysql等

    后台启动java项目

    1
    nohup java -jar .\sky-user-center-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod &

宝塔Linux部署

原始部署方法比较麻烦,这时候我们可以在服务器安装宝塔面板,通过可视化的方式部署项目

宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板 (bt.cn)

界面友好

image-20230311195602394

  • 前端部署

    点击左侧的网站 –> 添加站点 –>创建站点,域名栏输入域名(必须是经过dns解析的域名)或者服务器ip,然后点击提交。

    image-20230311195845068

​ 然后去到站点根目录,将前端的dist里面的文件全部上传上去

image-20230311200344329

​ 上传完成后,通过域名或者ip的方式就可以正常访问前端项目啦!

  • 后端部署

    后端部署和前端类似,也是在网站那里,选择java项目,添加java 项目把后端打包好的jar包上传到某个文件,在jar路

    径栏正确填写该路径,并且记得要在项目执行命令末尾补上 --spring.profiles.active=prod,这样我们才是启用

    生产环境的配置

    image-20230311203735982

    通过ip:端口号或者域名:端口号的方式就能访问到后端了

前端托管

前端腾讯云 web 应用托管(比容器化更傻瓜式,不需要自己写构建应用的命令,就能启动前端项目)

https://console.cloud.tencent.com/webify/new

  • 小缺点:需要将代码放到代码托管平台上
  • 优势:不用写命令、代码更新时自动构建

Docker部署

Docker 是容器,可以将项目的环境(比如 java、nginx)和项目的代码一起打包成镜像,所有人都能下载镜像,更容易分发和移植。再启动项目时,不需要敲一大堆命令,而是直接下载镜像、启动镜像就可以了。

docker 可以理解为软件安装包。

Docker 安装:https://www.docker.com/get-started/ 或者宝塔安装

Dockerfile 用于指定构建 Docker 镜像的方法

Dockerfile 编写:

  • FROM 依赖的基础镜像
  • WORKDIR 工作目录
  • COPY 从本机复制文件
  • RUN 执行命令
  • CMD / ENTRYPOINT(附加额外参数)指定运行容器时默认执行的命令
1
2
3
4
5
6
7
8
9
10
11
12
13
FROM maven:3.5-jdk-8-alpine as builder

# Copy local code to the container image.
WORKDIR /app
COPY pom.xml .
COPY src ./src

# Build a release artifact.
RUN mvn package -DskipTests

# Run the web service on container startup.
CMD ["java","-jar","/app/target/sky-user-center-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"]

或者可以在本地打包,上传target

1
2
3
4
5
6
7
8
9
10
11
FROM openjdk:8-jdk-alpine

# Copy local code to the container image.
WORKDIR /app

# Build a release artifact.
ADD ./target/user-center-backend-0.0.1-SNAPSHOT.jar /app/sky-user-center-0.0.1-SNAPSHOT.jar

EXPOSE 8666
# Run the web service on container startup.
CMD ["java","-jar","/app/sky-user-center-0.0.1-SNAPSHOT.jarr","--spring.profiles.active=prod"]

docker build构建镜像

1
2
3
4
5
# 后端
docker build -t sky-user-center-backend:v0.0.1 .

# 前端
docker build -t sky-user-center-front:v0.0.1 .

docker run 启动

(-d 后台启动)

1
2
3
4
5
# 前端
docker run -p 80:80 -d sky-user-center-front:v0.0.1

# 后端
docker run -p 8666:8666 -d sky-user-center-backend:v0.0.1

虚拟化

  1. 端口映射:把本机的资源(实际访问地址)和容器内部的资源(应用启动端口)进行关联
  2. 目录映射:把本机的端口和容器应用的端口进行关联

docker 命令

可以参考菜鸟教程

Docker 教程 | 菜鸟教程 (runoob.com)

进入容器:

1
docker exec -i -t  [container-id] /bin/bash

查看进程:

1
docker ps 

查看日志:

1
docker logs -f [container-id]

杀死容器:

1
docker kill [container-id]

强制删除镜像:

1
docker rmi -f [container-id]

Docker平台部署

  1. 云服务商的容器平台(腾讯云、阿里云)
  2. 面向某个领域的容器平台(如微信云托管)

绑定域名访问

准备一个已备案的域名,配置好dns解析

sky-user-center-front: 用户中心项目前端 (gitee.com)项目为例,前端已经定义了生产环境要请求的域名。

image-20230312142151424

前端项目访问流程:用户输入网址 => 域名解析服务器(把网址解析为 ip 地址 / 交给其他的域名解析服务(CNAME)) => 服务器 =>(防火墙)=> nginx 接收请求,找到对应的文件,返回文件给前端 => 前端加载文件到浏览器中(js、css) => 渲染页面

后端项目访问流程:用户输入网址 => 域名解析服务器 => 服务器 => nginx 接收请求 => 后端项目(比如 8080端口)

nginx 反向代理的作用:替服务器接收请求,转发请求

  1. 前端和后端用相同的域名

    • 在nginx的html目录放入前端编译完成的dist文件
    • 修改nginx的配置(80端口监听,绑定 server_name为域名或者IP)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server
    {
    listen 80;
    server_name 域名 ip;
    index index.php index.html index.htm default.php default.htm default.html;
    root 指向dist目录;
    ...
    location /api { 假设后端有一个url前缀 /api
    proxy_pass 127.0.0.1:8080 当请求/api url时,nginx会帮我们代理到本机8080端口
    }
    }
    • 这种配置并不涉及到跨域问题,是一种比较简单的配置,此时输入域名或者ip即可访问项目
  2. 后端绑定前端定义的生产环境域名,前端绑定项目域名

    • 这就涉及到了跨域问题(详见下面 跨域问题的解决
    • 后端就需要使用nginx的反向代理,即(当nginx服务器接收到后端域名(80端口)的请求,就反向代理到[某某ip:端口])
    • 前端服务如果不是运行在80端口,也需要使用nginx反向代理

跨域问题

浏览器为了用户的安全,仅允许向 同域名、同端口 的服务器发送请求。

那么如何解决跨域?

域名和端口都改成相同的

前后端不能运行在同一服务器的同一端口,可以使用nginx的正向或反向代理解决

添加跨域头

让服务器告诉浏览器:允许跨域(返回 cross-origin-allow 响应头)

1.网关支持(Nginx)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 跨域配置
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080/api/;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers '*';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}

2.修改后端服务

  1. 配置 @CrossOrigin 注解

  2. 添加 web 全局请求拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
//当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】
.allowedOrigins("http://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
}
  1. 定义新的 corsFilter Bean