<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          (實(shí)戰(zhàn)篇)Vue + Node.js 從 0 到 1 實(shí)現(xiàn)自動(dòng)化部署工具

          共 13946字,需瀏覽 28分鐘

           ·

          2022-04-07 21:17

          點(diǎn)擊上方?前端Q,關(guān)注公眾號(hào)

          回復(fù)加群,加入前端Q技術(shù)交流群


          最近寫了一個(gè)自動(dòng)化部署的 npm 包 zuo-deploy[1],只需點(diǎn)擊一個(gè)按鈕,就可以執(zhí)行服務(wù)器部署腳本,完成功能更新迭代。客戶端使用 Vue + ElementUI,服務(wù) koa + socket + koa-session 等。基礎(chǔ)功能代碼 300 行不到,已開源在 github。zuoxiaobai/zuo-deploy 歡迎 Star、Fork。這里介紹下具體實(shí)現(xiàn)細(xì)節(jié)、思路。

          目錄結(jié)構(gòu)

          ├──?bin?#?命令行工具命令
          │???├──?start.js?#?zuodeploy?start?執(zhí)行入口
          │???└──?zuodeploy.js?#?zuodeploy?命令入口,在?package.json?的?bin?屬性中配置
          ├──?docImages?#?README.md?文檔圖片?
          ├──?frontend?#?客戶端頁面/前端操作頁面(koa-static?靜態(tài)服務(wù)指定目錄)
          │???└──?index.html?#?Vue?+?ElementUI?+?axios?+?socket.io
          ├──?server?#?服務(wù)端
          │???├──?utils
          │???│???├──?logger.js?#?log4js?
          │???│???└──?runCmd.js?#?node?child_process?spawn(執(zhí)行?shell?腳本、pm2?服務(wù)開啟)
          │???└──?index.js?#?主服務(wù)(koa?接口、靜態(tài)服務(wù)?+?socket?+?執(zhí)行?shell?腳本)
          ├──?.eslintrc.cjs?#?eslint?配置文件?+?prettier
          ├──?args.json?#?用于?pm2?改造后,跨文件傳遞端口、密碼參數(shù)
          ├──?CHANGELOG.md?#?release?版本功能迭代記錄
          ├──?deploy-master.sh?#?用于測(cè)試,當(dāng)前目錄開啟服務(wù)偶,點(diǎn)擊部署按鈕,執(zhí)行該腳本
          ├──?index.js?#?zuodeploy?start?執(zhí)行文件,用于執(zhí)行?pm2?start?server/index.js?主服務(wù)?
          ├──?package.json?#?項(xiàng)目描述文件,npm?包名、版本號(hào)、cli?命令名稱、
          ├──?publish.sh?#?npm?publish(npm包)?發(fā)布腳本
          └──?README.md?#?使用文檔
          復(fù)制代碼

          前后端技術(shù)棧、相關(guān)依賴

          • 前端/客戶端
            • 靜態(tài) html + css,非前端工程化,庫都以 cdn 形式引入,通過庫以 UMD 打包方式暴露的全局變量使用
            • vue3,MVVM 框架,不用操作 dom
            • element-plus,基礎(chǔ)表單樣式統(tǒng)一、美化
            • axios,請(qǐng)求接口
            • socket.io,接收實(shí)時(shí)部署 log
          • 服務(wù)端
            • 普通接口,可能需要等完全部署好后,才能拿到結(jié)果
            • 基于 Node.js 技術(shù)棧,無數(shù)據(jù)庫
            • commander,用于生成的命令 zuodeploy 運(yùn)行時(shí)幫助文檔、提示,zuodeploy start 執(zhí)行入口
            • prompts,參照 vue-create,引導(dǎo)用戶輸入端口、密碼
            • koa,http 服務(wù)端, 提供接口、靜態(tài)服務(wù)運(yùn)行容器(類似 nginx、tomcat 等)
            • koa-bodyparser,用于解析 post 請(qǐng)求參數(shù)(login 鑒權(quán)接口需要)
            • koa-router,用于不同接口(路徑,比如 /login, /deploy等)執(zhí)行不同的方法
            • koa-session,用于接口鑒權(quán),防止他人獲取到部署接口后瘋狂請(qǐng)求部署
            • koa-static,靜態(tài)服務(wù)器,類似 nginx 啟動(dòng)靜態(tài)服務(wù)
            • socket.io,socket 服務(wù)端,當(dāng) git pull, npm run build 部署時(shí)間較長時(shí),實(shí)時(shí)發(fā)送 log 到前端
            • log4js,帶時(shí)間戳的 log 輸出
            • pm2,直接執(zhí)行,當(dāng) terminal 結(jié)束服務(wù)會(huì)被關(guān)掉,用 pm2 以后臺(tái)方式靜默執(zhí)行

          基礎(chǔ)功能實(shí)現(xiàn)思路

          最初目標(biāo):前端頁面點(diǎn)擊部署按鈕,可以直接讓服務(wù)器執(zhí)行部署,并將部署 log 返回給前端

          怎么去實(shí)現(xiàn)?

          • 1.要有一個(gè)前端頁面,給出 部署 按鈕,日志顯示區(qū)域。
          • 2.前端頁面與服務(wù)器交互,必須要有一個(gè)服務(wù)端 server
            • 2.1 提供接口,前端頁面點(diǎn)擊部署,請(qǐng)求該接口,知道什么時(shí)候要執(zhí)行部署,
            • 2.2 后端接口接收到請(qǐng)求后,怎么執(zhí)行部署任務(wù),
            • 2.3 shell 腳本執(zhí)行的 log,怎么搜集并發(fā)送給前端。同上,spawn 支持 log 輸出

          技術(shù)棧確定:

          • 1.Vue + ElementUI 基本頁面布局+基本邏輯,axios 請(qǐng)求接口數(shù)據(jù)
          • 2.使用 node 技術(shù)棧來提供 服務(wù)端 server
            • 2.1 使用 koa/koa-router 實(shí)現(xiàn)接口
            • 2.2 部署一般是執(zhí)行 shell 腳本,node 使用內(nèi)置子進(jìn)程 spawn 可以執(zhí)行 shell 腳本文件、跑 terminal 下運(yùn)行的命令操作
            • 2.3 spawn 執(zhí)行時(shí),子進(jìn)程 stdout, stderr 可以獲取到腳本執(zhí)行 log,收集后返回給前端

          考慮到前端頁面的部署問題,可以與 koa server 服務(wù)放到一起,使用 koa-static 開啟靜態(tài)文件服務(wù),支持前端頁面訪問

          這里不使用前端工程化 @vue/cli ,直接使用靜態(tài) html,通過 cdn 引入 vue 等

          1.客戶端 Vue+ElementUI+axios

          前端服務(wù)我們放到 frontend/index.html,koa-static 靜態(tài)服務(wù)直接指向 frontend 目錄就可以訪問頁面了

          前端基本按鈕log區(qū)域

          核心代碼如下:

          注意:cdn 鏈接都是 // 相對(duì)路徑,需要使用 http 服務(wù)打開頁面,不能以普通的 File 文件形式打開!可以等到后面 koa 寫好后,開啟服務(wù)再訪問

          <head>
          ??<title>zuo-deploytitle>
          ??
          ??<link?rel="stylesheet"?href="http://unpkg.com/element-plus/dist/index.css"?/>
          ??
          ??<script?src="http://unpkg.com/vue@next">script>
          ??
          ??<script?src="http://unpkg.com/element-plus">script>
          ??<script?src="https://unpkg.com/axios/dist/axios.min.js">script>
          head>

          <body>
          ??<div?id="app"?style="margin:0?20px;">
          ????<el-button?type="primary"?@click="deploy">部署el-button>
          ????<div>
          ??????<p>部署日志:p>
          ??????<div?class="text-log-wrap">
          ????????<pre>{{?deployLog?}}pre>
          ??????div>
          ????div>
          ??div>
          ??<script>
          ????const?app?=?{
          ??????data()?{
          ????????return?{
          ??????????deployLog:?'點(diǎn)擊按鈕進(jìn)行部署',
          ????????}
          ??????},
          ??????methods:?{
          ????????deploy()?{
          ??????????this.deployLog?=?'后端部署中,請(qǐng)稍等...'
          ??????????axios.post('/deploy')
          ????????????.then((res)?=>?{
          ??????????????//?部署完成,返回?log
          ??????????????console.log(res.data);
          ??????????????this.deployLog?=?res.data.msg
          ????????????})
          ????????????.catch(function?(err)?{
          ??????????????console.log(err);
          ????????????})
          ????????}
          ??????}
          ????}

          ????Vue.createApp(app).use(ElementPlus).mount('#app')
          ??
          script>
          body>
          復(fù)制代碼

          2.服務(wù)端koa+koa-router+koa-static

          koa 開啟 http server,寫 deploy 接口處理。koa-static 開啟靜態(tài)服務(wù)

          //?server/index.js
          const?Koa?=?require("koa");
          const?KoaStatic?=?require("koa-static");
          const?KoaRouter?=?require("koa-router");
          const?path?=?require("path");

          const?app?=?new?Koa();
          const?router?=?new?KoaRouter();

          router.post("/deploy",?async?(ctx)?=>?{
          ??//?執(zhí)行部署腳本
          ??let?execFunc?=?()?=>?{};
          ??try?{
          ????let?res?=??await?execFunc();
          ????ctx.body?=?{
          ??????code:?0,
          ??????msg:?res,
          ????};
          ??}?catch?(e)?{
          ????ctx.body?=?{
          ??????code:?-1,
          ??????msg:?e.message,
          ????};
          ??}
          });

          app.use(new?KoaStatic(path.resolve(__dirname,?"../frontend")));
          app.use(router.routes()).use(router.allowedMethods());
          app.listen(7777,?()?=>?console.log(`服務(wù)監(jiān)聽?${7777}?端口`));
          復(fù)制代碼

          將項(xiàng)目跑起來

          1. 在當(dāng)前項(xiàng)目目錄,執(zhí)行 npm init 初始化 package.json
          2. npm install koa koa-router koa-static --save 安裝依賴包
          3. node server/index.js 運(yùn)行項(xiàng)目,注意如果 7777 端口被占用,需要換一個(gè)端口

          訪問 http:// 127.0.0.1:7777 就可以訪問頁面,點(diǎn)擊部署就可以請(qǐng)求成功了

          node-base.png

          3.Node執(zhí)行shell腳本并輸出log到前端

          node 內(nèi)置模塊 child_process 下 spawn 執(zhí)行 terminal 命令,包括執(zhí)行 shell 腳本的 sh 腳本文件.sh 命令

          下來看一個(gè) demo,新建一個(gè) testExecShell 測(cè)試目錄,測(cè)試效果

          //?testExecShell/runCmd.js
          const?{?spawn?}?=?require('child_process');
          const?ls?=?spawn('ls',?['-lh',?'/usr']);?//?執(zhí)行?ls?-lh?/usr?命令

          ls.stdout.on('data',?(data)?=>?{
          ??//?ls?產(chǎn)生的?terminal?log?在這里?console
          ??console.log(`stdout:?${data}`);
          });

          ls.stderr.on('data',?(data)?=>?{
          ??//?如果發(fā)生錯(cuò)誤,錯(cuò)誤從這里輸出
          ??console.error(`stderr:?${data}`);
          });

          ls.on('close',?(code)?=>?{
          ??//?執(zhí)行完成后正常退出就是?0?
          ??console.log(`child?process?exited?with?code?${code}`);
          });
          復(fù)制代碼

          運(yùn)行 node testExecShell/runCmd.js 就可以使用 node 執(zhí)行 ls \-lh /usr,并通過 ls.stdout 接收到 log 信息并打印

          testShellLs.png

          回到正題,這里需要執(zhí)行 shell 腳本,可以將 ls \-lh /usr 替換為 sh 腳本文件.sh 即可。下面來試試

          //?testExecShell/runShell.js
          const?{?spawn?}?=?require('child_process');
          const?child?=?spawn('sh',?['testExecShell/deploy.sh']);?//?執(zhí)行?sh?deploy.sh?命令

          child.stdout.on('data',?(data)?=>?{
          ??//?shell?執(zhí)行的?log?在這里搜集,可以通過接口返回給前端
          ??console.log(`stdout:?${data}`);
          });

          child.stderr.on('data',?(data)?=>?{
          ??//?如果發(fā)生錯(cuò)誤,錯(cuò)誤從這里輸出
          ??console.error(`stderr:?${data}`);
          });

          child.on('close',?(code)?=>?{
          ??//?執(zhí)行完成后正常退出就是?0?
          ??console.log(`child?process?exited?with?code?${code}`);
          });
          復(fù)制代碼

          創(chuàng)建執(zhí)行的 shell 腳本,可以先 sh estExecShell/deploy.sh 試試是否有可執(zhí)行,如果沒執(zhí)行權(quán)限,就添加(chmod +x 文件名)

          #?/testExecShell/deploy.sh
          echo?'執(zhí)行?pwd'
          pwd
          echo?'執(zhí)行?git?pull'
          git?pull
          復(fù)制代碼

          運(yùn)行 node testExecShell/runShell.js 就可以讓 node 執(zhí)行 deploy.sh 腳本了,如下圖

          node-exec-shell.png

          參考:child\_process \- Node.js 內(nèi)置模塊筆記[2]

          4.deploy接口集成執(zhí)行shell腳本功能

          修改之前的 deploy 接口,加一個(gè) runCmd 方法,執(zhí)行當(dāng)前目錄的 deploy.sh 部署腳本,完成后接口將執(zhí)行 log 響應(yīng)給前端

          //?新建?server/indexExecShell.js,將?server/index.js?內(nèi)容拷貝進(jìn)來,并做如下修改
          const?rumCmd?=?()?=>?{
          ??return?new?Promise((resolve,?reject)?=>?{
          ????const?{?spawn?}?=?require('child_process');
          ????const?child?=?spawn('sh',?['deploy.sh']);?//?執(zhí)行?sh?deploy.sh?命令

          ????let?msg?=?''
          ????child.stdout.on('data',?(data)?=>?{
          ??????//?shell?執(zhí)行的?log?在這里搜集,可以通過接口返回給前端
          ??????console.log(`stdout:?${data}`);
          ??????//?普通接口僅能返回一次,需要把?log?都搜集到一次,在?end?時(shí)?返回給前端
          ??????msg?+=?`${data}`
          ????});

          ????child.stdout.on('end',?(data)?=>?{
          ??????resolve(msg)?//?執(zhí)行完畢后,接口?resolve,返回給前端
          ????});

          ????child.stderr.on('data',?(data)?=>?{
          ??????//?如果發(fā)生錯(cuò)誤,錯(cuò)誤從這里輸出
          ??????console.error(`stderr:?${data}`);
          ??????msg?+=?`${data}`
          ????});

          ????child.on('close',?(code)?=>?{
          ??????//?執(zhí)行完成后正常退出就是?0?
          ??????console.log(`child?process?exited?with?code?${code}`);
          ????});
          ??})
          }

          router.post("/deploy",?async?(ctx)?=>?{
          ??try?{
          ????let?res?=??await?rumCmd();?//?執(zhí)行部署腳本
          ????ctx.body?=?{
          ??????code:?0,
          ??????msg:?res,
          ????};
          ??}?catch?(e)?{
          ????ctx.body?=?{
          ??????code:?-1,
          ??????msg:?e.message,
          ????};
          ??}
          });
          復(fù)制代碼

          修改完成后,運(yùn)行 node server/indexExecShell.js 開啟最新的服務(wù),點(diǎn)擊部署,接口執(zhí)行正常,如下圖

          deploy-no-file.png

          執(zhí)行的是當(dāng)前目錄的 deploy.sh,沒有對(duì)應(yīng)的文件。將上面 testExeclShell/deploy.sh 放到當(dāng)前目錄再點(diǎn)擊部署

          deploy-sh-log.png

          這樣自動(dòng)化部署基礎(chǔ)功能基本就完成了。

          功能優(yōu)化

          1.使用 socket 實(shí)時(shí)輸出 log

          上面的例子中,普通接口需要等部署腳本執(zhí)行完成后再響應(yīng)給前端,如果腳本中包含 git pull、npm run build 等耗時(shí)較長的命令,就會(huì)導(dǎo)致前端頁面一直沒log信息,如下圖

          deploy-pending.png

          測(cè)試 shell

          echo?'執(zhí)行?pwd'
          pwd
          echo?'執(zhí)行?git?pull'
          git?pull
          git?clone[email protected]:zuoxiaobai/zuo11.com.git?#?耗時(shí)較長的命令
          echo?'部署完成'
          復(fù)制代碼
          longtime-res.png

          這里我們改造下,使用 socket.io[3] 來實(shí)時(shí)將部署 log 發(fā)送給前端

          socket.io 分為客戶端、服務(wù)端兩個(gè)部分

          客戶端代碼


          <script?src="https://cdn.socket.io/4.4.1/socket.io.min.js">script>
          <script>
          ??//?vue?mounted?鉤子里面鏈接?socket?服務(wù)端
          ??mounted()?{
          ????this.socket?=?io()?//?鏈接到?socket?服務(wù)器,發(fā)一個(gè)?http?請(qǐng)求,成功后轉(zhuǎn)?101?ws?協(xié)議
          ????//?訂閱部署日志,拿到日志,就一點(diǎn)點(diǎn)?push?到數(shù)組,顯示到前端
          ????this.socket.on('deploy-log',?(msg)?=>?{
          ??????console.log(msg)
          ??????this.msgList.push(msg)
          ????})
          ??},??
          script>
          復(fù)制代碼

          后端 koa 中引入 socket.io 代碼

          //?server/indexSoket.js
          //?npm?install?socket.io?--save
          const?app?=?new?Koa();
          const?router?=?new?KoaRouter();

          //?開啟?socket?服務(wù)
          let?socketList?=?[];
          const?server?=?require("http").Server(app.callback());
          const?socketIo?=?require("socket.io")(server);
          socketIo.on("connection",?(socket)?=>?{
          ??socketList.push(socket);
          ??console.log("a?user?connected");?//?前端調(diào)用?io(),即可連接成功
          });
          //?返回的?socketIo?對(duì)象可以用來給前端廣播消息

          runCmd()?{
          ??//?部分核心代碼
          ??let?msg?=?''
          ??child.stdout.on('data',?(data)?=>?{
          ????//?shell?執(zhí)行的?log?在這里搜集,可以通過接口返回給前端
          ????console.log(`stdout:?${data}`);
          ????socketIo.emit('deploy-log',?`${data}`)?//socket?實(shí)時(shí)發(fā)送給前端
          ????//?普通接口僅能返回一次,需要把?log?都搜集到一次,在?end?時(shí)?返回給前端
          ????msg?+=?`${data}`
          ??});
          ??//?...
          ??child.stderr.on('data',?(data)?=>?{
          ????//?如果發(fā)生錯(cuò)誤,錯(cuò)誤從這里輸出
          ????console.error(`stderr:?${data}`);
          ????socketIo.emit('deploy-log',?`${data}`)?//?socket?實(shí)時(shí)發(fā)送給前端
          ????msg?+=?`${data}`
          ??});
          }
          //?app.listen?需要改為上面加入了?socket?服務(wù)的?server?對(duì)象
          server.listen(7777,?()?=>?console.log(`服務(wù)監(jiān)聽?${7777}?端口`));
          復(fù)制代碼

          我們?cè)谥暗?demo 中加入上面的代碼,即可完成 socket 改造,node server/indexSocket.js,打開 127.0.0.1:7777/indexSocket.html,點(diǎn)擊部署,即可看到如下效果。完成 demo 訪問地址[4]

          socket-pending.png
          socket-ws-msg.png

          相關(guān)問題

          1. 關(guān)于 http 轉(zhuǎn) ws 協(xié)議,我們可以通過打開 F12 NetWork 面板看前端的 socket 相關(guān)連接步驟
          • GET http://127.0.0.1:7777/socket.io/?EIO=4&transport=polling&t=Nz5mBZk 獲取 sid
          • POST http://127.0.0.1:7777/socket.io/?EIO=4&transport=polling&t=Nz5mBaY&sid=DKQAS0fxzXUutg0wAAAG
          • GET http://127.0.0.1:7777/socket.io/?EIO=4&transport=polling&t=Nz5mBav&sid=DKQAS0fxzXUutg0wAAAG
          • ws://127.0.0.1:7777/socket.io/?EIO=4&transport=websocket&sid=DKQAS0fxzXUutg0wAAAG

          ws 這個(gè)里面可以看到 socket 傳的數(shù)據(jù)

          socket-upgrade.png
          1. http 請(qǐng)求成功狀態(tài)碼一般是 200, ws Status Code 為 101 Switching Protocols

          2.部署接口添加鑒權(quán)

          上面只是用接口實(shí)現(xiàn)的功能,并沒有加權(quán)限控制,任何人知道接口地址后,可以通過 postman 請(qǐng)求該接口,觸發(fā)部署。如下圖

          postman-deploy.png

          為了安全起見,我們這里為接口添加鑒權(quán),前端增加一個(gè)輸入密碼登錄的功能。這里使用 koa-session 來鑒權(quán),只有登錄態(tài)才能請(qǐng)求成功

          //?server/indexAuth.js
          //?npm?install?koa-session?koa-bodyparser?--save
          //?..
          const?session?=?require("koa-session");
          const?bodyParser?=?require("koa-bodyparser");?//?post?請(qǐng)求參數(shù)解析
          const?app?=?new?Koa();
          const?router?=?new?KoaRouter();

          app.use(bodyParser());?//?處理?post?請(qǐng)求參數(shù)

          //?集成?session
          app.keys?=?[`自定義安全字符串`];?//?'some?secret?hurr'
          const?CONFIG?=?{
          ??key:?"koa:sess"?/**?(string)?cookie?key?(default?is?koa:sess)?*/,
          ??/**?(number?||?'session')?maxAge?in?ms?(default?is?1?days)?*/
          ??/**?'session'?will?result?in?a?cookie?that?expires?when?session/browser?is?closed?*/
          ??/**?Warning:?If?a?session?cookie?is?stolen,?this?cookie?will?never?expire?*/
          ??maxAge:?0.5?*?3600?*?1000,?//?0.5h
          ??overwrite:?true?/**?(boolean)?can?overwrite?or?not?(default?true)?*/,
          ??httpOnly:?true?/**?(boolean)?httpOnly?or?not?(default?true)?*/,
          ??signed:?true?/**?(boolean)?signed?or?not?(default?true)?*/,
          ??rolling:?false?/**?(boolean)?Force?a?session?identifier?cookie?to?be?set?on?every?response.?The?expiration?is?reset?to?the?original?maxAge,?resetting?the?expiration?countdown.?(default?is?false)?*/,
          ??renew:?false?/**?(boolean)?renew?session?when?session?is?nearly?expired,?so?we?can?always?keep?user?logged?in.?(default?is?false)*/,
          };
          app.use(session(CONFIG,?app));

          router.post("/login",?async?(ctx)?=>?{
          ??let?code?=?0;
          ??let?msg?=?"登錄成功";
          ??let?{?password?}?=?ctx.request.body;
          ??if?(password?===?`888888`)?{?//?888888?為設(shè)置的密碼
          ????ctx.session.isLogin?=?true;
          ??}?else?{
          ????code?=?-1;
          ????msg?=?"密碼錯(cuò)誤";
          ??}
          ??ctx.body?=?{
          ????code,
          ????msg,
          ??};
          });

          router.post("/deploy",?async?(ctx)?=>?{
          ??if?(!ctx.session.isLogin)?{
          ????ctx.body?=?{
          ??????code:?-2,
          ??????msg:?"未登錄",
          ????};
          ????return;
          ??}
          ??//?有登錄態(tài),執(zhí)行部署
          })
          復(fù)制代碼

          前端相關(guān)改動(dòng),加一個(gè)密碼輸入框、一個(gè)登錄按鈕


          <div?class="login-area">
          ??<div?v-if="!isLogin">
          ????<el-input?v-model="password"?type="password"?style="width:?200px;">el-input>
          ???? 
          ????<el-button?type="primary"?@click="login">登錄el-button>
          ??div>
          ??<div?v-else>已登錄div>
          div>
          <script>
          data()?{
          ??return?{
          ????isLogin:?false,
          ????password:?''
          ??}
          },
          methods:?{
          ??login()?{
          ????if?(!this.password)?{
          ??????this.$message.warning('請(qǐng)輸入密碼')
          ??????return
          ????}
          ????axios.post('/login',?{?password:?this.password?})
          ??????.then((response)?=>?{
          ????????console.log(response.data);
          ????????let?{?code,?msg?}?=?response.data
          ????????if?(code?===?0)?{
          ??????????this.isLogin?=?true
          ????????}?else?{
          ??????????this.$message.error(msg)
          ????????}
          ??????})
          ??????.catch(function?(err)?{
          ????????console.log(err);
          ????????this.$message.error(err.message)
          ??????})
          ??}
          }
          script>
          復(fù)制代碼

          node server/indexAuth.js,打開 127.0.0.1:7777/indexAuth.html,登錄成功之后才能部署

          fe-login.png
          postman-login.png

          3.封裝成一個(gè)npm包c(diǎn)li工具

          為什么封裝成 npm 包,使用命令行工具開啟服務(wù)。主要是簡單易用,如果不使用命令行工具形式,需要三步:

          1. 先下載代碼到服務(wù)器
          2. npm install
          3. node index.js 或者 pm2 start index.js -n xxx 開啟服務(wù)

          改成 npm 包命令行工具形式只需要下面兩步,而且更節(jié)省時(shí)間

          1. npm install zuo-deploy pm2 -g
          2. 運(yùn)行 zuodeploy start 會(huì)自動(dòng)使用 pm2 開啟服務(wù)

          下面先來看一個(gè)簡單的例子,創(chuàng)建一個(gè) npm 包并上傳到 npm 官方庫步驟

          • 需要有 npm 賬號(hào),如果沒有可以到 www.npmjs.com/[5] 注冊(cè)一個(gè),我的用戶名是 'guoqzuo'
          • 創(chuàng)建一個(gè)文件夾,用于存放 npm 包內(nèi)容,比如 npmPackage
          • 在該目錄下,運(yùn)行 npm init 初始化一個(gè) package.json,輸入的 name 就是 npm 包名,這里我設(shè)置 name 為 'zuoxiaobai-test'
          • 包名有兩種形式,普通包 vue-cli,作用域包 @vue/cli,區(qū)別參見 npm包前面加\@是什么意思\(vue-cli與\@vue/cli的區(qū)別\)[6]
          • 一般默認(rèn)入口為 index.js,暴露出一個(gè)變量、一個(gè)方法
          //?index.js
          module.exports?=?{
          ??name:?'寫一個(gè)npm包',
          ??doSomething()?{
          ????console.log('這個(gè)npm暴露一個(gè)方法')
          ??}
          }
          復(fù)制代碼
          • 這樣就可以直接發(fā)布了,創(chuàng)建一個(gè) publish 腳本,并執(zhí)行(linux 下 chmod +x publish.sh;./publish.sh;)
          #?publish.sh
          npm?config?set?registry=https://registry.npmjs.org
          npm?login?#?登陸?,如果有?OTP,?郵箱會(huì)接收到驗(yàn)證碼,輸入即可
          #?登錄成功后,短時(shí)間內(nèi)會(huì)保存狀態(tài),可以直接?npm?pubish
          npm?publish?#?可能會(huì)提示名稱已存在,換個(gè)名字,獲取使用作用域包(@xxx/xxx)
          npm?config?set?registry=https://registry.npm.taobao.org?#?還原淘寶鏡像
          復(fù)制代碼
          npm-publish.png

          到 npmjs.org 搜索對(duì)應(yīng)包就可以看到了

          npm-official.png

          使用該 npm 包,創(chuàng)建 testNpm/index.js

          const?packageInfo?=?require('zuoxiaobai-test')

          console.log(packageInfo)?
          packageInfo.doSomething()
          復(fù)制代碼

          在 testNpm 目錄下 npm init 初始化 package.json,再 npm install zuoxiaobai-test --save; 再 node index.js,執(zhí)行情況如下圖,調(diào)用 npm 包正常

          test-npm.png

          這樣我們就知道怎么寫一個(gè) npm 包,并上傳到 npm 官方庫了。

          下面,我們來看怎么在 npm 包中集成 cli 命令。舉個(gè)例子:在 npm install @vue/cli \-g 后,會(huì)在環(huán)境變量中添加一個(gè) vue 命令。使用 vue create xx 可初始化一個(gè)項(xiàng)目。一般這種形式就是 cli 工具。

          一般在 package.json 中有一個(gè) bin 屬性,用于創(chuàng)建該 npm 包的自定義命令

          //?package.json
          "bin":?{
          ????"zuodeploy":?"./bin/zuodeploy.js"
          ??},
          復(fù)制代碼

          上的配置意思是:全局安裝 npm install xx -g 后,生成 zuodeploy 命令,運(yùn)行該命令時(shí),會(huì)執(zhí)行 bin/zuodeploy.js

          本地開發(fā)時(shí),配置好后,在當(dāng)前目錄下運(yùn)行 sudo npm link 即可將 zuodeploy 命令鏈接到本地的環(huán)境變量里。任何 terminal 里面運(yùn)行 zuodeploy 都會(huì)執(zhí)行當(dāng)前項(xiàng)目下的這個(gè)文件。解除可以使用 npm unlink

          一般 cli 都會(huì)使用 commander 來生成幫助文檔,管理指令邏輯,代碼如下

          //?bin/zuodeploy.js
          #!/usr/bin/env?node

          const?{?program?}?=?require("commander");
          const?prompts?=?require("prompts");

          program.version(require("../package.json").version);

          program
          ??.command("start")
          ??.description("開啟部署監(jiān)聽服務(wù)")?//?description?+?action?可防止查找?command拼接文件
          ??.action(async?()?=>?{
          ????const?args?=?await?prompts([
          ??????{
          ????????type:?"number",
          ????????name:?"port",
          ????????initial:?7777,
          ????????message:?"請(qǐng)指定部署服務(wù)監(jiān)聽端口:",
          ????????validate:?(value)?=>
          ??????????value?!==?""?&&?(value?3000?||?value?>?10000)
          ??????????????`端口號(hào)必須在?3000?-?10000?之間`
          ????????????:?true,
          ??????},
          ??????{
          ????????type:?"password",
          ????????name:?"password",
          ????????initial:?"888888",
          ????????message:?"請(qǐng)?jiān)O(shè)置登錄密碼(默認(rèn):888888)",
          ????????validate:?(value)?=>?(value.length?6???`密碼需要?6?位以上`?:?true),
          ??????},
          ????]);
          ????require("./start")(args);?//?args?為?{?port:?7777,?password:?'888888'?}
          ??});

          program.parse();
          復(fù)制代碼

          使用 commander 可以快速管理、生成幫助文檔,分配具體指令的執(zhí)行邏輯

          zuodeploy.png

          上面的代碼中,指定了 start 指令,zuodeploy start 執(zhí)行時(shí)會(huì)先通過 prompts 以詢問的方式搜集參數(shù),再執(zhí)行 bin/start.js

          zuodeploy-start.png

          在 start.js 中,我么可以將 server/index.js 的代碼全部拷貝過去即可完成 zuodeploy start 開啟服務(wù),點(diǎn)擊部署的功能

          4.穩(wěn)定性提高-pm2改造

          為了提升穩(wěn)定性,我們可以在 start.js 中以代碼的方式執(zhí)行 pm2 src/index.js 這樣服務(wù)更穩(wěn)定可靠,另外可以再加入 log4js 輸出帶時(shí)間戳的 log,這樣有利于排查問題。

          • 具體代碼參考:zuo-deploy -github[7]
          • 所有測(cè)試 demo 地址: zuo-deploy 實(shí)現(xiàn) demo - fedemo -github[8]

          最后

          將上面零碎的知識(shí)點(diǎn)匯聚到一起就是 zuo-deploy 的實(shí)現(xiàn),代碼寫的比較隨意,歡迎 star、fork、提改進(jìn) PR!

          其他問題

          前端/客戶端為什么只有一個(gè) html 沒有使用工程化

          1. 前端工程化方式組織代碼比較重,沒必要
          2. 這里功能比較簡單、只有部署按鈕、部署 log 查看區(qū)域、鑒權(quán)(輸入密碼)區(qū)域
          3. 便于部署,直接 koa-static 開啟靜態(tài)服務(wù)即可訪問,無需打包構(gòu)建

          為什么從 type: module 改為普通的 CommonJS

          package.json 里面配置 type: module 后默認(rèn)使用 ES Modules,有些 node 方法會(huì)有一些問題

          雖然可以通過修改文件后綴為 .cjs 來解決,但文件多了,還不如直接去掉 type: module 使用 node 默認(rèn)包形式

          1. __dirname 報(bào)錯(cuò)。__dirname 對(duì)于 cli 項(xiàng)目來講非常重要。當(dāng)你需要使用當(dāng)前項(xiàng)目內(nèi)文件,而非 zuodeploy start 執(zhí)行時(shí)所在目錄的文件時(shí),需要使用 __dirname
          2. require("../package.json") 改為 import xx from '../package.json' 引入 JSON 文件時(shí)會(huì)出錯(cuò)

          關(guān)于本文

          作者:做前端的左小白

          https://juejin.cn/post/7070921715492061214


          聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。



          往期推薦


          秒啊!答好這5個(gè)問題,就入門Docker了
          你知道如何提升JSON.stringify()的性能嗎?
          Vue3!煥然一新的 Vue3 中文文檔來了!

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一区二区免费在线 | 黄色性爱视频网站 | 久久久久久成人无码 | 亚洲色无码专区观看在线观 | 五月婷婷AV |