前言
作为一个前端开发,在日常开发工作中,当我们完成了一个功能的开发,需要发版到测试环境时,或多或少都会接触到一些发版工作的,在一些相对规范点的公司可能会配有CI/CD,那么我们直接推了代码,点立即构建即可。
但在相对小型一些的公司,因为各种条件限制吧,还是采用一些相对原始的方法,比如使用XShell,Xftp,finalShell等工具去发版,虽然就是打包完,一拖一粘贴,也不难,但当我们手头工作比较多,而测试又等着测时,就容易手忙脚乱,边打包边忙别的(比如在掘金mo学习~),等再次想起来要拖上去发版的时候,就已经不知道是什么时候了。
而且每次发版都要经历下面这些流程:
- 打开finalShell
- 找到发版地址
- 找到云端环境前端文件夹的位置
- 打开本地项目的打包文件夹,并将文件拖入finalShell中
- 等待上传完毕,完成发版
这个流程多少还是有些繁琐,基于此,就有了这篇文章,我们可以自己编写一个发版的脚本工具,输入命令,即可完成发版,不再需要上面这一坨繁琐的步骤。
正文
连接工具——ssh2
首先,我们需要了解一个能够用于连接云端服务器的库——ssh2
这个库会为我们暴露一个Client构造函数,可以通过该构造函数创建连接的实例conn,调用connect方法同时传入配置信息,就可以连接到我们的云端服务器了。同时,我们可以监听conn的ready事件,当完成连接后,去执行我们后续的操作——上传前端文件。
const { Client } = require('ssh2');
const conn = new Client();
conn.on('ready', function() {
...
// 连接建立后的回调函数
}).connect(
{
host: '192.168.100.100',
port: 22,
username: 'root',
password: '123456'
}
);
接下来,由于我们是想向云端环境去上传文件,而SFTP提供了安全性更高的文件传输方式,所以可以写出下面代码:
conn.sftp(function(err, sftp) {
if (err) throw err;
sftp.fastPut(本地文件地址,云端文件地址, (err) => {
if(err) {
console.log('错误' + err)
return
}
console.log('上传成功')
...
})
当上传完毕之后,可以通过调用conn.end()
,来断开与云端的连接。
到这里就完成了整个的连接,以及上传逻辑。但目前似乎使用fastPut
方法一次只能上传一个文件,而且并不能上传整个文件夹,所以我们可以采用先将dist文件夹打成压缩包,将压缩包上传到云端,在云端进行解压这样一个操作来曲线救国~
压缩工具——archiver
这里再引入一个用于压缩文件的库——archiver
我们仿照官网,将路径信息配置进去即可。
var output = fs.createWriteStream(压缩文件的路径)
var archive = archiver('zip', {
zlib: { level: 9 }
})
output.on('close', function() {
resolve()
});
archive.on('error', function(err) {
throw err;
});
archive.pipe(output);
// 配置第二个参数为false的话,会将内部文件打包,而不是将文件夹打包
archive.directory(要压缩的文件夹路径, false);
archive.finalize();
整合流程
前面这两个工具我们已经了解了,那接下来开始将整个的流程进行一个整合,来编写最终的脚本文件。
再梳理一次:
- 首先,在连接云端之前先将要上传项目打包文件的压缩包准备好
- 然后,通过ssh2与云端测试环境建立连接
- 连接建立完成,通过sftp将压缩包进行上传
- 上传完成后,在云端将压缩包进行解压
- 解压完成,删除压缩包
- 断开连接
代码实现:
// upload.js
const fs = require('fs');
const archiver = require('archiver');
const { Client } = require('ssh2');
// 删除本地压缩文件
function deleteFile(config) {
return new Promise((resolve,reject) => {
fs.access(__dirname + '/' + config.zipFileName, fs.constants.F_OK,(err) => {
if(err) {
resolve()
return console.log('文件不存在')
}
fs.unlinkSync(__dirname + '/' + config.zipFileName)
resolve()
})
})
}
// 压缩
function zipFile(config) {
return new Promise((resolve,reject) => {
var output = fs.createWriteStream(__dirname + '/' + config.zipFileName)
var archive = archiver('zip', {
zlib: { level: 9 }
})
output.on('close', function() {
resolve()
});
archive.on('error', function(err) {
throw err;
});
archive.pipe(output);
// 配置第二个参数为false的话,会将内部文件打包,而不是将文件夹打包
archive.directory(__dirname + '/' + config.fileDir + '/', false);
archive.finalize();
})
}
// 上传
function uploadFile(config) {
return new Promise((resolve,reject) => {
conn.on('ready', function() {
console.log('Client ready');
conn.sftp(function(err, sftp) {
if (err) throw err;
// 业务逻辑
sftp.fastPut(`${__dirname}/${config.zipFileName}`,`${config.remoteDir}/${config.zipFileName}`,
(err) => {
if (err) {
console.log('错误' + err)
return
}
console.log('上传成功')
// 执行解压命令
conn.exec(`unzip -o ${config.remoteDir}/${config.zipFileName} -d ${config.remoteDir}`, (err,stream) => {
if(err || !stream) {
reject('失败')
}else{
stream.on('close', () => {
console.log('上传完成')
resolve()
}).on('data', (data) => {
console.log(data.toString())
})
}
})
})
});
}).connect(config);
})
}
// 删除压缩包文件
async function removeZip(config) {
return new Promise((resolve,reject) => {
conn.exec(`rm -rf ${config.remoteDir}/${config.zipFileName}`, (err,stream) => {
if(err || !stream) {
reject('失败')
}else{
stream.on('close', () => {
console.log('删除完成')
resolve()
}).on('data', (data) => {
console.log(data.toString())
})
}
})
})
}
// 汇总
async function handle(config) {
if(!config.host) {
console.log('配置错误')
return
}
await deleteFile(config)
await zipFile(config)
await uploadFile(config)
await removeZip(config)
conn.end()
console.log('打包并上传完成')
}
// 配置对象
let finalConfig = {
host: '192.168.100.100',
port: 22,
username: 'root',
password: '123456',
remoteDir: '/home/web',
zipFileName: 'dist.zip',
fileDir: '../hello-world/dist'
}
// 执行
handle(finalConfig)
当执行这个脚本文件,node upload.js
后,就可以在控制台看到:
同时在云端服务器上也能看到,已经上传成功了:
补充
那么,到这里我每次只需要打开控制台然后执行脚本就可以了,确实节省了很大一部分操作。但如果我们同时维护的系统有多个该怎么办呢?
这里可以在执行的脚本命令后加参数,比如node upload.js sys1
,而在脚本中,通过process.argv[2]
就可以获取到键入的参数了,这样就可以去配置更多的项目发版地址喽~
let finalConfig = {}
let x = process.argv[2]
if(x == 'sys1') {
finalConfig = sysConfig1
}else if(x == 'sys2') {
finalConfig = sysConfig2
}
...
handle(finalConfig)
甚至可以再简单一点,把执行脚本的命令做成一个cmd文件,这样连手动唤起命令行都不用了,用鼠标点开cmd文件,输入要发版的系统,就可以自动去读取对应的配置,帮我们完成发版了~
@echo off
chcp 65001
set /p UserInput=请输入要发版的项目:
node ./upload.js %UserInput%
最后附上这个发版工具的体验地址,需要的掘友可以试一下,在原有的基础上也可以做更多有意思的操作~