自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端賦能業(yè)務(wù):Node實(shí)現(xiàn)自動(dòng)化部署平臺(tái)

開(kāi)發(fā) 前端 自動(dòng)化
本文主要為筆者針對(duì)當(dāng)前團(tuán)隊(duì)內(nèi)的一些業(yè)務(wù)問(wèn)題,實(shí)現(xiàn)的一個(gè)自動(dòng)化部署平臺(tái)的技術(shù)方案。

前言

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

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

本文主要為筆者針對(duì)當(dāng)前團(tuán)隊(duì)內(nèi)的一些業(yè)務(wù)問(wèn)題,實(shí)現(xiàn)的一個(gè)自動(dòng)化部署平臺(tái)的技術(shù)方案。

背景

去年年初,由于團(tuán)隊(duì)里沒(méi)有前端,剛好我是被招過(guò)來(lái)的第一個(gè),也是唯一一個(gè)FE,于是我接手了一個(gè)一直由后端維護(hù)的JSSDK項(xiàng)目,其實(shí)也說(shuō)不上項(xiàng)目,接手的時(shí)候它只是一個(gè)2000多行代碼的胖腳本,沒(méi)有任何工程化痕跡。

業(yè)務(wù)需求

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

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

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

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

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

項(xiàng)目痛點(diǎn)

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

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

整個(gè)過(guò)程都需要手動(dòng)進(jìn)行,相對(duì)來(lái)說(shuō)非常繁瑣,并且一不小心就會(huì)填錯(cuò),每次都需要對(duì)腳本和接入文檔進(jìn)行檢查。

針對(duì)以上情況,得到我們需要解決的問(wèn)題:

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

自動(dòng)化方案

介紹方案之前,先上一張平臺(tái)截圖,以便先有一個(gè)直觀的認(rèn)識(shí):

SDK自動(dòng)化部署平臺(tái)主要實(shí)現(xiàn)了JSSDK的編譯,發(fā)布測(cè)試(在線預(yù)覽),上傳CDN功能。

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

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

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

項(xiàng)目搭建參考:

Vue+Express+Mysql 全棧初體驗(yàn)

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

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

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

版本管理

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

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

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

  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. }; 

在自動(dòng)化部署平臺(tái)中,使用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.     }  

Release包

Release包就是我們?cè)谏蟼鞯紺DN之前需要準(zhǔn)備的壓縮包。因此,打包JSSDK之后,我們需要生成的文件有,接入文檔、JSSDK DEMO預(yù)覽頁(yè)面、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ù)庫(kù)查詢對(duì)應(yīng)的業(yè)務(wù)方的CDN文件名  
  8.         const [sdkInfoErr, sdkInfo] = await this.supplyDao.getSupplyInfo(supplyId);  
  9.         if (sdkInfoErr) {  
  10.             return this.fail('服務(wù)器錯(cuò)誤', 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.             // 生成預(yù)覽DEMO html文件 
  26.              const demoHtmlResult = await this.generateDemoHtml(data, 'sdk-demo.html', `JSSDK-接入頁(yè)面-${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目標(biāo)目錄  
  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的編譯很簡(jiǎn)單,只需要加載對(duì)應(yīng)版本的JSSDK的編譯函數(shù),然后將對(duì)應(yīng)的參數(shù)傳入編譯函數(shù)得到一個(gè)Rollup Compiler,然后將 Compiler 結(jié)果寫(xiě)入Release路徑即可。 

  1. export default class SupplyService extends Service {  
  2.     async buildSdk(supplyId, appKey, sdkVersion) {  
  3.         try {  
  4.             const sdkBackupPath = this.sdkBackupPath;  
  5.             // 加載對(duì)應(yīng)版本的備份的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.     }  

生成接入文檔

原理很簡(jiǎn)單,使用JSZip,打開(kāi)接入文檔模板,然后使用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)建目標(biāo)目錄  
  27.                 shell.mkdir(releasePath).then((code, stdout, stderr) => {  
  28.                     if (code !== 0 ) {  
  29.                         resolve(false);  
  30.                         return;  
  31.                     }  
  32.                     // 將替換后的結(jié)果寫(xiě)入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.     }  

生成預(yù)覽DEMO頁(yè)面

與接入文檔生成原理類似,打開(kāi)一個(gè)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.             // 打開(kāi)文件  
  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.             // 寫(xiě)入文件  
  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.     }  

生成Release包描述文件

將當(dāng)前打包的一些參數(shù)存在一個(gè)文件中的,一并打包到Release包中,作用很簡(jiǎn)單,用來(lái)描述當(dāng)前打包的一些參數(shù),方便上線CDN的時(shí)候記錄當(dāng)前上線的是哪個(gè)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.                 // 寫(xiě)入release目錄  
  9.                 fs.writeFileSync(writePath, JSON.stringify(data));  
  10.                 resolve(true);  
  11.             } catch (e) {  
  12.                 console.error(e);  
  13.                 resolve(false);  
  14.             }  
  15.         });  
  16.     }  

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

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

  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.     }  

CDN部署

大部分上傳到CDN都為像CDN源站push文件,而正好我們運(yùn)維在我的自動(dòng)化部署平臺(tái)的機(jī)器上掛載了NFS,即我只需要本地將JSSDK文件拷貝到共享目錄,就實(shí)現(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描述文件丟失,請(qǐng)重新打包');  
  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信息記錄失敗,請(qǐng)重試', null, jssdkInfoResult);  
  18.             }  
  19.             return this.success('上傳成功', {url})  
  20.         }  
  21.         return this.fail('上傳失敗');  
  22.     }  

項(xiàng)目成效

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

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

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

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

總結(jié)

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

  1.  了解業(yè)務(wù)的背景
  2.  發(fā)現(xiàn)業(yè)務(wù)的痛點(diǎn)
  3.  尋找解決方案并主動(dòng)推進(jìn)實(shí)現(xiàn)
  4.  解決問(wèn)題

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

前端技術(shù)離不開(kāi)業(yè)務(wù),技術(shù)永遠(yuǎn)服務(wù)于業(yè)務(wù),離開(kāi)了業(yè)務(wù)的技術(shù),那是完全沒(méi)有落腳點(diǎn)的技術(shù),完全沒(méi)有意義的技術(shù)。所以,除了寫(xiě)寫(xiě)頁(yè)面,利用前端頁(yè)面實(shí)現(xiàn)工具化、自動(dòng)化,從而推進(jìn)到平臺(tái)化也是一個(gè)不錯(cuò)的落腳點(diǎn)選擇。 

 

責(zé)任編輯:龐桂玉 來(lái)源: 前端大全
相關(guān)推薦

2018-07-16 10:49:53

自動(dòng)化

2019-11-01 10:00:14

前端業(yè)務(wù)代碼

2021-07-15 20:02:12

AI 數(shù)據(jù)人工智能

2025-02-08 08:16:16

2020-10-19 17:37:29

物聯(lián)網(wǎng)自動(dòng)化中小企業(yè)

2021-05-05 11:36:31

Node前端自動(dòng)化熱重載頁(yè)面

2023-04-06 07:09:25

自動(dòng)化部署Actions

2022-11-15 17:07:40

開(kāi)發(fā)自動(dòng)化前端

2021-05-20 10:26:17

企業(yè)業(yè)務(wù)自動(dòng)化數(shù)字化轉(zhuǎn)型信創(chuàng)

2020-10-19 15:23:22

物聯(lián)網(wǎng)智慧城市智能建筑

2020-12-08 06:20:49

前端重構(gòu)Vue

2024-08-23 10:31:14

2022-08-16 15:05:55

Neo4j圖數(shù)據(jù)庫(kù)大數(shù)據(jù)

2019-08-07 17:31:52

2024-06-28 08:21:20

前端自動(dòng)化部署

2021-11-02 20:12:47

弘璣CycloneRPA

2023-08-16 08:34:34

WEB UI

2015-11-09 14:27:36

Ansiblelinux自動(dòng)化運(yùn)維

2022-12-20 15:17:13

RPA自動(dòng)化UiPath

2021-04-28 16:49:27

自動(dòng)化設(shè)備制藥
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)