问题描述
需求描述:
- 前端代码写完以后,执行打包命令,npm run build命令会打包出一个dist文件夹
- 然后再把这个dist文件夹,丢到远程服务器上
- 然后通过nginx反向代理去给用户使用
这个环节不同公司的操作方式不同,简单举例,有以下几种方式:
代码部署服务器发布常见方式
方式一,使用一些DevOps平台
- 一般来说,大公司都会有自己的代码打包发布运维平台,比如DevOps系统平台。
- 在代码中,进行相关的Docker或者shell脚本的编写等...
- 然后就可以做到自动化发布部署脚本
- 即,一键部署功能
这种方式,有的是公司自研的,有的是买人家的服务,比如阿里云的云效...
方式二,使用winscp或xmanager之类的文件传输工具
- 关于什么是winscp、xmanager笔者不赘述
- 如下图,一目了然
- 登录winscp以后,可以直接把本地的文件,丢到远端服务器上
这种方式,缺点在于,每次都要登录,都要复制前端的dist文件夹,再粘贴到远端服务器上
长此以往,的确是有一些时间成本
方式三,自己写一个自动化脚本
- 现在是这样的问题
- 假设你所在的公司,因为某些原因没有DevOps平台
- 可能是没钱买吧...
- 但是你又不想,每次都去登录winscp,复制dist文件夹,再粘贴到对应目录
- 于是乎,你就需要一个脚本,自动帮你做这件事
- 而这个脚本,就是 效能工具 (提高开发效率,减少能量损耗的工具)
自己写一个node脚本(第一版没有上传进度的)
效果图
- 我们把这个脚本再加入到项目的package.json文件中
- 就可以实现,执行完npm run build以后,再自动执行这个自动化发布脚本
- 如下代码:
"scripts": {
"start": "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build && node ./auto.js",
},
"build": "vue-cli-service build && node ./auto.js"
通过&&
符号,让build脚本执行完毕以后,再让node执行 同级目录下 auto.js脚本,这样即可做到脚本自动发布了
思路分析
- 第一步,连接远程服务器(使用ssh2-sftp-client包)
- 第二步,删除远程服务器上的dist文件夹
- 第三步,把本地文件夹dist上传到远端服务器上对应位置
ssh2-sftp-client用于实现与远程服务器的文件传输操作
- ssh2-sftp-client包的github地址:https://github.com/theophilusx/ssh2-sftp-client#readme
- 这个包提供了很多功能,可以去连接远程服务器,并可以去对上面的文件夹和文件进行相关操作(增删改查之类的...)
- 本文不赘述此包的相关api语法(大家可自行查看官方文档)
ora包用于在终端做出loading文字加载中效果
- 因为连接服务器后,的文件夹删除和上传相关操作需要一定时间
- 所以,为了增强用户体验,我们需要加上一个loading加载效果
- 就像上述gif动态图的转圈效果
- ora包的github地址:https://github.com/sindresorhus/ora#readme
- 本文不赘述此包的相关api语法(大家可自行查看官方文档)
如果想要做出进一步色彩丰富的效果,可以搭配chalk这个包:https://www.npmjs.com/package/chalk
第一版完整代码
- node版本:16.20.1
- npm版本:8.19.4
- 上述两个包,大家在项目中,自行安装即可
package如下:
{
"name": "upload-script",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.2",
"ora": "^5.4.1",
"ssh2": "^1.14.0",
"ssh2-sftp-client": "^9.1.0"
}
}
完整代码:
- 假设远程dist文件夹在这个位置:C:/test/dist
- 本地,正常来说就是dist
- 便可以有如下代码:
const Client = require('ssh2-sftp-client'); // 用了连接服务器进行文件or文件夹相关操作
const ora = require('ora'); // 用来实现loading加载效果
const fs = require('fs');
// 因为ssh2-sftp-client需要连接远程服务器,所以我们需要告知这个包,连服务器的ip端口用户名密码
const config = {
host: '111.222.333.444',
port: '22',
username: 'Admin',
password: 'xxxyyyzzz111222333444',
// 当然也可以使用私有Key
// privateKey: fs.readFileSync('/path/to/private/key.pem').toString()
};
async function uploadLocalFolder(localFolderPath, remoteFolderPath) {
const spinner = ora('发布脚本开始执行--->').start(); // 开始加载...
const sftp = new Client(); // 实例化用于调用其自带的方法
try {
spinner.text = '连接服务器...'
await sftp.connect(config); // 使用上述配置连接远程服务器
spinner.text = '连接服务器成功'
spinner.text = '旧的dist文件夹删除...'
await sftp.rmdir(remoteFolderPath, true); // 把旧的dist删除掉
spinner.text = '旧的dist文件夹删除成功'
spinner.text = '新的dist文件夹上传...'
await sftp.uploadDir(localFolderPath, remoteFolderPath); // 新的dist删除掉
spinner.text = '新的dist文件夹上传成功'
// throw new Error('错错错错错.......') // 模拟报错
} catch (err) {
console.error(err)
} finally {
await sftp.end();
spinner.info('脚本执行完毕');
}
}
// 使用示例
uploadLocalFolder('./dist', 'C:/test/dist');
自己写一个node脚本(第二版带上传进度百分比的)
- 上述已经基本完成了一个自动化发布
- 但是,我们想进一步,优化这个脚本
- 比如,最重要的就是,加上上传进度条功能
- 如下效果图:
效果图
思路分析
- ssh2-sftp-client这个包,并没有提供上传文件大小进度的api,需要我们自行计算
- 目前计算的思路是:
- 首先看看dist有多少文件,假设有10个文件(不看文件夹)
- 然后通过递归逐个文件上传,上传成功一个,那就是10%,两个就是20%
- 即:(上传成功的文件数/总文件数)* 100%
- 按照这个思路来的话,就有以下代码:
第二版完整代码
const path = require('path');
const fs = require('fs');
const Client = require('ssh2-sftp-client');
const ora = require('ora');
let spinner = null // 加载实例
const config = {
host: '666.777.888.999',
port: '7527',
username: 'root',
password: 'passwordForLunix',
};
let totalFileCount = 0 // 本地dist文件夹中总文件数量
let num = 0 // 已成功上传到远端服务器上的文件数量
// 统计本地dist文件夹中有多少个文件(用于计算文件上传进度)
function foldFileCount(folderPath) {
let count = 0;
const files = fs.readdirSync(folderPath); // 读取文件夹
for (const file of files) { // 遍历
const filePath = path.join(folderPath, file);
const stats = fs.statSync(filePath);
if (stats.isFile()) { // 文件就+1
count = count + 1
} else if (stats.isDirectory()) { // 文件夹就递归加
count = count + foldFileCount(filePath);
}
}
return count;
}
// 把本地打包好的dist递归上传到远端服务器
async function uploadFilesToRemote(localFolderPath, remoteFolderPath, sftp) {
const files = fs.readdirSync(localFolderPath); // 读取文件夹
for (const file of files) { // 遍历
let localFilePath = path.join(localFolderPath, file); // 拼接路径
let remoteFilePath = path.join(remoteFolderPath, file); // 拼接路径
remoteFilePath = remoteFilePath.replace(/\\/g, '/'); // 针对于lunix服务器,需要做正反斜杠的转换
const stats = fs.statSync(localFilePath); // 获取文件夹文件信息
if (stats.isFile()) { // 是文件
await sftp.put(localFilePath, remoteFilePath); // 把文件丢到远端服务器
num = num + 1 // 完成数量加1
let progress = ((num / totalFileCount) * 100).toFixed(2) + '%' // 算一下进度
spinner.text = '当前上传进度为:' + progress // loading
} else if (stats.isDirectory()) { // 是文件夹
await sftp.mkdir(remoteFilePath, true); // 给远端服务器创建文件夹
await uploadFilesToRemote(localFilePath, remoteFilePath, sftp); // 递归调用
}
}
}
// 主程序
async function main() {
const localFolderPath = './dist'; // 本地dist文件夹路径
const remoteFolderPath = '/var/www/test/dist'; // 远程lunix的dist文件夹路径
totalFileCount = foldFileCount(localFolderPath) // 统计打包好的dist文件夹中文件的数量
if (!totalFileCount) return // dist是空文件夹就不操作
const sftp = new Client(); // 实例化sftp可调用其方法
try {
console.log('连接服务器');
await sftp.connect(config);
console.log('服务器连接成功');
console.log('删除旧的dist文件夹');
await sftp.rmdir(remoteFolderPath, true)
console.log('删除旧的dist文件夹成功');
console.log('新建新的dist文件夹');
await sftp.mkdir(remoteFolderPath, true)
console.log('新建新的dist文件夹成功');
spinner = ora('自动化脚本执行开始').start(); // loading...
await uploadFilesToRemote(localFolderPath, remoteFolderPath, sftp); // 耗时操作,递归上传文件
} catch (err) {
console.log(err);
} finally {
sftp.end();
spinner.info('自动化脚本执行结束')
}
}
// 执行脚本
main();
Attention
区分windows服务器和linux的正反斜杠
- windows服务器的斜杠:
C:\Program Files\MyApp
- linux服务器:
/home/user/myapp
remoteFilePath = remoteFilePath.replace(/\\/g, '/');
// 针对于lunix服务器,需要做正反斜杠的转换
config配置项不要提交代码
- config配置项可以单独拎出来作为一个配置项文件
- 此文件不要提交代码
- 可以写在.gitignore文件配置中
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。