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

从零开始 更新: ?现已支持添加多个配置信息,自动化部署时支持选择配置信息运行 ?现已支修改服务器连接端口,支持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 ,积极交流。

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):