免费国产精品自在自线-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
返回資訊列表
五月婷婷开心色伊人| 丁香九月激情| 亚洲婷婷激情综合激情999精品| 97超级啪啪在线观看| 噜综合| 五月色网| 日韩精品无码99| 一本婷婷丁香久久| 五月激情综合网| 丁香六月啪啪| 国产九月婷婷| 特级西西4444www无码| 五月丁香六月激情综合网| 99久re热视频精品98| 婷婷的久久网站| 亚洲视频99| 激情网站综合五月天| 五月婷婷黄网站大全| 九九热re99re6在线精品| 欧美97色| 91亚洲视频| 天天综合中文| w婷婷五月婷婷w| 97黑人精品区| 婷婷色狠狠| 97超碰人人操| 天天操天天干天天日| 精品婷婷五| 亚洲乱码日产精品BD| 久久黄色片| 色婷久久| 日韩 中文 欧美| 激情九月婷婷| 超碰九色| 久久久久久久人妻| 亚洲欧美999| 中文字幕成人| 91干在线视频| 草久私拍| 超碰国产一区| 精品国产AV色一区二区深夜久久 | www.色9| 亚洲综合网激情小说| 婷婷狠狠狠爱| 99久久終合| 天天操天天操天天操天天操天天操| 国语精品探花| 久久五月天黄色五月天色网址| 激情丁香久久久久久| 久久视频婷婷视频| 六月撸婷婷| www夜夜操wwwcon| 日日干五月天婷婷| 人人操日| 9久久精品| 九九精品少妇| 六月丁香成人| 婷婷伊人綜合| 丁香六月婷婷社区| 五月婷av| 综合婷| 被男人添B超爽视频| 色吧婷婷| 日韩无码专区| 色婷五月天| 人人人操| 色婷婷五月综合| 亚洲视频99| 欧美综合婷婷网| 五月色综合| www、丁香五月天| 玖玖99婷婷| 丁香色色网| 五月天婷婷社区| 久久一热免费视频| 天天摸日日舔狠狠添婷婷婷| AV 3P| 9色免费网| www.99精品日操伊人乱碰在线| 国产乱妇无乱码大黄AA片| 五月天丁香| 婷婷色五月激情| 开心五月婷婷| AV片一区在线观看| 另类图片五月天激情| 91人妻人人操| 五月丁香激情婷婷| 综合色99| 五月丁香婷婷久久| 五月天综合视频网| 久久色亭亭五月天| 伊人网大香| 99热国产在线| 精品久久久999| 久久五月综合| 大操人妻| 精a品a视a频| 91碰超| 久久精品9| 大香蕉丁香婷婷| 亚洲色频| Xx色综合| 婷婷色六月| 欧美色频| 最新高清无码专区| 99视频网址| 中文精品在| 99网址在线看| 久久丁香五月婷婷| 九九热视频这里只有精品| 亚洲综合色棒| 色色色色色色色色网站| 99亚洲精品| 色情网综合| 视频1区2区| 五月丁香啪啪综合网| 日本欧美在线| 五月丁香天堂网婷婷| 五月天色社区| 婷婷在线视频| 久久久WWW| 婷婷丁香一月| 婷婷操逼| 日韩一级一片内射视频4K| 嫩草AV久久伊人妇女超级a| 性色99| 人人干人人看| 中文字幕成人| 九九99视频精品| 亚洲小电影在线观看黄999| 激情五月天婷婷图| 五月天色综合| 婷婷五月欧美AA片免费| 五月丁香网中文字幕| 婷婷色五月综合| 热久久91| 91视频综合网| 久久怕怕视频| AV免费在线网站| 色色操| 啪啪六月婷婷| 一区=区操屄高清大全av| 黄色成人AV在线| 久久久久9999| 亚洲成人一区| 99玖玖视频| 婷婷天天舔| 99热日| 97av在线视频| 91婷婷色五月| 五月丁香色婷婷久久| 天天爽爽日日做做| caop在线视频| 色六月婷婷| 日日操日日射| 牛牛澡牛牛爽| 丁香五月第四色88| 99精品久久| 日韩久久欧亚| 开心五月深爱五月| 91 九色 入口| 色婷婷五月天久久| 色噜久| 2w在线视频| 开心五月激情网| 97干网站| 五月丁香六月激情| 91免费在线视频6| 91久久| 五月丁香婷婷综合视频| 在线观看亚洲视频影院| www.99热视频| 97五月久久丁香婷婷| 久草丁香婷婷1024| 久久久久丁香婷婷五月天| 婷婷成人基地| 深爱五月亚洲| 69精品无码一区二区三区| 人人操9| 影音先锋xfplay资源男人网| 国产国产乱老熟女视频网站97| 久久婷婷热| www.日本91| 123日本不卡在线| 天天干天天操天天上| 99九无网码| 日本久碰| 色色丁香婷婷| 深爱激情九九五月天| 成人精品在线观看| 丁香婷婷色情社区成人小说| 超碰色女| 日日操日日撸| 开心五月婷| 9|人妻人人操| 丁香五月最新地址| 色色综合日韩| 五月婷婷色色| 日韩aaa| 丁香五月婷婷欧美激情-中文天堂最新版在线观看 | 欧美综合激情五月丁香| 久久九九热38| 婷婷五月激情视频| 丁香五月性| 丁香五月婷婷基地| 婷婷伊人网| site:wpjngj.com| 国产国产乱老熟女视频网站97| 99热在线99| 九九九激情网| 婷婷伊人久久| 五月停停丁香| 亚洲av网站| 久久性爱视频| 激情五月婷婷综合| 亚洲激情免费久久| 超碰国产在线播放| 五月丁香激情综合| 无码天天操| 色婷婷AV在线观看| aaa久久| 亚洲AV网址| 五月天激情小说网| 夜夜操天天爽| 99操99| 91操人| 天天综合网网欲色| 久热9热| 日韩一66精品| 色色色1网址| 婷婷五月,偷窥偷拍网| 色五月在线| 天天碰夜夜操| www.婷婷,com| 色欲婷婷夜夜| 色情五月天导航| 婷婷久久久久| 国产色丁香| 日韩成人精品中文字幕电影| 亚洲欧美婷婷五月色综合| 91色涩| 人人草人人舔| 99热这里有精力| Av大香蕉| 久久五月天免费网站| 欧美婷婷五月丁香| 婷婷色五月丁香六月欧美啪| 超碰人人操在线| 日产精品一线二线三线芒果| 五月天色丁香| 先锋av性爱成人电影| 色色五月激情| 怎么样可以看免费的一级av| 91偷拍视频| 日本熟妇人妻在线| 日韩啪啪视频| www。久久久久一b。Cc| 婷婷丁香五月亚洲| 丁香久久五月天视频在线观看 | 日本色色网| 色五月六月| 99热永久在线观看| 在线观看免费狠狠色丁香香综合| 少妇人妻人伦A片| 狠狠五月天| 日本3级片偷拍网站| 在线只有精品| 亚洲va在线∨a天堂va欧美va| 丁香五月性| 伊人激情综合| 欧美三级巜人妻互换| 超碰在线中文字幕| 婷婷五月天第三页| 激情五月天激情综合网| 色婷婷九月综合| 色 免费网站视频| 操逼巨乳91| 色婷婷呢狠禁久禁| 丰满少妇猛烈A片免费看观看| AV成人在线播放| 青青草视频福利| 色情综合网| 亚洲欧洲另类图片| 国产精品第一国产精品| 久久久人妻| 色七七九九| 国产成人99久久亚洲综合精品| 99热在线免费观看精品| 亚洲妇女熟BBW| 国产成人在线精品| 九九激情| 这里只有精彩视频| 五月婷婷之综合激情| 天天干天天操天天干天天操天天干天天操| WWW,激情五月天,COM| 99久精品| 99超级碰碰| 亚洲天堂有码| 六月丁香婷婷综合影院| 五月天丁香啪啪啪啪| 亚洲 在线 另类| 丁香午夜天| 六月婷婷av| 狠狠操狠狠操| 97色在线观看视频| 五月综合激情| 99人人干| 婷婷五月丁香五月| 国产FREESEXVIDEOS性中国| 丁香五月在线| 婷婷深爱五月天| 天天日夜夜拍| 搡BBBB搡BBB搡18| 成人做爰A片免费看网站找不到了 噼里啪啦在线观看免费完整版视频 | 欧美噜噜久久久XXX| 97人人操人人干| 五月婷婷色综图片| 日韩无码成人电影| 极品人妻VIDEOSSS人妻| 思思久久99热只有频精品66| 人妻日日日| 99热这里只有精品8| 日韩视频99| 五月婷婷AV| 99热99色| 日本WwW色偷偷丁香花久久久京东热| 青柠影视免费高清电视剧| 99这里有精品视频| ..真实国产乱子伦毛片 | 青草青草久热这里只有精品| 精品久久人妻热| 五月色婷婷影视在线电影| 亚洲视频在线观看99| 色五月婷婷中文字幕| 婷婷六月丁香开心深深爱| 婷婷色5月天在线。| 人妻久久久久久久久妻久久久久| 久热网站| 欧美熟女99| 色丁香久久| 202丰满熟女妇大| 久久色大香蕉| 五月丁香色婷婷久久| 五月婷婷99热| 99热只有国产在线精品| 99成人| 91超级碰| 久久综合五月| 色色五月天婷婷| 婷婷综合激情| 婷婷狠狠综合网入口| 一区二区三区四区牛| 五月丁香色综合| 婷婷丁香五月综合| 五月婷婷无码| 婷婷五月天久久久| 五月丁香婷婷99| 婷婷精品免费久久| 丁香五月中文字幕久色| 色激情综合| 风流少妇A片一区二区蜜桃| 草草色情综合网| 伊人玖玖婷婷| hd五月婷婷在线| 伊人婷婷五月天| 久综合网| 麻豆雪千夏| 这里只有精品,日韩视频| 色综合天天天天做夜夜| 99成人| 五月婷婷激情网| 丁香婷婷久久综合在线| 婷婷五月天在线综合导航| 五月天激情小说欧美激情| 91精品91久久久中77777| A片试看120分钟做受图片| 婷婷五月天AV在线| anquye五月| 99re热精品在线视频| 九九色99| 丁香婷婷啪啪| 五月婷精品| 99免费视频网| 激情内射人妻1区2区3区| 99九九在线精品热动漫| 婷婷性爱视频在线| 小视频一区| 色色色成人网| 婷婷五月天黄色网址| 99久久成人| 五月婷婷官网色| 激情内射人妻1区2区3区| 97碰超级人人看| 日本成人噜噜| 大伊香蕉精品视频在线| 日韩婷婷五月| 五月丁香婷婷综合| 亚洲乱码w在线观看| 99re思思精品视频在线观看| AV性爱在线| 亚州综合色| 婷婷香五月综合激情| 亚洲综合色色色| 综合色婷婷| 国产另类综合| 婷婷六月丁香五月图区| 丁香婷婷六月天| 五月丁香啪啪综合| 综合网狠狠| 九九热这里只有精品31| 亚洲熟妇AV乱码在线观看| 婷婷在线观看五月天在线视频| 99毛片| 天天射色五月天| 爱婷婷久久视频| 蜜桃人妻无码AV天堂三区| 欧美影院婷婷| 久久久这里有精品| 亚洲人人操| 六月婷婷在线| 9色免费网| 精品无码99| 岛国在线观看91| 五月天婷婷丁香蜜桃91| 日本乱子人伦在线视频| 色99综合视频| 五月丁香六月| 中文不卡一二三区| 五月婷婷 激情按摩| 婷婷五月天日日日干干干| 少妇被躁爽到高潮无码文| 亚洲激情高潮| yjzz亚洲国产| 婷婷五月免费视频| 亚洲操逼片| 亚洲另类电影| 成人必爱视| 色婷婷成人做爰A片免费看网站| peg 2区三区四区的| 五月天综合在线| 五月激情另类| 婷婷五月丁香四射| 亚洲AV网址| 中文字幕成人| 嫩草AV久久伊人妇女超级A| 深爱五月天天| 五月丿香啪啪| 激情五月六月婷婷| 婷婷瑟五月天久久综合| 日本在线观看aaa 99| 色偷偷色婷婷| 99久久9| 婷婷色在线| 婷婷五月丁香成人| 99 福利 导航| 欧美婷婷综合| 婷婷五月天综合色| 久久码久久无清| 天天综合色丁香| www.99在线| 亚洲精品字幕在线观看| 色色色色色色色色网站| 中文字幕AV在线播放| 久9视频| 国际国外精品欧洲南美洲专区无码不卡| 五月激情四射网站| 亚洲第一成人无码A片| 五月花综合网| 4438全国最大视频成人网站在线观看| 日韩高清成人| 婷婷六月色情| 五月丁香婷婷激情四射迷人| 色色A| m色激情网| 99色五月| 色婷婷丁香女女| 九九热re99re6在线精品| 色噜噜狠狠色综合无码久久欧美| 久久大大香| 婷婷久久五月天丁香| 8区视频在线| 中文字幕日产A片在线看| 俺去也五月天婷婷| 欧美狠狠一在草| 亚洲av综合在线| 中文字幕在线资源| 婷婷五月天六点丁香五月| 超碰人人操人人干| 内射爽无广熟女亚洲| www.婷婷.com| 综合狠狠五月婷婷| 99热人人| 婷婷综合偷拍| av免费在线看不卡无毒| 九九久久99| 99综合免费视频| 色一情一乱一乱一区91Av| 538在线精品| 精品99在线| 五月天婷婷丁香视频| 人妻视频在线| 伊人久久大香线蕉亚洲五月天,| 99热在线播放| 久久丁香五月婷婷| 99热都是精品| 五月激情综合深爱| 五月激情综合激情五月| 色一情一乱一乱一区91Av| 精品国婬伦V无码久久久| 六月婷婷激情| 99视频久久| 嫩草乱码一区三区四区| 激情婷婷狠狠干| 大香网伊人久久综合| 综合精品啪啪| 婷婷在线五月综合| 国产乱子轮XXX农村| www色色色com| 久久婷婷网站| 中文AV在线观看| 能直接看的av网站| 婷婷精品性性性性性性性| Se.婷婷五月天| 999婷婷综合| 国产亚洲色婷婷久久99精品9j| 99热99艹在线观看| 久久性爱视频| 九九aV| 日本啪啪天堂| 影音先锋一区| 色婷婷888| 五月丁香偷拍| 俺去也婷婷| 五月深爱网| 国产探花AV在线| 26uuu国产色| 激情六| 日韩另类| 久操大香蕉| 久久久宗合| 丁香五月停停基地| 久9久成人精品视频| 中文字幕无码成人电影| 亚洲热久| 婷婷少妇激情| 91色五月| 99久久亚洲精品视频| 国产婷婷综合在线免费视频| 久久婷婷五月天激情四射| www.综合久久.com| 五月婷婷色影院| 九九婷婷综合| 热99精品视频在线观看| 性小说五月天| 丁香六月爱综合| 天天添天天摸天天天天做| 五月丁香AV、伊人业余、性色熟妇| 亚洲视频色色| 青青草日本亚洲| 影音先锋人妻出差| 天天综合中文| 五月天婷综合| 99精品在这里| 精品人妻伦九区久久AAA片| 国产偷人爽久久久久久老妇APP| 99热在线观看| 91av成人| 伊人9在线| 99久在线视频| 99re在线观看视频| 五月丁香婷婷三级| 99re在线视频| 激情综合五月婷婷| 色色色色丁香| 超碰9在| 999热在线视频| 玖玖99免费视频| 婷婷久久五月| 亚洲婷婷婷| 色久五月天| 九艹在线| 粉嫩AV久久一区二区三区| www.91五月| 一起肏在线视频| 五月激情视频| 五月婷六月天| 97热这里精品在线视频| 天天插天天插| 久久久www| www.色婷婷.com| 变态 另类 在线| 色色色色色色色色色色色色色五月天| 婷婷丁香在线| 欧美综合激情五月天| 婷婷五月花| 婷婷深爱五月天| 秋霞少妇AV网站| 97碰碰在线观看视频| 2014天天爽| 伊人AV五月婷| 婷婷综合网| 久久性爱激情| 丁香五月 综合| 狠狠五月激情丁香六月| 婷婷久久五月天| 精品操逼一区二区| 丁香婷婷五月天色综合| 九九热在线观看视频| 婷色五月天| 色丁香五月婷婷综合久久| 色伊人啪| 色五月综合网| 天天做夜夜爽| 色婷婷亚洲在线观看| 久久九九怡红院| 亚洲这里只有精品| 国产真实乱对白精彩| 狠狠色丁香婷婷久久综合| 丁香五月婷婷社区| 这里只有精品日韩| 99精品无码| 日韩黄在免| 亚洲超碰在线| 激情五月婷婷色播网| 成人精品在线观看| 超碰在线综合| 99热这里只有精品最新| 九九黄色网| 色婷婷偷拍| 丁香五月a| 被强行糟蹋的女人A片| 久久精品99久久久久久| 婷婷五月天综合色| 国产乱子轮XXX农村| 一本久道综合99| 激情5月婷婷狠狠干| 人妻激情在线| 久久全色| 激情色视频| 五月天激情国产综合婷婷婷| 久久综合九九| 婷婷五月天AV| 91碰操| 色五月激情综合| 五月丁香天堂网| 玖玖午夜视频| 99热这里只有精品在线| 超级碰 久久9| 色婷五月| 天天躁日日躁狠狠躁日日躁2022年5月9日 | www.av骚货| 另类在线| 在线观看中文字幕亚洲| 久热免费视频| 亚洲av午夜精品一区二区| 丁香六月天婷婷开心综合| www.婷婷| mmm1717.6dbm人人爱人人操| 这里只有精品免费| 日本色道视频网站| 婷婷久久久| 五月丁香无码| 97婷婷色| 日韩色色网| 婷婷,五月天,丁香,第一| 中文字幕av在线| 色噜噜狠狠色综合成人网| 丁香 亚洲 久久| 色五月婷婷五月天激情综合| 丁香五月激情网| 99五月婷| 天天色图| 婷色五月| 五月丁香六月婷| 久久九九怡红院| 天天日天天操心| 免费国产视频| 色色五月婷| 九九AV| 一区二区三区视频| 五月人妻婷婷| 日韩在线视频9色| 99热在线观看这里只有精品| 综合久久8| 天天影院色| 操97免费超级视频| 夜夜骑夜夜撸| 快乐激情五月色婷婷| 婷婷综合五月| 五月天婷婷开心| 9 7总站超级碰免费视频| www.成人婷婷综合| 亚洲免费av在线| 丁香五月性| 激情五月天视频| 亚洲精品视频在线| 99九九精品视频| 伊人九九九久| 婷婷玖玖五月天| 久久九九@| 久久婷婷五月天蜜桃| 人人爱操| 九九热精品6| 操逼视频网址| 久久婷丁香五月| 丁香五月婷婷无码AV| 超喷97免费在线视频| 思思热久久久在线| 婷婷五月丁香综合激情| 久久久人妻| 亚洲天码视频www蛋播视频| 偷拍91九色| 另类在线| 涩涩涩五月天| 丁香婷婷人妻| 久久99草五月婷婷| 欧美日朝成人| 综合久久五月| 天堂中文国产| 影音先锋91网站在线观看| 天天综合精品| 日韩狠狠色婷婷| 伊人大蕉香| 九九色逼| 最新va在线播放| 麻豆科斗777| 狠狠色婷婷色| 婷婷欧美色| 97AV人人插人人操| 久久草大香蕉| 无码橾| 亚洲成色综合网站免费观看| 久久日韩婷婷五月| 思思热在线| 五月天婷婷免费视频| 亚洲丁香五月天在线视频| 超碰国产在线观看| 日韩人妻AV在线| 激情五月深爱婷婷| 国产精产国品一二三在观看| 舔色婷婷| 丁香婷停五月激情综合深爱| 91九色丨国产丨爆乳| 九九精品9| 任你草| www.99热视频| 日本成人噜噜噜噜噜| 狠狠色97| 五月婷天堂视频| 专区无日本视频高清8| 九九大香蕉黄色影院| 丁香五月在线观看完整版| 99久久久久久www| 午夜微拍福利| 偷偷与邻居做爰完整视频| 999婷婷综合| 毛片色五月| 亚洲激情亚洲激情 | www99精品在线观看| 天天操综合网| 超碰免费人妻| 天天热夜夜操| aaaaaa片| 天天艹天天综合网| 成人综合AV| 国产黄色大片| 综合色图婷婷| 99ER热精品视频| 狠狠色狠狠操| 六月激情婷婷| 97在线碰| 丁香婷婷五月色成人网站| 婷婷五月激情五月激情| 米奇影视五月天| 狠狠擼综合| 婷婷,五月天,丁香,第一| 中文字幕婷婷| 久操激情| 婷婷丁香五月综合| 色情五月天丁香社区| 思思热99热| 婷婷五月亚洲一本在线丁香| 无码色色| 亚州色色色| www.开心激情| 丁香六月天堂| 操逼棍操逼| 五月婷婷丁香在线视频| 久久在线人妻| 天天热夜夜操| 九九机热| 99热成人在线观看| 懂色AⅤ| 69色色视频| 色婷婷五月天小说| 色狠狠综合入口| 五月丁香婷婷啪啪综合网| 日夜夜天天| 99久久婷婷五月天| 人人操人人爱丁香五月| 丁香五月无码| 久久九九热视频| 色噜噜狠噜噜视频| 精品久热69| 久久9视频欧美| 激情视频综合| 久99热| 五月天婷婷激情| 五月丁香大香蕉| 中文毛片无遮挡高潮免费| 思思re最新视频| 热99视频精品在线| 欧美久久久久久久久中文字幕| 亚洲色婷婷久久99精品91| 九九婷婷激情综合网| 色色六月| 99狠狠| 99在线观看| 99亚洲天堂| 婷婷五月天xxx| 婷婷五月天天aV| 久久六月天| 色五月色五天色情网| www.99热这里只有精品| 国产做爰视频免费播放| 大香蕉丁香婷婷| 伊人网啪啪| 五月婷婷色| 九九热区一区二区三区| 五月天婷久精视频| 五月色婷婷亚洲 | 五月草影视| 成人网在线视频| 激情五月婷| 婷婷激情四射| 91碰操| 99热新网址| 丁香六月久久| 丁香五月六月婷婷自拍| 人。妻久久| 丁香婷婷色五月| 夜夜爽天天爽| 夜色五月天| 91肏| 狠狠va| 99精品热| 狠狠色婷婷7777久| 亚洲电影中文字幕| 欧美一级色| 色婷婷狠狠久久综合五月| 国产性爱一级| 午夜天堂一区人妻| 亚洲无aV在线中文字幕| 热的国产,热的综合,热的有码| 99精品免费欧美小视频 | 欧美性交一区二区三区| 日本色频| 亚洲第一第二网站| 99天堂网| 九九九九九九毛片| 天天激情视频| 亚洲成人中心| 丁香婷婷成年| 五月婷婷色| 国产成人99久久亚洲综合精品| 亚洲这里只有精品| 饮料下药迷倒漂亮女同事强干| 亚洲av免费在线| 五月色婷丁香| 国外亚洲成AV人片在线观看| 丁香婷婷91在线观看视频| 日日肏天天操| www.25五月婷婷| 婷婷激情视频| 成人五月天在线视频在线观看| www.色婷婷| 免费黄网不卡AV| 九九AV| 五月天激情国产综合婷婷婷| 激情都市另类| 噼里啪啦完整版中文在线观看| 午夜色丁香| 怡红院院久久| 可以看的av| 超碰av天堂| 狠狠操狠狠干综合| 99色综合| 夜夜爱伊人| 国产精品久久7777777精品无码| 91碰碰| 亚洲AV成人在线| 天天操婷婷| 精品久9| 天天综合干| 久久综合五月| 五月天丁香综合| 亚洲va欧美va天堂v国产综合| 婷婷丁香五月综合| 99啪啪视频| 99热亚洲精品| 色婷婷女优有码五月亭| 伊人狠狠操| 中文字幕无码人妻少妇免费视频| 国产一级片| 日本色99| 先锋资源91| 激情五月天婷婷五月天| avh片在线观看| www.人人操人人看人人想人人摸 人人人人操,COM | 色婷婷成人做爰A片免费看网站| 亚洲国产成人综合| 思思热99在线视频| 伊人久久丁香狠狠婷婷综合香蕉| 久久婷婷五月草视频在线播放| 亚洲午夜AV| 操逼视频一区| 91视频精品99| 天天操中文字幕| 色婷另类| 成人电影在线免费试看| 成人AV中文字幕| 久久五月天网| 婷婷情色激情| www.1024久久| 色在线视频网2025| 色婷婷伊人激情在线观看| 久久性爱网站| 久久大国产香蕉| 操操操av| 国产AV一区二区三区日韩| 婷婷五月天Av| 香蕉久久国产AV一区二区| 六月丁香激情最新更新| 99国产精品久久久久久久久久久| 天天色五月| 亚洲免费综合一区| 五月天免费色| 亚洲中文字幕在线观看| 久久人妻系列| 99情色五月天| 国产密乳av一区二区三区四区| 婷婷的色色五月天| 婷婷五月丁香91| 91精品久久久久久| 9+1视频网址| 天天爽夜夜爽夜夜爽精品| 五月丁香综合激情| 色婷綜合网| 亚洲日本韩国| 激情五月天激情综合网| 大香蕉人人网| 天天天天干| 日本三级黄色大片| 激情五月综合网| 91免费试看| 婷婷欧美| 91操人| 国产AV一区二区三区最新精品| 激情五月色综合国产精品| 久久久久视剧HD| 丁香婷婷色九月| 大香蕉久热| 色综合伊人网| 小视频一区 | 中文精品久久久久人妻不| 亚洲精品白浆高清久久久久久| 精品人妻久久久久久久| 99热亚洲综合| 色婷婷99| 人人舔天天| 久久久久久久久久久44| 激情视频综合| 中文字幕丰满乱孑伦无码专区 | 五月停性愛| 久久人人九| 99亚洲综合| 亚洲成人综合网在线免费观看| 婷婷射综合| 色综合色欲综合天天免费 | 97碰在线视频| 综合色激情| 激情五月深爱五月| 日本123区日韩欧美不卡在线看| 五月天婷婷色| 66成人网| 久久 这里只有精品1| 久草 天堂| 五月丁香婷婷狠狠操| 五月花免费视频| 色播播五月| 丁香五月另类小说在线阅读| 婷婷五月丁香欧洲| 曰韩五月丁香色婷婷无码| 五月丁香婷婷综合网| 五月婷婷六月少妇激情| 美妞av| 99在线视频资源| 麻豆观看夏晴子| 丁香六月啪啪啪| 色五月婷激情| 丁香五月成人| 9久热在线精品| 99熟女| 久久小视频| 欧美天天爽| aaaa.黄| 香蕉久久国产AV一区二区| 能看的av| 天天插天天| 久久这里精彩免费在线观看| 97自拍视频网| 五月天婷婷小说| WWW,五月| 五月激情婷婷开心| 五月天伊人综合| 99热人人艹| 久久伊人五月天| 9l视频自拍9l九色9l成人| 日日操夜夜操中国无码| 五月丁香欧美综合| 夜夜撸天天日| 五月婷婷丁香大陆免费| 丁香婷婷五月激情四射网| 91久久久久久久久18| 大香蕉久久婷婷| 激情色情五月天| 亚洲激情丁香五月天色| √天堂资源在线人妻熟女| 97操碰免费视频| 97碰碰碰免费公开在线视频| 九九色逼| 五月天伊人久久久久| 狠狠色丁婷婷日日,伊人激情综合网| 成人版视频在线观看| 9色免费网| 五月天色综合| 色丁香五月婷婷| 激情网综合| 另类图片 五月激情| 亚洲性受XXXX五月丁香| 热五月婷婷| 免费看片在线观看| 影音先锋男人av资源站| 精品香蕉99久久久久网站| 99这里有精品视频| VA色婷婷| 97久久草草超级碰碰碰| 99精品视频在线观看| 五月丁香操亭亭网| 玖玖资源天天无码| ji'qing'luan'ren'lun| 久草久青福利| 五月天激情综合首页| 99久re热视频精品98| 久久久久亚洲AV成人无码电影| 丁香六月丁香婷婷激情| 国精产品一区一区三区免费视频| 九九热AV| 99久久久99久久91熟女| 五月激情小说| 热久久这里只有精品| 色玖玖| 亭亭五月色男人| 激情AV| 在线视频99| 激情图片久久| www99精品| 可以免费观看的av| 精品人妻伦九区久久AAA片| 久久国产一区二区三区| 不卡的AV网站| 激情av网| 久9精品视频| 99热99ai| 婷婷久久六月费| 热久久99热欧美国产亚洲| 这里只有精品视频在线| 亚洲五月婷婷| 天天色情站| 色色热日| 久久久99视频| 久久黄色免费视频| 亚洲丁香五月| 九月激情综合| www.激情| 天天干天天av天天射| 久久久久婷| 永久的网站AAAA| 99久久99热这里只有精品| 五月天亚洲综合网| 婷婷伊人五月天| 日韩欧美不卡| 日本人人草草| 婷婷色色综合| 日本99视频精品免费播放| 玖玖在线视频福利| 日韩AV色色色| 超碰在线免费9| 91碰碰碰| 午夜福利8055| 99在线精品视频免费| 色婷婷九月| caop在线| 国产成人AV不卡| 任你擦免费视频| 开心五月婷婷六月丁香| 26uuu在线观看| 天天爽天天操| 五月婷婷精品视频| 玖玖视频福利| 婷婷五月天开心激情网| 超碰碰碰碰| 182无码| 日本超碰在线| 色偷偷狠狠| 亚洲1区| 亚洲成人在线电影网站| 久草大| 99热无码| 97人人超| 无码橾| 五月丁香六月情婷婷久久| 日本一级一片免费视频| 久操欧美在线观看97| 五月婷婷偷拍| 婷婷五月天成人基地| 亚洲AV成人在线| 亚洲免费成人电影AV| 婷婷和五月天| 欧美黄色韩日网| 日本精品人妻无码77777| 久久久婷丁香五月天激情综合| 丁香五月婷婷激情123| 色五月成人| 无码人妻一区二区一牛影视| 久久视屏这里只有久久| 狠狠摸狠狠摸| 黄网在线免费观| 九九色色| 天天做天天爽| 九色综合网| 色婷五月婷婷| www.91在线观看| 麻豆忘忧草午夜| 婷婷五月丁香狠狠| 天堂A∨在线| 97艹| 岛国av电影网站| 狠狠操狠狠| 91久久99久久91熟女精品| 色播五月婷婷| 激情五月四色| 99区视频| 亚洲情欲| 99日精品视频| 婷婷伊人网| 久久婷婷五月天丁香| 午夜美女人啪最红院| 亚洲综合在线视频| 婷婷五月激情的图片| 岛国AV网| 久久免费少妇高潮99精品| 欧美日韩大黄| 丁香五月婷婷五月| 日夜操B| 丁香婷婷九月| 亚洲成人网站在线| 思思re99视频在线观看| 激情综合网激情五月婷婷| 人妻内射麻豆视频| 婷婷另类小说| 色色99| 国产无套精品一区二区| 8区视频在线| 夜夜久久综合网| 99人妻碰碰碰久久久久| 天天爽,夜夜爽| 无码人妻一区二区一牛影视| 狠色狠色综合久久| 美女久久婷婷| xfplayav在线| 综合色在线| 色宗合久久五月婷婷| 九月婷婷久久久| 99久久玖玖| 久久人人添人人爽添人人片αV| 婷婷色五月天第7色| 六月激情婷婷| 婷婷免费精品视频| 激情五月天色色网| VA日本视频| 操操啪| 69堂午夜视频最新地址| 1024日韩| 丁香婷五月天| 性生生活大片又黄又| 99精品视频在线| 五月综合激情久久| 日本激情五月天‘| 精品自拍99| 亚洲无码色色| 亚洲一区二区无遮挡A片| 欧美成人日韩| 97操视频| 激情五月婷婷五月| 成人必爱视| 国产SUV精品一区二区883| 99热精这里只有精品| 男女啪啪视频久 9| AAAA网站| 在线观看免费狠狠色丁香香综合 | 伊人色综合久久久| 九热精品| 丁香婷婷大香蕉| 六月丁香综合网| 99碰碰碰| 天天肏高清在线| 六月激情婷婷| 激情五月婷婷| 欧美va亚洲va在线播放| 久久香视频| 欧美精产国品一二三区| www.久久| 性做爰1一7伦| 欧美激情综合色综合色| 伊人玖玖婷婷| 亚洲激情婷婷| 五月丁香好婷婷A片网| www91色网站| 一月婷婷色色| 久久这里只有国产| 婷婷色色综合| 色五月激情五月| 欧美日韩一a.无| 五月丁香六月片| 激情综合五月婷婷丁香| 婷婷色婷婷亚洲成人| 国外亚洲成AV人片在线观看| 成人丁香婷婷| 亚洲综合婷婷| 中文中文在线| 五月婷婷综合丁香视频| 大香蕉伊人久久| 超碰av在| 97热精品| 六月丁香射婷婷欧美色图片| 色约约视频一区二区三区四区五区| 婷婷久久五月天丁香| caopeng超碰| 丁香婷婷综合色五月激情国产基地| 日本啪啪网| 四虎婷婷五月天| 九9九9无码| 激情五月综合| 婷婷五月天开心网| 色色无码日韩| 91主播在线| 操骚货在线| 999热在线视频| 伊人激情| 免费观看高清无码| 直接看的av| 天天舔天天操| 色A网| 久久婷五月综合| 婷婷六月天国产综合|