【文件上傳、秒傳、分片上傳、斷點續傳、重傳】

文章目錄

  • 獲取文件對象
  • 文件上傳(秒傳、分片上傳、斷點續傳、重傳)
  • 優化

獲取文件對象

input標簽的onchange方法接收到的參數就是用戶上傳的所有文件

<html lang="en"><head><title>文件上傳</title><style>#inputFile,#inputDirectory {display: none;}#dragarea{width: 100%;height: 100px;border: 2px dashed #ccc;}.dragenter{background-color: #ccc;}</style></head><body><!-- 1. 如何上傳多文件:multiple2. 如何上傳文件夾:為了兼顧各瀏覽器兼容性,需設置三個屬性:webkitdirectory mozdirectory odirectory3. 如何實現拖拽上傳:input默認是有拖拽性質的,但是由于瀏覽器兼容性問題,開發一般不使用,一般使用div阻止默認事件以及通過拖拽api實現4. 如何獲取選擇的所有文件--><div id="dragarea"></div><input id="inputFile" type="file" multiple><!-- 如果不想用input自帶的上傳文件的樣式,可以通過button的click觸發input的點擊事件來上傳文件 --><button id="buttonFile">上傳文件</button><input id="inputDirectory" type="file" multiple webkitdirectory mozdirectory odirectory><button id="buttonDirectory">上傳文件夾</button><ul class="fileList"></ul><script>const inputFile = document.getElementById("inputFile")const buttonFile = document.getElementById("buttonFile")const inputDirectory = document.getElementById("inputDirectory")const buttonDirectory = document.getElementById("buttonDirectory")const dragarea = document.getElementById("dragarea")const fileList = document.getElementById("fileList")const appendFile = (fileList) => {for(const file in fileList){const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)}}const traverseFile = (entry) => {if(entry.isFile){entry.file((file) => {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)})}else if(entry.isDirectory){traverseDirectory(entry)}}const traverseDirectory = (directory) => {const reader = directory.createReader()// 創建讀取器讀取文件夾reader.readEntries((entries) => {for(const entry of entries) {traverseFile(entry)}})}buttonFile.onclick = () => {inputFile.click()}inputFile.onchange = (e) => {const files = e.target.files// 獲得用戶上傳的所有文件appendFile(files)}inputDirectory.onchange = (e) => {console.log(e.target.files)const files = e.target.files// 獲得用戶上傳的所有文件appendFile(files)}buttonDirectory.onclick = () => {inputDirectory.click()}dragarea.ondragenter = (e) => {e.preventDefault();console.log("拖拽進入區域")dragarea.classList.add("dragenter")}dragarea.ondragover = (e) => {e.preventDefault();console.log("拖拽著懸浮在區域上方")dragarea.classList.add("dragenter")}dragarea.ondragleave = (e) => {e.preventDefault();console.log("拖拽離開")dragarea.classList.remove("dragenter")}// 拖拽放開dragarea.ondrop = (e) => {e.preventDefault();dragarea.classList.remove("dragenter")const items = e.dataTransfer.items// 拖拽進來的所有文件for(const item of items){const entry = item.webkitGetAsEntry()traverseFile(entry)}}</script></body>
</html>

文件上傳(秒傳、分片上傳、斷點續傳、重傳)

秒傳:調用后端的接口,將md5值傳過去,后端判斷如果這個md5值對應的文件是否已經合并,如果已經合并,則返回文件上傳成功
分片上傳:每片大小chunk_size為1m,假如文件1.5m,那么會被分成2片,使用file.slice截取[0,1),再截取[1,1.5)
斷點續傳:文件上傳前會調用后端的接口,將md5值傳過去,后端判斷如果這個md5值對應的文件是否已經合并,如果沒有合并,會返回這個md5值已經上傳的切片的索引,前端重新上傳剩余索引的片
并發控制:假如我們把文件切成了100片,如果一下子把這100片全傳給后端,會給后端造成并發壓力,所以在發送前可以在前端進行并發控制一下,我們將所有的請求都放在隊列里,每次從隊列里彈出幾個請求來發送

明明瀏覽器可以控制請求并發,為什么前端還要自己控制并發請求?

  1. 避免瀏覽器并發限制:瀏覽器對同一域名的并發請求數量是有限制的(通常是 6-8 個,具體取決于瀏覽器和協議)。如果前端不控制并發請求,可能會導致大量請求堆積,超出瀏覽器的并發限制,從而阻塞其他重要請求(如關鍵 API 或資源加載),
  2. 提升用戶體驗:如果一次性發送過多請求,可能會導致網絡帶寬被占滿,影響頁面其他資源的加載(如圖片、CSS、JS 等),并且可能會導致部分請求超時或失敗,從而浪費網絡資源和用戶流量。
  3. 錯誤處理和重試機制:手動控制并發可以更好地實現錯誤處理和重試機制。
    例如,某個請求失敗后,可以立即重試,而不是等待所有請求完成后再處理錯誤。
  4. 優先級控制:手動控制并發可以實現請求的優先級管理。例如,某些關鍵請求可以優先發送,而低優先級的請求可以稍后處理。
  5. 兼容性和穩定性:不同瀏覽器對并發請求的處理方式可能不同,手動控制并發可以確保應用在各種瀏覽器中表現一致。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>文件上傳</title><script src="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.js"></script><style>#inputFile,#inputDirectory {display: none;}#dragarea {width: 100%;height: 100px;border: 2px dashed #ccc;}.dragenter {background-color: #ccc;}</style>
</head><body><div class="dragarea"></div><input class="inputFile" type="file" multiple><!-- 如果不想用input自帶的上傳文件的樣式,可以通過button的click觸發input的點擊事件來上傳文件 --><button class="buttonFile">上傳文件</button><input class="inputDirectory" type="file" multiple webkitdirectory mozdirectory odirectory><button class="buttonDirectory">上傳文件夾</button><button class="buttonUpload">點擊上傳</button><ul class="fileListElement"></ul><script>// 文件交互相關const fileList = []const chunk_size = 1 * 1024 * 1024const requestQueue = []const maxRequest = 2// 最大請求數量let currentRequest = 0// 當前請求數const inputFile = document.getElementsByClassName("inputFile")[0]const buttonFile = document.getElementsByClassName("buttonFile")[0]const inputDirectory = document.getElementsByClassName("inputDirectory")[0]const buttonDirectory = document.getElementsByClassName("buttonDirectory")[0]const dragarea = document.getElementsByClassName("dragarea")[0]const fileListElement = document.getElementsByClassName("fileListElement")[0]const buttonUpload = document.getElementsByClassName("buttonUpload")[0]// 將上傳的文件展示在按鈕下方const showFileList = (files) => {for (const file in files) {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileListElement.appendChild(li)fileList.push(file)}}const traverseFile = (entry) => {// 拖拽進來的如果是文件,直接展示在按鈕下方if (entry.isFile) {entry.file((file) => {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)})} else if (entry.isDirectory) {// 拖拽進來的如果是文件夾,讀文件夾,獲得文件夾里面的文件traverseDirectory(entry)}}const traverseDirectory = (directory) => {const reader = directory.createReader()reader.readEntries((entries) => {for (const entry of entries) {traverseFile(entry)}})}buttonFile.onclick = () => {inputFile.click()}inputFile.onchange = (e) => {const files = e.target.files // 獲得用戶上傳的所有文件showFileList(files)}inputDirectory.onchange = (e) => {console.log(e.target.files)const files = e.target.files // 獲得用戶上傳的所有文件showFileList(files)}buttonDirectory.onclick = () => {inputDirectory.click()}dragarea.ondragenter = (e) => {e.preventDefault();console.log("拖拽進入區域")dragarea.classList.add("dragenter")}dragarea.ondragover = (e) => {e.preventDefault();console.log("拖拽著懸浮在區域上方")dragarea.classList.add("dragenter")}dragarea.ondragleave = (e) => {e.preventDefault();console.log("拖拽離開")dragarea.classList.remove("dragenter")}// 拖拽放開dragarea.ondrop = (e) => {e.preventDefault();dragarea.classList.remove("dragenter")const items = e.dataTransfer.itemsfor (const item of items) {const entry = item.webkitGetAsEntry()traverseFile(entry)}}// 文件上傳buttonUpload.onclick = () => {for (const file of fileList) {if (file.size <= chunk_size) {uploadSingleFile(file)} else {uploadLargeFile(file)}}}// 單文件一整個文件上傳// 文件上傳通過formData傳輸,因為formData是前后端都認識的格式,file是只有前端才認識的格式(后端不認識)const uploadSingleFile = (file) => {const formData = new FormData()formData.append("file", file) // 通過append往formData身上添加對象,如果formData身上已有file對象,會覆蓋try {axios.post("http://127.0.0.1:3001/upload", formData, {headers: {"content-type": "multipart/form-data"}})} catch (error) {throw error}}// 大文件上傳const uploadLargeFile = async (file) => {// 創建文件hash。創建整個文件的hash即可,每個片不用創建hash,因為每片是調用后端的方法上傳的,返回成功即上傳成功const md5 = await createFileMd5(file)// 大文件分片const chunksList = createChunkFile(file)// 創建文件分片對象const chunkListObj = createChunkFileObj(chunksList, file, md5)// 將md5值傳給后端接口,判斷文件是否在服務器上存在,如果存在,后端返回isExistObj.isExists為true,則秒傳成功,// 如果不存在,后端會返回給你此md5值上傳了哪些片,已上傳的片的索引放在chunkIds中const isExistObj = await juedgeFileExist(file, md5)if (isExistObj && isExistObj.isExists) {alert('文件已秒傳成功!')return}// 文件上傳await asyncPool(chunkListObj, isExistObj.chunkIds) // chunkIds:后端返回的,已上傳的分片的索引// await Promise.all(promises)concatChunkFile(file, md5)// 文件上傳完畢,調用后端合并文件的接口}// 創建文件的md5值const createFileMd5 = (file) => {return new Promise((resolve, reject) => {const reader = new FileReader()// reader.readAsArrayBuffer(file)讀取完畢后會調用onload,讀取失敗調用onerror,讀取到的內容在e.target.result中reader.onload = (e) => {const md5 = SparkMD5.ArrayBuffer.hash(e.target.result)resolve(md5)}reader.onerror = () => {reject(error)}reader.readAsArrayBuffer(file)})}// 創建文件分片:每片大小chunk_size為1m,假如文件1.5m,那么會被分成2片,使用file.slice截取[0,1),再截取[1,1.5)const createChunkFile = (file) => {let current = 0const chunkList = []while (current < file.size) {chunkList.push(file.slice(current, Math.min(current + chunk_size, file.size)))current += chunk_size}return chunkList}// 創建文件分片對象。將文件的md5、文件名、本片在整個文件中的索引,都傳入這個對象,調用后端接口上傳時會用到的數據都可以封裝進來const createChunkFileObj = (chunkList, file, md5) => {return chunkList.map((chunk, index) => {return {file: chunk,md5,name: file.name,index: index,}})}// 文件分片上傳const uploadChunkFile = (chunkListObj, chunkIds) => {return chunkListObj.filter((item,index) => (!chunkIds.includes(index)))// 過濾掉已經上傳的切片,讓已經上傳的切片沒有下面那個函數.map((chunk, index) => {return () => {const formData = new FormData()formData.append("file", chunk.file, `${chunk.md5}-${chunk.index}`)formData.append("name", chunk.name)formData.append("timestamp", Date.now().toString()) // 防止走緩存try {axios.post("http://127.0.0.1:3001/upload/large", formData, {headers: {"content-type": "multipart/form-data"}})} catch (error) {return Promise.reject(error)throw error}}})}// 判斷文件是否存在const juedgeFileExist = async (file, md5) => {try {const response = await axios.post("http://127.0.0.1:3001/upload/exists", formData, {params: {"name": file.nam,md5,}})return response.data.data} catch (error) {return {}throw error}}// 合并請求const concatChunkFile = (file, md5) => {try {axios.post("http://127.0.0.1:3001/upload/concatFiles", {"name": file.nam,md5,})} catch (error) {throw error}}// 把要發送的函數放在隊列里,每次從頭部取一個函數調用,這樣就可以控制并發數量const asyncPool = (chunkListObj, chunkIds) => {return new Promise((resolve,reject) => {requestQueue.push(...uploadChunkFile(chunkListObj, chunkIds))run(resolve,reject)})}const run = (resolve,reject) => {while(currentRequest < maxRequest && requestQueue.length > 0){const task = requestQueue.shift()currentRequest++task().then().finally(() => {currentRequest--run(resolve,reject)})}if(currentRequest === 0 && requestQueue.length === 0) {resolve()}}</script>
</body></html>

優化

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894925.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894925.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894925.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Office/WPS接入DS等多個AI工具,開啟辦公新模式!

在現代職場中&#xff0c;Office辦公套件已成為工作和學習的必備工具&#xff0c;其功能強大但復雜&#xff0c;熟練掌握需要系統的學習。為了簡化操作&#xff0c;使每個人都能輕松使用各種功能&#xff0c;市場上涌現出各類辦公插件。這些插件不僅提升了用戶體驗&#xff0c;…

FPGA的IP核接口引腳含義-快解

疑問 手冊繁瑣&#xff0c;怎樣快速了解IP核各輸入輸出接口引腳的含義。 答疑 不慌不慌&#xff0c;手冊確實比較詳細但繁瑣&#xff0c;如何快速知曉該部分信息&#xff0c;濤tao道長給你們說&#xff0c;簡單得很&#xff0c;一般新入門的道友有所不知&#xff0c;往往后面…

GnuTLS: 在 pull 函數中出錯。 無法建立 SSL 連接。

提示信息 [root@localhost ~]# wget https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz --2025-02-06 12:45:34-- https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz 正在解析主機 download.docker.com (download.docker.…

Python3+Request+Pytest+Allure+Jenkins 接口自動化測試[手動寫的和AI寫的對比]

我手動寫的參考 總篇:Python3+Request+Pytest+Allure+Jenkins接口自動化框架設計思路_jenkins python3+request-CSDN博客 https://blog.csdn.net/fen_fen/article/details/144269072 下面是AI寫的:Python3+Request+Pytest+Allure+Jenkins 接口自動化測試[AI文章框架] 在軟…

告別手動操作!用Ansible user模塊高效管理 Linux賬戶

在企業運維環境中&#xff0c;服務器的用戶管理是一項基礎但非常重要的任務。比如&#xff0c;當有新員工加入時&#xff0c;我們需要在多臺服務器上為他們創建賬戶并分配合適的權限。而當員工離職或崗位發生變化時&#xff0c;我們也需要迅速禁用或刪除他們的賬戶&#xff0c;…

ADC模數轉換器概念函數及應用

ADC模數轉換器概念函數及應用 文章目錄 ADC模數轉換器概念函數及應用1.ADC簡介2.逐次逼近型ADC2.1逐次逼近型ADC2.2stm32逐次逼近型2.3ADC基本結構2.4十六個通道 3.規則組的4種轉換模式3.1單次轉換&#xff0c;非掃描模式3.2連續轉換&#xff0c;非掃描模式3.3單次轉換&#xf…

探索前端框架的未來:Svelte 的崛起

引言 在前端開發的世界里&#xff0c;框架更新換代的速度仿佛光速。從 jQuery 到 Angular&#xff0c;再到如今大熱的 React 和 Vue&#xff0c;開發者們不斷追逐更輕量、更快、更易于維護的框架。如今&#xff0c;Svelte 正悄然崛起&#xff0c;并引發了關于前端框架未來的熱烈…

DeepSeek在FPGA/IC開發中的創新應用與未來潛力

隨著人工智能技術的飛速發展&#xff0c;以DeepSeek為代表的大語言模型&#xff08;LLM&#xff09;正在逐步滲透到傳統硬件開發領域。在FPGA&#xff08;現場可編程門陣列&#xff09;和IC&#xff08;集成電路&#xff09;開發這一技術密集型行業中&#xff0c;DeepSeek憑借其…

數據結構在 Web 開發中的重要性與應用

數據結構是 Web 開發的基石&#xff0c;直接關系到應用程序的效率、可擴展性和可維護性。 根據實際需求選擇合適的數據結構&#xff0c;能夠有效優化性能、簡化代碼&#xff0c;并提升用戶體驗。 本文將深入探討 PHP 和 Laravel 中的常用數據結構&#xff0c;并結合實際案例&am…

20240824 美團 筆試

文章目錄 1、單選題1.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.171.181.191.202、編程題2.12.2崗位:硬件開發工程師(嵌入式系統軟件開發方向) 題型:20 道單選題,2 道編程題題 1、單選題 1.1 C 語言中,如果輸入整數 v 是 2 的冪,下面表達式中哪個會返…

【Elasticsearch】nested聚合

在 Elasticsearch 中&#xff0c;嵌套聚合&#xff08;nestedaggregation&#xff09;的語法形式用于對嵌套字段&#xff08;nestedfields&#xff09;進行聚合操作。嵌套字段是 Elasticsearch 中的一種特殊字段類型&#xff0c;用于存儲數組中的對象&#xff0c;這些對象需要獨…

【Uniapp-Vue3】創建DB schema數據表結構

右鍵uniCloud文件下的database文件&#xff0c;點擊“新建DB schema”&#xff0c;選擇模板&#xff0c;修改文件名&#xff0c;點擊“創建” 創建完成后會出現對應的文件&#xff0c;進入該文件進行配置 對文件中的必填選項&#xff0c;用戶權限&#xff0c;字段進行配置 其…

解決react中函數式組件usestate異步更新

問題&#xff1a;在點擊modal組件確認后 調用后端接口&#xff0c;使用setstateone&#xff08;false&#xff09;使modal組件關閉&#xff0c;但是設置后關閉不了&#xff0c;在設置setstateone&#xff08;false&#xff09;前后打印出了對應的stateone都為true&#xff0c;但…

OpenAI 實戰進階教程 - 第六節: OpenAI 與爬蟲集成實現任務自動化

爬蟲與 OpenAI 模型結合&#xff0c;不僅能高效地抓取并分析海量數據&#xff0c;還能通過 NLP 技術生成洞察、摘要&#xff0c;極大提高業務效率。以下是一些實際工作中具有較高價值的應用案例&#xff1a; 1. 電商價格監控與智能分析 應用場景&#xff1a; 電商企業需要監控…

BFS算法篇——廣度優先搜索,探索未知的旅程(上)

文章目錄 前言一、BFS的思路二、BFS的C語言實現1. 圖的表示2. BFS的實現 三、代碼解析四、輸出結果五、總結 前言 廣度優先搜索&#xff08;BFS&#xff09;是一種廣泛應用于圖論中的算法&#xff0c;常用于尋找最短路徑、圖的遍歷等問題。與深度優先搜索&#xff08;DFS&…

解決使用python提取word文檔中所有的圖片時圖片丟失的問題

python解析word文檔&#xff0c;提取文檔中所有的圖片并保存&#xff0c;并將原圖位置用占位符替換。 問題描述 利用python-dox庫解析word文檔&#xff0c;并提取里面的所有圖片時發現會出現一摸一樣的圖片只解析一次&#xff0c;導致圖片丟失&#xff0c;數量不對的情況。 …

Swipe橫滑與SwipeItem自定義橫滑相互影響

背景 vue項目&#xff0c;H5頁面&#xff0c;使用vant的組件庫輪播組件<Swipe>&#xff0c;UI交互要求&#xff0c;在每個SwipeItem中有內容&#xff0c;可自橫滑&#xff0c;查看列表內容 核心代碼 <template><Swipeclass"my_swipe":autoplay&quo…

3. 【.NET Aspire 從入門到實戰】--理論入門與環境搭建--環境搭建

構建現代云原生應用程序時&#xff0c;開發環境的搭建至關重要。NET Aspire 作為一款專為云原生應用設計的開發框架&#xff0c;提供了一整套工具、模板和集成包&#xff0c;旨在簡化分布式系統的構建和管理。開始項目初始化之前&#xff0c;確保開發環境的正確配置是成功的第一…

藍耘智算平臺使用DeepSeek教程

目錄 一.平臺架構與技術特點 二、DeepSeek R1模型介紹與優勢 DeepSeek R1 模型簡介 DeepSeek R1 模型優勢 三.藍耘智算平臺使用DeepSeek教程 展望未來 耘元生代智算云是藍耘科技推出的一款智算云平臺有著以下特點&#xff1a; 一.平臺架構與技術特點 基于 Kubernetes 原…

.net的一些知識點6

1.寫個Lazy<T>的單例模式 public class SingleInstance{private static readonly Lazy<SingleInstance> instance new Lazy<SingleInstance>(() > new SingleInstance());private SingleInstance(){}public static SingleInstance Instace > instance…