從零開始 Node實現前端自動化部署 從零開始 效果展示 前言 開始 使用

從零開始

更新:

🎉現已支持添加多個配置信息,自動化部署時支持選擇配置信息運行

🎉現已支修改服務器連接端口,支持ssh私鑰及解密密碼連接(ps:不使用此方法時,請註釋privateKey)

🎉現已更新模塊引用邏輯,遠端備份時間格式改爲 yyyy-MM-dd_HH:mm:ss

效果展示

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

2. 確定遠端部署目錄及發佈文件夾

3. 修改配置

4. 運行自動化部署

5. 查看遠端效果

6. 再次部署 原目錄已備份(開啓遠端備份生效)

前言

前端項目部署時,nginx配置完成後,只需將打包後的文件上傳至服務器指定目錄下即可。

一般使用以下方式完成:

  • xshell 等命令行工具上傳
  • ftp 等可視化工具上傳
  • jenkins 等自動化部署服務

對於簡單前端項目,頻繁部署時,xshellftp兩種方式較爲繁瑣,而jenkins 等自動化部署服務需要提前安裝軟件、並熟悉配置流程。
因此希望藉助本地 node 服務實現對前端打包後文件的上傳工作,既不需要服務器額外安裝程序,還可以幫助我們實現快速上傳部署,更能幫助我們深入瞭解 node

開始

1. 明確需求

進行開發前需要首先明確需求,根據常見的前端部署流程總結爲以下過程:

根據部署流程明確自動化部署的需求:

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_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 哦😘,有疑問🧐歡迎提出 issues ,積極交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章