3

前言

在一些小公司中,发布还处于手工部署的阶段。
对于曾经接触过自动化发布的笔者来说,这种“手动打包、手动上传”的效率实在太低。
但奈何公司的技术还停留在SVN时代,无法像以前在大学时使用Gitlab成套的CI/CD工具。

因此本文记录了用最原始的技术,借助JS脚本实现自动化部署的过程。

一、 原理

本文假设生产环境是Linux或FreeBSD环境,使用Nginx加载静态文件来完成网站的部署。
并且已经具备前置条件:Nginx已经设置好文件路径。

此时,如果我们手动上线一个项目,步骤是:

  1. 编译: vite buildng build
  2. 打包: zip -q -r dist.zip ./dist
  3. 图形化SCP终端连接服务器,上传文件
  4. 服务器上删除旧版本: rm -rf projectName
  5. 解压: unzip dist.zip
  6. 重命名: mv dist projectName

而手写的自动化部署就是用一个脚本替我们执行这些命令。

二、探索

SCP2(失败)

如果你用中文关键词前端 自动发布去搜索,排名靠前的全是使用名为 scp2 的npm插件:

Screenshot_20230728_171152.png

抱着试一试的态度把别人的代码复制下来,然后运行,代码见外链外链外链

运行就遇到了问题:scp2上传没有任何反应(没有error),代码直接结束执行,文件没有上传。
Github上也有同样的问题:
Screenshot_20230728_171636.png

由于SCP2这个包太小众了,并没有什么资料,而排名靠前的搜索结果都是用的SCP2,于是在这里卡了好久。

突然笔者发现:这个包最后更新是七年前......
已经是上古时代的产物了,怎么没早点看见:

Screenshot_20230728_172024.png

于是第一个教训:NPM引用小众的包要看发布时间

SSH2(可用)

SCP2行不通就只能换思路了,于是去看了一眼它基于的包:SSH2 , 而这个包是保持更新的:
Screenshot_20230728_172547.png

所以去查文档,直接上代码了。

三、 实现

安装

npm install ssh2 --save-dev

引用

// 文件名为autoDepoly.js
const {Client} = require('ssh2');
const conn = new Client();

如果你使用的是Vite托管的VUE3,会提示require用法已经不再支持,
我们可以在vite.config.js中加入transformMixedEsModules

export default defineConfig({
  // 加入下面几行
  build: {
    commonjsOptions: {
      transformMixedEsModules: true,
    },
  }
})

这样就可以在vite中使用require了。
来源:StackOverFlow

连接

这一步主要是创建一个SSH连接,写入主机名、端口、用户名、密码(或私钥)的过程。

/**
 * 基本信息
 * path: 项目文件夹的名称
 * base: 项目文件夹所在的上一级目录
 */
const server =
  {
    host:"",
    port:"",
    username:"",
    password:"",
    path:"folderName/",
    base:"/www/"
  }

/**
 * 建立连接
 */
conn.on('ready', () => {
  // 这里是连接成功之后执行的代码
}).connect({
  host: server.host,
  port: server.port,
  username: server.username,
  // 如果使用私钥,把password改为:privateKey,即私钥的路径
  password: server.password
});

基本用法——执行一条Bash命令

/**
 * 执行bash命令
 * 命令和Linux中一致
 * stream.on-close表示执行完成后的操作
 * on-data和stderr.on-data是监听执行过程中的数据
 * 可以什么也不做,但必须带着后面两个回调函数
 */
  conn.exec('你的bash命令', (err, stream) => {
    if (err) throw err;
    stream.on('close', (code, signal) => {
      console.log('执行完成');
    }).on('data', (data) => {
      console.log(data);
    }).stderr.on('data', (data) => {
      console.log(data);
    });
  });

组合——执行多条命令

实际上就是把第二条命令写在第一条命令执行后的回调里:

// 第一层
conn.exec('第一条bash命令', (err, stream) => {
  if (err) throw err;
  stream.on('close', (code, signal) => {
    console.log('第一条命令执行完成');
    // 第二层
    conn.exec('第一条bash命令', (err, stream) => {
      if (err) throw err;
      stream.on('close', (code, signal) => {
        console.log('第二条命令执行完成');
      }).on('data', (data) => {
        console.log( data);
      }).stderr.on('data', (data) => {
        console.log(data);
      });
    });
    // 退出第二层
  }).on('data', (data) => {
    console.log(data);
  }).stderr.on('data', (data) => {
    console.log(data);
  });
});

这种“套娃”写法看着有点乱,有一种优化方式就是把多条命令合起来写:

 A && B && C

SFTP——上传文件

开头我们提到,有一个步骤就是上传文件,这一步的特殊性在于:
SSH都是在服务器上执行命令,而上传文件同时关系到本地和服务器。

SFTP就是基于SSH的文件传输协议,其写法如下:

/**
 * 启动SFTP
 * 上传使用fastPut方法,完成后回调
 * 无论是否成功都是唯一的回调函数,如果成功err为null
 * 只能上传一个文件,如果有文件夹需要压缩
 */
conn.sftp((err, sftp) => {
  if (err) throw err;
  console.log('SFTP启动,正在上传(等待时间可能较长)');
  sftp.fastPut('./dist.zip', server.base + 'dist.zip', (err) => {
    if (err) throw err;
    console.log('上传完成');
  });
});

最终的代码

把上面的代码拼起来,就是最终的功能了:

// 建立连接
conn.on('ready', () => {
  console.log('SSH连接成功');
  // 第一条命令
  conn.exec('rm -rf ' + server.base + server.path, (err, stream) => {
    if (err) throw err;
    stream.on('close', (code, signal) => {
      console.log('删除历史版本完成');
      // SFTP
      conn.sftp((err, sftp) => {
        if (err) throw err;
        console.log('SFTP启动,正在上传(等待时间可能较长)');
        // SFTP上传
        sftp.fastPut('./dist.zip', server.base + 'dist.zip', (err) => {
          if (err) throw err;
          console.log('上传完成,即将解压');
          // 第二条命令
          conn.exec('unzip ' + server.base + 'dist.zip -d ' + server.base, (err, stream) => {
            if (err) throw err;
            stream.on('close', (code, signal) => {
              console.log('解压完成');
              // 第三条命令
              conn.exec('mv ' + server.base + 'dist ' + server.base + server.path + ';rm '+ server.base +'dist.zip;', (err, stream) => {
                if (err) throw err;
                stream.on('close', (code, signal) => {
                  console.log('重命名成功,即将断开连接,请打开网站查看更新是否正常');
                  conn.end();
                }).on('data', (data) => {
                  console.log(data);
                }).stderr.on('data', (data) => {
                  console.log(data);
                });
              })
            }).on('data', (data) => {
              console.log( data);
            }).stderr.on('data', (data) => {
              console.log(data);
            });
          })
        });
      });
    }).on('data', (data) => {
      console.log( data);
    }).stderr.on('data', (data) => {
      console.log(data);
    });
  });
}).connect({
  host: server.host,
  port: server.port,
  username: server.username,
  password: server.password
});

提炼一下命令:

  1. 'rm -rf ' + server.base + server.path 删除历史项目文件夹:base+path等于完整路径
  2. 'unzip ' + server.base + 'dist.zip -d ' + server.base 把zip解压到原来历史文件夹所在的目录
  3. 'mv ' + server.base + 'dist ' + server.base + server.path + ';rm '+ server.base +'dist.zip;' 其实是两条命令:先把dist文件夹改名为项目文件夹,然后删除dist.zip文件

把这个文件扔在项目根目录,如果环境、参数都正确,使用node autoDepoly就可以正常执行了(autoDepoly.js是文件名,换成你自己的),将会返回:

07b452bc9914f47ab184fe61e10a2c7.png

第一次执行很可能不成功,因为每个人的情况不一样,所以:
教训二:脚本的编写需要耐心,当出现问题时,需要拆分功能,逐个功能调试,定位问题

四、整合

到目前为止,已经实现“自动推送zip到服务器并解压”了
接下来我们要把编译压缩的过程也自动化了。
很简单,项目的package.json中有快捷指令,比如运行npm run dev实际上就是执行vite因为项目构建的时候已经写好了:
Screenshot_20230728_181222.png

我们只需要加一行:

"auto-depoly": "vite build && zip -q -r dist.zip ./dist && node autoDepoly.cjs && rm dist.zip"

这是很多条命令:

  1. vite build 编译
  2. zip -q -r dist.zip ./dist 创建压缩文件
  3. node autoDepoly.js 执行自动部署
  4. rm dist.zip 删除压缩文件

注意:

  • && 的作用是:当前一天命令执行成功时,后一条命令才会执行
  • 文中的zip命令是Linux系统的,Windows需要换成等效的命令

至此,从编译、到打包、到上传、到解压,整个流程都是自动化执行了。
花费了一些时间,总算达成了目的。

五、总结

一点体会

对于大厂来说,已经实现了高度自动化和严格的规范,靠着公司内部的私有轮子就可以维持正常的开发活动,员工可以轻松学到前人踩过坑整理好的知识,甚至公司对于员工的成长会有所要求。

而在小公司中,前人的经验是一片空白,使用的工具也停留在远古时代,大多数的知识和经验都是自己去查,一点一点踩坑,最后总结出的东西还远远不如大厂的轮子。

客观上大厂和小厂的资源差距是相当大的,即使靠着更多的努力,也只能勉强弥补一部分环境的差距。


LYX6666
1.6k 声望73 粉丝

一个正在茁壮成长的零基础小白