免费国产精品自在自线-91精品国产色综合久久久浪潮-99热久久免费频精品-国产精品国模在线观看-久久亚洲国产精品成人?V秋霞-久久国产一级A片免费播放-亚洲国产欧洲综合97久久-久久国产白嫩美女呻吟高潮

ARTICLE DETAIL

資訊詳情

深耕商務建站與企業(yè)官網運營的一線實戰(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)實世界里大文件直接傳輸的體驗非常差。瀏覽器里的File對象本質上是一個Blob你把一個幾個GB的文件塞進FormData一個HTTP請求發(fā)出后整個請求體一直占著網絡連接服務器那邊如果用的是Express這類框架默認也會對整個請求做緩沖內存一下就繃緊。更關鍵的是傳輸中間只要有一點抖動TCP連接斷了整個請求就沒了瀏覽器不會因為文件太大就對你手下留情用戶體驗就是“卡在99%然后重來”。還有一個容易被忽略的問題很多網關和Web服務器對單次請求體都有硬性限制Nginx默認的client_max_body_size很小就算你配置了十幾GB也架不住中間機器超時回收。所以“把一個文件變成很多個小文件分別上傳”是繞開這些限制的最實用手段這也是斷點續(xù)傳方案的核心一個文件被切成N片每片都是一個獨立請求失敗了對單一片重試而不是對整個文件重試。1.2 斷點續(xù)傳和普通上傳到底差在哪普通上傳里文件的二進制流從頭傳到尾中間任何一個地方斷掉已經傳出去的字節(jié)就全部作廢。斷點續(xù)傳則圍繞“狀態(tài)”來做文章先記住這個文件的唯一身份再記住哪些分片已經上傳成功下次重新打開頁面或者點擊續(xù)傳時只需要從沒傳過的分片繼續(xù)已經傳過的分片直接跳過。這個“記住”的過程就是后端只認hash和分片序號hash是文件指紋分片序號是位置。把斷點續(xù)傳和完整上傳放在一起對比差異就非常明顯傳輸粒度完整上傳是單一數據流斷點續(xù)傳是一個個獨立分片。失敗成本完整上傳一旦失敗全部重來斷點續(xù)傳只需要重傳失敗的那幾片理想狀態(tài)下接近零成本。可暫停性完整上傳不能中途暫停恢復斷點續(xù)傳隨時可以暫停下次繼續(xù)。秒傳能力如果服務端已經保存過同樣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底層可能仍然引用原文件的內存區(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”到處用。片越小請求數量越多服務端文件句柄和前端Promise對象都會壓滿片越大單請求傳輸時間越長斷點續(xù)傳的“續(xù)”就變得不精細。通常我建議從2MB到10MB之間取值公網環(huán)境取4MB左右比較平衡內網環(huán)境可以放大到10MB請求數量和恢復精度都兼顧了。2.2 用 spark-md5 算出文件唯一指紋分片之后遇到第一個問題怎么讓服務端知道“這兩個文件其實是同一個”最可靠的方案不是文件名因為同名文件內容可以完全不一樣也不是文件大小因為大小相同內容也可能不同而是給文件內容本身算一個摘要也就是hash。前端把整個文件的hash發(fā)給后端后端拿hash作為分片目錄名這樣同一個文件即使你改了文件名再傳也能直接續(xù)上。spark-md5是前端算文件hash最常用的庫它支持增量計算可以邊讀分片邊更新hash不用一口氣把整個文件讀進內存。核心代碼如下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模式的性能更好因為瀏覽器底層可以直接給你二進制內存塊。每次循環(huán)只把當前這一片讀成buffer算完就釋放內存占用量被控制在一個分片大小內。如果文件達到了幾個GB這個計算過程可能要花幾十秒為了不阻塞界面真實項目最好放到Web Worker里跑demo階段為了少繞一圈直接放主線程后面我會專門講怎么處理卡UI的問題。有一點必須提前說清楚hash算出來的值只代表你本地看到的內容不代表傳輸過程中沒有損壞。真正要求嚴謹的場景服務端在合并完成后還要再算一次hash和前端提交的hash比對不一致就說明傳壞了。demo里我會在合并接口里做一次最簡單的數量校驗。2.3 分片上傳并發(fā)控制的實現(xiàn)思路分片準備好了hash也有了是不是直接把所有分片一次性全部發(fā)出去不行。幾百個分片同時發(fā)起請求瀏覽器連接數有限服務器也會被瞬間打掛進度條回升得又慢又不穩(wěn)定。正確做法是控制并發(fā)比如最多同時傳3到5片。并發(fā)控制寫起來一點都不難核心就是一個消費者隊列維護一個游標分給固定數量的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里我把它收成三個接口這也是大多數斷點續(xù)傳服務的最小集合接口方法核心參數職責/api/checkPOSThash查詢服務端已存在哪些分片序號/api/uploadPOSTfile、hash、index上傳單個分片/api/mergePOSThash、name、total通知服務端合并所有分片check接口的作用是“斷點查詢”。用戶打開頁面重新選擇一個文件前端算完hash后先問服務端這個文件傳過嗎傳到了第幾片服務端把已有分片的索引數組返回給前端前端過濾掉這些序號只傳剩下的。這個接口讓“續(xù)傳”真正成立不然每次重開頁面都不知道從哪里繼續(xù)。upload接口接收的就是某一個分片的二進制這里有個重要約定分片必須帶hash和index。hash決定它落到哪個目錄index決定它在合并時的位置。為了簡化設計后端按hash建目錄目錄里的文件名直接就是“index.part”查找已上傳分片就是讀取目錄下有哪些part文件。merge接口是最后一擊前端把所有分片都傳完后通知后端合并。這個接口不能少因為如果每傳一片就直接往最終文件尾部追加網絡亂序會讓你根本無法還原文件正確做法是先落盤成獨立分片最后統(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回調里通過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) // 再放文件斷點記錄不需要引入數據庫因為文件系統(tǒng)本身就是記錄某個hash目錄下有多少個part文件已經自然記錄了上傳進度。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前面必須把文件名里的序號解析成數字再比較。第二大文件合并不要用readFileSync把所有分片同時讀進內存幾個GB的文件可能會把Node進程內存撐爆。正確姿勢是用流式管道或者用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開頭的請求轉發(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 // 網絡失敗時重試一次 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數組并刷新進度這樣即使頁面中途刷新下一次續(xù)傳也能通過check接口拿回這些記錄。另一個細節(jié)是暫停時調用controller.abort讓正在傳輸的axios請求真的中斷而不是只把標志位改成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: 分片數量不完整 }) } 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接口里對分片總數做了一個校驗前端傳過來的total必須等于實際part文件數量否則直接拒絕合并。這是防止“漏傳了幾個分片就去合并”的第一道防線但只靠數量校驗還不夠更好的做法是校驗所有分片大小之和是否等于原始文件大小或者合并后重新計算整文件hash。4.4 完整請求時序與體驗對照跑起來之后一個典型的斷點續(xù)傳請求鏈路是這樣的用戶選擇文件前端切片并計算hash狀態(tài)顯示“正在計算”。前端POST /api/check拿到服務端已存在的分片序號數組。前端過濾出剩余分片以3路并發(fā)逐片POST /api/upload。每傳完一片前端將index加入已上傳集合進度條前進一步。全部傳完后POST /api/merge服務端按index排序合并返回最終文件路徑。第一次全量傳可能要幾十秒。第二次選擇同一個文件再上傳check接口直接返回所有分片序號已存在前端不會發(fā)出任何upload請求直接走merge整個過程在1秒內完成這就是秒傳。中斷的體驗也能復現(xiàn)上傳到一半直接把后端進程殺掉前端會拋出一堆請求失敗然后暫停再重啟后端點續(xù)傳前端重新計算hash后通過check發(fā)現(xiàn)已經上傳了前10片就從第11片開始繼續(xù)傳剩余分片都是秒過。這個過程中真正網絡傳輸的只有中斷后剩余的部分前面的分片一個都沒有重傳。5. 斷點續(xù)傳DEMO里最容易踩的坑我?guī)湍阏砗昧?.1 hash怎么算才不卡UIdemo里我為了可讀性直接把hash計算放在主線程小文件沒問題一旦文件超過1GB你就能明顯看到頁面卡在“計算中”轉圈按鈕點不動。原因很簡單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()會把這一片完整拷進內存4MB一片沒問題但如果你大面積用整文件arrayBuffer幾個GB的文件直接內存溢出。demo里我始終基于切片來讀這一點不要改成file.arrayBuffer()一次性讀取。5.2 分片大小和并發(fā)數到底怎么配分片大小和并發(fā)數是斷點續(xù)傳最影響吞吐的兩個參數它們的合理值取決于你的用戶網絡環(huán)境。我整理了一份經驗值可以直接當初始基準場景推薦分片大小推薦并發(fā)數公網普通寬帶、用戶量大2MB-4MB3內網高速、服務端帶寬充足5MB-10MB5-8手機弱網環(huán)境1MB-2MB2-3公網環(huán)境并發(fā)數別開太大看起來快實際上容易觸發(fā)服務端連接數上限、代理超時一個分片失敗還會帶動整條連接排隊。弱網環(huán)境分片小一點這樣中斷發(fā)生時損失的字節(jié)更少重試速度也更快。這些值可以在后臺做成配置項按文件大小動態(tài)調整比如小于100MB走普通單請求大于100MB再啟用分片。還有一個容易忽略的參數是axios的超時時間。給每個分片請求設置一個合理的timeout比如30秒不要讓一個分片卡在服務端永遠等下去否則所有worker都等在同一片上后續(xù)分片全部排隊表現(xiàn)就是進度條卡住不動。5.3 斷點失效、秒傳失敗、進度不準逐個排查先看最常見的“斷點續(xù)傳不生效”重新打開頁面選擇同一個文件check接口返回的uploaded卻是空數組。這種情況十有八九是hash對不上檢查點有這幾個前端hash計算是否基于原始文件的完整內容有沒有因為切片邊界算錯。后端保存分片目錄時是否真的用了hash而不是用索引值拼錯路徑。同一個文件在前后兩次選擇時File對象的size和lastModified是否一致。如果文件內容沒變但hash不同多半是讀取邏輯里混入了無關字節(jié)。再看“秒傳失敗”所有分片明明都在前端還是把幾百個分片重新傳了一遍。先把check接口返回的數組打印出來如果返回的index是字符串而前端用includes判斷時類型不匹配就會全部判斷成不存在。這是JavaScript弱類型最容易踩的坑之一要么后端返回數字要么前端parseInt之后再去includes。進度不準的問題多數出在進度計算公式上。整個上傳過程其實包含hash計算和上傳兩個階段很多人只算了上傳階段的片數百分比用戶從0%開始等hash算了半天進度一直是0體驗很差。我的習慣是給hash計算分配一個固定小百分比比如5%上傳階段占95%然后總體進度等于hash階段完成后5%再加上uploadedIndexes.length / totalChunkCount * 95。最后一個坑是關于端口和代理的。Vite開發(fā)服務器默認5173后端3000如果跨域或不走代理前端發(fā)出的/api請求會404。我建議demo里統(tǒng)一通過vite的proxy轉發(fā)生產環(huán)境則用nginx把/api反代到后端這是最不容易出問題的部署姿勢。6. 演示之外我把這套方案搬進真實項目的經驗demo寫完之后我在自己維護的一個網盤類小工具里把這套邏輯改了改上線遇到了一些demo里沒有體現(xiàn)的問題分享幾個方向你可以作為下一步擴展的參考。第一是取消和續(xù)傳的狀態(tài)管理。demo里暫停用一個布爾標志位搞定但真實場景用戶可能在上傳中刷新頁面、切走再回來前端需要把上傳任務持久化到localStorage甚至IndexedDB記錄文件hash、分片序號和進度。重新加載后先恢復任務列表再逐個調check接口確定真實進度。這一步不做用戶中途刷新一次前面?zhèn)鞯钠腿皝G了”心智雖然服務端物理上還在但前端不知道從哪里繼續(xù)。第二是Web Worker里跑hash。我在worker里維護了一個“取消計算”的信號主線程傳給worker或者直接調用worker.terminate()用戶取消上傳時能把hash計算立刻停下來。這一改動對體驗的提升非常明顯尤其面對10GB以上的文件hash計算可能要好幾分鐘。第三是服務端的整潔度。文件系統(tǒng)做斷點記錄在單機demo里很舒服但上線后最好把“分片狀態(tài)”抽到Redis緩存文件本體放對象存儲或者分布式文件系統(tǒng)合并操作交給后端異步任務隊列去跑而不是在HTTP請求里同步讀寫大文件。同步讀寫會導致請求一直掛著網關很快超時斷開合并卻還在后臺寫文件返回包用戶根本收不到。如果只是要一個能跑的DEMO照著上面的代碼敲一遍就夠了如果要把斷點續(xù)傳做成生產功能分片存儲、失敗重試、秒傳校驗、權限控制這幾個模塊都值得單獨花時間打磨。我現(xiàn)在回看這個項目最大的收獲不是某個API有多難而是“把大問題拆成小問題、再對小問題分別處理”這個思路在斷點續(xù)傳里體現(xiàn)得尤其明顯。真遇到大文件傳輸需求時先別急著找現(xiàn)成的上傳組件把文件分片、hash指紋、斷點記錄這三件事想清楚代碼自然就出來了。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
97热在线精品| 桃色五月婷婷| 狠狠穞A片一區二區三區| 欧美美女国产日韩一区二区久| 五月天丁香婷婷视频网址| 激情五月天综合网| 欧美草久久五月天91| 99久久玖玖| av九九| 色日本网| 可以直接看的AV网站| 99精品在线播放| www.99操.com| 伊人五月婷| 丁香五月天激情视频| 久久99激情丁香婷婷小说网| 亚州性爱99| 丁香五月天在线观看视频| 国产亚洲99久久精品熟| 日本色色影片| 国产精品久久久爽爽爽麻豆色哟哟 | 欧美日韩国产一区二区| 激情婷婷护士激情| 免费观看日韩成人av| 亚洲激情网| 97视频91| 97干在线视频| 欧美性丁香色色五月天干干| 成年人丁香五月| 久色| 久久五月网| 五月丁香龟婷婷| 色色网站观看| 亚洲婷婷丁香五月视频| WWW.开心五月天.COM| 九九热这里只有精品5| 婷婷丁香激情综合色情| 五月大香蕉| 激情宗合 激情宗合| 99婷婷综合| 亚洲12p| 激情丁香五月| 久久婷婷五月综合色丁香花| 五月天丁香啪啪啪啪| 五月天色小说| 五月婷婷,六月婷婷| 欧美性猛交XXXX乱大交极品| 热日韩欧美| 亚洲中文字幕在线观看| 伊人超碰在线| www久久艹| 狠狠色狠狠爱| 99久久精| 五月婷婷黄色视频| 九九精品网| 丁香婷婷视频一区二区| 欧美婷婷成人| 激情深爱五月天| 美女五月天| 六月丁香五月天| 色五月婷婷老师| 亚洲色色色| 网站免费一站二站| ztEJj| 日日操夜夜爽白洁| 欧美日韩成人在线| 丁香婷婷偷拍| 美女五月天| 激情婷婷五月| 丁香五月天激情综合| 日韩久久色| 久久色五月| 99热精品中文字幕| 碰人人97| 伊人五月天综合网| 天天操夜夜橾| 五月天精品视频| 91久久久久久| 婷婷综合九月| 婷婷丁香一月| 99这里有精品视频| 四LLL少妇BBBB槡BBBB| 丁香五月-激情综合| 五月天激情日色在线| 蜜桃婷婷狠狠久久| 色丁香婷婷美女视频网站| 99热这里只有精品23| 五月婷婷影| 另类小说色婷婷| 99久久性爱| 国产日产亚系列精品版优势| 9操在线| 立川无码av| 五月丁香基地| 一起草AV| a九九热www| 婷婷激情五月天在线视频| 婷婷五月天BBw| 97超碰在线免费观看| 激情综合五月| 天天色综| 丁香婷婷色情社区成人小说| 色色99| 99在线小视频| 六月丁香婷婷开心综合基地| 99热色精品| 免费观看高清无码| 婷婷天天综合| 成人免费120分钟啪啪| 久久9RE热视频精品98| 99热这里只有精品98| 亚洲日日操| 五月天婷婷激情| 9色免费网| 五月天成人网婷婷| 亚洲亚洲人成综合网络| 99色色热| 婷婷综合五月| 色婷婷五月天中文字幕| 99性爱| 国外亚洲成AV人片在线观看| 操笔无码| 97福利视频| 亚洲一区二区无遮挡A片| 婷婷六月婷婷| 国产乱妇无乱码大黄AA片| 玖玖五月丁香| 欧美婷婷日本| 色婷婷欧美| 这里只有精品1| 超碰免费在线| 九九在线视频| 五月色情婷婷| 国产精产国品一二三在观看| 欧美激情 日韩无码 婷婷 五月天| 久久久久久人妻| 69凹凸成人综合网| 五月天堂在线| 日韩AV中文字幕在线| 五月婷婷免费视频| 婷婷的五月天另类视频| 99免费视频在线观看爱| 嫩草哈哈操| 久久五月丁香综合17C| 丁香五月电影| 五月丁香少妇A| 日韩成人电影AV| 91久操| 五月天丁香网站| 色99色| 亚洲精品激情| 91九色 婷婷| 综合色色婷婷| 婷婷午夜| 久久一级AV| 五月丁香在线偷拍视频| 婷色视频| 色五月av| 色区域网站视频| 婷婷97色| 99九九玖玖| 99热综合在线| 狠狠色色| 狠狠色噜噜色狠狠狠综合久久成人波 | 人妻系列久久久久久久久久久| 婷婷九月丁香| 丁香五色月婷婷网| www:99热视频| 久久婷婷五月综合| 天天橾夜夜爽| 97碰碰人人| 九九色综合| 国在线激情网| 色护士综合| 激情五月天社区| 九九精品热播| 麻豆WWWCOM内射软件| 欧美综合五月丁香六月婷| 中文字幕操比影片| 久久婷婷视频| 色色丁香激情五月| 一级黄色影片| 久久码久久无清| 99九九视频精彩在线| 人妻人人操| 欧美成人精品A片免费一区99| 亚洲小说欧美激情| 丁香六月色婷婷| 午夜性做爰电影| 丁香五月综合网亚洲综合欧美狠狠 | 五月婷婷五月天在线 | 色五月丁香婷婷综合| 99亚洲精品综合在线| 五月激情婷婷在线| 26uuu色噜噜精品一区| 久久婷婷伊人| 欧美三级欧美一级| 色五月激情五月| 丁香5月婷婷| 五月天婷婷情色| 天天艹夜夜艹| www.黄色片-久久成人国产精品在线播放-999AV | 五他月天啪啪啪| 男人天堂99| 五月在在观看| 五月婷婷性爱| www久久久久| 婷婷五月天小说| www.99情趣网| 色 五月俺去也| 五月丁香色综合| 被男人添B超爽视频| 噜噜狠狠色综合久| 日本婷色| 日本久久爱| 99爱视频精品| 99视频在线| AV操一操| 婷婷五月在线影院| 亚洲乱码日产精品BD| 五月天婷综合| 五月婷婷激情中文字幕| 天天天干夜夜夜操| 十月色综合| 婷婷伊人无码| 久久99这里只有精品| 五月婷免费视频| 成人综合网站| 亚洲精品字幕在线观看| 99ri视频| 中文字幕欧美日韩VA免费视频| 久久婷婷色色| 婷婷久热| 91超级碰| 久机视频这只有精品| 丁香五月六月婷婷综合| 日韩综合久| 五月丁香做爱视频| www.色擼擼.com| 一二三区视频韩国| 天天拍夜夜爽| 五月婷婷自拍| 五月丁香777| 久久机热思思热| 色婷婷五月婷婷五月婷婷五月| 色婷婷婷av | 97色干| 超碰伊人碰婷婷五月| 黄色av网站在线免费播放| 亚洲中文乱字字幕在线永久| 丁香五月成人| 久久AAAA片一区二区| 久久性视频| 五月天婷婷激情在线色图| 欧美五月婷婷| 在线播放成人网站| 99热国产在线| 久久在线92| 丁香五月综合无码趴趴| 久久九九玖玖| WWW.激情| 一本伊人色婷| 天天色天天日| 久久久精品人妻| 久久66精品| seuuu婷婷| 综合色图婷婷| 五月丁香趴趴| 五月香婷婷| 丁香 婷婷 激情 综合 五月| 婷婷五月婷婷五月天| 婷婷日日天天| 九九热最新视频| 亚洲激情综| 色色色99| 亚洲天堂制| 午夜色丁香| 九热久| www.五月婷婷| 亚洲欧美日韩另类| 99在这里有精品| 色婷婷五月天视频在线| 九九热精品| 先锋资源91| www.99婷婷| 丁香综合网| 天天操天天操天天操天天操天天操 | 色五月天天| 亚洲色色色色| 久久五月综合| 日本在线观看aaa 99| 五月丁香亭亭操逼| 黄页大全十八禁| 久色视频| 亚洲乱码日产精品BD| 一区中文字幕电影| 天天草天天爱| 伊人网碰碰| 丁香五月天社区| 色婷婷呢狠禁久禁| 操人无码| 丁香五月婷婷大香蕉| 开心激情综合| 98永久精品| 色婷婷五月天激情久久| 久久婷婷色色| 五月草视频| 1024日韩| 狠狠色婷婷综合开心影视| 黄色片精品| 99热国产国产| 黄网在线播放| 色月丁| 亚洲五月天第一综合干| 色婷婷五月天成人网| 亚洲精品V天堂中文字幕| 丁香天堂夜| 国产综合激情五月久久| 六月婷婷五月天| 哇嘎成人久久| 小视频久久久aaa| 色丁香五月| 色色丁香色五月| 《》【无码】想被搞到爽AV应募而来的超M素人 西纯子 10musume-011723-01 | 少妇性按摩无码中文A片| 在线观看亚洲AV| 九九热99精品| 欧美大肥婆大肥BBBBB| 亚洲AV无码影院| 涩五月丁香| 久久精彩视频| 第四色婷婷日本| 五月天淫乱视频| 亚洲 在线 另类| 五月婷婷激情性爱| 九热...av| 色吊操色妞| 色高清无码视频| 99视频精品在线| 99热色婷婷| 天天插AV丝袜中| AV中文网| 色婷婷社区| 久久色大香蕉| 天天爽天天摸| 婷婷五月激情视频网| 婷婷五月天亚洲天堂| 免费黄色片子| 97涩涩丁香五月天| 97色射| 五月丁香色五月| 97精品综合| 99热色无码| 久久婷婷精品| 亚洲午夜精品久久久久久人妖| 超碰免费人人| 狠狠操.com| 五月丁香啪啪| 激情五月婷婷综合网| 极品少妇高潮啪啪AV无码| 婷婷激情五月天激情在线| 五月天另类小说| 亚洲第一成人无码A片| 五月天婷婷AV| 久久婷色| 欧美交换配乱吟粗大25P| 久久这里只有精品07 | 婷婷久久五月| 日韩黄在免| 好好干av| 婷婷五月丁香色综合| 五月丁香网站| 五月婷婷啪啪| 99精品偷自拍| 六月婷婷综合| 天天舔天天插天天爱| 激情开心五月天| 桃色激情婷婷伊人网| 精品久久穴| 久久66成人网站| 久久天堂婷婷五月| 日日色综合| 激情综合播播| 亚洲天堂碰碰婷婷| 色色色色色色色色网站| 午夜无码精品色综合久久| 性爱激情久久| 五月丁香激情综合网| 六月丁香成人| 日本三日本三级少妇三级66| 日本五月婷婷| 99热日韩| 九九精品在线视频观看| 婷婷狠狠青青| 色偷偷五月天| 五月天激情www| 久久人人九九| 精品一区久热| 五月丁香啪啪啪| 色五月婷婷五月久久| 激情婷婷视频在线| 激情五月天综合网| www.日本91| 天天 日综合| 成年视频免费观看| 久久久久久久久久久-久五月天婷婷| 夜夜夜夜撸夜夜操| 深爱开心激情网| 亚洲xx网| 亚洲AV成人无码精品| 成人短视频在线| 久热视频这里只有精品68| www.婷婷,com| 亚洲综合色婷婷文学| 色情五月丁香| 久久久久人妻精品| 日日操日日射| 激情综合婷婷五月| 激情综合九月| 99热一本| 香蕉曰比| 国产精品第一国产精品| 亚洲无码色色| 久色激情| 人人摸人人摸| 人人摸人人干人人做| 丁香六月开心| 九九久久五月天综合伊人| 99爱在线| 人人爽网| 成人无码精品1区2区3区免费看| 噜噜色五月| 在热视频精品| 婷婷激情综合网| 五月天丁香六月综合| 狠狠久综合| 人人操9| 色婷婷导航| 国产精品视频免费看| 天天日天天干天天插天天射| 777精品成人a v久久| 激情五月婷婷丁香六月| 五月丁香久久网| 婷五月天| 丁香六月婷婷社区| 99五月婷| 伊人网啪啪| 丁J香六月首页| 精品成人无码A片观看香草视频| 五月丁香六月欧美综合网站| 九九色逼| 99精品热| 国产精品天天狠天天看| 日日影院 | 婷婷精品| 国产精品第一国产精品| 99热在线观看| 亚洲九N| 99精品国产乱码久久久人妻| 亚洲无AV在线中文字幕| 亚洲中文字幕AV在线| 国产精品VIDEOSSEX久久发布| 99久视频| 操人妻AV| 五月天色欧美| 五月丁香网站| 色婷婷丁香五月| 五月丁香啪啪网| 天天爱天天做天天日| 激情综合网五月在线播放| 中文字幕不卡+婷婷五月| 91在线日| 天天狠狠插| 五月婷婷激清网| 久99热| 99热6这里只有精品| 色色哒五月婷婷六月丁香| 日本VA视频| 日本狠狠干| 超碰女人天堂| 青青草国产亚洲精品久久| 99热在线观看| 五月天婷婷久草丁香| 999热成人在线综合网| 婷婷伊人| 精品九九视频| www.五月婷婷.com| 大香蕉五月天婷婷丁香91| 五月婷婷啪啪网| 天天日天天添| 伊人婷婷激情| 欧美α√| 五月天激情婷婷| 丁香五月网络网络| 激情五月丁香激情综合网| 伊人深爱综合| 97婷婷狠狠| 97干在线| 久久艹网| 国产Va视频| 五月婷婷久草在线视频综合| 99在线播放视频| 67194国产| 亚洲AV日韩在线观看| 婷婷五月成人| 青青久久五月天丁香婷婷| 天天天天天天天干| 影音先锋AV资源男人站| 波多野结衣成人作品在线| 人妻久久久久久久| 99精品国产乱码久久久人妻| 思思热在线播放| 影音先锋女人AA鲁色资源| 五月丁香综合啪啪啪啪啪| 久久您您综合网| 九草性爱| 天天日,夜夜爽| 在线另类视频| 香蕉视频性爱BB做爱| 五月丁香 啪啪| 天天操天天日天天爽| www,超碰| 婷婷狠狠青青| 色99亚洲| 日韩成人免费电影| cao久久| 99热20| 性爱视频久久| 96丁香婷婷九月蜜桃综合久久| 五月天色色婷婷| 99热99精品在线观看| 中文字幕在线播放视频| 免费操超碰| 婷婷丁香五月久久| 9l视频自拍9l九色成人| 69精品人人人人人人| 五月天色婷婷图片| JAPANRCEP老熟妇乱子伦视频| 搡BBBB搡BBB搡18| 五月丁香激情综合啪啪| 亚洲色五月天在线| 丁香网站| 九九色色| 九热视频| 丁香花五月天激情| 99人人操人人爱久久久| 久久九九蜜| 五月天婷婷色色| 超碰大香蕉网| AV在线免费网站| 九九精品婷| 米奇影视五月天| 伊人激情AV一区二区三区| 色五月91| 99热最新网址| 久久99综合| 熟女网站久久| 色欲av伊人久久大香线蕉影院| 欧洲激情精品婷婷| 色色五月丁香婷婷综合| 丁香五月Av| ou洲色吧| 日韩五月丁香| 久草九九| 97在线/亚洲| 五月天婷婷色色首页| 国产精品香蕉| 色五月婷婷91在线| 99热综合| 综合网啪啪| 日韩成人AV在线播放| 日本欧特黄色刺激一区影视久精品无码| 这里只有精品视频在线看| 这里只有精品96| 极品另类| 婷婷综合网| 狠狠色五月| 天天色综合网1| seuuu婷婷| 婷婷五月丁香久久| 精品综合网在线| 天天爽天天干天天| pom538精品视频| 欧美在线97| 九月激情网| 免费亚洲婷婷| 中文不卡一二三区| 色综合中文综合网| 五月丁香中文| 福利视频在线播放| 色 五月 天 婷婷 丁香 九月| 丁香五月婷婷网| 精典久久| 综合久久婷婷| 99热这里只有精品18| 91xxxx九色| 五月婷婷啪啪啪| 99 色色吧| 国产成人片| 99热线观看9| 99超级碰免费视频| 五月丁香啪啪啪啪| 久久日韩婷婷五月| 国产色色视频| 欧美成人精品一区二区| 9久久久久| 免费视频无码| 婷婷五月成人| 五月婷婷久久内射| 五月丁香花激情综合网| 丁香五月久久| 九九这里只有精品| 日本天天综合| 91久久综合亚洲噜噜成人在线| 九九热最新视频| 9久热这里只有精品| 99riAv1国产在线观看| www.99热| 99激情网| 色色色色色色色色网站| 狠狠五月激情在线| 日本99婷婷| 丁香六月婷婷综合| 婷婷五月在线视频| 激情五月视频| 美女亚洲五月丁香| 日韩AV在线免费观看| 免费观看欧美成人AA片爱我多深| 亚洲激情综合| 思思久久青草热| 五月天亚洲图片婷婷| 内射 无码 伊人| www.99热这里只有精品| 色爱亚洲| 久久人人人人妻| 久热九九| 99男人天堂| 色婷婷激情Av久久久| 色五月综合网| WWW.17C亚洲精品| 深爱激情五月天婷婷网| 久久九九99| 狠狠爱夜夜| 五月天婷a在线| 婷婷在线播放| 久久香蕉影院| 五月天婷婷基地综合网| 五月丁香亚洲综合| 性天天中文网| www国产亚洲色婷婷com| 99爱在线| 99热99思午夜精品| 五月婷婷六月丁香免费| 啊V视频在线观看| 久久婷婷电影| 色吊丝av中文字幕| 97在线天堂| 五月丁香婷婷欧美色图视频五月丁香777电影 | 99久在线视频| 人橾人| 97碰 在线视频观看| 国产乱子轮XXX农村| 天天操综合网| 99激情视频| 思思热在线观看| 第五色婷婷| 久久精典| 国产欧洲欧洲精品久久| 五月天婷婷在线视频| 久草五月天| 色色丁香| 国产精品久久久久久久久久| 大学生高潮无套内谢视频| 色婷婷影院| 亚洲熟妇AV乱码在线观看| 日韩免费视频| 淫视馆av三区| 色色婷婷色色| 激情丁香六月| 五月丁香| 6080av| 婷婷六月激情丁香| 色综合久久88色综合天天| 久久人妻伊人| 思思热在线| 色婷婷伊人| 色色A| 丁香五月社区| 欧美综合五月天婷婷tin| 激情精品久久| 五月天狠狠色| 9九热视频| 激情综合婷婷| 五月的色婷婷高潮| 超碰AV在线| 国产精品涩涩涩视频网站| 97luluse| 成人网丁香五月| 五月婷婷综合激情网| 日本丁香五月婷婷| 99热99热在线| 亚洲激情在线| 久久六月婷婷| 99在线公开视频| 高清无码.com| 99愛国产| 99久久综合| 中文字幕日本最新乱码视频| 国产4P视频精品五区| 99操逼| 伊人婷婷五月| 超碰9| 专区无日本视频高清8| 变态另类色图 | 性爱激情久久| AV电影在线播放| 在线A色| 婷婷六月综合基地| 亚洲精品字幕在线观看| 91岛国片| 日日婷婷不卡| 狠狠久久婷五月| 久99婷婷色综合| 日本三级中国三级99| 国产又黄又爽又激情不遮挡视频在线观看| 丁香五月成人av| 亚洲激情久久| 波多野结衣AV无码Porn| 玖玖精品资源| 99综合视频| 一级黄色操B| 三区激情四射av| 丁香激情四射| 丁香五月婷婷香| 丁香五月综合激情久久潮喷| 九九热99熟女| 九九色中文| 色五月婷婷丁香国产在线| 91九色在线视频| 丁香五月婷婷色综合| 天搞天天天天天| 丁香六月婷婷综合缴| 色狠狠色噜噜AV天堂五区| 久久久香| 饮料下药迷倒漂亮女同事强干| 免费无码毛片一区二区A片| 丁香五月欧美色综合| 色婷婷裸体色性在线| 五月天婷婷色色| 色色丁香婷婷综合| 五月色丁香| 色情网综合| 婷婷六月丁香色| 五月丁香婷婷欧美| 亚洲在线综合| www.五月天婷婷| 日本全黄一级999| 五月花婷婷丁香| 开心五月激情网| 欧美A级网站| 天天色播| 婷婷五月电影院| 9久热免费视频99| 成人啪啪色婷婷久| 婷婷丁香小说| www.刺激色网站www.| 婷婷丁香色情| 97人人妻人人艹| 婷婷色情小说| 大香蕉丁香五月| 思思热久久爱| 丁香五月激情五月| 五月激情综| 超碰99热在线观看| 久久丁香五月天| 色色色五月| 精品成人a v无码内射| 91制片厂久久久国产电影| 婷婷基地五月色| 666555。COm毛片| 亚洲色欲AAAAAA| 97五月婷| 久久五月天激情| 玖玖玖婷婷婷| 久久色五月天| 亚洲啪视频| 九九热精品| 日韩抽插操逼| 婷婷99视频在线| 国产精品热搜丁香五月婷婷| WWW.久久99| 天堂久久婷婷| 天天爱天天做天天日| www.99色| 丁香婷婷综合激情五月色| 激情婷婷22月间| 99狠狠操一| 丁香五月激情六月欧亚激情综合导航 | 91丨九色丨丰满人妖| 在线中文字幕av| 日韩三级片一区二区| 99噜噜噜在线播放| 色色色图| 丁香激情网| 99无码| 俺也高清无码高清视频| 久人操| 色婷婷狠狠| sewuyuejiqingwang| 激情婷婷丁香色五月| 九九九色综合| 丁香五月激情婷婷激情| 五月婷av| 色色色色综合| 丁香五月六月激情久久| 91精品久久久久久久久久久久| 婷婷五月婷婷| www,av好吊操| 97狠狠色| 麻豆观看夏晴子| 超碰资源在线| 综合网激情五月天| 99偷拍视频在线日本| 五月丁香六月激情欧美综合| www.sezonghe| 色欲天天综合网| 日韩限制级大尺度黑料泄密大尺度视频一区二区在线观看 | 色狠狠综合| 色亚洲欧洲| 色婷婷六月性 | 大香蕉手机视频| 另类五月激情| 色播五月丁香| 婷婷深爱五月丁香| se色99| 操人视频91| 国自产拍偷拍精品啪啪一区二区| 99色综合| 五月婷婷色吧!| 久热成人| 色九九一二| 国产成人精品一区二区三区视频| www久久艹| 午夜激情五月| 影音先锋xfplay资源男人网| 涩涩婷婷五月| 亚洲欧美综合7777色亭亭| 久久99精品久久只有精品| 99热这里只有精品2| 丁香六月婷月91婷月| 超碰高清在线| 有码一区二区三区| 99在线精品免费视频| 五月丁香六月婷| 99热这里只有精品免费观看| 狠狠色情婷婷| 性爱动图国产麻豆一区二区三区| 99伊人性爱在线影院| 色九四色| 亚洲欧洲中文日韩久久AV乱码| 日韩一级片| 99∨VTV| 婷婷五月亚洲一本在线丁香| 五月天色导航| 99精品视频在线6| 色婷五月| 九九色综合网| 久久性刺激| 激情久久网 | 丁香五月婷婷免费视频| 九九热AV| 狠狠色成人影片| 五月婷婷综合激情网| 丁香五月影院| 这里只有精品久久| 婷婷五月天综合在线| 亚洲小视频| 日本成人小说婷婷六月| 丁香亚洲婷婷五月| 五月丁香 久久久| 天天摸,天天爽| 天天日日人| 99国产小视频2013| 色视频2025| 丁香六月婷婷综合色| 99热99ai| 99色色网站| 天天拍夜夜撸| 激情六月五月婷婷综合网| www。88热在线视频免费观看| 99色在线视频观看| 丁香六月激情综合| 蜜乳9188| 婷婷五月情色| 色色网站免费观看| 久久激情五月网| 亚洲五月色| 久久九九热视频| 中文av网| 丁香九九九九| 五月激情站| 激情综合五月| 亚洲欧美综合7777色亭亭| 色天使久久综合| 成人短视频免费| 久9免费视频| 婷婷五月天奸女| 超碰久热| www.五月婷婷久久.com| 丁香五月天天高清在线| 亚洲激情综合| 激情五月天丁香| 亚洲妇女熟BBW| www.玖玖九| www.精品久9| 日本色道视频网站| 五月婷庭丁香在线| 精品人妻久久久久| 久久久99久久| 国产全是老熟女太爽了| 99操网站| 色色爽爽天天| 五月婷婷,六月激情| 99热在线观看免费| 日韩成人AV在线播放| 99爱视频| 日韩av高清| 国产精品久久久久久白浆色欲| 天天搽天天射| 另类综合婷婷五月天欧美视频| 激情九色| 黑人糟蹋人妻HD中文字幕| 久久天堂女人| 婷婷九月色| 成 久久| 日本女人久久| 嫩BBB搡BBBB榛BBBB| 日本色色影院| 高清无码.com| 婷婷婷婷色| 久久这里有精品| 亚洲黄色操逼| 91人妻色色网| 六月丁AV| 97福利视频| 黄色99热| 五月丁香网站在线播放| www久久艹| 不卡在线中文字幕无| 一区二区免费看| xfplayav在线| 九月丁香婷婷| 婷婷性爱综合| 五月丁香六月婷婷亚洲激情综合| 99久精品视频| 大香蕉 婷婷| 日本老女人黄页在线播放| 丁香五月婷综合网| 思思热在线观看| 超碰93在线观看| 99热久草| 婷婷五月花| 九热...av| 91精品综合久久久久久五月丁香| 大香蕉520| 超碰com| 亚洲色激情| 国产人妻777人伦精品HD| 骚五月婷婷| 婷婷.com| 香蕉AV777XXX色综合一区| 五月丁香色色| 深爱激情综合| WWW色色色COM| xxx综合在线| 99无码| 色欲丁香| 亚洲乱码w在线观看| 亚洲123区高清入口| 夜夜噜夜夜奇| 六月激情网| 99re最新地址视频| 一起草Av| 色婷婷久久综| 丁香五月六月综合激情| 六月婷欧美丁香综合| 97色五月天| 操人91| 九九国产视频| 91精品丝袜久久久久久| 成人视频在线免费播放| 欧美色婷婷| 亚洲黄色av网站| 另类五月激情| 麻豆忘忧草午夜| 亚洲无码九九| 午夜]香婷婷深深爱| 青青草视频福利| 欧美三级巜人妻互换| 婷婷国产日本欧美| 在线观看国产高清视频免费网站| 色五月婷婷天天干| 激情综合五月丁香| 琪琪理论片| 99∨VTV| 综合五月亭亭9| 少妇高潮呻吟A片免费看软件| 婷婷日| AV在线不卡播放| 色婷婷色五月综合| 99久久国产综合精品五月天喷水\| 天天拍久久| 九月丁香婷婷基地| 深爱激情69热| 色播五月丁香综合| 婷婷五月天天爽| 五月激情网站| 日韩亚洲视频| 欧美va精品va老师va| 亚洲天堂久久| 丁香六月高清视频| www.com久久久久久久久久久久久久久久久| 人妻久久久久久久久妻久久久久| 久久99久久99精品,久国产,久久精品免费,99久在线,久久久久国产精品免费网站,9 | 亚洲成人免费电影| 狠狠干总合| 一区二区三区四日本| 日本va视频| 成人VAV视频在线观看| 日本狠狠干| 九九热av| 成人片黄网站色大片免费毛片| 天天拍久久| 丁香五月婷婷综合91| 色噜噜狠狠色综合成人99| 丁香5月激情网| 国产精产国品一二三在观看 | 开心五月婷婷婷美女| 99爱在线精品视频免费观看| 色月视频| 激情五月狠狠喔| 人人干99| 青青草a在线| 久久久噜噜噜操操操| 狠狠色97| 婷婷五月丁香综合激情小说| 久草免费福利视频| 亚洲无码11| 婷婷另类开心| 91oumei| 婷婷五月天综合在线| 婷婷五月激情欧美大胆视频| 久9精品| 俺去也婷婷| 人人爱国产| 金桔一区二区ab地址| 最新无毒无码AV| 91成人电影| 国产va在线视频| 人妖色AV色综合| 99色色| 久久精热| 九九九九九无码| 色婷婷综合电影| 色婷婷综合网| 亚洲五月天另类小说图片| 天天人人综合| 中文字幕性爱视频| 色播五月丁香| 丁香五月婷婷亚洲色图| 深爱婷婷色| 人妻久热| 思思干精品| 综合网亚洲| 99热手机在线精品| 日韩激情婷婷五月天| 铁牛TV人妻| 六月丁花香啪啪激情欧美| 91丨九色丨国产打屁股| 五月婷婷色激情| a69在线视频| 曰曰久久| 丁香五夜激情四射夜夜夜| 日本色婷婷| 啪啪99| 天天干天天操| 婷婷五月天激情综合婷婷五月天激情综合| 五月丁香六月婷婷久久| 9久热精品在线视频| 国产激情综合五月久久| 欧美成人色婷婷| 色婷婷色情| 激情五月开心五月在线视频| 久久久jd| 热久久婷婷| 日韩av手机在线观看| 丁香六月综合| 五月婷婷色影院| 国产精品人人妻人人爽| 五月天婷婷导航| 乱女乱妇熟女熟妇综合网站| 久久久久久激情| 激情九色| 色综合久网| 婷婷情色五月| 天天久综合网永久入口18| 天天插天天射| 日本99热| 欧美色色色色色色色| 婷婷五月天激情网| www天天色天天射| 五月婷婷六月基地| 色色色.com| 啪啪干伊人婷婷| mmm1717.6dbm人人爱人人操| 五月丁欧美| 五月天无码| 色狠狠五月天| 九九色逼| 91精品久久久久久77777| 26.uuu丁香五月婷婷| 色婷婷AⅤ| 9婷婷内射| 亚洲欧洲一二| 色色色五月天激情资源| 激情综合五月丁香六月婷婷| 丁香,开心成人,久久| wwwav大香蕉| 色综合久久久无码中文字幕999| 三级片AAA久久久AAA久久久AAA| 成人做爰A片免费看视频| 五月天丁香| 国产精品一区在线观看你懂的| 97色碰| 婷婷久久五月天| 人妻少妇色综合| 亚洲欧洲中文日韩久久AV乱码| 第九色区av天堂| 亚洲国产精品VA在线看黑人| 刘玥av在线| 五月丁香另类网| 婷婷激情啪啪| 色色色地址| 国产亚洲色婷婷久久99精品91| 99热超碰在线| 久久这里只有精品07| 日韩六六久久电影| 久久五月激情| 亚洲色视频| 91九色精品女同系列| 深爱激情综合| 99原创自拍视频在线观看| 狠狠色婷| 亚洲视频国产一区| 69精品人人人人| 99re这里只有精品国产99| 26UUU精品一区二区Com| 九九爱激情| 人人干女人| 五月丁香婷婷综合网| 欧美69久成人做爰视频| www.9色色色| 五月玖玖| 亚洲激情高潮| 五月花在线观看视频| 中文字幕AV在线| 99这里只有精品| 变态另类色图 | 99久久综合网| 五月天婷婷久久视频| 婷婷五月天色色| 草了bav视频在线观看| 99色视| 日木狠狠干| 国产乱妇乱子伦| 偷偷操99| 日本91在线播放| 九九色影视| www.婷婷久久五月天| 色色婷婷综合| 91九色中文字幕女在线观看| 精品久久二6| 午夜做爱影院| 综合激情站| 99操不停| 天天做天天爱天天爽夜夜揉| 丁香五月婷婷成人网| w婷婷五月婷婷w| 亚洲精品第一色色色色色色| 亚洲国产网站| 91九色精品| 夜夜骑夜夜操| 成人AV片播放| 激情婷婷| 色爱五月天| 91久久久久久| 色婷婷综合在线| 色五月丁香总合网| 久草热在线视频| 丁香六月婷婷综合| 日韩av网站在线观看| 天天在线久久综合| 婷婷五月丁香啪啪| 九热网站| 天天五月香欧美| 第五色婷婷| 久久久亚洲精品一区二区三区浴池| 色婷婷五月影院| 99热在线这里只有精品| 99日韩| 久热99久热| 91啪级电影| 日韩无码色色| 五月丁香激情啪啪网| 丁香激情六月天婷婷| www.日日夜夜.com| 亚洲无aV在线中文字幕| 成年人99热| 99成人| 色婷婷av在线| 五月天最新网| 婷婷丁香无码专区| 天天干夜夜操A片| 中文资源在线a| 97在线天堂| 激情五月丁香激情综合网 | 六月丁AV| 99久久99综合| 精品一二三区视频立| 97碰在线视频| 亚洲一区国产传媒| 99综合视频| 色婷婷丁香AV综合| 激情五月六月丁香| 99精品国产在热久久| 亚洲色婷婷色| 六月激情网| 色色色色欧洲| 啊V视频在线观看| 婷婷月综合| 激情久久丁香| 欧美日韩国产一区二区| 五月天色社区| 色色色色色日韩午夜激情| 色六月天| 艳妇野外情欲放荡HD| 丁香婷婷五月天色播| 北京熟妇搡BBBB搡BBBB| 天堂婷婷五月在线| 99在线观看精品视频| 婷婷五月天综合激情| 婷婷激情四射| 九九碰九九爱97| 91久久久久久| 激情五月天久久丁香| 特级西西4444www无码| 99热九九这里只有精品| 综合激情站| 久色视频在线| 丰满人妻一区二区三区| 安息电影在线观看完整版| www.色婷婷.com| 伊人五月人妻精品| 天堂美国久久| 五月婷天天搞视频| 99久在线精品99re5热视频|