在实际工作中,经常需要将构建好的静态资源上传到服务器的指定目录中。之前一直都是手动完成此操作,效率实在不高。近期,使用 Node.js 编写了一个简单地脚本,用来完成此工作。全部的代码我已上传到 gist。
人工操作
在没有写脚本之前,这些步骤都是人工操作,大致的步骤如下:
- 执行
npm run build
进行项目构建。 - 将打包生成的静态资源压缩成 zip 包。
- 使用 scp 将压缩包上传到服务器中。
- SSH 连接服务器在服务器上完成解压工作。
或者,如果只是修改部分文件,我也会使用 sshfs 将服务器目录挂载到本地,然后进行修改。
脚本实现
脚本即是将上面的人工操作该为代码实现,主要需要解决以下几个问题:
- 服务器的 ip 和登录密码是不固定,需要能通过参数读取。
- 需要使用 Node.js 打包静态资源并上传服务器。
- 如何在服务器上执行解压命令?
参数读取
Node.js 脚本如何读取命令行参数可以参考阮老师的博客,我使用的是 yargs 模块。
const argv = require('yargs').argv;
const host = argv.h || '192.168.1.188';// 读取主机 ip,默认是 192.168.1.188
const password = argv.p || '';// 主机密码 SSH 密码
因为这个项目中文件路径、服务器用户名等都是固定的,所有就没有支持参数填写的方式。
打包并上传
文件打包可以通过 child_process 模块新建子进程,执行系统的 zip 命令。
const util = require('util');
const exec = util.promisify(require('child_process').exec);
...
console.log('[2/4] 打包静态资源,生成 zip 包...');
result = await exec(`cd dist && zip -r ${ARCHIVE_NAME} ./*`);
console.log(result.stdout);
静态资源打包好以后,需要上传到服务器中,我这里使用是 scp2。原本我打算像打包一下,使用系统的 scp 命令,但是我没有配置 SSH 免密登录,需要在 scp 时指定服务器密码,而 scp2 能很好地解决此问题。
const scpClient = require('scp2');
...
function scp() {
return new Promise((resolve, reject) => {
scpClient.scp(`dist/${ARCHIVE_NAME}`, {
host,
username: USER,
password: password,
path: REMOTE_PATH,
}, function(err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
服务器上解压
静态资源包上传完毕之后,我们需要在服务器上完成解压,这里就需要能在服务器上远程执行命令。我使用了 simple-ssh 来完成此工作。它可以为我们建立一个与服务器之间的 SSH 连接,然后链式的在服务器上执行命令。
const scpClient = require('scp2');
...
function execRemoteOperations() {
return new Promise((resolve, reject) => {
const ssh = new SSH({
host,
user: USER,
pass: password,
});
ssh
.exec(`unzip -o -d ${REMOTE_PATH} ${REMOTE_PATH}/${ARCHIVE_NAME}`, {
out: (stdout) => console.log(stdout),
})
.exec(`rm ${REMOTE_PATH}/${ARCHIVE_NAME}`, {
out: (stdout) => console.log(stdout),
exit: () => {
resolve();
},
})
.on('error', function(err) {
ssh.end();
reject(err);
})
.start();
});
}
上述代码主要完成服务器上解压,并在解压完成后删除压缩包操作。
总结
使用脚本减少了很多的工作量,以前需要一步步手动操作,现在只用在命令行敲一个命令即可。
原本是打算写一个 Shell 脚本的,不过并不是特别属性,尤其是没有配置 ssh 免密登录,不太清楚如何处理输入密码这类交互情况。我知道 expect 可以实现,但是不了解如何使用,所以就放弃了。不过,后面我还是会尝试使用 expect 编写一个。