從零開始
更新:
🎉現已支持添加多個配置信息,自動化部署時支持選擇配置信息運行
🎉現已支修改服務器連接端口,支持ssh私鑰及解密密碼連接(ps:不使用此方法時,請註釋privateKey)
🎉現已更新模塊引用邏輯,遠端備份時間格式改爲 yyyy-MM-dd_HH:mm:ss
效果展示
1. 待部署工程本地完成打包構建
2. 確定遠端部署目錄及發佈文件夾
3. 修改配置
4. 運行自動化部署
5. 查看遠端效果
6. 再次部署 原目錄已備份(開啓遠端備份生效)
前言
前端項目部署時,nginx配置完成後,只需將打包後的文件上傳至服務器指定目錄下即可。
一般使用以下方式完成:
-
xshell
等命令行工具上傳 -
ftp
等可視化工具上傳 -
jenkins
等自動化部署服務
對於簡單前端項目,頻繁部署時,xshell
、ftp
兩種方式較爲繁瑣,而jenkins
等自動化部署服務需要提前安裝軟件、並熟悉配置流程。
因此希望藉助本地 node
服務實現對前端打包後文件的上傳工作,既不需要服務器額外安裝程序,還可以幫助我們實現快速上傳部署,更能幫助我們深入瞭解 node
。
開始
1. 明確需求
進行開發前需要首先明確需求,根據常見的前端部署流程總結爲以下過程:
根據部署流程明確自動化部署的需求:
2. 開發前準備
2.1 導入依賴模塊
由於需要實現文件壓縮、及連接遠程服務器、實現遠程命令調用,因此至少需要以下模塊:
-
ssh
模塊(可實現連接服務器、命令調用等常見操作) -
文件壓縮
模塊(可實現.zip
等常見壓縮文件的本地打包) -
命令行選擇
模塊(可實現對多配置項文件進行選擇和使用)
查找資料,最終選擇 node-ssh
、 archiver
、inquirer
分別實現上述功能。
# 安裝依賴
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.js
、 handleTime.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
,積極交流。