精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

前端賦能業務:Node實現自動化部署平臺

開發 前端 自動化
本文主要為筆者針對當前團隊內的一些業務問題,實現的一個自動化部署平臺的技術方案。

前言

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

其實換個角度,我們所學的所有前端技術都是服務于業務的,那我們為什么不想辦法使用前端技術為業務做點東西?這樣既能解決業務的困擾,也能讓自己擺脫每天只能寫重復繁瑣代碼的困擾。

本文主要為筆者針對當前團隊內的一些業務問題,實現的一個自動化部署平臺的技術方案。

背景

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

業務需求

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

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

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

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

那既然我們改變不了現狀,那就只能適應現狀。

項目痛點

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

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

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

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

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

自動化方案

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

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

服務端技術棧包括:

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

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

項目搭建參考:

Vue+Express+Mysql 全棧初體驗

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

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

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

版本管理

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

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

lib/build.js,JSSDK中發包的入口文件,提供給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.     // 注入系統變量  
  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.                 // 拷貝安裝后的文件,方便后續使用  
  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包就是我們在上傳到CDN之前需要準備的壓縮包。因此,打包JSSDK之后,我們需要生成的文件有,接入文檔、JSSDK DEMO預覽頁面、JSSDK編譯結果、描述文件。

首先,打包函數如下: 

  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.         // 數據庫查詢對應的業務方的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.         // 需要替換的數據  
  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.                 // 壓縮所有結果文件  
  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的編譯函數,然后將對應的參數傳入編譯函數得到一個Rollup Compiler,然后將 Compiler 結果寫入Release路徑即可。 

  1. export default class SupplyService extends Service {  
  2.     async buildSdk(supplyId, appKey, sdkVersion) {  
  3.         try {  
  4.             const sdkBackupPath = this.sdkBackupPath;  
  5.             // 加載對應版本的備份的JSSDK包的Rollup編譯函數  
  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 編譯結果至release目錄  
  13.             await bundle.write(releasePath);  
  14.             return true;  
  15.         } catch (e) {  
  16.             console.error(e);  
  17.             return false;  
  18.         }  
  19.     }  

生成接入文檔

原理很簡單,使用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.                 // 替換`[[`前綴和`]]`后綴的內容 
  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.                 // 創建目標目錄  
  27.                 shell.mkdir(releasePath).then((code, stdout, stderr) => {  
  28.                     if (code !== 0 ) {  
  29.                         resolve(false);  
  30.                         return;  
  31.                     }  
  32.                     // 將替換后的結果寫入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.     }  

生成預覽DEMO頁面

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

  1. export default class SupplyService extends Service {  
  2.     generateDemoHtml(data, file, toFile) {  
  3.         return new Promise((resolve, reject) => {  
  4.             const supplyId = data.supplyId;  
  5.             // 需要替換的數據  
  6.             const replaceData = data 
  7.             // 打開文件  
  8.             const content = fs.readFileSync(path.resolve(supplyFilesPath, file), 'utf-8');  
  9.             // 字符串替換`{{`前綴和`}}`后綴的內容  
  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.     }  

生成Release包描述文件

將當前打包的一些參數存在一個文件中的,一并打包到Release包中,作用很簡單,用來描述當前打包的一些參數,方便上線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描述數據  
  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.     }  

打包所有文件結果

將之前生成的JSSDK編譯結果、接入文檔、預覽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.     }  

CDN部署

大部分上傳到CDN都為像CDN源站push文件,而正好我們運維在我的自動化部署平臺的機器上掛載了NFS,即我只需要本地將JSSDK文件拷貝到共享目錄,就實現了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包描述文件的數據同步到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.     }  

項目成效

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

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

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

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

總結

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

  1.  了解業務的背景
  2.  發現業務的痛點
  3.  尋找解決方案并主動推進實現
  4.  解決問題

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

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

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2018-07-16 10:49:53

自動化

2019-11-01 10:00:14

前端業務代碼

2021-07-15 20:02:12

AI 數據人工智能

2020-10-19 17:37:29

物聯網自動化中小企業

2025-02-08 08:16:16

2021-05-05 11:36:31

Node前端自動化熱重載頁面

2023-04-06 07:09:25

自動化部署Actions

2022-11-15 17:07:40

開發自動化前端

2021-05-20 10:26:17

企業業務自動化數字化轉型信創

2020-10-19 15:23:22

物聯網智慧城市智能建筑

2022-08-16 15:05:55

Neo4j圖數據庫大數據

2020-12-08 06:20:49

前端重構Vue

2024-08-23 10:31:14

2019-08-07 17:31:52

2021-04-28 16:49:27

自動化設備制藥

2023-08-16 08:34:34

WEB UI

2015-11-09 14:27:36

Ansiblelinux自動化運維

2021-11-02 20:12:47

弘璣CycloneRPA

2024-06-28 08:21:20

前端自動化部署

2021-03-11 11:00:38

IBM自動化AI
點贊
收藏

51CTO技術棧公眾號

欧美一区二区三区不卡视频| 国产精九九网站漫画| 五月天精品视频| 在线观看涩涩| 国产日韩欧美一区二区三区乱码| 国产精品久久久久999| 老湿机69福利| 亚洲黄色中文字幕| 国产精品久久久99| 2019中文字幕在线观看| 色婷婷成人在线| 日本美女一级视频| 日精品一区二区| 亚洲黄色在线观看| www.超碰com| 欧美孕妇孕交xxⅹ孕妇交| 轻轻草成人在线| 欧美激情视频一区| 国产成人免费观看网站| 97久久精品| 欧美午夜片在线看| 草b视频在线观看| 精产国品自在线www| 99久久综合99久久综合网站| 亚洲一区美女视频在线观看免费| 四虎成人永久免费视频| 综合一区av| 亚洲最新中文字幕| 国产精品久久久免费观看| av中文在线资源| 国产河南妇女毛片精品久久久| 色婷婷成人综合| 182在线视频| 亚洲精品18| 在线电影一区二区三区| 色诱视频在线观看| 欧美办公室脚交xxxx| 亚洲精品成人天堂一二三| 视频在线一区二区三区| 四虎成人免费在线| 成人动漫av在线| 97se国产在线视频| 国产美女永久免费| 欧美96在线丨欧| 一区二区欧美日韩视频| 亚洲久久久久久| 一区二区三区亚洲变态调教大结局| 欧美色视频在线观看| 国产精品免费观看久久| 97蜜桃久久| 亚洲国产日韩a在线播放性色| 色呦呦网站入口| 91在线免费看| 国产精品免费久久| 婷婷久久五月天| 国产福利小视频在线| 久久综合精品国产一区二区三区| 国产日韩精品一区观看| 涩涩视频免费看| caoporm超碰国产精品| 国产一区免费在线| 亚洲av色香蕉一区二区三区| 国产乱色国产精品免费视频| 国产69精品久久久久9| 欧美久久久久久久久久久久| 欧美久久一级| 亚洲精品色婷婷福利天堂| 亚洲av成人片色在线观看高潮| silk一区二区三区精品视频| 精品国产乱码久久久久久老虎| 国产精品欧美性爱| 免费观看亚洲| 日韩欧美在线一区| 成人午夜免费剧场| av片哪里在线观看| 一区二区三区日韩欧美| 日本a级片在线播放| 国产精品偷拍| 欧美性生活大片免费观看网址| www.浪潮av.com| 欧美舌奴丨vk视频| 欧美午夜精品一区| 超碰中文字幕在线观看| 亚洲经典视频| 亚洲女人天堂网| 四虎地址8848| 亚洲精品偷拍| 国产精品爽黄69天堂a| 久久久一区二区三区四区| 韩国一区二区三区在线观看| 欧美中文字幕在线播放| 中文字幕第31页| 国产精品亚洲一区二区三区在线| 国产免费一区二区三区| 国产一区二区三区福利| 亚洲视频一区二区在线观看| 老子影院午夜伦不卡大全| 台湾佬成人网| 日韩写真欧美这视频| 日韩网站在线播放| 天天综合网91| 欧美专区第一页| 国产视频一二三四区| 2017欧美狠狠色| 最新精品视频| www.日韩| 精品乱人伦小说| 嘿嘿视频在线观看| 亚洲欧洲日本一区二区三区| 国产精品夜间视频香蕉| 日本精品999| 亚洲视频一区二区免费在线观看 | 国产在线一二| 亚洲制服欧美中文字幕中文字幕| 韩国日本美国免费毛片| 国产成人一二片| 日韩一区二区高清| 日韩精品电影一区二区| 欧美久久一区| 成人中文字幕+乱码+中文字幕| 日韩美女一级视频| 一区二区在线观看不卡| 欧美午夜aaaaaa免费视频| 美女扒开腿让男人桶爽久久动漫| www.久久撸.com| 黄色av一区二区| 91丨porny丨国产| 成人免费性视频| 免费观看亚洲视频大全| 社区色欧美激情 | 欧美男人亚洲天堂| 99久久99久久综合| 国产www免费| 日韩三级不卡| 欧美成人精品一区二区| 国产亚洲精品码| 麻豆91在线观看| 日韩欧美精品一区二区| www.国产精品.com| 日韩欧美在线视频观看| avtt香蕉久久| 99日韩精品| 国产精品欧美激情| 可以在线观看的av网站| 色哟哟一区二区在线观看| 中文字幕三级电影| 亚洲黄色影片| 国产精品免费一区二区三区在线观看| 在线观看小视频| 日韩欧美一二三四区| 午夜爽爽爽男女免费观看| 欧美三级特黄| 国产精品视频免费一区| 色呦呦在线资源| 精品国产凹凸成av人网站| 九九视频免费在线观看| 成人网页在线观看| 奇米精品一区二区三区| 婷婷精品视频| 国产成人av在线播放| 97视频在线观看网站| 欧美日韩激情在线| 亚洲天堂一级片| 国产成人自拍高清视频在线免费播放| 999久久欧美人妻一区二区| 97久久精品| 51精品在线观看| 精品乱码一区二区三四区视频 | 国产日韩精品一区| 波多野结衣xxxx| 欧美黄免费看| 久久久99国产精品免费| 视频在线日韩| 日韩中文字幕网址| 超碰在线人人干| 狠狠爱在线视频一区| 在线观看免费小视频| 国产一区三区三区| 日韩伦理在线免费观看| 精品中文一区| 91色在线视频| 高清视频在线观看三级| 在线播放国产一区二区三区| 国产乱人乱偷精品视频| 亚洲妇女屁股眼交7| 蜜桃av免费看| 国产精品香蕉一区二区三区| 日韩少妇内射免费播放18禁裸乳| av亚洲在线观看| 99re视频在线| www.亚洲资源| 精品久久久久一区二区国产| 久久久久久在线观看| 亚洲日韩欧美一区二区在线| wwwxx日本| 蜜臀va亚洲va欧美va天堂| 无码日本精品xxxxxxxxx| vam成人资源在线观看| 欧美精品激情在线| 国产ts人妖调教重口男| 欧美日韩另类字幕中文| 99热在线观看精品| 99久久精品费精品国产一区二区| 久久人人爽av| 亚洲欧美日韩国产一区二区| 熟妇熟女乱妇乱女网站| 伊人精品一区| 超碰97在线播放| 国产精品黄色片| 性欧美在线看片a免费观看| 色欧美激情视频在线| 亚洲精品国产品国语在线| 国产精品区在线观看| 粉嫩老牛aⅴ一区二区三区| 免费在线观看a级片| 久久精品一区二区三区不卡| 337p日本欧洲亚洲大胆张筱雨| 美女视频黄频大全不卡视频在线播放| 91视频 -- 69xx| 伊人激情综合| 992tv快乐视频| 久久免费大视频| 欧美中文娱乐网| 美日韩黄色大片| caoporn国产精品免费公开| 涩涩涩久久久成人精品| 久久精品国产2020观看福利| 日韩有码电影| 亚洲精品国精品久久99热一| 国产黄色大片网站| 欧美高清视频不卡网| 欧美人一级淫片a免费播放| 亚洲成av人**亚洲成av**| 欧美黄色免费看| 亚洲精品国产精品乱码不99| 国产喷水在线观看| 国产精品女同一区二区三区| 永久免费毛片在线观看| 久久青草欧美一区二区三区| 第四色在线视频| 成人免费毛片aaaaa**| 国产成人精品一区二区三区在线观看| 韩国欧美国产1区| 在线观看日本www| 激情欧美一区二区三区| 亚洲啊啊啊啊啊| 国产精品大片免费观看| www污在线观看| 国产综合激情| 国产av人人夜夜澡人人爽麻豆| 在线免费高清一区二区三区| av女优在线播放| 99在线热播精品免费99热| 欧美视频在线播放一区| 久久久www| 国产免费人做人爱午夜视频| 丝袜亚洲另类欧美| 蜜桃网站在线观看| 欧美视频久久| 九一国产精品视频| 免费日韩一区二区| 能在线观看的av网站| 免费视频一区二区| 日本高清一区二区视频| 国产二区国产一区在线观看 | 99久久99久久免费精品蜜臀| 日本黄色免费观看| 国产视频一区在线观看| 任你操精品视频| 亚洲精品成人精品456| 亚洲免费激情视频| 日本韩国一区二区| 一二三四区视频| 日韩精品一区二区三区中文精品| 日本黄色大片视频| 亚洲女人天堂av| 黄色视屏免费在线观看| 国模精品系列视频| 日韩精品专区| 亚洲精品欧美日韩| 欧美成a人免费观看久久| 日韩av电影免费在线| 欧美在线观看天堂一区二区三区| 日韩日韩日韩日韩日韩| 丝袜美腿亚洲一区二区图片| 九九热精品国产| 不卡一卡二卡三乱码免费网站| 美女洗澡无遮挡| 亚洲视频免费观看| 国产a∨精品一区二区三区仙踪林| 在线观看视频欧美| www.蜜臀av| 一二美女精品欧洲| 国产精品一品| 国产在线日韩在线| 日韩电影免费观| 99re6在线| 色婷婷一区二区三区| 欧美一级视频免费看| 麻豆91精品视频| 朝桐光av一区二区三区| 国产精品国产三级国产a | 日本不卡在线| 57pao成人国产永久免费| 四虎国产精品免费久久| 含羞草久久爱69一区| 91九色精品国产一区二区| 欧美亚洲精品一区二区| 国产乱码字幕精品高清av| 最近中文字幕在线mv视频在线| 一区二区三区精品在线| 成人黄色免费网| 日韩成人激情视频| av在线麻豆| 久久国产精品久久精品| 欧美人体一区二区三区| 加勒比在线一区二区三区观看| 五月久久久综合一区二区小说| 超碰97人人射妻| 成人爽a毛片一区二区免费| 国产免费久久久久| 在线观看国产一区二区| 日韩a在线观看| 国内外成人免费激情在线视频网站 | 亚洲婷婷伊人| 国精产品一区一区三区视频| 国产成a人无v码亚洲福利| 韩国一区二区三区四区| 中文字幕日韩av资源站| 最近中文字幕免费在线观看| 精品中文字幕久久久久久| av影视在线看| 不卡一区二区三区视频| 欧美黄在线观看| 国产999免费视频| 亚洲丝袜精品丝袜在线| 国产乱淫片视频| 久久伊人精品一区二区三区| 亚洲精品伦理| 一区二区视频在线播放| 六月丁香综合在线视频| 免费91在线观看| 欧美日韩一区二区在线视频| 在线视频91p| 91精品久久久久久久久不口人| 成人亚洲一区| 国产永久免费网站| 亚洲视频一区二区在线观看| 国产超碰人人模人人爽人人添| 欧美成人全部免费| aaa国产精品| 黄色www网站| 久久综合色综合88| 一级黄色av片| 中文字幕久久久| 成人久久精品| 欧美人xxxxx| 久久综合亚州| 日日操免费视频| 日韩区在线观看| 久久五月精品中文字幕| 国新精品乱码一区二区三区18| 国产亚洲一区在线| www.av天天| 欧美精品乱码久久久久久| 99福利在线| 国产乱码一区| 天堂一区二区在线免费观看| 婷婷丁香综合网| 日韩精品一区二区三区四区 | 亚洲一级av无码毛片精品| 天天综合日日夜夜精品| 国产高清视频在线观看| 成人久久精品视频| 亚洲一级黄色| 久久丫精品忘忧草西安产品| 欧美精品在线视频| 999精品网| 亚洲欧美日产图| 豆国产96在线|亚洲| 麻豆成人免费视频| 久久精品精品电影网| 国产精品色在线网站| 精品久久久久久久免费人妻| 亚洲欧美成人一区二区三区| 天天操天天干天天爱| 国产精品美女午夜av| 欧美久久视频| 美女100%露胸无遮挡| 欧美成人乱码一区二区三区| 中文字幕av一区二区三区佐山爱| 小说区视频区图片区| 91蜜桃免费观看视频| 国产精品伦理一区| 日本久久久久久| 欧美人成网站| 亚洲精品天堂网| 亚洲精品一区二区三区不| 91丨精品丨国产| 男人日女人逼逼|