av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

前端賦能業(yè)務:Node實現(xiàn)自動化部署平臺

前言

創(chuàng)新互聯(lián)建站服務項目包括杜集網(wǎng)站建設、杜集網(wǎng)站制作、杜集網(wǎng)頁制作以及杜集網(wǎng)絡營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,杜集網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務的客戶以成都為中心已經(jīng)輻射到杜集省份的部分城市,未來相信會繼續(xù)擴大服務區(qū)域并繼續(xù)獲得客戶的支持與信任!

是否有很多人跟我一樣有這樣的一個煩惱,每天有寫不完的需求、改不完的BUG,每天擼著重復、繁瑣的業(yè)務代碼,擔心著自己的技術(shù)成長。

其實換個角度,我們所學的所有前端技術(shù)都是服務于業(yè)務的,那我們?yōu)槭裁床幌朕k法使用前端技術(shù)為業(yè)務做點東西?這樣既能解決業(yè)務的困擾,也能讓自己擺脫每天只能寫重復繁瑣代碼的困擾。

本文主要為筆者針對當前團隊內(nèi)的一些業(yè)務問題,實現(xiàn)的一個自動化部署平臺的技術(shù)方案。

背景

去年年初,由于團隊里沒有前端,剛好我是被招過來的第一個,也是唯一一個FE,于是我接手了一個一直由后端維護的JSSDK項目,其實也說不上項目,接手的時候它只是一個2000多行代碼的胖腳本,沒有任何工程化痕跡。

業(yè)務需求

這個JSSDK,主要作用是在后端了為業(yè)務方分配appKey之后,前端將appKey寫死在JSSDK中,上傳到CDN后,為業(yè)務方提供數(shù)據(jù)采集服務的腳本。

有的同學可能有疑問,為什么不像一些正常的SDK一樣,appKey是以參數(shù)的形式傳入到JSSDK中,這樣就可以統(tǒng)一所有業(yè)務方使用同一個JSSDK,而不需要為每個業(yè)務業(yè)務方都提供一個JSSDK。其實我剛開始也是這么想的,于是我向我的leader提出了我的這個想法,被拒絕了,拒絕原因如下:

  •  appKey如果以參數(shù)形式傳入,對業(yè)務方的接入成本有所增加,會出現(xiàn)appKey填錯的問題。
  •  業(yè)務方接入JSSDK之后,希望每次JSSDK版本迭代對業(yè)務方來說是無感知的(也就是版本迭代是覆蓋式發(fā)布),如果所有業(yè)務方使用同一個JSSDK,每次JSSDK的版本迭代,一次發(fā)版會一次性對所有業(yè)務方都有影響,會增加風險。

由于我的leader現(xiàn)在主要是負責產(chǎn)品推廣,經(jīng)常和業(yè)務方打交道,可能他更能站在業(yè)務方的角度來考慮問題。所以,我的leader選擇犧牲項目的維護成本來降低SDK的接入成本和規(guī)避風險,可以理解。

那既然我們改變不了現(xiàn)狀,那就只能適應現(xiàn)狀。

項目痛點

那么針對原來沒有任何工程化情況的胖腳本,每次新增一個業(yè)務方,我需要做的事情如下:

  •  打開一個胖腳本和JSSDK接入文檔,拷貝一份新的。
  •  找后端要分配好的appKey,找對對應的appKey那一行代碼手動修改。
  •  手動混淆修改完好的腳本并上傳到CDN。
  •  修改JSSDK接入文檔中CDN的地址,保存后發(fā)送給業(yè)務方。

整個過程都需要手動進行,相對來說非常繁瑣,并且一不小心就會填錯,每次都需要對腳本和接入文檔進行檢查。

針對以上情況,得到我們需要解決的問題:

  •  怎樣針對一個新的業(yè)務方快速輸出一份新的JSSDK和接入文檔?
  •  怎樣快速對新的JSSDK進行混淆并上傳到CDN。

自動化方案

介紹方案之前,先上一張平臺截圖,以便先有一個直觀的認識:

SDK自動化部署平臺主要實現(xiàn)了JSSDK的編譯,發(fā)布測試(在線預覽),上傳CDN功能。

服務端技術(shù)棧包括:

  •  框架 Express
  •  熱更新 nodemon
  •  依賴注入 awilix
  •  數(shù)據(jù)持久化 sequelize
  •  部署 pm2

客戶端技術(shù)棧就不介紹了,Vue全家桶 + vue-property-decorator + vuex-class。

項目搭建參考:

Vue+Express+Mysql 全棧初體驗

https://juejin.im/post/5ce96694f265da1bc5523f69

自動化部署平臺主要依賴于 GIT + 本地環(huán)境 + 私有NPM源 + MYSQL,各環(huán)節(jié)之間進行通信交互,完成自動化部署。

主要達到的效果:本地環(huán)境拉取git倉庫代碼后,進行需求開發(fā),完成后發(fā)布一個帶Rollup的SDK編譯器包到私有NPM倉庫,自動化部署平臺在工程目錄安裝指定版本的SDK,并且備份到本地,在SDK編譯時,選擇特定版本的Rollup的SDK編譯器,并傳參(如appKey,appId等)到編譯器中進行編譯,同時自動生成JSSDK接入文檔等后打包成帶描述文件的Release包,在上傳到CDN時,將描述文件的對應的信息寫入MYSQL中進行保存。

版本管理

由于JSSDK原本只是一個腳本,我們必須實現(xiàn)項目的工程化,從而完成版本管理,方便快速版本切換進行發(fā)布,回滾,進而快速止損。

首先,我們需要將項目工程化,使用Rollup進行模塊管理,并且在發(fā)包NPM包的時候,輸入為各種參數(shù)(如appKey)輸出為一個Rollup Complier的函數(shù),然后使用rollup-plugin-replace在編譯時候替換代碼中具體的參數(shù)。

lib/build.js,JSSDK中發(fā)包的入口文件,提供給SDK編譯時使用

 
 
 
 
  1. import * as rollup from 'rollup'; 
  2. const replace = require('rollup-plugin-replace'); 
  3. const path = require('path'); 
  4. const pkgPath = path.join(__dirname, '..', 'package.json'); 
  5. const pkg = require(pkgPath); 
  6. const proConfig = require('./proConfig'); 
  7. function getRollupConfig(replaceParams) { 
  8.     const config = proConfig; 
  9.     // 注入系統(tǒng)變量 
  10.     const replacereplacePlugin = replace({ 
  11.         '__JS_SDK_VERSION__': JSON.stringify(pkg.version), 
  12.         '__SUPPLY_ID__': JSON.stringify(replaceParams.supplyId || '7102'), 
  13.         '__APP_KEY__': JSON.stringify(replaceParams.appKey) 
  14.     }); 
  15.     return { 
  16.         input: config.input, 
  17.         output: config.output, 
  18.         plugins: [ 
  19.             ...config.plugins, 
  20.             replacePlugin 
  21.         ] 
  22.     }; 
  23. }; 
  24. module.exports = async function (params) { 
  25.     const config = getRollupConfig({ 
  26.         supplyId: params.supplyId || '7102', 
  27.         appKey: params.appKey 
  28.     }); 
  29.     const { 
  30.         input, 
  31.         plugins 
  32.     } = config; 
  33.     const bundle = await rollup.rollup({ 
  34.         input, 
  35.         plugins 
  36.     }); 
  37.     const compiler = { 
  38.         async write(file) { 
  39.             await bundle.write({ 
  40.                 file, 
  41.                 format: 'iife', 
  42.                 sourcemap: false, 
  43.                 strict: false
  44.              }); 
  45.         } 
  46.     }; 
  47.     return compiler; 
  48. };

在自動化部署平臺中,使用shelljs安裝JSSDK包:

 
 
 
 
  1. import {route, POST} from 'awilix-express'; 
  2. import {Api} from '../framework/Api'; 
  3. import * as shell from 'shell'; 
  4. import * as path from 'path'; 
  5. @route('/supply') 
  6. export default class SupplyAPI extends Api {
  7.     // some code 
  8.     @route('/installSdkVersion') 
  9.     @POST() 
  10.     async installSdkVersion(req, res) { 
  11.         const {version} = req.body; 
  12.         const pkg = `@baidu/xxx-js-sdk@${version}`; 
  13.         const registry = 'http://registry.npm.baidu-int.com'; 
  14.         shell.exec(`npm i ${pkg} --registry=${registry}`, (code, stdout, stderr)  => { 
  15.             if (code !== 0) { 
  16.                 console.error(stderr); 
  17.                 res.failPrint('npm install fail'); 
  18.                 return; 
  19.             } 
  20.             // sdk包備份路徑 
  21.             const sdkBackupPath = this.sdkBackupPath; 
  22.             const sdkPath = path.resolve(sdkBackupPath, version); 
  23.             shell.mkdir('-p', sdkPath).then((code, stdout, stderr) => { 
  24.                 if (code !== 0) { 
  25.                     console.error(stderr); 
  26.                     res.failPrint(`mkdir \`${sdkPath}\` error.`); 
  27.                     return; 
  28.                 } 
  29.                 const modulePath = path.resolve(process.cwd(), 'node_modules', '@baidu', 'xxx-js-sdk'); 
  30.                 // 拷貝安裝后的文件,方便后續(xù)使用 
  31.                 shell.cp('-rf', modulePath + '/.', sdkPath).then((code, stdout, stderr) => { 
  32.                     if (code !== 0) { 
  33.                         console.error(stderr); 
  34.                         res.failPrint(`backup sdk error.`); 
  35.                         return; 
  36.                     } 
  37.                     res.successPrint(`${pkg} install success.`); 
  38.                 }); 
  39.             }) 
  40.         }); 
  41.     } 
  42. }

Release包

Release包就是我們在上傳到CDN之前需要準備的壓縮包。因此,打包JSSDK之后,我們需要生成的文件有,接入文檔、JSSDK DEMO預覽頁面、JSSDK編譯結(jié)果、描述文件。

首先,打包函數(shù)如下:

 
 
 
 
  1. import {Service} from '../framework'; 
  2. import * as fs from 'fs'; 
  3. import path from 'path'; 
  4. import _ from 'lodash';  
  5. export default class SupplyService extends Service { 
  6.     async generateFile(supplyId, sdkVersion) { 
  7.         // 數(shù)據(jù)庫查詢對應的業(yè)務方的CDN文件名 
  8.         const [sdkInfoErr, sdkInfo] = await this.supplyDao.getSupplyInfo(supplyId); 
  9.         if (sdkInfoErr) { 
  10.             return this.fail('服務器錯誤', null, sdkInfoErr); 
  11.         } 
  12.         const {appKey, cdnFilename, name} = sdkInfo; 
  13.         // 需要替換的數(shù)據(jù) 
  14.         const data = { 
  15.             name,
  16.             supplyId, 
  17.             appKey, 
  18.             'sdk_url': `https://***.com/sdk/${cdnFilename}` 
  19.         }; 
  20.         try { 
  21.             // 編譯JSSDK 
  22.             const sdkResult = await this.buildSdk(supplyId, appKey, sdkVersion); 
  23.             // 生成接入文檔 
  24.             const docResult = await this.generateDocs(data); 
  25.             // 生成預覽DEMO html文件
  26.              const demoHtmlResult = await this.generateDemoHtml(data, 'sdk-demo.html', `JSSDK-接入頁面-${data.name}.html`); 
  27.             // 生成release包描述文件 
  28.             const sdkInfoFileResult = await this.writeSdkVersionFile(supplyId, appKey, sdkVersion);          
  29.              const success = docResult && demoHtmlResult && sdkInfoFileResult && sdkResult; 
  30.             if (success) { 
  31.                 // release目標目錄 
  32.                 const dir = path.join(this.releasePath, supplyId + ''); 
  33.                 const fileName = `${supplyId}-${sdkVersion}.zip`; 
  34.                 const zipFileName = path.join(dir, fileName); 
  35.                 // 壓縮所有結(jié)果文件 
  36.                 const zipResult = await this.zipDirFile(dir, zipFileName); 
  37.                 if (!zipResult) { 
  38.                     return this.fail('打包失敗'); 
  39.                 } 
  40.                 // 返回壓縮包提供下載 
  41.                 return this.success('打包成功', { 
  42.                     url: `/${supplyId}/${fileName}` 
  43.                 }); 
  44.             } else { 
  45.                 return this.fail('打包失敗'); 
  46.             } 
  47.         } catch (e) { 
  48.             return this.fail('打包失敗', null, e); 
  49.         } 
  50.     }
  51.  }

編譯JSSDK

JSSDK的編譯很簡單,只需要加載對應版本的JSSDK的編譯函數(shù),然后將對應的參數(shù)傳入編譯函數(shù)得到一個Rollup Compiler,然后將 Compiler 結(jié)果寫入Release路徑即可。

 
 
 
 
  1. export default class SupplyService extends Service { 
  2.     async buildSdk(supplyId, appKey, sdkVersion) { 
  3.         try { 
  4.             const sdkBackupPath = this.sdkBackupPath; 
  5.             // 加載對應版本的備份的JSSDK包的Rollup編譯函數(shù) 
  6.             const compileSdk = require(path.resolve(sdkBackupPath, sdkVersion, 'lib', 'build.js')); 
  7.             const bundle = await compileSdk({ 
  8.                 supplyId, 
  9.                 appKey: Number(sdkInfo.appKey) 
  10.             }); 
  11.             const releasePath = path.resolve(this.releasePath, supplyId, `${supplyId}-sdk.js`); 
  12.             // Rollup Compiler 編譯結(jié)果至release目錄 
  13.             await bundle.write(releasePath); 
  14.             return true; 
  15.         } catch (e) { 
  16.             console.error(e); 
  17.             return false; 
  18.         } 
  19.     } 
  20. }

生成接入文檔

原理很簡單,使用JSZip,打開接入文檔模板,然后使用Docxtemplater替換模板里的特殊字符,然后重新生成DOC文件:

 
 
 
 
  1. import Docxtemplater from 'docxtemplater'; 
  2. import JSZip from 'JSZip'; 
  3. export default class SupplyService extends Service { 
  4.     async generateDocs(data) { 
  5.         return new Promise(async (resolve, reject) => { 
  6.             if (data) { 
  7.                 // 讀取接入文檔,替換appKey,cdn路徑 
  8.                 const supplyId = data.supplyId;
  9.                  const docsFileName = 'sdk-doc.docx'; 
  10.                 const supplyFilesPath = path.resolve(process.cwd(), 'src/server/files'); 
  11.                 const content = fs.readFileSync(path.resolve(supplyFilesPath, docsFileName), 'binary'); 
  12.                 const zip = new JSZip(content); 
  13.                 const doc = new Docxtemplater(); 
  14.                 // 替換`[[`前綴和`]]`后綴的內(nèi)容
  15.                  doc.loadZip(zip).setOptions({delimiters: {start: '[[', end: ']]'}}); 
  16.                 doc.setData(data); 
  17.                 try { 
  18.                     doc.render(); 
  19.                 } catch (error) { 
  20.                     console.error(error); 
  21.                     reject(error); 
  22.                 } 
  23.                 // 生成DOC的buffer 
  24.                 const buf = doc.getZip().generate({type: 'nodebuffer'}); 
  25.                 const releasePath = path.resolve(this.releasePath, supplyId); 
  26.                 // 創(chuàng)建目標目錄 
  27.                 shell.mkdir(releasePath).then((code, stdout, stderr) => { 
  28.                     if (code !== 0 ) { 
  29.                         resolve(false); 
  30.                         return; 
  31.                     } 
  32.                     // 將替換后的結(jié)果寫入release路徑 
  33.                     fs.writeFileSync(path.resolve(releasePath, `JSSDK-文檔-${data.name}.docx`), buf); 
  34.                     resolve(true); 
  35.                 }).catch(e => { 
  36.                     console.error(e); 
  37.                     resolve(false); 
  38.                 }); 
  39.             } 
  40.         }); 
  41.     } 
  42. }

生成預覽DEMO頁面

與接入文檔生成原理類似,打開一個DEMO模板HTML文件,替換內(nèi)部字符,重新生成文件:

 
 
 
 
  1. export default class SupplyService extends Service { 
  2.     generateDemoHtml(data, file, toFile) { 
  3.         return new Promise((resolve, reject) => { 
  4.             const supplyId = data.supplyId; 
  5.             // 需要替換的數(shù)據(jù) 
  6.             const replaceData = data; 
  7.             // 打開文件 
  8.             const content = fs.readFileSync(path.resolve(supplyFilesPath, file), 'utf-8'); 
  9.             // 字符串替換`{{`前綴和`}}`后綴的內(nèi)容 
  10.             const replaceContent = content.replace(/{{(.*)}}/g, (match, key) => { 
  11.                 return replaceData[key] || match; 
  12.             }); 
  13.             const releasePath = path.resolve(this.releasePath, supplyId); 
  14.             // 寫入文件 
  15.             fs.writeFile(path.resolve(releasePath, toFile), replaceContent, err => { 
  16.                 if (err) { 
  17.                     console.error(err); 
  18.                     resolve(false); 
  19.                 } else { 
  20.                     resolve(true); 
  21.                 } 
  22.             }); 
  23.         }); 
  24.     } 
  25. }

生成Release包描述文件

將當前打包的一些參數(shù)存在一個文件中的,一并打包到Release包中,作用很簡單,用來描述當前打包的一些參數(shù),方便上線CDN的時候記錄當前上線的是哪個SDK版本等

 
 
 
 
  1. export default class SupplyService extends Service { 
  2.     async writeSdkVersionFile(supplyId, appKey, sdkVersion) { 
  3.         return new Promise(resolve => { 
  4.             const writePath = path.resolve(this.releasePath, supplyId, 'version.json');
  5.             // Release描述數(shù)據(jù) 
  6.             const data = {version: sdkVersion, appKey, supplyId}; 
  7.             try { 
  8.                 // 寫入release目錄 
  9.                 fs.writeFileSync(writePath, JSON.stringify(data)); 
  10.                 resolve(true); 
  11.             } catch (e) { 
  12.                 console.error(e); 
  13.                 resolve(false); 
  14.             } 
  15.         }); 
  16.     } 
  17. }

打包所有文件結(jié)果

將之前生成的JSSDK編譯結(jié)果、接入文檔、預覽DEMO頁面文件,描述文件使用archive打包起來:

 
 
 
 
  1. export default class SupplyService extends Service { 
  2.     zipDirFile(dir, to) { 
  3.         return new Promise(async (resolve, reject) => { 
  4.             const output = fs.createWriteStream(to); 
  5.             const archive = archiver('zip'); 
  6.             archive.on('error', err => reject(err)); 
  7.             archive.pipe(output); 
  8.             const files = fs.readdirSync(dir); 
  9.             files.forEach(file => { 
  10.                 const filePath = path.resolve(dir, file); 
  11.                 const info = fs.statSync(filePath); 
  12.                 if (!info.isDirectory()) { 
  13.                     archive.append(fs.createReadStream(filePath), { 
  14.                         'name': file 
  15.                     }); 
  16.                 } 
  17.             }); 
  18.             archive.finalize(); 
  19.             resolve(true); 
  20.         }); 
  21.     } 
  22. }

CDN部署

大部分上傳到CDN都為像CDN源站push文件,而正好我們運維在我的自動化部署平臺的機器上掛載了NFS,即我只需要本地將JSSDK文件拷貝到共享目錄,就實現(xiàn)了CDN文件上傳。

 
 
 
 
  1. export default class SupplyService extends Service { 
  2.     async cp2CDN(supplyId, fileName) {
  3.          // 讀取描述文件 
  4.         const sdkInfoPath = path.resolve(this.releasePath, '' + supplyId, 'version.json'); 
  5.         if (!fs.existsSync(sdkInfoPath)) { 
  6.             return this.fail('Release描述文件丟失,請重新打包'); 
  7.         } 
  8.         const sdkInfo = JSON.parse(fs.readFileSync(sdkInfoPath, 'utf-8')); 
  9.         sdkInfo.cdnFilename = fileName; 
  10.         // 將文件拷貝至文件共享目錄 
  11.         const result = await this.cpFile(supplyId, fileName, false); 
  12.         // 上傳成功 
  13.         if (result) {
  14.              // 將Release包描述文件的數(shù)據(jù)同步到MYSQL 
  15.             const [sdkInfoErr] = await this.supplyDao.update(sdkInfo, {where: {supplyId}}); 
  16.             if (sdkInfoErr) { 
  17.                 return this.fail('JSSDK信息記錄失敗,請重試', null, jssdkInfoResult); 
  18.             } 
  19.             return this.success('上傳成功', {url}) 
  20.         } 
  21.         return this.fail('上傳失敗'); 
  22.     } 
  23. }

項目成效

項目效益還是很明顯,從本質(zhì)上解決了我們需要解決的問題:

  •  完成了項目的工程化,自動化生成JSSDK和接入文檔。
  •  編譯過程中自動化進行混淆,并實現(xiàn)了一鍵上傳至CDN。

節(jié)省了人工上傳粘貼代碼的時間,大大地提高了工作效率。

這個項目還是19年前半年個人花業(yè)余時間完成的工具項目,后來得到了Leader的重視,將工具正式升級為平臺,集成了很多業(yè)務相關(guān)的配置在平臺,我19年的前半年KPI就這么來的,哈~~~

總結(jié)

或者這一套思路對每個業(yè)務都比較適用

  1.  了解業(yè)務的背景
  2.  發(fā)現(xiàn)業(yè)務的痛點
  3.  尋找解決方案并主動推進實現(xiàn)
  4.  解決問題

其實每個項目中的痛點都一般都是XX的性能低下、XX非常低效,還是比較容易發(fā)現(xiàn)的,這個時候只需要主動的尋找方案并推進實現(xiàn)就OK了。

前端技術(shù)離不開業(yè)務,技術(shù)永遠服務于業(yè)務,離開了業(yè)務的技術(shù),那是完全沒有落腳點的技術(shù),完全沒有意義的技術(shù)。所以,除了寫寫頁面,利用前端頁面實現(xiàn)工具化、自動化,從而推進到平臺化也是一個不錯的落腳點選擇。


分享文章:前端賦能業(yè)務:Node實現(xiàn)自動化部署平臺
網(wǎng)頁URL:http://uogjgqi.cn/article/djhhecd.html
掃二維碼與項目經(jīng)理溝通

我們在微信上24小時期待你的聲音

解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流