夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 任务管理日历

    • 前言
    • 前端搭建

    • 后端搭建

    • 部署前置篇
    • 前端部署

      • 服务器部署
        • 技术方案
        • 安装 Nginx
          • 基于 Docker 安装 Nginx
          • 安装命令
          • 启动一个简单的 Nginx 镜像
          • 是否成功安装 Nginx 镜像
          • 运行一个 nginx 容器
          • 修改 Nginx 配置文件
          • 进入容器内部直接修改
          • 配置文件映射到宿主机文件
          • 关闭并删除之前启动的 Nginx 容器
          • 启动新的 Nginx 容器
          • 测试是否挂载成功
        • 上传前端静态资源
          • 创建一个 Vue3 项目
          • 编写部署脚本
          • 大致流程
          • 需要用的三方库
          • deploy.js
          • 添加 package.json script 脚本
          • 效果图
        • 配置 Nginx
          • 修改 html 文件路径
          • 配置静态资源缓存
          • 缓存策略
          • 配置域名
          • 配置 DNS 解析(阿里云为例)
          • 修改 nginx 配置
          • 开启 gzip
      • Gitlab CICD
    • 后端部署

  • 无代码平台

  • 图书管理系统

  • 《全栈项目》
  • 任务管理日历
  • 前端部署
夜猫子
2023-07-11
目录

服务器部署

# 技术方案

  1. 通过 docker 运行一个 nginx 容器,并挂载宿主机目录;
  2. 前端编写 deploy.js 上传脚本,将静态资源上传到与该 nginx 容器关联的宿主机目录中

# 安装 Nginx

# 基于 Docker 安装 Nginx

# 安装命令

docker pull nginx
1

# 启动一个简单的 Nginx 镜像

# 是否成功安装 Nginx 镜像

img

# 运行一个 nginx 容器

docker run -d -p 80:80 --name nginx nginx
1

img

# 修改 Nginx 配置文件

根据场景有以下两种方式,进入容器直接修改 或者 将镜像内的配置文件与宿主机的文件目录做一个映射

# 进入容器内部直接修改

只适用于修改频率不高的场景,每次修改配置需进入容器内部

# 查看容器 id 并进入容器内部

docker ps 查看容器列表

docker exec -it 容器id /bin/bash
1

exit退出容器

# Nginx 容器内部大致目录结构

/etc/nginx: 配置文件目录

/var/log/nginx: 日志目录

/usr/share/nginx/html:默认前端代码目录

# 配置文件映射到宿主机文件

# 创建宿主机挂载目录
  1. 回到服务器根目录
cd ~
1
  1. 创建文件
mkdir -p home/nginx/{html,log,conf}
1
    1. html: 存放前端 html 页面
    2. log: 存放 Nginx log,后期可以查看 access 和 error 日志
    3. conf: 存放 nginx 配置
# 将容器内配置文件拷贝到宿主

根目录下执行

docker cp 容器id:/etc/nginx/nginx.conf ./home/nginx/conf/nginx.conf
docker cp 容器id:/etc/nginx/conf.d ./home/nginx/conf/conf.d
docker cp 容器id:/usr/share/nginx/html ./home/nginx
1
2
3

# 关闭并删除之前启动的 Nginx 容器

docker stop 容器id
docker rm -f 容器id
1
2

# 启动新的 Nginx 容器

使用 --mount命令挂载宿主机的文件和目录作为数据卷

docker run -d \
-p 80:80 \
--name nginx \
--mount type=bind,source=$HOME/home/nginx/conf/nginx.conf,target=/etc/nginx/nginx.conf \
--mount type=bind,source=$HOME/home/nginx/conf/conf.d,target=/etc/nginx/conf.d \
--mount type=bind,source=$HOME/home/nginx/log,target=/var/log/nginx \
--mount type=bind,source=$HOME/home/nginx/html,target=/usr/share/nginx/html \
nginx
1
2
3
4
5
6
7
8

# 测试是否挂载成功

  1. 更改 home/nginx/html/index.html 页面

img

  1. docker restart 容器id

img

# 上传前端静态资源

上一步我们挂载了宿主机目录/文件作为 Nginx容器的数据卷;

接下来我们将 Vue 或者 React 打包生成的 dist 或 build 上传到 home/nginx/html中,并简单配置一下 Nginx,我们的前端部署就算是完成了

# 创建一个 Vue3 项目

pnpm create vite vue3-app --template vue-ts

cd vue3-app

pnpm install

pnpm run dev
1
2
3
4
5
6
7

# 编写部署脚本

# 大致流程

img

# 需要用的三方库

npm i node-ssh compressing dotenv inquirer ora --save-dev
1
  1. compressing:文件压缩
  2. inquirer:命令行交互工具
  3. dotEnv:获取在 .env 文件中定义的环境变量
  4. node-ssh:ssh 工具,登录远程服务器
  5. ora:执行 npm run build 时,在命令行中添加 loading效果

# deploy.js

import fs from "node:fs/promises";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import childProcess from "node:child_process";

import ora from "ora";
import { NodeSSH } from "node-ssh";
import compressing from "compressing";
import dotEnv from "dotenv";
import inquirer from "inquirer";

const BUILD_SCRIPT = "npm run build";

const deploy = async () => {
  // 需要创建 .env 文件,并在 .env 文件中定义服务器 host 地址
  const { HOST } = process.env;
  if (!HOST) {
    console.log("🤔🤔部署失败,环境变量无法加载");
    process.exit(0);
  }
  const config = {
    host: HOST,
    deployType: "deploy",
  };
  inquirer
    .prompt([
      {
        type: "input",
        message: "😄😄请输入服务器用户名",
        name: "username",
      },
      {
        type: "password",
        message: "🫣 🫣 请输入服务器密码",
        name: "password",
        when: (answers) => answers.username,
      },
      {
        type: "list",
        message: "请选择部署方式",
        name: "deployType",
        choices: [
          { value: "deploy", name: "部署新版" },
          { value: "reset", name: "回退版本" },
        ],
        when: (answers) => answers.username && answers.password,
      },
    ])
    .then(({ username, password, deployType }) => {
      config.username = username;
      config.password = password;
      config.deployType = deployType;
      // 连接服务器
      connectServer(config);
    })
    .catch((err) => {
      console.log(err);
    });
};

const connectServer = async (config) => {
  try {
    const ssh = new NodeSSH();
    await ssh.connect({
      host: config.host,
      username: config.username,
      password: config.password,
      tryKeyboard: true,
    });
    console.log("🚀 服务器连接成功");
    if (config.deployType === "deploy") {
      handleUpload(ssh);
    }

    if (config.deployType === "reset") {
      handleReset(ssh);
    }
  } catch (error) {
    console.log("❌ 服务器连接失败,请检查服务器配置🤔🤔");
    process.exit(0);
  }
};

const handleReset = async (ssh) => {
  const sftp = await ssh.requestSFTP();
  sftp.exists("/root/home/nginx/html/dist-last", async (boolean) => {
    if (boolean) {
      await ssh.execCommand("cd home/nginx/html && rm -rf dist");
      await ssh.execCommand("cd home/nginx/html && mv dist-last dist");
      await ssh.execCommand("docker restart nginx");
      console.log("🚀 docker nginx 镜像开始重启");
      console.log("🚀🚀🚀 回退成功 🚀🚀🚀");
      process.exit(0);
    } else {
      console.log("❌ 回滚失败,未发现备份产物");
      process.exit(0);
    }
  });
};

const handleZipFile = () => {
  // eslint-disable-next-line no-async-promise-executor
  return new Promise(async (resolve, reject) => {
    try {
      await compressing.tar.compressDir("dist", "dist.tar");
      await compressing.gzip.compressFile("dist.tar", "dist.tgz");
      resolve(true);
    } catch (err) {
      console.log("❌ 文件压缩失败", err);
      reject(false);
      process.exit(0);
    }
  });
};

const handleRunBuild = () => {
  const spinner = ora("开始构建 build 产物").start();
  try {
    childProcess.execSync(BUILD_SCRIPT);
    spinner.stop();
    console.log("🚀 build产物构建成功, 开始压缩文件");
  } catch (error) {
    console.log("❌ npm run build 执行失败");
    spinner.stop();
    process.exit(0);
  }
};

const handleUpload = async (ssh) => {
  handleRunBuild();

  const isSuccess = await handleZipFile();
  if (isSuccess) {
    console.log("🚀 文件压缩成功,开始上传文件");
    const __filename = fileURLToPath(import.meta.url);
    const distPath = `${dirname(__filename)}/dist.tgz`;

    try {
      await ssh.putFile(distPath, "/root/home/nginx/html/dist.tgz");
      console.log("🚀 上传文件成功");
    } catch (error) {
      console.log("❌ 上传文件失败", error);
      process.exit(0);
    }

    const sftp = await ssh.requestSFTP();
    sftp.exists("/root/home/nginx/html/dist", async (boolean) => {
      if (boolean) {
        await ssh.execCommand(
          "cd home/nginx/html && rm -rf dist-list && cp -r dist dist-last"
        );
        console.log("🚀 构建产物备份成功");
      } else {
        console.log("⚠️ 未发现上次构建产物");
      }
    });

    try {
      await ssh.execCommand("cd home/nginx/html && tar -zxvf dist.tgz -C ./");
      console.log("🚀 服务器解压文件成功");
      await ssh.execCommand("docker restart nginx");
      console.log("🚀 docker nginx 镜像开始重启");
      await ssh.execCommand("cd home/nginx/html && rm -rf dist.tgz");
      console.log("🚀 服务器压缩文件已删除");
      await removeLocalFile();
      console.log("🚀🚀🚀 部署成功 🚀🚀🚀");
      process.exit(0);
    } catch (error) {
      console.log("❌ 部署失败,请检查服务器", error);
      process.exit(0);
    }
  }
};

const removeLocalFile = async () => {
  const __filename = fileURLToPath(import.meta.url);
  try {
    await fs.unlink(`${dirname(__filename)}/dist.tar`);
    await fs.unlink(`${dirname(__filename)}/dist.tgz`);
    console.log("🚀本地压缩文件已删除");
  } catch (error) {
    console.log("❌本地压缩文件删除失败", error);
  }
};
// 获取环境变量
dotEnv.config();
// 执行部署代码;
deploy();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

# 添加 package.json script 脚本

"script": {
  ...忽略其它
  "deploy": "node deploy.js"
}
1
2
3
4

# 效果图

img

# 配置 Nginx

# 修改 html 文件路径

  1. 打开 Nginx 配置文件
vim home/nginx/conf/conf.d/default.conf
1
  1. 修改 root 路径

img

  1. 重启 nginx 容器
docker restart nginx
1
  1. 查看结果

img

# 配置静态资源缓存

img

# 缓存策略

带 hash 的资源,配置 Cache-Control: max-age=31536000

不带 hash 的资源,配置 Cache-Control: no-cache

img

# 配置域名

前提是你有一个域名,并且已经成功备案

# 配置 DNS 解析 (opens new window)(阿里云为例)

配置完成后,静等几分钟就会生效

img

img

# 修改 nginx 配置

修改 default.conf 文件将 server_name 修改成域名地址即可

img

修改完成后,docker restart nginx 重启 nginx 容器

# 开启 gzip

参考文章 (opens new window)

配置文件地址: nginx/config/conf/nginx.conf

http {
	gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
}
1
2
3
4
5
6
7
8
9
10
编辑 (opens new window)
上次更新: 2024/6/18 13:14:59
部署前置篇
Gitlab CICD

← 部署前置篇 Gitlab CICD→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式