首页 > 其他分享 >轻量化前端更新方案

轻量化前端更新方案

时间:2023-02-17 11:11:38浏览次数:40  
标签:console log err 前端 更新 轻量化 ssh config objname

一句话介绍:它是可以一行命令将代码更新到服务器的脚本
轻量级更新方案最开始源于掘金的文章,后来从零实现了一个更新脚本,并且已在是生产环境中进行使用很长时间,算是非常稳定的版本,个人认为轻量化更新方案是非常使用小型开发团队
现在切换到了 gitlab 的 CI/CD。所以这种方案已经不再是主流方案,但是一路使用过来,非常稳定的解决了更新问题,还是非常不错的

优点: 快速,稳定,自动备份指定文件夹(灵活性高,但是需要自己实现)
缺点:需要手动回滚(自动回滚需要编码),相对来说没那么规范,没有留下记录,敏感数据存储在电脑中,配置文件可以 git 忽略

核心流程

确认并打包项目
通过 node-ssh 连接线上服务器
将打包代码指定名称进行压缩
备份之前的代码,删除以前的代码包,并解压压缩包
删除本次打包代码,断开 ssh 链接

如何使用

代码

  1. 将仓库文件放入项目
  2. 安装依赖
    npm install node-ssh inquirer archiver -D
  3. 修改 upload.config.js 内容
  4. 增加脚本命令
    "upload": "node build/upload.js"
  5. 运行命名,验证是否功能正常

核心代码

build/upload.js

const fs = require("fs");
const path = require("path");
const { NodeSSH } = require("node-ssh");
const archiver = require("archiver");
const inquirer = require("inquirer");
const exec = require("child_process").exec;
const ssh = new NodeSSH();
const uploadFun = require("../upload.js");

/**
 * 获取当前平台
 */
let objName = process.argv[2]; // 更新名字
let startTime = null; // 程序开始更新的时间
// 获取上传服务器配置
let config = uploadFun(objName);
const verifyList = [
  {
    type: "input",
    message: "您正在更新到线上环境,请确认接口域名",
    name: "objName",
  },
];
inquirer.prompt(verifyList).then(() => {
  uploadBuild();
});

function uploadBuild() {
  startTime = new Date();
  console.log(`${objName}开始更新`);
  let buildcmd = exec(config.buildScript, (error, stdout, stderr) => {
    if (!error) {
      console.log("打包完成", stdout);
      app();
    } else {
      console.error("打包出现错误", stderr);
      process.exit(0);
    }
  });
  buildcmd.stdout.on("data", (data) => {
    console.log(data.toString());
  });
}

/**
 * 通过ssh链接服务器
 */
function app() {
  ssh
    .connect({
      host: config.host,
      username: config.username,
      password: config.password,
    })
    .then((res) => {
      // 上传代码压缩包
      uploadData();
    })
    .catch((err) => {
      console.log(err);
    });
}

/**
 * 上传代码 压缩现有代码
 */
function uploadData() {
  // 创建文件输出流
  let output = fs.createWriteStream(
    `${path.join(__dirname, "../")}${config.buildPath}/${config.objname}.zip`
  );
  // 设置压缩级别
  let archive = archiver("zip", {
    zlib: {
      level: 8,
    },
  });
  // 存档警告
  archive.on("warning", function (err) {
    if (err.code === "ENOENT") {
      console.warn("stat故障和其他非阻塞错误");
    } else {
      throw err;
    }
  });
  // 存档出错
  archive.on("error", function (err) {
    throw err;
  });
  // 通过管道方法将输出流存档到文件
  archive.pipe(output);
  archive.directory(
    `${path.join(__dirname, "../")}${config.buildPath}/${config.buildobj}`,
    "/"
  );
  archive.finalize();
  // 文件输出流结束
  output.on("close", function () {
    console.log(
      `总共 ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB,完成源代码压缩`
    );
    ssh
      .putFile(
        `${path.join(__dirname, "../")}${config.buildPath}/${
          config.objname
        }.zip`,
        `${config.uploadDir}/${config.objname}.zip`
      )
      .then(() => {
        console.log("程序zip上传成功,判断线上是否需要备份");
        runcmd();
      })
      .catch((err) => {
        console.log(err);
      });
  });
}

/**
 * 执行ssh命令 判断当前是否存在备份
 */
function runcmd() {
  ssh
    .execCommand("ls", {
      cwd: config.uploadDir,
    })
    .then((res) => {
      if (res.stdout) {
        let fileList = res.stdout.split("\n");
        if (config.objname == config.backObject) {
          if (fileList.includes(config.objname)) {
            console.log("当前更新为线上正常环境,开始进行备份");
            backupData();
          } else {
            console.log("当前更新为线上正常环境,并且是第一次,将跳过备份");
            cmdunzip();
          }
        } else {
          console.log("当前为测试环境,无需备份,直接解压上传压缩包");
          cmdunzip();
        }
      } else if (res.stderr) {
        console.log("查询指定目录失败");
      } else {
        console.log("ssh链接发生了错误");
      }
    });
}

/**
 * 备份项目
 */
function backupData() {
  ssh
    .execCommand(
      `mv ${config.objname} backup/${
        config.objname
      }_backup${new Date().getTime()}`,
      {
        cwd: config.uploadDir,
      }
    )
    .then((res) => {
      if (res.stderr) {
        console.log("备份发生错误", res.stderr);
      } else {
        console.log("完成备份,解压最新代码");
        cmdunzip();
      }
    })
    .catch((err) => {
      console.log("备份发生未知链接错误", err);
    });
}

/**
 * 解压最新代码zip
 */
function cmdunzip() {
  // 解压程序
  ssh
    .execCommand(
      `rm -rf ${config.objname} && unzip -o -d ${config.uploadDir}/${config.objname} ${config.objname}.zip  && rm -f ${config.objname}.zip`,
      {
        cwd: config.uploadDir,
      }
    )
    .then(() => {
      console.log(`项目包完成解压,${config.objname}项目部署成功了!`);
      console.log(
        `项目更新时长${(new Date().getTime() - startTime.getTime()) / 1000}s`
      );
      return deletelocalFile().then(() => {
        console.log("本地缓存zip清除完毕");
      });
    })
    .then(() => {
      ssh
        .execCommand(`rm -rf ${config.objname}/static/.DS_Store`, {
          cwd: config.uploadDir,
        })
        .then(() => {
          console.log("线上项目.DS_Store删除完成");
          ssh.dispose();
          process.exit(0);
        })
        .catch((err) => {
          console.log(err);
        });
    })

    .catch((err) => {
      console.log("解压出现错误", err);
    });
}
/**
 *删除本地生成的压缩包
 */
function deletelocalFile() {
  return new Promise((resolve, reject) => {
    fs.unlink(
      `${path.join(__dirname, "../")}${config.buildPath}/${config.objname}.zip`,
      (err) => {
        if (err) {
          reject(err);
          throw err;
        } else {
          resolve();
        }
      }
    );
  });
}

配置文件

upload.config.js

// 打包核心配置文件
let Available = ["dist-a", "dist-b"]; // dist-a 环境a代码包 dist-b 环境b代码包 npm run upload dist-a
/**
 * 获取更新配置
 * @param {String} objName 当前更新名称
 * @returns
 */
module.exports = (objName) => {
  if (!Available.includes(objName)) {
    console.log("当前项目不存在您输入的更新命令,请检查更新名称");
    process.exit(0);
  }
  return {
    host: "xx.xx.xx.xx", // 服务器地址
    username: "root",
    password: "xxxxxxxxxx",
    buildPath: "", // 本地打包项目地址(多层路径用这个)
    buildobj: "dist", // 本地打包文件名称
    uploadDir: "/xx/xx/xx", // 服务端项目地址
    objname: objName, // 打包项目名称
    backObject: "objName", // 备份的文件夹名称
    buildScript: "npm run build", // 更新命令
  };
};

触发命令

最后在 package.json 增加一行命令,运行前面的脚本文件

 "scripts": {
    // ......
    "upload": "node build/upload.js"
  },

依赖版本

因为更新脚本是在项目里面的,所以需要额外安装依赖
推荐版本号
"node-ssh": "^12.0.0",
"inquirer": "^7.3.3",
"archiver": "^3.1.1",

实际使用

npm run upload xxxx // 线上代码文件夹名称

upload 命令后面的字符串就是服务器上的文件夹名称,这里为了防止更新命名敲错了,需要首先在 upload.config.js 中进行更新白名单声明,如果配置都正确的情况下,你就可以看到,这就代表成功了~

标签:console,log,err,前端,更新,轻量化,ssh,config,objname
From: https://www.cnblogs.com/wp-leonard/p/17129416.html

相关文章

  • 2023前端开发最新面试题收集-Javascript篇
    前台、中台、后台-前台:面向用户、客户可以感知的,如商城-中台:可以看着对前台的补充,公共服务功能,如支付系统、搜索系统、客服-后台:面向运营、比如商品管理、物流管理1......
  • 2023前端开发最新面试题收集-Vue2/3篇
    Vue整理1、谈谈MVVM的理解MVC(react):数据流是单向的,View和Model之间通过controller连接通信,用户操作会请求服务器,路由拦截分发请求,调用对应的控制器controller,控制器会......
  • 2023前端开发最新面试题收集-Webpack篇
    webpack整理谈谈webpack的理解webpack是一个静态模块打包器。当webpack处理应用程序时,会递归构建一个依赖关系图,其中包括应用程序所需的所有模块,最后将这些模块打包成一......
  • nginx热更新流程记录
    已经做过不止一次的nginx热更新了,之前都是按照别人的路子照葫芦画瓢,现在根据我在的项目单独写一个nginx热更新流程备份自用。nginx热升级流程上传新tar到/home/cp命令复制......
  • 华为OD机试100题,同步考试更新
    ......
  • 转载-“检测到#include错误。请更新includePath
    1、按下Ctrl+Shift+P,会在VSCode上方出现一个弹窗。2、在弹窗中输入"opensettings",在下拉列表中选中"Preferences:OpenSettings(UI)":3、在搜索框内输入launch4、编......
  • win10关闭自动更新的方法
    win10关闭自动更新的方法和步骤:一、禁用WindowsUpdate服务1、打开服务项,win+r输入services.msc,或者控制面板-管理工具-服务。2、找到WindowsUpdate项。3、双击......
  • drf回顾,前端发展历史,vue介绍,第一个helloword,插值语法
    目录drf回顾,前端发展历史,vue介绍,第一个helloword,插值语法今日内容概要今日内容详细1drf回顾2前端发展历史3vue介绍4第一个helloworld5插值语法drf回顾,前端发展历史,vu......
  • 前端通过post下载文件,文件乱码的解决
    有时候数据量大或者需要上传文件,但接口又必须返回一个下载的流,就必须前端设置一下进行下载过程也很简单网上一搜一大堆博客,标红的地方必须这样写其余的可以根据你的需求......
  • 读Java实战(第二版)笔记11_语言特性和类库更新
    1. 注解1.1. 一种使用附加信息装饰程序元素的机制1.2. Java8之前,只有声明可以被注解1.3. 一种语法元数据(syntacticmetadata)1.4. 可以用于文档编制1.4.1. @De......