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

如何實現一個前端監控回放系統

開發 前端
本文通過各類調研以及實際項目開發中總結的經驗,以記錄在實現前端監控回放系統的過程中會用到的一些 Web API 與新技術能力,為大家提供一些可行的實現思路。

隨著 Web 項目日趨復雜,除了不斷還原 UI 稿、搞好各端與各機型兼容外,如何解決用戶在訪問頁面時實際遇到的各種“疑難雜癥”,逐漸成為開發者需要面臨的問題之一。

傳統手段上,我們有各式各樣的埋點方案,無痕埋點、全鏈路監控、用戶行為上報、接口狀態監控等等,但這些都是針對真實用戶行為以及表現的采樣,遇到問題時他們并不一定能發揮最大用處,很多時候還需要程序員的參與介入。有什么辦法可以最完整的將用戶問題展現在我們面前呢?無外乎我們可以完整、真實地重現用戶當時的操作行為與結果,要是在用戶端能安裝一個隨時隨地的錄屏軟件就好了。

本文通過各類調研以及實際項目開發中總結的經驗,以記錄在實現前端監控回放系統的過程中會用到的一些 Web API 與新技術能力,為大家提供一些可行的實現思路。本文未涉及到的方面也歡迎評論告知補充。

本文結構如下:

  1. 實現方案思路
  2. 關鍵技術基礎
    • 應用與狀態的序列化
    • 記錄 DOM 變化與交互的快照
    • 回放重演
    • 沙箱
  3. 技術項拆分與相關 Web API
  4. 結語

1 / 實現方案思路

要想給用戶的訪問做一次完整的應用狀態監控錄制與回放,除了錄制視頻外,通過 Web API 實現大致有兩類主流的解決思路。

第一種方案是通過記錄 DOM 的每次變更,并將內容序列化下來,然后在沙箱中還原并回放這些 UI 變化。這種方案的優點之一是能夠給 DOM 創建快照的概念,在應用每一次狀態變化后進行收集,把這個序列串起來后我們便可以靈活掌握回放速度、并針對關鍵性節點進行自定義回放。在社區開源方案中,這類技術最成熟的莫過于 rrweb https://github.com/rrweb-io/rrweb ,此外若干互聯網企業也有提供一些商業解決方案,但技術設計上大同小異,大致都可以拆分為 DOM 序列化、構建快照序列、反序列化回放以及運行沙箱環境等四方面。

另一種可行方案,從我們的調研結果來看,是將用戶側所有數據收集起來,然后在一個可控運行環境下嚴格按照校準時間對用戶事件操作進行派發,從而控制回放。這里主要分為兩部分,一方面因為我們通過派發事件來重演用戶的行為,便需要高精度計時器以及完善的用戶事件收集策略;另一方面,由于要保證應用狀態在兩端的變化一致性,只嚴格觸發用戶的操作還不夠,我們還要將任何應用與網絡之前的交互操作(請求與響應內容)精準對齊,所以我們需要攔截所有請求、即時的前端構建產物以及用戶操作序列等等。

第二種方案由于可以近乎模擬用戶側運行時的狀態變化,相當于將當時的用戶整個搬到了我們面前,因此可以方便開發同學進一步調試。在社區開源方案中,我們沒有找到類似的實現,但商業方案中 https://logrocket.com/ 最接近這個思路,其提供的不少功能甚至比這里提及的功能要更強大。

但本文中,我們只討論當需要實現這樣一個系統時的涉及技術項。所以接下來,來看看我們在調研中記錄的一些有用的 Web API,希望對你們實現有幫助。

2 / 關鍵技術基礎

本章主要介紹要實現這樣一個前端監控回放系統的四個環節,關于這些內容,rrweb 的描述篇幅會更加翔實,可以更進一步查閱他們的文檔描述。

2.1 應用與狀態的序列化

如何回放一個應用的狀態變化?首先,我們要將應用的內容以及狀態變化收集起來。如果用 jQuery 我們可以這樣實現 body 內容的收集與替換:

  1. // record 
  2. const snapshot = $('body').clone(); 
  3. // replay 
  4. $('body').replaceWith(snapshot); 

如果換用MediaRecorder API,利用 ondataavailable 和 onstop 兩個事件處理 API,我們還可以將 DOM 轉變成可播放的媒體文件,比如這樣:

  1. startRecording() { 
  2.     const stream = (this.canvas as any).captureStream(); 
  3.     this.recorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); 
  4.     const data = []; 
  5.    
  6.     this.recorder.ondataavailable = (event) => { 
  7.       if (event.data && event.data.size) { 
  8.         data.push(event.data); 
  9.       } 
  10.     }; 
  11.   
  12.     this.recorder.onstop = () => { 
  13.       const url = URL.createObjectURL(new Blob(data, { type: 'video/webm' })); 
  14.       this.videoUrl$.next( 
  15.         this.sanitizer.bypassSecurityTrustUrl(url) 
  16.       ); 
  17.     }; 
  18.   
  19.     this.recorder.start(); 
  20.     this.recordVideo$.next(true); 

但這些內容是沒法序列化的,而媒體體積又過于龐大且無法進一步分析。舉個例子,一個 input 標簽,如果我們不做任何額外處理只將其轉化成文本進行存儲(類似 innerHTML),那么其中包含的 value 等狀態便會丟失,這便是我們首先需要將 DOM 及其視圖狀態進行序列化的原因所在。關于如何序列化 DOM 也有不少開源方案比如 https://github.com/inikulin/parse5 ,rrweb 在文檔中有提到為什么沒有采用的原因,而我這里簡單列一下在序列化環節中需要考慮的幾點細節:

  • 如何將 DOM 樹轉化成一個帶視圖狀態的樹狀結構,包括未反映在 HTML 中的視圖狀態;
  • 如何定義唯一標識,方便應用狀態變化(快照)的溯源;
  • 如何處理特殊標簽諸如 script 以及樣式等內容,因方案而異;

簡而言之,這部分的目的是完成一個 DOM 樹至可存儲狀態的數據結構映射。

2.2 記錄 DOM 變化與交互的快照

如果只是記錄 DOM 變更的話,我們可以很方便的利用 MutationObserver API 達到變更監聽與記錄這一目的,但此外我們還需要考慮如何將 Mutation 的批量序列轉化為快照上的增量更新。

比如,為了方便針對 Node 進行增刪時可以唯一確定其在樹形結構中的位置,我們最好設計一個合適的 DOM 唯一標記策略,此外,如何優化諸如 mousemove 以及大量頻繁 input 輸入導致的視圖變更等。前者的設計可以繼續用在增量快照的實現上,而后者的表現則直接影響用戶體驗,容易導致 DOM 在更改時 Node 記錄的出現順序錯誤。

這一環節,主要依賴 DOM 的序列化方案繼續處理。在添加一些其他必要信息諸如時間序列編號等,便可以進行存儲等操作了。

2.3 回放重演

簡單來說,重演就是將收集到的數據按照順序依次“播放”一遍,視頻文件的播放需要音視頻解碼器,而我們的重演環節要做的工作就可以簡單理解成一個 Web 應用解碼器,從用戶端收集上來的數據結構除了要做清洗和存儲外,還不能直接被回放側使用,其中有不少需要考慮的細節。

舉個簡單的例子,我們利用 Web API 是沒法達到派發 hover 事件的,但是我們的項目中一定存在大量的 hover 樣式,那么如何針對這些交互做額外處理,對 Node 狀態變化做相應樣式的補全,便成為一個需要考慮的環節結合 mousedown 和 mouseup 兩個事件的觸發時機是否夠用?事件收集的掛載節點如何圈定?這些都是需要考慮的地方。再比如,回放中想跳過被認為無意義的操作片段,如何設計才能保持應用在前后兩個時間節點上不因為跳過的操作而缺失視圖狀態?

這一環節,目的是為了實現對快照存儲下來的數據結構進行回放。因為要保證回放側與收集側的嚴格一致,諸如高精度計時、DOM 補全以及交互效果模擬等細節,都需要詳細設計。

2.4 沙箱

沙箱,是為了給回放提供一個安全可控的運行環境。如何采用 DOM 快照方案,那么便需要考慮如何禁止一些“不安全”的 DOM 操作。例如應用內鏈接跳轉、我們不太可能會直接給用戶打開一個新的 tab,為了保證快照狀態依次回放,我們還需要考慮如何安全準確的反序列化構建 DOM。如果實現上考慮通過派發事件的思路來實現,那么如何準確定位派發的 Node 節點、如何匹配數據請求并響應等等都是需要重點考慮的。

兩種思路都有一些需要共同考慮的事情,比如如何保證運行環境符合瀏覽器的安全限制、需要展示和用戶操作保持一致的渲染層等等。這部分在技術項拆分一節,會提到兩個解決方案,分別是 iframe 以及 puppeteer,此處不再贅述。

3 / 技術項拆分與相關 Web API

Web 有強大的 API List,本章節針對可能用到的 API 與相關技術做一一講解。

3.1 MutationObserver

MutationObserver API 可以用于監聽觀察 DOM 對象的變化并予以記錄數組的形式返回,這可以用在應用初始化和增量快照的記錄部分。關于 MutationObserver 的方法與入參這里不做詳細介紹,簡單來看,要用 MutationObserver API 監聽一個 Node 的變更大致分為這么幾步:

  • 利用 document.getElementById 等 API 定位你所需要的 Node
  • 定義一個 MutationObserverInit 對象,此對象的配置項描述了 DOM 的哪些變化應該提供給當前觀察者的回調函數
  • 定義上述回調函數被調用時的執行邏輯
  • 創建一個觀察器實例并傳入回調函數
  • 調用 MutationObserver 的 observe() 方法開始觀察

以下節選自 MDN 的一段示例代碼用于釋義:

  1. const targetNode = document.getElementById('some-id'); 
  2. const config = { attributes: true, childList: true, subtree: true }; 
  3.  
  4. const callback = function(mutationsList, observer) { 
  5.     for(let mutation of mutationsList) { 
  6.         if (mutation.type === 'childList') { 
  7.             console.log('A child node has been added or removed.'); 
  8.         } 
  9.     } 
  10. }; 
  11.  
  12. const observer = new MutationObserver(callback); 
  13. observer.observe(targetNode, config); 

3.2 iframe 標簽

iframe 大家肯定都用過,它能夠將另一個 HTML 頁面嵌入到當前頁面中。利用 iframe ,我們可以快速構建一個安全的沙箱機制,比如將其用于限制 JavaScript 執行等。除了我們平時直接給 iframe 的 src 屬性賦值外,iframe 還有不少其他值得了解的屬性,這些在完善運行沙箱環境上都會有所幫助,比如其中的 sandbox 屬性便可以對呈現在 iframe 中的內容啟用一些額外的限制條件。

此外,要實現沙箱中不同容器的的通信,可以通過 postMessage API 來完成。這里有一篇非常詳細的文章介紹了 iframe 的方方面面,可以進一步查閱 https://blog.logrocket.com/the-ultimate-guide-to-iframes/ 。

3.3 HTTP Archive

HTTP 請求與響應匯集,即我們常說的 HAR 格式數據。打開 chrome devtools,network tab 下的每一條數據流都代表一個請求,從 http 到 weoscket,request 到 response,包含 request 入參、headers、連接耗時、發起時間、響應時間、TTFB、內容大小等等。如同前面所述,如果要在回放側派發用戶操作的話,那么即需要在采集時將所有請求攔截并標號進行存儲,如此一來,直接收集 HAR 便成了最佳的選擇。

但想要收集 HAR 會遇到一些限制,比如這類數據只在 chrome devtools API 中開放,所以要實現這塊數據的收集,必須采用類似 chrome 插件的形式進行開發,但這樣一來,如何進行用戶無感知的數據收集與上報又成了一個難題。

3.4 network 與 webRequest API

利用 chrome.webRequest API 可以允許我們觀察和分析流量,并在運行中攔截、阻止或修改它。從上文描述來看,要收集 HAR 只能通過這個 API 來實現,下面給出一個調用 network 對請求攔截處理的示例,詳細用法可參照文檔 https://developer.chrome.com/extensions/webRequest

  1. chrome.devtools.network.onRequestFinished.addListener(function (req) { 
  2.     // Only collect Resource when XHR option is enabled 
  3.     if (document.getElementById('check-xhr').checked) { 
  4.         console.log('Resource Collector pushed: ', req.request.url); 
  5.         req.getContent(function (body, encoding) { 
  6.             if (!body) { 
  7.                 console.log('No Content Detected!, Resource Collector will ignore: ', req.request.url); 
  8.             } else { 
  9.                 reqs[req.request.url] = { 
  10.                     body, 
  11.                     encoding 
  12.                 }; 
  13.             } 
  14.             setResourceCount(); 
  15.         }); 
  16.         setResourceCount(); 
  17.     } 
  18. }); 

3.5 Service Worker 與 proxy

我們都知道 Servcie Worker 的 cache API 在 PWA 應用中廣泛使用,但其實除了可以將其用于離線應用體驗增強外,由于 Servcie Worker 擁有更精細、更完整的控制特性,它完全可以作為一個頁面與服務器之間的代理中間層,用于捕獲它所負責的頁面請求,并返回相應資源。

一般來說,基于框架 HTTPClient (Angular) 或者原生 XMLHttpRequest 的監聽,對頁面的請求攔截都或多或少存在一些無法捕獲的盲區,但 Service Worker 不會,你所需要注意的是需要將它放在你的應用根目錄下,或者通過入參在注冊時指定 scope 以使你的 Service Worker 在指定范圍內生效。

關于它,除了理解其生命周期外,還有些細節需要注意,比如作用域 scope。單個 Service Worker 可以控制多個頁面,每個頁面不會有自己獨有的 worker,所以請注意 scope 的生效范圍;在你 scope 范圍內的頁面在加載完時,Service Worker 便可以開始控制它,所以請小心定義 Service Worker 腳本里的全局變量。

當然,為了方便開發,你可以使用 TypeScript、Babel、webpack 等語言和工具,來加速你的開發體驗。MDN 有一篇教程對 Service Worker 入門介紹的挺詳細,可以一看 https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

上圖為 Service Worker 的生命周期

3.6 Web Worker

Web Worker 的作用,就是為 JavaScript 創造多線程環境,允許主線程創建 Worker 線程,將一些任務分配給后者運行。在主線程運行的同時,Worker 線程在后臺運行,兩者互不干擾。等到 Worker 線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 線程負擔了,主線程(通常負責 UI 交互)就會很流暢,不會被阻塞或拖慢。—— 阮一峰的網絡日志

用于收集數據上報的計算工作,由于重計算邏輯且需要頻繁做數據處理,最好不要在主線程操作,否則很影響交互體驗。Web Worker 是一個很好的解決方案,在采用它之后,其余涉及到 BOM/DOM 的相關操作還可以將需要的數據直接事件傳遞通知或者 SharedArrayBuffer 共享。

那么,如何構建一個 Web Worker 呢?一個 worker 文件由簡單的 JavaScript 代碼組成,在寫完之后,你需要在主線程中傳入 URI 來構建這么一個 worker 線程,如下圖所示:

  1. const myWorker = new Worker('worker.js'); 

在 worker 中,除了完善用于計算的邏輯代碼外,我們還可以引入腳本、通過 postMessage 與主線程通信等等:

  1. // 引入腳本 
  2. importScripts('foo.js''bar.js'); 
  3.  
  4. // 在 Web Worker 中監聽消息與向外通信 
  5. onmessage = function(e) { 
  6.   console.log('Message received from main script'); 
  7.   var workerResult = 'Result: ' + (e.data[0] * e.data[1]); 
  8.   console.log('Posting message back to main script'); 
  9.   postMessage(workerResult); 

上面提到的 Service Worker 也可以算作 Web Worker 的一個相似品,但與一般的 Web Worker 不同,Service Worker 有一些額外的特性來實現代理的目的。只要它們被安裝且被激活,Service Worker 就可以攔截主線程中發起的任何網絡請求。

此外,還有一個特別的 Worker 叫做 Worklet,這些 API 都挺有意思,值得另開篇幅介紹,但與本文暫不相關,便不詳述。

3.7 空閑調度與 requestIdleCallback

window.requestIdleCallback() 方法將在瀏覽器的空閑時段內調用的函數排隊。這使開發者能夠在主事件循環上執行后臺和低優先級工作,而不會影響延遲關鍵事件,如動畫和輸入響應。函數一般會按先進先調用的順序執行,然而,如果回調函數指定了執行超時時間 timeout,則有可能為了在超時前執行函數而打亂執行順序。 —— MDN

由于系統需要對用戶數據進行全量收集,除了計算邏輯的負擔分攤外,包含序列化節點、快照等結構數據的上傳勢必又會成為項目潛在的瓶頸與需要考慮的優化點。利用 requestIdleCallback API,我們可以保證數據在處理后的上報(網絡請求)不對用戶交互造成影響,例如使用戶頁面卡頓等。

更為人熟知的一個 Web API 是 requestAnimationFrame,這個 API 可以告訴瀏覽器在下次重繪之前執行傳入的回調函數,由于是每幀執行一次,所以其每秒的執行次數與瀏覽器屏幕刷新次數一致,通常是每秒60次。而 requestIdleCallback 與其相反,它會在每幀的最后執行,但并不是每一幀都保證會執行 requestIdleCallback。這個原因很簡單,我們無法保證每一幀結束時我們還有時間,所以并不能保證 requestIdleCallback 的執行時間。

requestIdleCallback API 的設計很簡單,一個空閑調度函數,一個可選配置項參數。

  1. const handle = window.requestIdleCallback(callback[, options]) 

舉個例子,假設我們現在需要追蹤用戶的點擊事件,并將數據上報服務器,利用這個 API 我們可以這樣完成數據收集以及上報調度:

  1. const btns = btns.forEach(btn =>  
  2. btn.addEventListener('click', e => { 
  3.     // 其他交互     
  4.  
  5.     putIntoQueue({ 
  6.       type: 'click' 
  7.       // 收集數據 
  8.     })); 
  9.     schedule(); 
  10. }); 
  11.  
  12. function schedule() { 
  13.     requestIdleCallback( 
  14.       deadline => { 
  15.           while (deadline > 0) { 
  16.             const event = queues.pop(); 
  17.             send(event); 
  18.           } 
  19.       }, 
  20.       { timeout: 1000 } 
  21.    ); 

3.8 本地持久化存儲 - localStorage 與 IndexedDB

既然要上報,那么就要考慮在數據未完成上報時用戶的意外退出或者網絡斷開等。在這些情況下,瀏覽器的本地存儲方案便派上了用場。由于需要保證數據上報的完整性,持久化存儲推薦 localStorage API 以及 IndexedDB API。

利用 localStorage API,我們可以快速的存取字符串形式的鍵值對,但受瀏覽器限制,存儲大小一般有限,僅幾兆而已。

利用 IndexedDB API,我們可以在客戶端存儲大量的結構化數據(也包括文件/二進制大型對象等),IndexedDB 被瀏覽器存在本地磁盤中,于是,你可以將其存儲上限近似看成計算機的剩余存儲容量。

IndexedDB 是一個事務型數據庫系統,類似于基于 SQL 的 RDBMS。 然而,不像 RDBMS 使用固定列表,IndexedDB 是一個基于 JavaScript 的面向對象數據庫。IndexedDB 允許您存儲和檢索用鍵索引的對象;可以存儲結構化克隆算法支持的任何對象。您只需要指定數據庫模式,打開與數據庫的連接,然后檢索和更新一系列事務。

localStorage 與 IndexedDB 的使用都相對容易,MDN 上有較為完善的入門指導,此處便不貼代碼了。

3.9 puppeteer - Headless Chrome Node.js API

假設我們不使用 iframe 來做便捷沙箱環境,那么一個更強大的解決方案便是 puppeteer。

puppeteer 是谷歌官方出品的一個通過 DevTools 協議控制 headless Chrome 的 Node 庫,我們可以通過 puppeteer 提供的 API 直接控制 Chrome,進而模擬大部分用戶在瀏覽器上的操作,來進行 UI Test 或者作為爬蟲訪問頁面來收集數據。有關 puppeteer 的使用可以參考文檔 https://pptr.dev/

回到我們的場景,當需要重演用戶的操作時,我們可以便捷的利用 page API 來做,比如下面這個例子:

  1. const puppeteer = require('puppeteer'); 
  2.  
  3. (async () => { 
  4.   const browser = await puppeteer.launch() 
  5.   const page = await browser.newPage() 
  6.    
  7.   await page.goto('https://hijiangtao.github.io/'
  8.    
  9.   await page.setViewport({ width: 2510, height: 1306 }) 
  10.    
  11.   await page.waitForSelector('.ant-tabs-nav-wrap > .ant-tabs-nav-scroll > .ant-tabs-nav > div'
  12.   await page.click('.ant-tabs-nav-wrap > .ant-tabs-nav-scroll > .ant-tabs-nav > div > .ant-tabs-tab:nth-child(2)'
  13.    
  14.   await page.waitForSelector('.ant-tabs-nav-wrap > .container'
  15.   await page.click('.ant-tabs-nav-wrap > .ant-tabs-nav-scroll > .ant-tabs-nav > div > .ant-tabs-tab:nth-child(1)'
  16.    
  17.   await browser.close() 
  18. })() 

當然,puppeteer 存在廣泛的使用場景,比如生成頁面截圖或者 PDF、進行自動化 UI 測試、構建爬蟲系統、捕獲頁面時間軸進行性能診斷等等,曾經寫過一篇文章介紹如何利用 puppeteer 實現網頁自動分頁截圖,感興趣的朋友可以戳此《 用 puppeteer 實現網站自動分頁截取的趣事 》查看,本文不再對其余細節做詳細解讀。

4 / 結語

本文通過組合介紹了各類 Web API 及一些新技術,試圖通過技術調研以及實際項目開發中總結的經驗,為大家在考慮解決「如何實現一個前端監控回放系統」這一問題上提供思路,本文介紹中若有未涉及到的缺漏之處,也歡迎評論告知補充。

 

責任編輯:張燕妮 來源: Joe’s Blog
相關推薦

2022-04-08 09:52:13

前端監控系統

2020-05-19 10:45:31

沙箱前端原生對象

2017-03-02 13:31:02

監控系統

2023-03-01 09:39:40

調度系統

2019-12-11 10:45:08

Python 開發編程語言

2022-09-05 08:07:25

goreplay監控工具

2017-07-07 15:54:26

Linux監控場景

2025-01-09 06:00:00

Checkmate監控系統開源

2017-12-12 15:24:32

Web Server單線程實現

2018-07-19 09:15:27

2018-09-18 09:38:11

RPC遠程調用網絡通信

2020-11-30 06:20:13

javascript

2022-11-29 17:34:43

虛擬形象系統

2014-05-20 09:59:27

Mnitrix輕型監控系統系統管理員

2023-02-26 01:37:57

goORM代碼

2021-06-15 09:33:44

Kubernetes Prometheus 容器

2020-08-17 08:20:16

iOSAOP框架

2023-09-08 08:22:30

2023-09-08 08:10:48

2024-08-27 12:49:20

點贊
收藏

51CTO技術棧公眾號

国产精品私拍pans大尺度在线 | 亚洲精品在线不卡| 国产亚洲精品网站| 91xxx在线观看| 粉嫩久久99精品久久久久久夜| 国外成人免费在线播放| 亚洲精品成人无码熟妇在线| 日韩在线激情| 欧美日韩国产一区在线| 亚洲欧美日韩精品在线| 亚洲精品18p| 青青草精品视频| 欧美激情中文字幕乱码免费| 少妇精品无码一区二区免费视频| 精品视频国内| 在线看国产一区二区| 在线观看av的网址| av福利在线播放| www.在线欧美| 92看片淫黄大片欧美看国产片| 制服.丝袜.亚洲.中文.综合懂色| 久久精品青草| 亚洲偷熟乱区亚洲香蕉av| 韩国三级hd中文字幕有哪些| 午夜无码国产理论在线| 亚洲6080在线| 糖心vlog在线免费观看| av在线免费播放网站| 91在线观看下载| 3d动漫啪啪精品一区二区免费 | 久88久久88久久久| 欧美一级电影在线| 日干夜干天天干| 欧美精品播放| 久久精品亚洲94久久精品| 非洲一级黄色片| 亚洲97av| 亚洲男人天堂网| 性色av蜜臀av色欲av| 一区二区三区在线资源| 91麻豆精品久久久久蜜臀| 九九热免费精品视频| 欧美成人a交片免费看| 精品欧美一区二区三区| heyzo亚洲| 999精品网| 五月天精品一区二区三区| 国产成人一区二区三区别| av在线官网| 亚洲日穴在线视频| 特大黑人娇小亚洲女mp4| 成人黄色在线电影| 亚洲日本一区二区三区| gogogo免费高清日本写真| 1024免费在线视频| 亚洲欧洲成人自拍| 浴室偷拍美女洗澡456在线| 麻豆传媒视频在线| 亚洲码国产岛国毛片在线| 先锋在线资源一区二区三区| av在线天堂播放| 国产精品国产自产拍高清av王其| 亚洲国产精品久久久久久女王| 成人在线观看网站| 国产精品视频线看| 少妇高潮流白浆| 波多野结衣中文字幕久久| 亚洲一区二区三区不卡国产欧美| 男人天堂av片| 手机av在线| 在线一区二区三区| 亚洲理论中文字幕| 国产精品极品国产中出| 亚洲精品天天看| 国产成人一区二区在线观看| 97精品国产福利一区二区三区| 久久九九亚洲综合| 久久精品国产亚洲av麻豆色欲 | 国产成人精品视频在线| 无码久久精品国产亚洲av影片| 日本视频免费一区| 亚洲aaa激情| 欧美 日韩 中文字幕| 久久蜜桃一区二区| 亚洲三区四区| 成全电影大全在线观看| 一本大道久久a久久精品综合| 尤蜜粉嫩av国产一区二区三区| 国产精品视频一区二区三区| 亚洲大胆美女视频| 四虎成人免费影院| 好看的亚洲午夜视频在线| 欧美一区二区三区…… | 国产一级特黄视频| 日韩激情av在线| 97久久夜色精品国产九色| 天堂av在线资源| 亚洲欧美综合另类在线卡通| 日韩一级性生活片| 成人国产一区| 亚洲精品国产精品国自产在线| 最新中文字幕av| 伊人蜜桃色噜噜激情综合| 国产成人福利视频| 丰满人妻一区二区三区免费视频| 国产亚洲综合av| 日韩精品一区二区在线视频| 小明成人免费视频一区| 日韩色视频在线观看| 亚洲色成人网站www永久四虎| 午夜亚洲福利| 国产精品丝袜一区二区三区| 无码精品在线观看| 亚洲人123区| 激情五月婷婷久久| 久久综合五月婷婷| 久久99青青精品免费观看| 久久国产乱子伦精品| 激情久久五月天| 视频一区二区三区在线观看| h片在线观看下载| 欧美一级黄色录像| 国精产品一区一区| 久久国产免费| 久久精品国产99精品国产亚洲性色| free性欧美hd另类精品| 欧美影院一区二区三区| 亚洲天堂网一区二区| 欧美体内she精视频在线观看| 国产精品专区一| 激情小视频在线观看| 亚洲va欧美va人人爽午夜| 欧美精品 - 色网| 久久中文字幕av| 国产精品www| 国产精品毛片一区二区三区四区| 精品国产电影一区| 少妇被狂c下部羞羞漫画| 国产精品成人一区二区网站软件| 成人黄色激情网| 欧美精品电影| 欧美精品视频www在线观看 | 国产精品成久久久久| 国产精品美女久久久久av超清| 美女欧美视频在线观看免费| 狠狠躁18三区二区一区| 变态另类丨国产精品| 亚洲一区中文| 热re99久久精品国产99热| 欧美美女日韩| 亚洲天堂男人的天堂| 成人一级免费视频| 亚洲国产高清在线观看视频| 亚洲一区二区蜜桃| 日本一区二区三区视频| 国产一区视频在线| 菠萝蜜视频国产在线播放| 3d成人动漫网站| 日韩视频中文字幕在线观看| 国产精品一区在线| 免费拍拍拍网站| 亚洲最大在线| 国产精品免费在线免费| 欧美视频www| sm捆绑调教国产免费网站在线观看| 欧美日韩www| 国产午夜手机精彩视频| 国产精品99久| 日韩中字在线观看| 亚洲桃色综合影院| 国产精品美女www爽爽爽视频| 婷婷在线视频| 日韩三级视频在线观看| 国产网站在线看| 91啦中文在线观看| 亚洲黄色小视频在线观看| 99精品视频在线| 国产精品高清一区二区三区| 精精国产xxx在线视频app| 亚洲人成网站色ww在线| 91色在线播放| 亚洲国产精品久久久久婷婷884| 国产制服丝袜在线| 精品一区二区三区蜜桃| 国产曰肥老太婆无遮挡| 中文字幕av一区二区三区人| 国产精品香蕉国产| 日本成人不卡| 亚洲美女精品久久| 国产精品伊人久久| 精品久久久久久久久久久久久久| 天天干天天舔天天操| 国产精品77777| 国产日韩一区二区在线| 91日韩视频| 久久精品久久精品国产大片| 青青久久精品| 欧美性在线视频| 国产福利视频在线观看| 日韩精品免费在线视频| 一级特黄录像免费看| 亚洲成人在线网站| 亚洲av无一区二区三区| 成人ar影院免费观看视频| 色免费在线视频| 91久久视频| 一本色道婷婷久久欧美| 欧美激情网址| 亚洲最大福利视频网站| 精品视频在线一区二区在线| 欧美黄色小视频| 日本在线天堂| 亚洲精品有码在线| 超碰人人人人人人| 欧洲av一区二区嗯嗯嗯啊| 日本网站免费观看| 亚洲欧美国产三级| 欧美福利第一页| 97久久精品人人爽人人爽蜜臀| 亚洲免费黄色录像| 日本中文字幕一区二区视频| 国产素人在线观看| 午夜精品久久久久99热蜜桃导演 | 欧美1级日本1级| 日韩精品在在线一区二区中文| 精品亚洲自拍| 成人免费在线看片| 精品久久亚洲| 国产日韩欧美综合| 成人黄色免费观看| 日本欧美中文字幕| 午夜激情在线播放| 韩日精品中文字幕| 国语对白在线刺激| 欧美日本亚洲视频| 国产黄a三级三级三级av在线看| 综合网中文字幕| 国产区视频在线| 亚洲欧美精品suv| 天堂在线资源库| 日韩av在线网页| 五月婷婷丁香网| 国产精品亚洲午夜一区二区三区| 国产福利久久精品| 日韩高清二区| 91av免费看| 91国内精品| 国产精品一区二区不卡视频| 亚洲一区二区免费在线观看| 国产超碰91| 精品视频自拍| 麻豆91蜜桃| 精品国产乱码久久久久久蜜坠欲下 | 中文字幕一区二区三区av| 91狠狠综合久久久久久| 国产精品美女久久久久久久网站| 黄大色黄女片18免费| 成人免费小视频| 欧美黑吊大战白妞| 亚洲一二三区视频在线观看| 久久精品欧美一区二区| 五月天视频一区| 久久永久免费视频| 欧美日韩情趣电影| 国产同性人妖ts口直男| 日韩一区二区在线观看视频| 丰满熟妇乱又伦| 亚洲国产天堂网精品网站| 午夜小视频免费| 在线视频日本亚洲性| 麻豆网在线观看| 欧美激情精品久久久久| 麻豆mv在线看| 国产女精品视频网站免费| 成人豆花视频| 国产一区二区在线观看免费播放| 免费观看不卡av| 在线观看欧美亚洲| 亚洲精选成人| 一区二区成人网| 国产超碰在线一区| 一区二区三区四区免费| 国产精品传媒入口麻豆| 精品一区二区三区人妻| 色哟哟国产精品免费观看| ,一级淫片a看免费| 精品国产一区二区三区四区四| 欧美孕妇性xxxⅹ精品hd| 日韩一区二区三区国产| 丰乳肥臀在线| 国产精品自产拍在线观看| а√中文在线天堂精品| 色乱码一区二区三在线看| 午夜久久tv| www.亚洲高清| k8久久久一区二区三区| 亚洲不卡的av| 精品成人在线视频| 国产日韩欧美中文字幕| 国产视频久久久久| 9191在线播放| 国产精品欧美久久久| 超碰成人免费| 伊人久久大香线蕉av一区| 国产精品久久国产愉拍| 免费不卡av网站| 久久这里只有精品视频网| 欧美丰满熟妇bbbbbb| 欧美视频一区二区三区在线观看| 蜜臀av中文字幕| 色噜噜国产精品视频一区二区| 国产精品一区二区日韩| 2022国产精品| 999精品视频| 国产一级不卡毛片| 91在线免费视频观看| 精品处破女学生| 777a∨成人精品桃花网| 爱久久·www| 热re99久久精品国产66热| 成人激情自拍| 乱熟女高潮一区二区在线| 久久国产精品一区二区| 精品人伦一区二区| 欧美日韩国产综合新一区| 人妻少妇精品无码专区久久| 伦理中文字幕亚洲| 精品女同一区二区三区在线观看| 欧美中日韩免费视频| 国产亚洲毛片| 一区二区视频观看| 亚洲国产精品久久人人爱蜜臀| 朝桐光av在线一区二区三区| 精品久久久999| 日本a人精品| 亚洲图片欧洲图片日韩av| 日韩av中文字幕一区二区三区| 熟女俱乐部一区二区| 精品国产户外野外| 污视频在线免费| 91av成人在线| 在线日韩网站| 色一情一乱一伦一区二区三区日本 | 色悠久久久久综合欧美99| 青青草视频在线观看| 国产91精品久久久久| 天堂俺去俺来也www久久婷婷| 九一国产精品视频| 91在线观看污| 欧美日韩 一区二区三区| 国产亚洲欧美视频| 成人免费视频观看| 日本福利视频导航| 国产精品亚洲一区二区三区妖精| 精品国产乱码久久久久久鸭王1| 欧美一级国产精品| 国产乱妇乱子在线播视频播放网站| av成人午夜| 国产欧美短视频| 国产手机在线观看| 欧美日本国产一区| а√天堂资源地址在线下载| 亚洲永久免费观看| 亚洲激情在线| 国产男男chinese网站| 欧美色图天堂网| 黄色免费网站在线观看| av一本久道久久波多野结衣| 99精品视频免费观看视频| 国产精品久久久久无码av色戒| 欧美在线不卡视频| 污污的视频在线观看| 国产一区二区三区色淫影院| 媚黑女一区二区| 东方av正在进入| 亚洲第一在线视频| 超碰一区二区| 欧美 另类 交| 99精品国产91久久久久久| 69xxxx国产| 色综合老司机第九色激情| 欧美黑人巨大videos精品| 搡女人真爽免费午夜网站| 亚洲精选视频在线| 日本一本草久在线中文| 成人精品久久一区二区三区| 亚洲精品社区| 99精品中文字幕| 亚洲第一综合天堂另类专| 成人激情综合| 国产欧美精品aaaaaa片| 久久久三级国产网站| 99er热精品视频| 日本亚洲欧洲色| 欧美一区影院| 日韩人妻一区二区三区| 欧美一区二区三区日韩视频| 中文字幕在线中文字幕在线中三区| 在线视频不卡一区二区三区| 99久久99久久免费精品蜜臀|