亚洲有码Av一区二区三区_国产高清啪啪免费视频_69色视频国产_国产成人人人爆出白浆_国产精品自在线拍国_一本久久伊人热热精品无码_午夜性刺激在线看免费带字幕_助力高品质欧美狂喷水_亚洲精品日韩无码_精品无码一区二区三区蜜臀_麻豆高清国产AV_熟妇人素无码中文字幕_亚洲a级片在线观看_国产欧美日韩三区_99国产成人高清在线观看

ARTICLE DETAIL

資訊詳情

深耕商務建站與企業(yè)官網(wǎng)運營的一線實戰(zhàn)洞察。

Vue3+Node.js大文件斷點續(xù)傳:文件分片、hash計算與并發(fā)上傳實踐

Vue3+Node.js大文件斷點續(xù)傳:文件分片、hash計算與并發(fā)上傳實踐 看到這個標題我想你大概是遇到了一個現(xiàn)實問題項目里要傳幾百MB甚至幾個G的文件結果每次傳到一半就斷要不就是頁面卡死、用戶關掉又重新從0%開始。這套基于JavaScript、Vue、Node.js寫出來的大文件斷點續(xù)傳DEMO就是為了解決這個痛點。我會用Vue 3配合File API里的slice方法做文件分片用spark-md5計算文件hash再用axios并發(fā)上傳分片最后寫一個不算復雜的Node.js服務端做分片存儲和合并。整個過程下來有Vue基礎的人照著敲一遍就能跑通也能理解斷點續(xù)傳背后的原理。1. 先把斷點續(xù)傳這件事想明白拆片、記錄、重組1.1 一次上傳為什么靠不住在開始寫DEMO之前先倒回去想一個問題為什么不能直接拿一個文件往后端丟不是不能而是現(xiàn)實世界里大文件直接傳輸?shù)捏w驗非常差。瀏覽器里的File對象本質(zhì)上是一個Blob你把一個幾個GB的文件塞進FormData一個HTTP請求發(fā)出后整個請求體一直占著網(wǎng)絡連接服務器那邊如果用的是Express這類框架默認也會對整個請求做緩沖內(nèi)存一下就繃緊。更關鍵的是傳輸中間只要有一點抖動TCP連接斷了整個請求就沒了瀏覽器不會因為文件太大就對你手下留情用戶體驗就是“卡在99%然后重來”。還有一個容易被忽略的問題很多網(wǎng)關和Web服務器對單次請求體都有硬性限制Nginx默認的client_max_body_size很小就算你配置了十幾GB也架不住中間機器超時回收。所以“把一個文件變成很多個小文件分別上傳”是繞開這些限制的最實用手段這也是斷點續(xù)傳方案的核心一個文件被切成N片每片都是一個獨立請求失敗了對單一片重試而不是對整個文件重試。1.2 斷點續(xù)傳和普通上傳到底差在哪普通上傳里文件的二進制流從頭傳到尾中間任何一個地方斷掉已經(jīng)傳出去的字節(jié)就全部作廢。斷點續(xù)傳則圍繞“狀態(tài)”來做文章先記住這個文件的唯一身份再記住哪些分片已經(jīng)上傳成功下次重新打開頁面或者點擊續(xù)傳時只需要從沒傳過的分片繼續(xù)已經(jīng)傳過的分片直接跳過。這個“記住”的過程就是后端只認hash和分片序號hash是文件指紋分片序號是位置。把斷點續(xù)傳和完整上傳放在一起對比差異就非常明顯傳輸粒度完整上傳是單一數(shù)據(jù)流斷點續(xù)傳是一個個獨立分片。失敗成本完整上傳一旦失敗全部重來斷點續(xù)傳只需要重傳失敗的那幾片理想狀態(tài)下接近零成本??蓵和P酝暾蟼鞑荒苤型緯和;謴蛿帱c續(xù)傳隨時可以暫停下次繼續(xù)。秒傳能力如果服務端已經(jīng)保存過同樣hash的文件可以直接提示上傳完成根本不用再上傳。這些差異是斷點續(xù)傳被選來對付大文件的核心原因。1.3 整體方案選型為什么選Vue 3 Node.js嚴格說起來斷點續(xù)傳是前端工程和前后端契約共同完成的不涉及什么黑魔法。demo里我用的組合是Vue 3 axios spark-md5后端用Node.js Express multer主要是因為這套組合和你標題里的“JavaScript怎么編寫”完全在一個語言體系里前端JavaScript負責切片、算hash、并發(fā)控制后端JavaScript負責收分片、存狀態(tài)、合并文件前后端不需要切換技術棧看起來更順。Vue 3的Composition API在管理上傳任務時比Vue 2的Options API直觀得多ref、computed這些響應式原語能把文件、hash、已上傳分片、進度這些狀態(tài)統(tǒng)一管理起來。demo里我不會引入太重量的UI庫直接用原生button和簡單的div滾動條因為斷點續(xù)傳的核心不在UI組件而在于分片、hash和狀態(tài)同步你以后接Element Plus還是Ant Design Vue都很容易。提示如果你在真實項目里用的是React、Angular或原生JS這套思路同樣成立因為真正核心的是File.slice、FormData和XHR/fetch這些瀏覽器標準API框架只是外層殼。2. 前端分片與指紋計算從代碼層面吃透2.1 用 Blob.slice 把大文件切成小塊文件分片最根本的API就是Blob.prototype.sliceFile繼承自Blob所以可以直接file.slice(start, end)拿到原文件的一部分。這個操作不是把文件物理切開而是生成一個新的Blob底層可能仍然引用原文件的內(nèi)存區(qū)域所以性能很高不會因為切一下就把大文件整個復制一遍。以4MB為一片舉例分片邏輯如下const CHUNK_SIZE 4 * 1024 * 1024 function createFileChunks(file) { const chunks [] let start 0 while (start file.size) { const end Math.min(start CHUNK_SIZE, file.size) const chunk file.slice(start, end) chunks.push(chunk) start end } return chunks }這里有幾個細節(jié)要特別注意。第一最后一片的大小往往小于CHUNK_SIZE切片時end要用Math.min兜住文件大小否則會拿到一個空的Blob。第二每個分片必須帶上它在整個文件中的序號否則服務端合并時不知道先后順序這也就是后面接口里一直出現(xiàn)的index字段。第三切片時盡量讓每片大小保持一致這樣服務端合并、前端重試時邏輯都簡單。實際上分片大小不是一個固定的最佳值我見過很多人直接套一個“5MB”到處用。片越小請求數(shù)量越多服務端文件句柄和前端Promise對象都會壓滿片越大單請求傳輸時間越長斷點續(xù)傳的“續(xù)”就變得不精細。通常我建議從2MB到10MB之間取值公網(wǎng)環(huán)境取4MB左右比較平衡內(nèi)網(wǎng)環(huán)境可以放大到10MB請求數(shù)量和恢復精度都兼顧了。2.2 用 spark-md5 算出文件唯一指紋分片之后遇到第一個問題怎么讓服務端知道“這兩個文件其實是同一個”最可靠的方案不是文件名因為同名文件內(nèi)容可以完全不一樣也不是文件大小因為大小相同內(nèi)容也可能不同而是給文件內(nèi)容本身算一個摘要也就是hash。前端把整個文件的hash發(fā)給后端后端拿hash作為分片目錄名這樣同一個文件即使你改了文件名再傳也能直接續(xù)上。spark-md5是前端算文件hash最常用的庫它支持增量計算可以邊讀分片邊更新hash不用一口氣把整個文件讀進內(nèi)存。核心代碼如下import SparkMD5 from spark-md5 async function calcFileHash(file) { const spark new SparkMD5.ArrayBuffer() let offset 0 while (offset file.size) { const chunk file.slice(offset, offset CHUNK_SIZE) const buffer await chunk.arrayBuffer() spark.append(buffer) offset CHUNK_SIZE } return spark.end() }這里值得說一下ArrayBuffer模式spark.append可以接收ArrayBuffer、binary string等對大文件來說用ArrayBuffer模式的性能更好因為瀏覽器底層可以直接給你二進制內(nèi)存塊。每次循環(huán)只把當前這一片讀成buffer算完就釋放內(nèi)存占用量被控制在一個分片大小內(nèi)。如果文件達到了幾個GB這個計算過程可能要花幾十秒為了不阻塞界面真實項目最好放到Web Worker里跑demo階段為了少繞一圈直接放主線程后面我會專門講怎么處理卡UI的問題。有一點必須提前說清楚hash算出來的值只代表你本地看到的內(nèi)容不代表傳輸過程中沒有損壞。真正要求嚴謹?shù)膱鼍胺斩嗽诤喜⑼瓿珊筮€要再算一次hash和前端提交的hash比對不一致就說明傳壞了。demo里我會在合并接口里做一次最簡單的數(shù)量校驗。2.3 分片上傳并發(fā)控制的實現(xiàn)思路分片準備好了hash也有了是不是直接把所有分片一次性全部發(fā)出去不行。幾百個分片同時發(fā)起請求瀏覽器連接數(shù)有限服務器也會被瞬間打掛進度條回升得又慢又不穩(wěn)定。正確做法是控制并發(fā)比如最多同時傳3到5片。并發(fā)控制寫起來一點都不難核心就是一個消費者隊列維護一個游標分給固定數(shù)量的worker去消費待上傳列表每個worker取到一片傳一片傳完繼續(xù)取下一下片直到全部消費完。我習慣寫成這樣async function uploadWithConcurrency(pendingList, poolLimit) { let cursor 0 const workers [] const runWorker async () { while (cursor pendingList.length) { const current cursor const item pendingList[current] await uploadChunk(item.chunk, item.index) } } const workerCount Math.min(poolLimit, pendingList.length) for (let i 0; i workerCount; i) { workers.push(runWorker()) } await Promise.all(workers) }這樣寫的好處是讓每個worker異步循環(huán)拉任務而不是先分配固定任務這樣慢的分片不會拖累其他worker。實際跑的時候你可以在uploadChunk里給每個分片附加index和hash并在成功后把index記錄進已上傳集合中。暫停功能的核心也是這個隊列暫停時用一個標志位讓worker的while循環(huán)直接退出并且取消掉正在進行的axios請求恢復時用當前已上傳集合過濾出還沒傳的分片重新建一個隊列跑起來。3. 服務端接口怎么設計才能支撐前端這套玩法3.1 約定三個核心接口check、upload、merge前端和后端的分工必須通過接口契約固定下來demo里我把它收成三個接口這也是大多數(shù)斷點續(xù)傳服務的最小集合接口方法核心參數(shù)職責/api/checkPOSThash查詢服務端已存在哪些分片序號/api/uploadPOSTfile、hash、index上傳單個分片/api/mergePOSThash、name、total通知服務端合并所有分片check接口的作用是“斷點查詢”。用戶打開頁面重新選擇一個文件前端算完hash后先問服務端這個文件傳過嗎傳到了第幾片服務端把已有分片的索引數(shù)組返回給前端前端過濾掉這些序號只傳剩下的。這個接口讓“續(xù)傳”真正成立不然每次重開頁面都不知道從哪里繼續(xù)。upload接口接收的就是某一個分片的二進制這里有個重要約定分片必須帶hash和index。hash決定它落到哪個目錄index決定它在合并時的位置。為了簡化設計后端按hash建目錄目錄里的文件名直接就是“index.part”查找已上傳分片就是讀取目錄下有哪些part文件。merge接口是最后一擊前端把所有分片都傳完后通知后端合并。這個接口不能少因為如果每傳一片就直接往最終文件尾部追加網(wǎng)絡亂序會讓你根本無法還原文件正確做法是先落盤成獨立分片最后統(tǒng)一排序合并。3.2 分片落盤與斷點記錄后端我用Express multer來接分片。multer是表單文件處理的標準庫配置磁盤存儲后文件會自動保存到指定目錄我們只需要在存儲配置里把req.body里的hash和index取出來拼路徑即可。const path require(path) const fs require(fs) const multer require(multer) const upload multer({ storage: multer.diskStorage({ destination(req, file, cb) { const dir path.join(UPLOAD_DIR, req.body.hash) fs.mkdirSync(dir, { recursive: true }) cb(null, dir) }, filename(req, file, cb) { cb(null, ${req.body.index}.part) } }) }) app.post(/api/upload, upload.single(file), (req, res) { res.json({ code: 0, message: ok }) })很多初次寫斷點續(xù)傳的人會在這里犯一個錯誤在destination回調(diào)里通過req.body拿到字段。如果你去打印發(fā)現(xiàn)req.body是空的原因通常是multipart字段順序不對。multer解析表單時文件字段之前的文本字段會先進入req.body所以前端構造FormData時最好把hash和index放在file字段之前append這個問題就自然消失了const formData new FormData() formData.append(hash, fileHash.value) // 先放文本字段 formData.append(index, index) formData.append(file, chunk) // 再放文件斷點記錄不需要引入數(shù)據(jù)庫因為文件系統(tǒng)本身就是記錄某個hash目錄下有多少個part文件已經(jīng)自然記錄了上傳進度。check接口做的事情就是讀目錄app.post(/api/check, (req, res) { const { hash } req.body const dir path.join(UPLOAD_DIR, hash) const uploaded fs.existsSync(dir) ? fs.readdirSync(dir).map((name) Number(name.split(.)[0])) : [] res.json({ code: 0, uploaded }) })在真實場景里如果你的服務端是多機部署或者有幾十萬人同時傳文件文件系統(tǒng)就不是可靠的記錄方式了那時候得換Redis之類的記住分片狀態(tài)。demo階段用文件系統(tǒng)邏輯最透明也最容易排查問題。3.3 合并分片時的順序與校驗問題合并接口收到請求后要讀取hash目錄下所有part文件按序號從小到大依次寫入最終文件。順序錯了整個文件就是亂碼。寫合并邏輯時有兩點值得注意。第一sort不能直接按文件名字符串來因為10.part會排在2.part前面必須把文件名里的序號解析成數(shù)字再比較。第二大文件合并不要用readFileSync把所有分片同時讀進內(nèi)存幾個GB的文件可能會把Node進程內(nèi)存撐爆。正確姿勢是用流式管道或者用ReadableStream的異步迭代邊讀邊寫const { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync } require(fs) async function mergeChunks(hash, name) { const dir path.join(UPLOAD_DIR, hash) const chunkFiles readdirSync(dir) .filter((item) item.endsWith(.part)) .sort((a, b) Number(a.split(.)[0]) - Number(b.split(.)[0])) const outputPath path.join(MERGED_DIR, ${Date.now()}-${name}) if (!existsSync(MERGED_DIR)) mkdirSync(MERGED_DIR, { recursive: true }) const ws createWriteStream(outputPath) for (const file of chunkFiles) { const rs createReadStream(path.join(dir, file)) for await (const data of rs) { if (!ws.write(data)) { await new Promise((resolve) ws.once(drain, resolve)) } } } ws.end() await new Promise((resolve, reject) { ws.on(finish, resolve) ws.on(error, reject) }) }合并完成后強烈建議用絕對路徑讀寫文件避免文件名里帶上../或者/之類的路徑穿越問題。真實項目中文件名不能直接拿來拼路徑應該用hash作為存儲名或者做一層白名單過濾demo里用name主要是方便看到合并結果。注意不要在真實項目里讓用戶傳來的文件名直接落盤一定要對文件名做清洗或重命名。這是很多文件上傳服務被上傳惡意文件的入口。4. 完整DEMO實操從前端Vue到后端Node的落地過程4.1 初始化工程與依賴先建一個最簡單的工程。我特意不引入復雜的腳手架只保留能跑通斷點續(xù)傳的核心依賴方便你放進自己的項目里改造。mkdir vue-big-file-upload-demo cd vue-big-file-upload-demo npm init -y npm install express multer axios spark-md5 npm install -D vite vitejs/plugin-vue后端部分需要express和multer前端部分需要axios和spark-md5。vite只是demo的啟動工具你可以理解成幫我們把Vue單文件組件編譯到瀏覽器能跑的程度。目錄結構我習慣把前后端放在同一個工程里前端代碼放src后端代碼放servervue-big-file-upload-demo ├── src │ ├── App.vue │ └── main.js ├── server │ └── index.js ├── index.html ├── vite.config.js └── package.jsonvite.config.js里配置devServer代理把/api開頭的請求轉(zhuǎn)發(fā)到后端的3000端口import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: http://localhost:3000 } } })main.js和index.html最簡單能掛載App.vue就行// src/main.js import { createApp } from vue import App from ./App.vue createApp(App).mount(#app)!-- index.html -- !DOCTYPE html html head title大文件斷點續(xù)傳DEMO/title /head body div idapp/div script typemodule src/src/main.js/script /body /html4.2 前端頁面與上傳邏輯App.vue是整個demo的前端核心。我用ref維護文件對象、hash、已上傳分片集合和進度再用一個上傳狀態(tài)字段標記當前處于“計算hash中、上傳中、已暫停、已完成”哪個階段。這三個狀態(tài)字段看著簡單但斷點續(xù)傳最怕的就是前端狀態(tài)沒管好。template div input typefile changehandleFileChange / button clickstartUpload上傳/button button clickpauseUpload暫停/button button clickresumeUpload續(xù)傳/button div文件指紋{{ fileHash || 等待計算 }}/div div總體進度{{ percent }}%/div div狀態(tài){{ status }}/div /div /template script setup import { ref } from vue import axios from axios import SparkMD5 from spark-md5 const CHUNK_SIZE 4 * 1024 * 1024 const file ref(null) const fileHash ref() const uploadedIndexes ref([]) const totalChunkCount ref(0) const percent ref(0) const status ref(idle) const pauseFlag ref(false) const cancelControllers ref([]) function handleFileChange(e) { file.value e.target.files[0] } function createChunks(sourceFile) { const chunks [] let start 0 while (start sourceFile.size) { const end Math.min(start CHUNK_SIZE, sourceFile.size) chunks.push(sourceFile.slice(start, end)) start end } return chunks } async function calcHash(sourceFile) { const spark new SparkMD5.ArrayBuffer() let offset 0 while (offset sourceFile.size) { const chunk sourceFile.slice(offset, offset CHUNK_SIZE) spark.append(await chunk.arrayBuffer()) offset CHUNK_SIZE } return spark.end() } async function uploadChunk(chunk, index) { const formData new FormData() formData.append(hash, fileHash.value) formData.append(index, index) formData.append(file, chunk) const controller new AbortController() cancelControllers.value.push(controller) try { await axios.post(/api/upload, formData, { signal: controller.signal }) uploadedIndexes.value.push(index) percent.value Math.round((uploadedIndexes.value.length / totalChunkCount.value) * 100) } finally { const i cancelControllers.value.indexOf(controller) if (i -1) cancelControllers.value.splice(i, 1) } } async function runTaskWithLimit(pendingList, limit) { let cursor 0 const runWorker async () { while (cursor pendingList.length) { if (pauseFlag.value) return const current cursor const item pendingList[current] try { await uploadChunk(item.chunk, item.index) } catch (e) { if (pauseFlag.value) return // 網(wǎng)絡失敗時重試一次 await uploadChunk(item.chunk, item.index) } } } const workers [] for (let i 0; i Math.min(limit, pendingList.length); i) { workers.push(runWorker()) } await Promise.all(workers) } async function startUpload() { const sourceFile file.value if (!sourceFile) return status.value computing-hash pauseFlag.value false fileHash.value await calcHash(sourceFile) const chunks createChunks(sourceFile) totalChunkCount.value chunks.length status.value checking const { data } await axios.post(/api/check, { hash: fileHash.value }) const uploaded data.uploaded || [] uploadedIndexes.value uploaded if (uploaded.length 0) { percent.value Math.round((uploaded.length / totalChunkCount.value) * 100) } const pending chunks .map((chunk, index) ({ chunk, index })) .filter((item) !uploaded.includes(item.index)) if (pending.length 0) { await mergeRequest(sourceFile) status.value done percent.value 100 return } status.value uploading try { await runTaskWithLimit(pending, 3) if (pauseFlag.value) return await mergeRequest(sourceFile) status.value done percent.value 100 } catch (e) { status.value failed } } async function mergeRequest(sourceFile) { await axios.post(/api/merge, { hash: fileHash.value, name: sourceFile.name, total: totalChunkCount.value }) } function pauseUpload() { pauseFlag.value true status.value paused cancelControllers.value.forEach((controller) controller.abort()) } function resumeUpload() { if (!file.value) return startUpload() } /script這段代碼里有一個很重要的細節(jié)上傳成功后立即把index推進uploadedIndexes數(shù)組并刷新進度這樣即使頁面中途刷新下一次續(xù)傳也能通過check接口拿回這些記錄。另一個細節(jié)是暫停時調(diào)用controller.abort讓正在傳輸?shù)腶xios請求真的中斷而不是只把標志位改成true等著它自己跑完。之所以用AbortController而不是axios舊版的cancelToken是因為這是現(xiàn)在更推薦的標準方式axios新版也支持signal配置。4.3 后端服務完整實現(xiàn)server/index.js里放一個完整的Express服務包含前面說的三個接口。為了演示直接我合并文件時用時間戳加原始文件名作為輸出文件名但真實項目里建議換成hash加后綴可讀性差一點安全性高很多。const express require(express) const multer require(multer) const fs require(fs) const path require(path) const app express() const PORT 3000 const UPLOAD_DIR path.join(__dirname, uploads) const MERGED_DIR path.join(__dirname, merged) fs.mkdirSync(UPLOAD_DIR, { recursive: true }) fs.mkdirSync(MERGED_DIR, { recursive: true }) app.use(express.json()) const storage multer.diskStorage({ destination(req, file, cb) { const dir path.join(UPLOAD_DIR, req.body.hash) fs.mkdirSync(dir, { recursive: true }) cb(null, dir) }, filename(req, file, cb) { cb(null, ${req.body.index}.part) } }) const upload multer({ storage }) app.post(/api/upload, upload.single(file), (req, res) { res.json({ code: 0, message: chunk uploaded, index: Number(req.body.index) }) }) app.post(/api/check, (req, res) { const { hash } req.body const dir path.join(UPLOAD_DIR, hash) if (!fs.existsSync(dir)) { return res.json({ code: 0, uploaded: [] }) } const uploaded fs.readdirSync(dir) .filter((name) name.endsWith(.part)) .map((name) Number(name.split(.)[0])) res.json({ code: 0, uploaded }) }) app.post(/api/merge, async (req, res) { const { hash, name, total } req.body const dir path.join(UPLOAD_DIR, hash) if (!fs.existsSync(dir)) { return res.status(400).json({ code: 1, message: 分片目錄不存在 }) } const partFiles fs.readdirSync(dir) .filter((item) item.endsWith(.part)) .sort((a, b) Number(a.split(.)[0]) - Number(b.split(.)[0])) if (partFiles.length ! Number(total)) { return res.status(400).json({ code: 1, message: 分片數(shù)量不完整 }) } const outputPath path.join(MERGED_DIR, ${Date.now()}-${name}) const ws fs.createWriteStream(outputPath) for (const partFile of partFiles) { const rs fs.createReadStream(path.join(dir, partFile)) try { for await (const data of rs) { if (!ws.write(data)) { await new Promise((resolve) ws.once(drain, resolve)) } } } catch (e) { ws.destroy(e) return res.status(500).json({ code: 1, message: 合并失敗 }) } } ws.end() ws.on(finish, () res.json({ code: 0, message: merge ok, filePath: outputPath })) ws.on(error, (e) res.status(500).json({ code: 1, message: e.message })) }) app.listen(PORT, () { console.log(server running at http://localhost:${PORT}) })這里merge接口里對分片總數(shù)做了一個校驗前端傳過來的total必須等于實際part文件數(shù)量否則直接拒絕合并。這是防止“漏傳了幾個分片就去合并”的第一道防線但只靠數(shù)量校驗還不夠更好的做法是校驗所有分片大小之和是否等于原始文件大小或者合并后重新計算整文件hash。4.4 完整請求時序與體驗對照跑起來之后一個典型的斷點續(xù)傳請求鏈路是這樣的用戶選擇文件前端切片并計算hash狀態(tài)顯示“正在計算”。前端POST /api/check拿到服務端已存在的分片序號數(shù)組。前端過濾出剩余分片以3路并發(fā)逐片POST /api/upload。每傳完一片前端將index加入已上傳集合進度條前進一步。全部傳完后POST /api/merge服務端按index排序合并返回最終文件路徑。第一次全量傳可能要幾十秒。第二次選擇同一個文件再上傳check接口直接返回所有分片序號已存在前端不會發(fā)出任何upload請求直接走merge整個過程在1秒內(nèi)完成這就是秒傳。中斷的體驗也能復現(xiàn)上傳到一半直接把后端進程殺掉前端會拋出一堆請求失敗然后暫停再重啟后端點續(xù)傳前端重新計算hash后通過check發(fā)現(xiàn)已經(jīng)上傳了前10片就從第11片開始繼續(xù)傳剩余分片都是秒過。這個過程中真正網(wǎng)絡傳輸?shù)闹挥兄袛嗪笫S嗟牟糠智懊娴姆制粋€都沒有重傳。5. 斷點續(xù)傳DEMO里最容易踩的坑我?guī)湍阏砗昧?.1 hash怎么算才不卡UIdemo里我為了可讀性直接把hash計算放在主線程小文件沒問題一旦文件超過1GB你就能明顯看到頁面卡在“計算中”轉(zhuǎn)圈按鈕點不動。原因很簡單spark-md5的ArrayBuffer.append雖然每次只處理一個分片但整個計算循環(huán)占著主線程的事件循環(huán)Vue的響應式更新根本沒機會插進來。解決辦法是放進Web Worker。把分片讀取和spark-md5計算全部丟到worker里主線程只負責接收最后的hash字符串和進度。還有一個折中方案在主線程計算時每隔幾個分片await一次setTimeout或requestAnimationFrame讓出事件循環(huán)界面能保持響應但是對超大文件仍然不夠穩(wěn)。真實項目里我建議直接開Worker并且把“計算hash”也做成可中斷的否則用戶等了幾十秒想取消算到一半也退不掉。另一個性能問題是讀文件的方式。chunk.arrayBuffer()會把這一片完整拷進內(nèi)存4MB一片沒問題但如果你大面積用整文件arrayBuffer幾個GB的文件直接內(nèi)存溢出。demo里我始終基于切片來讀這一點不要改成file.arrayBuffer()一次性讀取。5.2 分片大小和并發(fā)數(shù)到底怎么配分片大小和并發(fā)數(shù)是斷點續(xù)傳最影響吞吐的兩個參數(shù)它們的合理值取決于你的用戶網(wǎng)絡環(huán)境。我整理了一份經(jīng)驗值可以直接當初始基準場景推薦分片大小推薦并發(fā)數(shù)公網(wǎng)普通寬帶、用戶量大2MB-4MB3內(nèi)網(wǎng)高速、服務端帶寬充足5MB-10MB5-8手機弱網(wǎng)環(huán)境1MB-2MB2-3公網(wǎng)環(huán)境并發(fā)數(shù)別開太大看起來快實際上容易觸發(fā)服務端連接數(shù)上限、代理超時一個分片失敗還會帶動整條連接排隊。弱網(wǎng)環(huán)境分片小一點這樣中斷發(fā)生時損失的字節(jié)更少重試速度也更快。這些值可以在后臺做成配置項按文件大小動態(tài)調(diào)整比如小于100MB走普通單請求大于100MB再啟用分片。還有一個容易忽略的參數(shù)是axios的超時時間。給每個分片請求設置一個合理的timeout比如30秒不要讓一個分片卡在服務端永遠等下去否則所有worker都等在同一片上后續(xù)分片全部排隊表現(xiàn)就是進度條卡住不動。5.3 斷點失效、秒傳失敗、進度不準逐個排查先看最常見的“斷點續(xù)傳不生效”重新打開頁面選擇同一個文件check接口返回的uploaded卻是空數(shù)組。這種情況十有八九是hash對不上檢查點有這幾個前端hash計算是否基于原始文件的完整內(nèi)容有沒有因為切片邊界算錯。后端保存分片目錄時是否真的用了hash而不是用索引值拼錯路徑。同一個文件在前后兩次選擇時File對象的size和lastModified是否一致。如果文件內(nèi)容沒變但hash不同多半是讀取邏輯里混入了無關字節(jié)。再看“秒傳失敗”所有分片明明都在前端還是把幾百個分片重新傳了一遍。先把check接口返回的數(shù)組打印出來如果返回的index是字符串而前端用includes判斷時類型不匹配就會全部判斷成不存在。這是JavaScript弱類型最容易踩的坑之一要么后端返回數(shù)字要么前端parseInt之后再去includes。進度不準的問題多數(shù)出在進度計算公式上。整個上傳過程其實包含hash計算和上傳兩個階段很多人只算了上傳階段的片數(shù)百分比用戶從0%開始等hash算了半天進度一直是0體驗很差。我的習慣是給hash計算分配一個固定小百分比比如5%上傳階段占95%然后總體進度等于hash階段完成后5%再加上uploadedIndexes.length / totalChunkCount * 95。最后一個坑是關于端口和代理的。Vite開發(fā)服務器默認5173后端3000如果跨域或不走代理前端發(fā)出的/api請求會404。我建議demo里統(tǒng)一通過vite的proxy轉(zhuǎn)發(fā)生產(chǎn)環(huán)境則用nginx把/api反代到后端這是最不容易出問題的部署姿勢。6. 演示之外我把這套方案搬進真實項目的經(jīng)驗demo寫完之后我在自己維護的一個網(wǎng)盤類小工具里把這套邏輯改了改上線遇到了一些demo里沒有體現(xiàn)的問題分享幾個方向你可以作為下一步擴展的參考。第一是取消和續(xù)傳的狀態(tài)管理。demo里暫停用一個布爾標志位搞定但真實場景用戶可能在上傳中刷新頁面、切走再回來前端需要把上傳任務持久化到localStorage甚至IndexedDB記錄文件hash、分片序號和進度。重新加載后先恢復任務列表再逐個調(diào)check接口確定真實進度。這一步不做用戶中途刷新一次前面?zhèn)鞯钠腿皝G了”心智雖然服務端物理上還在但前端不知道從哪里繼續(xù)。第二是Web Worker里跑hash。我在worker里維護了一個“取消計算”的信號主線程傳給worker或者直接調(diào)用worker.terminate()用戶取消上傳時能把hash計算立刻停下來。這一改動對體驗的提升非常明顯尤其面對10GB以上的文件hash計算可能要好幾分鐘。第三是服務端的整潔度。文件系統(tǒng)做斷點記錄在單機demo里很舒服但上線后最好把“分片狀態(tài)”抽到Redis緩存文件本體放對象存儲或者分布式文件系統(tǒng)合并操作交給后端異步任務隊列去跑而不是在HTTP請求里同步讀寫大文件。同步讀寫會導致請求一直掛著網(wǎng)關很快超時斷開合并卻還在后臺寫文件返回包用戶根本收不到。如果只是要一個能跑的DEMO照著上面的代碼敲一遍就夠了如果要把斷點續(xù)傳做成生產(chǎn)功能分片存儲、失敗重試、秒傳校驗、權限控制這幾個模塊都值得單獨花時間打磨。我現(xiàn)在回看這個項目最大的收獲不是某個API有多難而是“把大問題拆成小問題、再對小問題分別處理”這個思路在斷點續(xù)傳里體現(xiàn)得尤其明顯。真遇到大文件傳輸需求時先別急著找現(xiàn)成的上傳組件把文件分片、hash指紋、斷點記錄這三件事想清楚代碼自然就出來了。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
日本高清视频在线观看黄已三辽| 人人操人人操人人操人人操人人操人人人11.CM | 99热这里| 曰韩无码777| 国产精品福利视频播放| 国产成人亚洲精品无| 欧美熟妇精品黑人巨大91| 第二页中文字幕| 国产激情片在线观看| 亚洲无码精品AV久久久| 日本韩国五十路六十路七十路老熟女作爱视频网站 | 神马视频久久久久久| 精品午夜福利导航| 神马九九九| 欧美激情综合色综合啪啪五月| 91在线视频国产网站| 久久久久久久久国产| 亚洲人人操| 亚洲好看强奸乱伦| 亚洲国产97| 色欧洲97| 精品人妻无码一区二区三区不卡-精品人妻无码一区二区...|精品少妇一区二区三 | 国产97亚洲| 欧美激情色婷婷花野真衣一区二区| 丝袜美腿制服人妻二区中文字幕 | 99热| 美女爽爽爽刺痛洞洞| 鲁鲁色综合网| 中文字幕一区二区三区视频播放| 欧美三级中文字幕hd| 色吧5亚洲| 欧成人精品一区二区三区| 熟女精品一区二区三区| 久久婷婷色| 亚一综合久久久久久久久久| 夜夜操美女| 夜夜爽夜夜爽| 久久永久无码人妻视频| 男同专区一区二区三区在线| 男人天堂新在线| 狠狠图片青青草| 激情丁香五月| 色优久久| 亚洲综合草草| 欧美页片| 老鸭窝日丰县女人| 中文字幕精品亚洲熟女| 淫纸中9区| 亚洲男人天堂视频| 亚洲97网站| 青娱乐 成人娱乐在线| 精品人妻一区二区三区四区不卡在| 国产乱人妻精品入口| 青青草原狼av| 美女高潮视频91| 啊啊啊啊啊在线观看网址 | 色爽——AV| 欧美九九99久久精品| 国内毛片无码一级毛片| 丁香婷婷大香蕉| 一区二区娱乐网站| 日韩乱码Av| 九九九九九九九九九九九免费国产| 五月丁香综合啪啪| 日韩成人色图| 日韩少妇无吗| 97这里有精品| 沈阳熟女高潮对白视频| 国产一区二区精品久久99| 成人精品一区二区91毛片不卡| 亚洲成人在线高清| ..日韩av毛片精品久久久| 免费αV在线视频| 国产精品999aaa| 第四色奇米影视777| 亚洲欧洲自拍图片专区满春格| 亚洲天天操| 国产无马在线| 99re公开精品免费视频 | 日韩欧美中文字| 欧美第一页| 亚洲做性| 男人午夜天堂| 国产精品一二三在线看| 高潮内射在线| 欧美人妻一区二区| 伊人影院中文字幕| 欧美乱妇狂野欧美在线视频| 亚洲丨在线| 四虎免费在线观看| 一区二区三区视频在线观看免费| 黄色二级片网站| 2003天天干夜夜操| 精品国产无码中文| 一区=区三区视频| 大香蕉日韩欧美| 超碰97COm中文| 久久精品国产亚洲AV清纯| 国产AV毛片| 97色插| 日本中文字幕一区| 97啪啪| 亚洲无码一二三区| 国产精品 午夜福利| 亚洲风情综合网| 亚洲欧美国产va在线播放频| 美女诱惑爱爱| 久久久久久中文| 亚洲欧美日韩偷拍色图| 9 9精品一区二区三区| 亚洲 se图 欧美电影| 天天天干977| 911av网站免费观看| 日本理论在线| 天天爱综合网| 男女啪啪啪18禁网站| 亚洲国产麻豆一区二区三区| 日日夜夜草草草| 久久久久久性爱免费视频| 人人看人人插| 国产精品对白内射| 久久精品人妻一区| 精品人妻一区二区乱码一区二区| 欧州一区二区三区四区| 久操视频免费在线观看| 天天综合97| 麻豆这里只有精品| 中文字幕青青草| AV污污污污| 插老姨肥穴| 亚洲美女AV无码| 亚洲激情综合| 97超碰超| 激激五月| 亚洲色婷婷久久91| 1024亚洲中文字幕久在线看片你懂的 | 国产欧美日本亚洲精品| 免費人妻夜夜爽天天爽爽一区| 亚洲精品欧洲色| 五月天色色色| 日韩精品人妻一区二区| 丁香激情五月天| 欧美色道啊| 黄色av一区二区在线| 欧美91久久久久| 黄色免费网页无码| 中文字幕色AV| 加勒比大香蕉视频在线| 久久精品国产亚洲妲己影视| 天天干人妻| 逼操网站| 国产又大又粗又长视频| 男人的天堂网免费| 在线αⅴ| 97一区二压| 26uuu性物| 97在线欧| 夜夜中出国产| 五月天黄色激情视频| 欧美专区17页| 国产亚卅97| 午夜偷拍久久熟女| 综合少妇网| 日本久久精品| 综合av社区| 日韩中文字幕宗合在线| 五月香婷婷| 亚洲国产熟妇综合色专区| 97人妻色| 国产一区二区三区精品观看啪| 操熟女91| 啊啊啊啊操死我了| 欧美黄色大香蕉一区二区| 骚女天天综合网| 青青草亚洲一区 | 久日91在线| 国产精品无码av| 啊啊啊啊啊舒服| 伊人久久88国产女| 国产精品久久99日日| 丰满少妇精品一区二区| 久久亚洲影院一区二区| 尤物国产一区在线观看| 麻豆国产精品午夜视频| 欧美色综合图片| 91无人区卡一卡二卡三乱码入口最新版:能让用户有更多选择的选择-经典说说-爱 | 激情色播| 欧美1区二区三区公司| 亚洲素人综合| 日日夜夜噜| 99久久综合| 日韩激情啪啪啪| 99av| 国产丁香精品露脸视频| 蜜桃视频一区二区三区| 天天综合欧美综合| 嗯嗯啊啊用力视频免费| 男人的天堂2019AV| 国产又色又爽又舒服的三级视频| 伊人操你| 日本黄色精品专区网站| 色99999| 精品无码一区二区三区| 中国熟女91| 婷婷激情一区二区三区俺也去| 91动漫操逼视频| 国产亚洲色停停久久99精品91| 丝袜天堂网| 欧美一二三区四五区| www.色综合| 丰满人妻av一区二区三区| 一二三区操逼国产91| 国产999精品久久久| 十八禁电影伊人网| 99精品高潮| 久草毛片电影怡| 男人亚洲91首页在线| 国产成人精品日本亚洲语言| 亚洲一本大道中文字幕无码在线| 久干9操| 久操网视频| 岛国1区2区3区在线观看| 日本视频在线中文字幕| 中文字幕在线观看网址| 欧美精品69性爱| 国产精品福利视频| 91亚洲综合| 欧美性性性| 欧美少妇高潮| 久久在肏| 无码人妻一区二区三区免费九色| 亚洲91网| av一区二区三区不卡| 久久男人天堂| 综合亚洲欧美精品日韩?v| 日躁天天爽爽| 人人模人人看| 国产精品嫩草影院免费| 韩国免费播放一级毛片| 97在线看| 91亚洲人| 一区二区三区一亚洲中文字幕、综合区灬 | 97超碰色屌| 97色论| 蜜桃色院一区久久| 97欧美色综合| juliaann欧美丝袜办公室| 美女网站91| 五十路熟女人妻一区二区在线观看 | 天天干人妻视频| 一本一道久久综合久久| 欧美丝袜中文字幕07在线| 97超碰9| 又粗又长又大国产不卡| 少妇高潮特黄A片| 亚卅熟女乱色| 97干在线看| 精品美女少妇一区二区三区| 97超级色碰碰| 99婷婷一区二区| 不卡一区二区日本视频| 天天干天天干天天干| 超碰97资源中文字幕| 久草精品视频| TS人妖另类精品视频系列| 久久久久久69国产一区二区| 日日干夜夜干| 97一区二压| 91n处女在线观看| 国产最火爆久久国产网站网站 | 人妻激情在线视频| 色姑娘综合网| 色爱三区| 国产成人啪一区二区| 蜜臀久久99精品久久久久| 国产不卡免费在线视频| 熟妇熟女一区二三区| 丰满欧美放荡少妇在线| www.av在线视频| 久久久蜜桃一区二区三区| 97色涩| 97超碰站| 97精彩视频网站| 色97综合中文字幕| 370p日韩欧美亚洲精品| 亚洲人成色9999精品久久 | 国产超碰| 校园春色亚洲无码| 欧美日韩亚洲一区二区在线观看| 亚洲色吧网| 天天影视网色欲色香| 另类TS人妖一区二区三区| 午夜丁香婷婷| 亚洲天堂男| 另类一区| 97天天在线| 日本性爰一道本| 天堂九九九九九九九九九| 欧美手机在线综合| 国产第25页在线观看| 一区| 国产操伦| 色综合20p| 日韩综合无码色欲vv| 殴美日韩m| 亚洲国产精品成人久久蜜臀| 狠狠综合网| 日本Suv精品一区二区| 婷婷六月色开| 清纯唯美亚洲综合| 久久久久13| 2019天天干| 欧美性天天影视| 亚洲天堂2020| 有码人妻系列| 人人插人人摸人人| 激情另类激情| 欧美一区91大爱| 国产白丝av| 亚洲另类色图片| 国产亚洲日韩欧| 国产白丝在线| 欧美性爱五月天| 成人婷婷丁香| 日本操逼无码| 91夜夜蜜桃臀1区2区3区| 免费一级性爱久久| 久久久女人| 色婷婷电影网| 国产毛片久久久久久久| 久久成人网站| 99热综合| 国产一区自拍欧美日韩| 日韩欧美蜜桃精品久久中文字幕久久| 免费夜夜爱黄色视频毛片| 搡老女人老熟女91老熟女综合网| 口爆欧美91| 午夜寂寞欧美| 久久专区| 中文字幕视频免费| 亚洲色图久久成人| 亚洲精品一区二区三区在线播放| 亚洲欧美另类激情小说| 岛国不卡超碰护士AV在线播放| 亚欧无码在线| 丝袜人妻av一区二区| 美女诱惑在线一区| 欧美日韩999| 51国产午夜精品视频| 97精品中文字幕| 91九色首页| 亚洲伊人久久综合97| 99热日| 亚洲国男人的天堂| 黑丝自慰喷水网站| 毛片久久| 天天噜| 中文字幕免费看| 国产九九久久久精品| 日韩人妻一二三区视频| 亚洲中文字幕在线视频一区二区| 97超级色碰碰| 欧美激情一区二区| 97日本超碰综合| 亚洲成人一区二区精品| 久久精品国产欧美日韩亚洲欧美日韩中文久久国产一区 | 欧美日韩妖精91com| 91久久午夜无码鲁丝片久久人妻| 国产92麻豆天美精品色欲5| 久草综合网| GVH-003 母子姦 青木玲-麻豆视频,麻豆视传媒短视频网站入口,麻豆视传媒官网直 | 国产极品久久久| 人妻av在线| 中国少妇XXXX做受| 婷婷久月| 啊啊啊好大好深| 极品AV网站在线观看| 国产白领连续中出在线观看| 蜜桃精品一区二区三区ww| 在线国产探花| 中文字幕一区二区日韩网| 国产高清自拍| 女人综合网| 日韩内| 婷婷五月天色网| 欧美日韩美女精品久草一区二区三区| 激情综合av| 殴美日韩m| 夜夜高潮夜夜爽| 中文字幕日韩综合| 人人考人人摸人人干| 国产亚洲精品美女久久久m| 国内精品a| 久久伊人亚洲AV无码网站| 极品白嫩美少妇在地板上位骑射淫水泛滥| 色噜噜日韩精品| 五月天欧美色图| 人妻黑丝袜电影| 97视频900| 色 亚洲 91| 大香蕉综合在线| 啊啊啊啊啊好舒服视频| 殴美色网| 天操天操夜操夜月操月年年操操| 97视频在线免费播放| 色哟哟-国产专区| 日本九九久久99播| 日日噜噜夜夜久久亚洲一区二区| 色天堂综合| 99久热精品99re6热| 人妻人妻天天碰| 2019亚洲男人天堂| 久草看看看| 久久久四区| 一级AV性爱| 无码天天操| 青草精品视频一日本久久久久网站| 欧美日韩淫加| 情色av电影| 国内外毛片在线观看| 国人欧美精品一区二区| 密臀在线视频| 99re3这里只有精品| 日韩本不卡视频在线观看 | 美女91在线观看| 曰韩少妇无码| 97亚洲国产| 啊啊嗯嗯好爽| 日本久久久久久久久久| 狠狠躁天天躁日日躁| 人人爽天天爽| 日韩人妻中文视频| 中文乱码99| blacked精品一区国产| 亚洲九月丁香| 午夜视频好爽啊| 中文字幕一区二区韩| 色在线亚洲视频www| 欧美在线永久天堂| 亚洲色欲天天天堂色欲网女| 欧差乱伦二三| 国产美女裸体秘 永久无遮挡| 蜜臀久久99精品久久久| 囯产精品久久久久久久久久梁医生| 99久久久无码国产精品性啊聊| 人人妻人人爱人人玩| 男女无套 免费网站| 久久久久久久久久久免费精品| 国产偷拍网站| 国产成人精品无码久久| 91干熟女| 色婷婷综合久久中文字幕雪峰 | 中文字幕精品探花视频| 1000部熟女视频在线观看| 久久国产乱子伦精品免费女人| 伊人色综合网电影| 96AV精品| 婷婷久久久精品| 欧洲特黄毛片免费看欧洲毛片| 久久熟妇五十路一区| 日韩一级二级三级免费看完整版国语版 | 天天操天天7| 久久系列| 囯产操逼片| 久草新免费| 亚洲天天自拍| 日韩成人性爱AV| 99r九九| 精品999一区二区| 欧美亚洲小说| 天美国产三级传媒| 日韩97视频!在线| 长长久久曰曰夜夜成人网| 破苞ⅩXXX性无码动漫无码| 天天看天天干| 日韩少妇无吗| 免费A V在线播放| 97玖玖超碰| 午夜无遮挡男女啪啪视频| 青青青国产| 草久在线| 久久精品| 人妻天堂综合网| 精品一国2| 黑操B| 亚洲图片 欧美电影| 色偷综合| 人成午夜免费大片| 男人天堂无码| 色小视频蜜乳| 黄色二级片网站| 五十路六十路七十路熟婆| 国产污视频麻豆传媒一区二区 | 91精品久久久久| 欧美一区二区观看在线| 亚欧美无遮挡| 人夜夜精品网站香蕉嫩草| 国产午夜精品在线观看| 男人天堂.AB| 久操网视频| 蜜臀va69| 精彩国产视频播放1区2区| 国产一区二区三区视频在线看| 日韩精品国产精品五码一区二区| 偷拍新久久| 91新在线欧美| 亚洲成av人片色午夜乱码| 97色97干| 青青草无码视频| 久热色情精品| 国产伦乱91| 日韩成人高清一区二区| 久久熟女精品不卡一区| 色av中文字| 26uuu国产免费观看| 国产精品人妻一区二区| 精品国产综合久久福利,热99这里有精品综合久久,99热这里只有免费国产精品,精 | 色天堂综合| 无遮挡h肉动漫在线观看| 亚洲精品久久一区二区三区蜜桃臀| 日韩AV无码网站| 激情小说亚洲色图| 午夜天堂精品久久久久91| KK色在线影院| 一区二区娱乐网站| 人妻激情在线视频| 97人人爱人人乐| 亚洲有薄码区久久在线一区| 九九九九九九九九九九九蜜桃| 国产11页| 精品国产99| 狠狠躁AV| 一区二区影视| 免费αV在线视频| 久久精品99| 99国内精品| 翔田千里Av在线| 天天色综合影视网| 神马久久69| 九九色色| 久草免费福利在线播放| 精品传媒在线一区| 久久久久久久久国产| 国产自产91区13区| 狠狠综合| 人人摸人人摸人人干| 女性喷水高潮在线观看| 天天欧美97| 97天天弄| 少妇滛荡视频| 91精品国产日韩欧美综合| 99久久99久久免费精品蜜臀| 欧美激情精品久久久| 97天天综合网| 嗯嗯嗯不要不要免费视频| 乱论91| 尤物视频视频官网| 天天干夜夜鈤| 老司机香蕉久久久久| 久久超碰av在线| 熟妇高潮精品一区二区三区下载| 少妇诱惑视频| 国色天香av| 日日干日日| 国产激情综合五月久久| 国产欧美日韩精品中文| 自拍偷拍亚洲熟女妇人精品| 亚洲人妻中文在线视频| 亚洲无码 国产无码| 91美女网站| 蜜臀视频网站| 亚洲天堂精品日韩电影| 少妇久久久| 亚洲欧美综合| 日韩三级在线观看网站| 91美女在线观看| 精品午夜福利导航| 日韩性色| 日韩大香蕉| 蜜臀99久久精品久久久久| 超碰久久.com| 97色综合中文网| 亚洲成人贴图| 欧美成熟性爱精品| 欧美色视频在线| 国产熟女完整版中字| 搡老女人老妇女AAA一VU麻豆| 欧美黑人精品在线播放| 久久久无码av精| 三级三久久线久久99久目本WW| 九九无码视频| 小少妇| 狠插 制服 自拍| 男人天堂综合| 大香蕉草草| 久久男人的天堂| 91丨九色丨国产丨人妻在线| 人人操人人搞人人草| 欧美系列在线一区二区| 免费夜夜爱黄色视频毛片| 超碰79人人乐| 日本九九久久99| 白嫩妹子国产骚| 高清孕妇孕交 交| 国产精品成久久久久午夜午夜| 乱伦一区二区三区‘| 女人双腿搬开让男人桶| 怡红院视频在线| 亚洲第一二区另类图| 亚洲国产97在线精品一区| 曰韩中文人妻视频| 人人操人人操草草| 激情一区二区三区在线观看| 黑人与人妻| 久久岛国| 久干9操| 97超碰国产精品| x97av| 人人么人人操| 国产精品亚洲一级av第二区| 狠狠干综合| 久久久专区| 国产精品久久久久久久久久久久久久吹 | 东亚亚洲无码高清| 青青草视频爽一爽| 青女偷拍网| 亚洲成人在线高清| 美女黄页| 久久9免费视频| 天天懆天天日| 精品四五区| 丝袜加勒比| 一级毛片久久久久久久女人18| 四虎AV在线观看| 在线情色电影 91大| 青久久| 国产日韩色综合| 天天摸天天插天天日| 五月天亚洲网| 日韩激情啪啪啪| 精品久久久久9999| 精品午夜福利| 五月婷在线| 欧美色综合图片| 无码人妻精品一区二区三区99不卡 | 天堂69亚洲精品中文字| 强奸乱伦αv片| 日韩ab网| 日韩免费福利在线观看| 东北夫妻性偷拍| 欧美 亚洲 在线| www.色婷婷色综合| 久久久9 9 9精品| 国产女人高潮嗷嗷嗷叫小说| 久久一二三级一一一| 欧美综合网| 国模无码一区二区三区在线| 97视频在线免费播放| 高清国产成人无码| 久久久久久久久久久久黄色| 一级性爱视频免费观看| 你操综合| 色综合国产在线观看| 岛国AV一区二区电影| 久久产精品一区二区三区电影| 日韩国语字幕| 色五月av| 婷婷久久五月| 999九九九九国产动| 日韩精品在线观看网站| 欧美日韩系列| 丁香五月婷婷色| 97超碰欧美精品| 亚洲第一页色| 1769成人国产精品视频| 大香蕉伊人网WWWn0n| 人妻9117c| 伊人久久大香线蕉亚洲五月天,青草青草欧美日本一区二区,欧美日产欧美日产国产 | 黄色网址在线免费观看| 九久久精品| 婷婷丁香六月| 国产精品ww久久| 欧美日韩电影一区二区| 伊人网综合在线视频| 久久久久久裸体| 人人摸人人干| 日韩中文字幕国产| 97在线播放| www.狠狠干.coom| A级国产欧美激情在线| 日韩美女,国产传媒,视频一区| 成人激情无码在线视频| 啊啊啊啊嗯嗯在线久久久| 爱射综合| 亚洲 一区二区 自拍| 4tube欧美女厕所| 四虎 精品 WWW| 精品成人亚洲午夜电影| 久久精品国产97欧美精品亚洲| 91欧美高清| 成年人三级黄色片视频| 91热热色| 成人日韩中文字幕| 欧美婷婷五月天| 国产中出内射一区二区| 玖玖爱综合网| 乱理日韩中文| 国产成人无码网站在线视频| 91亚洲网| 天天天操天天天爱| 福利风月五月天影院| 东亚亚洲无码高清| 69视频入口| 东京男人天堂| 超碰人妻中文在线| 欧美色图天堂在线| 乱老女人一区二区视频| 亚洲色图日韩精品| 亚洲精品一二区| 亚洲97成人在线观看| 97久久超碰日韩精品| 日韩资源网| 99999精品视频| 91精品黄在线观看| 综合免费无码中文| 三级日韩一区二区三区| 国产综合久| 欧美在线视频观看一二三四区高清| 国产精品老熟女一区二区| 屁屁影院一区二区三区国产 | 亚洲va有码在线天堂| 中欧人妻丝袜中文字幕| 老司机香蕉| 91处女在线视频| 欧美综合97www| 91狠| 啪啪91| 五月丁香社区婷婷日韩欧美精品影院 | 神马久久久久久伦理片| 高清国产精品福利网站| 一本色道久久综合精品婷婷| 99蜜月精品久久| 神马久久69| 国产女上位好爽在线| 日韩另类| 久久HD| 人人操人人摸人 | 9 9精品一区二区三区| 久久久久国产精品片区无码直播| 国产特级毛片AAAAAA高潮流水| av影片在线观看不卡| 99re99| 男人的天堂va在线| 午夜欧美精品久久久| 变态另类专区| 亚洲熟女乱综合一区二区在线-...亚洲国产日韩欧美一区二区三区,久久久久久精 | 6080yy午夜理论三级一区二区三区无码| 國產尤物AV尤物在線觀看| 久久一区二区高清免费| 国产毛片在线| 亚乱色| 国产呦精品一区二区三区下载| 精品亚洲国产成人av网站| 人人摸人人添人人操| 乱伦3P视频| 九九性爱网| 中文字幕一二区二三区人妻专区| 日韩国产不卡在线视频| 伊人网综合在线视频| 人妻人人做人人澡人人爽欧美一区| 91精品国产日韩欧美综合| 福利视频一区二区微拍| 性爱乱伦网址| 色婷婷香蕉| 少妇二级| 久插不卡| 精品女同一区| 97干在线视频| 呦呦影院| 综合欧美日韩在线观看| 亚洲综合九九| 91女网站| 亚洲日韩欧美一区二区| 性爱AV天堂| 欧美97se| 激情小说在线视频| 无码不卡亚洲成?人片| xxxx网站亚洲精品| 91精品大奶人妻| 精品人妻二区三区| 午夜毛片高清免费不卡| 欧美色爱综合| 亚洲无码视频免费在线观看网址!| 性久久久| 黄色AAAAA欧美| 99性爱| 欧美日韩一干二干| 亚洲清纯综合| 黄人人操人人操| 国产97色在线| 中文字幕精品一区二区精品| 伊人991| 春色91| 影视综合无码少妇| 色色国产| 9999九九九久久久| 91春色| 国产成人www免费人成看片| 丁香六月天| 国产视频小说| 亚洲色综合| 18禁超污无遮挡无码免费网| 超AV色女| 加勒比无码毛片| 伦激情人妻另类人妻| 色五月婷婷在线| 亚州日韩97| 秋霞色色影院| 亚洲精品亚洲人成人网| 丝袜天堂| 九九碰九九爱97超碰| 国产69精品久久久久99尤物| 7月婷婷综合| 九久久精品| 午夜精品久久一区二区| 亚欧性爱在线无码| 亚洲资源网| 婷婷干黄色| 欧美色图天堂在线| 久操| 欧美综合娱乐久久| 丁香五月激情五月| 日本不卡一区二区三区| 中日韩免费看男女操逼大全| 在线岛| 人人爽天天爽| 另类欧美色| 操逼日韩无码 | 亚洲 欧美 小说| 日韩特一级久久| 欧美巨大性舒爽顶到了| 熟女突然公开看18禁影片| 欧美综合色站| 少妇久久久| 五月丁香激情综合网| 久久视频少妇美女| 草久在线| 免費黃色視頻觀看一| 天天摸天天舔天天操| 内射老妇BBWX0C0CK| 另类图片五月| 97Ai亚洲| 黄污污污污| 亚洲色图第四色| 岛国网址国产| 好舒服视频| 岛国激情视频软件| 亚洲综合99999| 国产辣妈在线视频福利| 久久久精久久久| 男人天堂网站| 最新AVzaixian| 人妻精品一区二区| 伊人成人中文字幕久久网| 亚洲狠狠入| 久久91精品国产9丨久久分亭| 自拍二页| 无码丰满熟妇一区二区浪潮AV| 蜜桃视频精品一区二区三区| 精品无码秘 人妻一区二区| 开心激情婷婷| 精品午夜福利| 中文字幕精品人妻丝袜| 国产免费黄色一级大片| 日韩99神马视频播放| 性爱视频啪啪啪啪| 日日夜夜青青草母狗| 天天插夜夜操| 天天搞欧美| 色激情五月天| 翔田千里AV无码秘 三区| 成人无码专区精品视频| 天天操夜夜操| 日本一区99| 国产白嫩漂亮KTV在线| 五十路人妻在线| 色小视频蜜乳| 白丝av| 久久综合女优| 日本网色| 蜜臀99久久精品久久久久| 热天堂一区二区| 国产二区视频在线观看电影| 天天草AV| 超碰人人妻| 男女做爰猛烈动高潮A片免费应用 少妇厨房愉情理伦片bd在线观看 不卡中文字幕aⅴ在线 | 熟女人妻一区二区三区| 日韩精品 资源| 亚洲精品国产无码高清| 老熟女综合网| 啊啊啊好湿久久| 天天日天天干天天整| 97chaopengongkai| 欲香欲色天天天综合和网| 十八禁视频网站| 人妻性爱一区二区| 亚州大图综合色图| 按摩中文字幕| 亚洲色图殴美色图激情乱伦| 99无码狠狠久久| 91少妇通奸网站| 中文字幕二区日韩天堂| 国内毛片热久久思思热| 超碰 欧美| 国产亚洲欧洲在线观看| 国产精品国产拍高清AV| www.色五月| 强奸乱伦AV一天堂网| 4tube欧美女厕所| 伊人伊人LD| 男人天堂2012| 26uuu国产免费观看| 日韩一级性爱无码| 国产精品日日摸天天碰| 综合天天网| 久久国产精品91| 欧美自拍偷拍免费观看| 丰满人妻一区二区三区性色| 一区二区视频在看| 免费97视频| 欧美 传媒 麻豆 日韩 偷拍| 久热伊人| 天天色综合天天操| 国产精品久久久啊| julia国产在线| 加勒比东京热五月天天堂网| 超碰色中文| 欧美日韩超碰在线| 日本在线不卡一二区| 国产女乱淫真高清免费视频| 国产毛片毛片4p懂色| 亚洲男人天堂网站| 国产黑白丝在线| 新视频sss国产| 亚洲欧美经典一区二区 | 九X超碰| 97超碰国产亚洲精品资源| 在线有码中文字幕| 国产真乱mangent| 国产免费黄色一级大片| 久久久九九九| 撸无码不卡免费视频| 妇女乱色二区| 色香综合天天影视综合| 视频在线中文字幕| 久久 久久国内精品亚洲| 91美女色视频亚洲| 日本久久综合| 老女人老91妇女老热女| 国产成人天堂| 色老牛| 亚洲超碰综合网| 亚洲AV不卡在线观看| 久久久一区二区三区麻豆| 欧美亚洲色的图| 九九九九九九精品| 欧美偷拍| 日本熟妇人妻一区二区三区| 中出在线视频| 中日韩久久久免费看| 性欧美999| 骚女天天综合网| 高清国产精品福利网站| 99久久亚洲精品无码毛片潘甜甜| 中文字幕视频二区| 日本精品一区二区三区四区的功能| 色噜噜国产精品视频一区二区| 久操精品| 啊啊啊爽爽| 人人妻碰人人免费| 欧美日韩国产三级黄色| 午夜电影在线观看无码专区| 免费观看啪视频| 性色一线| 国产精品在线网站| 久久精品一区一起草| 人妻精品一区二区| 无码外流操逼视频| 成人免费不卡在线视频| 男人天堂一区二区| 91暧暧| 欧美日日人人天天| 91熟女丨老女人| 99亚洲精品| 久久99午夜精品一区人妻| 久操大香蕉| 97舔舔| 97爱碰| 夜夜躁狠狠躁日日躁av| 久久久久久国产成人| 抽插无码高清一区| 女人一区| 久久久久少妇| 六十路日本| 欧美亚洲高清不卡| 国产成年女黄特黄| 六月色色| 97欧美色| 免费在线黄片视频| 啊啊啊啊啊啊好湿好爽视频| 深夜视频| 色婷婷丁香| 亚洲天堂美臀在线| 男人的天堂午夜av| 日韩成人精品中文字幕| 欧美少妇色综合| 欧美亚洲激情| 国产黄色在线播放观看| 激情五月婷婷综合| 91高潮喷水美女| 四虎影视欧美| 欧美亚洲| 97久久久精品| 久久91| 国产精品成人蜜臀AV在线| 综合日韩激情另类图片| 色淫网站优优视频| 欧美日韩香蕉| 久久日本熟妇熟色高清| 九九热精品| 亚洲 一区二区 自拍| 天天综和| 亚洲丝袜综合| 99久热| 国产一| 欧美人妻精品一区二区| 国产乱弄免费在线视频。| 人人操人人肉久久精品| 9999免费精彩视频| 国产第11页| 一区二区精品日韩欧美在线观看| 久久,精品一二三| 婷婷性网| 国产一区二区三区白丝| 亚洲熟女综合| 国产一区二区二区按摩精品啪视频| 99久久9| 330dv亚洲成年视频网| 亚洲无码偷拍| 青青操97| 84YTCOM性无码| 97色插| 欧洲亚洲人妻无码久久三区四区| 凹凸久久人人| 91影视亚洲| 中文字幕一区二区在线日韩精品| 久超碰在| 亚洲熟女综合网| 免费观看欧美日韩操逼视频| 97伦综合| 正在播放:深夜激情大战,自带黑丝袜全力输出骚穴 | 亚洲第一页综合在线| 亚洲欧美精品福利在线| 极品人妻少妇综合| 99re这里只有精品中心播放 | 美女大乳久久久久久久女人18| 熟女这里只有精品6| 校园春色综合香蕉| 久久HD| 黑人猛交| 乱子伦一区二区三区国产精品| 91干熟女| 精品国产Av无码久久久伦古装| 亚洲男人的天堂一区二区| 亚洲av综合色区图片亚洲| 亚州精品人妻一二三区| 一色网男人的天堂| 亚洲综合另类小说色区亚洲成av人片在www| 欧美 亚洲 综合 制服 另类| 婷婷大香蕉| 亚洲欧洲中文日韩女优乱码| 精品人妻无码一区二区三区不卡-精品人妻无码一区二区...|精品少妇一区二区三 | 精品国产久久乱码| 久操大香蕉手机视频在线看 | 国产精品久久久亚洲一区| 久久69精品久久久久久久| 91操熟女视频| 九九成人精品| 五月色网| 亚洲密乳AV| 亚洲天堂男| 欧美传媒一区| 久久这里都是精品| 人人操人人叉人人插人人| 丰满人妻区一区二区三| 操淫穴亚洲五月丁香| 综合日本女人伊人| 国产精品成人无码av无码免费| 欧美综合网| …中文字幕亚洲乱,97人妻无码费视… | 九久9热| 去干网最新版| 欧美黄色片在线播放| 中日韓欧美高清| 2003天天干夜夜操| 日本女厕偷拍| 99热这里只有精| 成人八戒网站| 国产探花日韩援交| 亚洲 欧美 色图| 91在线丝袜| 欧美性爱网97| 麻豆久久久久久久久丝袜| 99超级碰免费视频| 激情小说成人日本无码一| 欧美高清无码免费视频高清版| 国产97av| 情色五月天就去干| 亚洲视频小说| 91欧美偷拍| 91丨国产丨白浆| 色色操| 日韩精品一区的| 久久久久久精品免费看A级| 欧美在线永久天堂| 3PAV乱伦视频| 亚洲一二三四区| 九九九九精品视频| 欧美图片校园春色| 少好三P| 天海翼久久| 91超碰碰在线| 性色av一区二区| 蜜桃视频一区二区三区在线观看| 殴美性色a级欧美| 久久国产三区| 狂操嫩妻视频一区二区三区| 亚洲色综合| 综合激情五月丁香| 婷婷午夜| 色婷婷丁香五月| 人妻熟女一区在| 五月丁香综合| 亚洲丝袜色| 大香蕉色十月| 亚洲 欧美 中文 日韩超碰 | 大香蕉综合网| 亚洲欧美高清| 97在线视频观看| 无码不卡亚洲成?人片| 襙一襙| 日韩综合成人免费视频| 中文人妻av高清一区| 超碰国产精品久| 亚洲精品第一| 日韩午夜啪啪视频| 一区二区三区精品黑丝白丝酒店对鸡 | 色综合久久av| 操逼操逼逼操操逼91 | 97天天日|