前端自动化部署

2023年 10月 9日 50.4k 0

前言

此文为本人两年半前发布的博客,整理到此处,对原文内容有删减

在实习之前,在学校做个人项目的时候,每每需要将前端构建产物部署到服务器的时候都需要自己手动的经历本地环境打包构建、打开sftp传输工具、代码上传至服务器指定目录(前端静态文件通过Tomcat部署,只需要更新文件即可,不需要重启Tomcat)等一系列繁琐却又简单的工作,也就是说如果我想要在生产环境看到自己在开发环境中产生的变化,那么我就需要经过如上步骤,有时候还要涉及云服务器登陆,登陆又需要去寻找登陆密码(因为密码都是使用的默认的,记不住)。因此,想要立马看到自己的更改,往往是需要几分钟的,而且有时候迭代频繁,这些工作都需要机械重复的去执行,那么就急需将上述重复的步骤交给程序来操作,因此我产生了发布一款自动化部署工具到npm仓库的想法,供大家(主要用户还是自己)使用。

以下所构思的“前端部署小帮手”npm包已经发布到了npm仓库,以下是github地址,喜欢的话给个star吧,谢谢大家^_^!
前端部署小帮手 - deploy-helper

前端自动化部署初探

之前想要实现的是,用户登陆目标服务器安装一款npm包(由自己开发)到服务器本地,然后执行一系列指令即可完成服务器生产环境的搭建(包括node环境、java环境、tomcat、数据库等环境),并通过webhook监听远程仓库的变化,自动拉取代码到服务器本地部署,并提供Web界面管理部署版本。

但是实现起来并不简单,而且工作量很大,为了解决当前项目部署繁琐、机械的过程,我仅采取了实现将本地开发环境的构建产物打包、传输、服务端解压缩并清理压缩产物的整个过程。而上述目标则作为更为宏远的目标,逐步完善。

deploy-helper

此包命名为deploy-helper,意为“部署小帮手”。它主要有以下几个核心功能:

deploy-helper.png

配置项 .deploy.config.json

{
  "host": "", // 域名或ip地址
  "port": 22, // 默认sftp连接端口号
  "localPath": "./dist", // 需要上传到服务器的本地文件夹目录
  "remotePath": "/root/dist", // 需要上传到目标服务器的文件夹目录
  "readyTimeout": 20000 // 默认连接超时时间
  "shellScripts": "" // shell脚本
}

构建产物压缩

/**
 * 文件压缩
 *
 * @param {*} localPath
 */
async function compress(localPath) {
  // 压缩产出文件夹名称
  const folderName = path.basename(localPath);
  // 产出文件夹位置
  const outDir = path.resolve(localPath, '../', `${folderName}.zip`);
  const output = fs.createWriteStream(outDir);
  const archive = archiver('zip', {
    zlib: { level: 9 } // 设置压缩级别.
  });

  // 监听压缩完成,文件流关闭事件
  output.on('close', function() {
    console.log(archive.pointer() + ' total bytes');
    console.log('archiver has been finalized and the output file descriptor has closed.');
  });

  // This event is fired when the data source is drained no matter what was the data source.
  // It is not part of this library but rather from the NodeJS Stream API.
  // @see: https://nodejs.org/api/stream.html#stream_event_end
  output.on('end', function() {
    console.log('Data has been drained');
  });

  // good practice to catch warnings (ie stat failures and other non-blocking errors)
  archive.on('warning', function(err) {
    if (err.code === 'ENOENT') {
      // log warning
    } else {
      // throw error
      throw err;
    }
  });

  // good practice to catch this error explicitly
  archive.on('error', function(err) {
    throw err;
  });

  // pipe archive data to the file
  archive.pipe(output);
  // 添加压缩文件(夹)
  await compressDir(archive, localPath);
  // 压缩
  await archive.finalize();
  return outDir;
}


/**
 * 压缩文件、文件夹
 *
 * @param {*} archive
 * @param {*} dir
 */
function compressDir(archive, dir) {
  return new Promise((resolve, reject) => {
    fs.stat(dir, (err,data)=>{
      if (err){
        reject(err);
        return console.log(err);
      }
      const filename = path.basename(dir);
      // 如果是文件夹 则压缩该文件夹
      if (data.isDirectory()) {
        archive.directory(dir, filename);
      } else {
        // 否则压缩该文件
        archive.append(fs.createReadStream(dir), { name: filename });
      }
      resolve(true);
    })
  })
}


连接目标服务器

目标服务器地址可配置

根据配置的**.deploy.config.json**里面的 host 以及 port,通过ssh2即可实现连接远程服务器。ssh2提供了丰富的api,可以上传、下载、执行shell脚本等。

const conn = new Client();
  conn.on('ready', () => {
    console.log('Client :: ready'); // 服务器连接就绪
	...
  }).connect({
    readyTimeout: 20000,
    ...config,
    ...args
  });

压缩文件上传

compress(localPath).then((outDir) => {
  const zipFileName = path.basename(outDir);
  let p = config.remotePath;
  p = p.replace(//+$/, '');
  const remotePath = p + '/' + zipFileName;
  console.log(outDir, remotePath);
  uploadFile(conn, outDir, remotePath, (res) => {
    console.log('上传成功!', res === undefined ? '' : res);
    console.log('开始解压文件...');
    ...
  }
}

执行自定义shell脚本

我们可以在文件上传完成后执行一些自定义的命令,如:

  • 文件解压缩
  • 执行npm scripts
  • 执行常见Linux命令
  • ...
  • // 执行shell脚本命令
    conn.shell((err, stream) => {
      if (err) throw err;
      stream.on('close', () => {
        console.log('Stream :: close');
        conn.end();
      }).on('data', (data) => {
        console.log('OUTPUT: ' + data.slice(0, 100));
      });
      // 进入指定文件夹解压缩文件到当前文件夹 并删除压缩包
      // 执行自定义脚本
      const scripsts = config.shellScripts === undefined ? '' : config.shellScripts;
      stream.end(`cd ${p}nrm -rf ${folderName}njar xvf ${zipFileName}&&rm -rf ${zipFileName}n${scripsts}nexitn`);
    });    
    

    以上就是整个deploy-helper执行过程的关键流程,它可以满足将静态文件打包上传至目标服务器的指定目录,并可以在此之后回调执行一些用户自定义指令。

    但是他目前还有很多缺陷,比如:

    对于shell脚本的执行结果没有进行判断,不知道是否执行成功
    远程文件夹采用直接覆盖的方式,无法进行版本控制,遇到问题无法快速回滚
    ...

    这些问题,在以后的迭代中,根据实际情况逐一解决。

    npm发包

  • 注册npm账号 对于填写的邮箱,一定要去验证,否则发包的时候会报403
    npm官网
  • 创建新的文件夹,文件夹命名为你的包名,执行npm init生成package.json文件
  • 如果是第一次发布,执行npm adduser,否则执行npm login
  • 进入项目文件夹,执行npm publish发布
  • 版本控制 执行npm version [updateType]
    • patch 补丁,小改动,版本最后一位数字加一
    • minor 次要修改,次版本号加一
    • major 主要修改,大改,版本首位加一
  • 发包过程截图:

    发包过程截图.png

    总结

    经过npm init 到源码构建,再到npm publish,至此,我们已经完成了deploy-helper前端部署小助手工具包的全部工作,它能帮我们压缩上传构建产物到目标服务器指定目录,并进行部署。我们不用再打开sftp工具,或者scp手动传输构建产物了,在一定程度上简化了我们的发布过程。正如我上面所说,小助手仍有很多问题需要进一步解决,正因为任何事情都不是一蹴而就的,蜂巢、蚁巢都是一点一点的筑成的,小助手会在今后的迭代中逐步完善,提供更为简洁和可靠的功能。
    同时也欢迎大家给我提出宝贵的改进意见,新人经验欠缺,文中若有不足的地方也望大家包涵!

    以下是通过小助手发包的运行过程实例截图:
    运行过程截图.png

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论