问题描述

需求描述:

  • 前端代码写完以后,执行打包命令,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文件配置中

github地址

完整代码:https://github.com/shuirongshuifu/upload-script


水冗水孚
1.1k 声望585 粉丝

每一个不曾起舞的日子,都是对生命的辜负