likes
comments
collection
share

从零开始 Node实现前端自动化部署

作者站长头像
站长
· 阅读数 55

从零开始

效果展示

1. 待部署工程本地完成打包构建

从零开始 Node实现前端自动化部署

2. 确定远端部署目录及发布文件夹

从零开始 Node实现前端自动化部署

3. 修改配置

从零开始 Node实现前端自动化部署

4. 运行自动化部署

从零开始 Node实现前端自动化部署

从零开始 Node实现前端自动化部署

5. 查看远端效果

从零开始 Node实现前端自动化部署

6. 再次部署 原目录已备份(开启远端备份生效)

从零开始 Node实现前端自动化部署

前言

前端项目部署时,nginx配置完成后,只需将打包后的文件上传至服务器指定目录下即可。

一般使用以下方式完成:

  • xshell 等命令行工具上传
  • ftp 等可视化工具上传
  • jenkins 等自动化部署服务

对于简单前端项目,频繁部署时,xshellftp两种方式较为繁琐,而jenkins 等自动化部署服务需要提前安装软件、并熟悉配置流程。 因此希望借助本地 node 服务实现对前端打包后文件的上传工作,既不需要服务器额外安装程序,还可以帮助我们实现快速上传部署,更能帮助我们深入了解 node

附:npm地址

开始

1. 明确需求

进行开发前需要首先明确需求,根据常见的前端部署流程总结为以下过程:

从零开始 Node实现前端自动化部署

根据部署流程明确自动化部署的需求:

从零开始 Node实现前端自动化部署

2. 开发前准备

2.1 导入依赖模块

由于需要实现文件压缩、及连接远程服务器、实现远程命令调用,因此至少需要以下模块:

  • ssh 模块(可实现连接服务器、命令调用等常见操作)
  • 文件压缩 模块(可实现 .zip 等常见压缩文件的本地打包)
  • 命令行选择 模块(可实现对多配置项文件进行选择和使用)

查找资料,最终选择 node-ssharchiverinquirer 分别实现上述功能。

# 安装依赖
npm i node-ssh --save
npm i archiver --save
npm i inquirer --save

2.2 如何实现规范

为实现需求中的 解耦合理逻辑清晰/灵活 ,需要关注整体程序逻辑,这里选择 封装 相关功能实现,并在 主程序 中自由调度(可灵活 调用/关闭/修改 相关功能),并对于当前所执行功能给与提示,以保证功能实现的 完整性异常提示

因为 文件压缩文件上传执行远端命令 等存在异步过程,因此需要明确功能完成的顺序,即 应在前置任务完成的回调中开启当前任务,为实现控制异步过程和代码逻辑清晰,这里选择使用 ES6 的 Promise 结合 ES7的语法糖 async awiat 实现逻辑流程控制。

到这里就完成了对程序功能构建的梳理工作,下面进入项目实现。

3. 功能实现

工程目录预览 这里先展示最终结果的工程目录,以供参考: 从零开始 Node实现前端自动化部署

  • node_modules
  • utils
    • compressFile.js【压缩本地文件】
    • handleCommand.js【调用远端命令】
    • handleTime.js【时间处理】
    • helper.js【部署项目选择提示】
    • ssh.js【连接远端务器】
    • uploadFile.js【上传本地文件】
  • app.js【主程序】
  • config.js【配置文件】
  • dist.zip【压缩后的文件】(当前版本不会自动删除)
  • package.json
  • README.md【项目介绍】

3.1 压缩本地文件

compressFile 接收 需要压缩的目录打包生成文件,传入后实现本地文件压缩。

compressFile.js 参考代码

// compressFile.js
const fs = require('fs')
const archiver = require('archiver')

function compressFile (targetDir, localFile) {
  return new Promise((resolve, reject)=>{
    console.log('1-正在压缩文件...')
    let output = fs.createWriteStream(localFile) // 创建文件写入流
    const archive = archiver('zip', {
      zlib: { level: 9 } // 设置压缩等级
    })
    output.on('close', () => {
      resolve(
        console.log('2-压缩完成!共计 ' + (archive.pointer() / 1024 /1024).toFixed(3) + 'MB')
      )
    }).on('error', (err) => {
      reject(console.error('压缩失败', err))
    })
    archive.pipe(output) // 管道存档数据到文件
    archive.directory(targetDir, 'dist') // 存储目标文件并重命名
    archive.finalize() // 完成文件追加 确保写入流完成
  })
}

module.exports = compressFile

3.2 连接远端服务器

connectServe 接收远端ip用户名密码等信息,完成远端服务器连接,具体配置参考 config.js

ssh.js 参考代码

// ssh.js
const node_ssh = require('node-ssh')
const ssh = new node_ssh()

function connectServe (sshInfo) {
  return new Promise((resolve, reject) => {
    ssh.connect({ ...sshInfo }).then(() => {
      resolve(console.log('3-' + sshInfo.host + ' 连接成功'))
    }).catch((err) => {
      reject(console.error('3-' + sshInfo.host + ' 连接失败', err))
    })
  })
}

module.exports = connectServe

3.3 远端执行命令

runCommand 接收 需执行的命令执行命令的远端路径,这里将其单独拆分,既方便 主程序 的单独调用,也方便 文件上传 等功能的模块封装,达到 解耦 的效果。

handleCommand.js 参考代码

// handleCommand.js
// run linux shell(ssh对象、shell指令、执行路径)
function runCommand (ssh, command, path) {
  return new Promise((resolve, reject) => {
    ssh.execCommand(command, {
      cwd: path
    }).then((res) => {
      if (res.stderr) {
        reject(console.error('命令执行发生错误:' + res.stderr))
        process.exit()
      } else {
        resolve(console.log(command + ' 执行完成!'))
      }
    })
  })
}

module.exports = runCommand

3.4 文件上传

uploadFile 接收 系统配置参数待上传的本地文件 ,完成本地文件上传至指定服务器目录,这里还引入 handleCommand.jshandleTime.js ,根据 openBackUp 是否开启远端备份,完成对存在解压后同名目录的处理。具体配置参考 config.js

uploadFile.js 参考代码

// uploadFile.js
const runCommand = require ('./handleCommand')
const getCurrentTime = require ('./handleTime')

// 文件上传(ssh对象、配置信息、本地待上传文件)
async function uploadFile (ssh, config, localFile) {
  return new Promise((resolve, reject) => {
    console.log('4-开始文件上传')
    handleSourceFile(ssh, config)
    ssh.putFile(localFile, config.deployDir + config.targetFile).then(async () => {
      resolve(console.log('5-文件上传完成'))
    }, (err) => {
      reject(console.error('5-上传失败!', err))
    })
  })
}

// 处理源文件(ssh对象、配置信息)
async function handleSourceFile (ssh, config) {
  if (config.openBackUp) {
    console.log('已开启远端备份!')
    await runCommand(
      ssh,
      `
      if [ -d ${config.releaseDir} ];
      then mv ${config.releaseDir} ${config.releaseDir}_${getCurrentTime()}
      fi
      `,
      config.deployDir)
  } else {
    console.log('提醒:未开启远端备份!')
    await runCommand(
      ssh,
      `
      if [ -d ${config.releaseDir} ];
      then mv ${config.releaseDir} /tmp/${config.releaseDir}_${getCurrentTime()}
      fi
      `,
      config.deployDir)
  }
}

module.exports = uploadFile

3.5 时间处理

getCurrentTime 获取并返回当前时间,远端远程备份时使用。

handleTime.js 参考代码

// 获取当前时间
function getCurrentTime () {
  const date = new Date
  const yyyy = date.getFullYear()
  const MM = coverEachUnit(date.getMonth() + 1)
  const dd = coverEachUnit(date.getDate())
  const HH = coverEachUnit(date.getHours())
  const mm = coverEachUnit(date.getMinutes())
  const ss = coverEachUnit(date.getSeconds())
  return `${yyyy}-${MM}-${dd}_${HH}:${mm}:${ss}`
}

// 转换时间中一位至两位
function coverEachUnit (val) {
  return val < 10 ? '0' + val : val
}

module.exports = getCurrentTime

3.6 主程序

当所有功能模块封装完成后,其中 异步流程 均使用 Promise 处理,这时结合 async awiat 实现,既保证了功能实现的顺序,也使得功能组合变得更加简洁、优雅。

main 函数中通关功能组合实现自动化部署的流程,后续增加其他功能实现,主程序 中引入、组合即可完成升级。

app.js 参考代码

const config = require ('./config')
const helper = require ('./utils/helper')
const compressFile = require ('./utils/compressFile')
const sshServer = require ('./utils/ssh')
const uploadFile = require ('./utils/uploadFile')
const runCommand = require ('./utils/handleCommand')

// 主程序(可单独执行)
async function main () {
  try {
    console.log('请确保文件解压后为dist目录!!!')
    const SELECT_CONFIG = (await helper(config)).value // 所选部署项目的配置信息
    console.log('您选择了部署 ' + SELECT_CONFIG.name)
    const localFile =  __dirname + '/' + SELECT_CONFIG.targetFile // 待上传本地文件
    SELECT_CONFIG.openCompress ? await compressFile(SELECT_CONFIG.targetDir, localFile) : '' //压缩
    await sshServer.connectServe(SELECT_CONFIG.ssh) // 连接
    await uploadFile(sshServer.ssh, SELECT_CONFIG, localFile) // 上传
    await runCommand(sshServer.ssh, 'unzip ' + SELECT_CONFIG.targetFile, SELECT_CONFIG.deployDir) // 解压
    await runCommand(sshServer.ssh, 'mv dist ' + SELECT_CONFIG.releaseDir, SELECT_CONFIG.deployDir) // 修改文件名称
    await runCommand(sshServer.ssh, 'rm -f ' + SELECT_CONFIG.targetFile, SELECT_CONFIG.deployDir) // 删除
  } catch (err) {
    console.log('部署过程出现错误!', err)
  } finally {
    process.exit()
  }
}

// run main
main()

3.7 配置文件

为方便前端自动化部署,这里抽离关键信息,生成配置文件。 用户只需修改配置文件即可实现 自动化部署

config.js 参考代码

/*
config.js
说明:
  请确保解压后的文件目录为dist
  ssh: 连接服务器用户信息
  targetDir: 需要压缩的文件目录(启用本地压缩后生效)
  targetFile: 指定上传文件名称(config.js同级目录)
  openCompress: 关闭后,将跳过本地文件压缩,直接上传同级目录下指定文件
  openBackUp: 开启后,若远端存在相同目录,则会修改原始目录名称,不会直接覆盖
  deployDir: 指定远端部署地址
  releaseDir: 指定远端部署地址下的发布目录名称
更新:
  🎉现已支持添加多个配置信息,自动化部署时支持选择配置信息运行
  🎉现已支修改服务器连接端口,支持ssh私钥及解密密码连接(ps:不使用此方法时,请注释privateKey)
  🎉现已更新模块引用逻辑,远端备份时间格式改为 `yyyy-MM-dd_HH:mm:ss`
  */

const config = [
  {
    name: '项目A-dev',
    ssh: {
      host: '192.168.0.110',
      port: 22,
      username: 'root',
      password: 'root',
      // privateKey: 'E:/id_rsa', // ssh私钥(不使用此方法时请勿填写, 注释即可)
      passphrase: '123456' // ssh私钥对应解密密码(不存在设为''即可)
    },
    targetDir: 'E:/private/my-vue-cli/dist', // 目标压缩目录(可使用相对地址)
    targetFile: 'dist.zip', // 目标文件
    openCompress: true, // 是否开启本地压缩
    openBackUp: true, // 是否开启远端备份
    deployDir: '/home/node_test' + '/', // 远端目录
    releaseDir: 'web' // 发布目录
  },
  {
    name: '项目A-prod',
    ssh: {
      host: '192.168.0.110',
      port: 22,
      username: 'root',
      password: 'root',
      privateKey: 'E:/id_rsa', // ssh私钥(不使用此方法时请勿填写, 注释即可)
      passphrase: '123456' // ssh私钥对应解密密码(不存在设为''即可)
    },
    targetDir: 'E:/private/my-vue-cli/dist', // 目标压缩目录(可使用相对地址)
    targetFile: 'dist.zip', // 目标文件
    openCompress: true, // 是否开启本地压缩
    openBackUp: true, // 是否开启远端备份
    deployDir: '/home/node_test' + '/', // 远端目录
    releaseDir: 'web2' // 发布目录
  }
]

module.exports = config

使用

拉取源码、安装依赖、修改配置文件、运行即可

npm install
npm run deploy

最后

🎉该项目已开源至 github 欢迎下载使用 后续会完善更多功能 🎉 源码及项目说明

Tip: 喜欢的话别忘记 star 哦😘,有疑问🧐欢迎提出 prissues ,积极交流。

其他文章:

转载自:https://juejin.cn/post/6844904049632870407
评论
请登录