前端開發中總免不了關於文件的上傳、下載需求。下面來總結一下經常使用的方法,歡迎討論和吐槽。javascript
最傳統的文件上傳方法是使用form
表單上傳文件的,只須要把enctype
設置爲 multipart/form-data
。這種方式上傳文件不須要 js ,並且沒有兼容問題,全部瀏覽器都支持,就是體驗不好,致使頁面刷新,頁面其餘數據丟失。前端
<form method="post" action="xxxxx" enctype="multipart/form-data"> 選擇文件:<input type="file" name="file" /> <br /> 標題:<input type="text" name="title" /> <br /> <button type="submit">提交</button> </form>
注意:input
必須設置 name
屬性,不然數據沒法發送vue
這種方法由服務端提供接口,設置相應的請求頭,前端提交 formData
形式的文件數據。java
<input id="uploadFile" type="file" name="file" accept="image/png,image/gif" />
accept
:表示能夠選擇的文件 MIME 類型,多個 MIME 類型用英文逗號分開multiple
:是否能夠選擇多個文件$('#uploadFile').on('change', function (e) { var file = this.files[0] var formData = new FormData() formData.append('file', file) $.ajax({ url: 'xxxx', type: 'post', data: formData, cache: false, contentType: false, processData: false, success: function (res) { // }, }) })
processData
設置爲 false。由於 data 值是 FormData
對象,不須要對數據作處理。cache
設置爲 false,上傳文件不須要緩存。contentType
設置爲 false。有時候咱們上傳的文件可能很大,好比視頻等可能達到 2 個 G,這樣會形成上傳速度太慢,甚至有時候會出現連接超時的狀況。並且有時候服務端會設置文件容許上傳的大小,太大的文件就不容許上傳了。爲解決這個問題,咱們能夠將文件進行分片上傳,每次只上傳很小的一部分 好比 1M。webpack
這樣由於每一個切片是併發上傳的,因此能夠有效地下降上傳時間。下面說一下具體的實現步驟。(PS:這是我司的實現方式,並非惟一方法,且涉及到具體接口的代碼就不貼在這裏了)ios
不管上傳文件信息仍是上傳切片文件,都必需要生成文件和切片的 hash
。最簡單粗暴的 hash
值能夠用文件名字+下標來標識,可是這樣文件名一旦修改就失去了效果,而事實上只要文件內容不變,hash
就不該該變化,因此正確的作法是根據文件內容生成 hash
。我司用的是 spark-md5
庫,在這裏就不一一細說了。web
在文件分片上傳以前須要把整個文件的信息如該文件的總的文件大小、文件名、哈希值等等,主要目的是初始化一個文件分片上傳事件,返回文件 id,用於每一個分片的提交。ajax
getFileId (file) { let vm = this let formData = new FormData() formData.append('file', file) axios({ timeout: 5 * 60 * 1000, headers: { 'Content-Type': 'application/json-', 'x-data': JSON.stringify({ fileName: file.fileName, size: file.size, hash: 'hashxxx', }), }, url: 'xxxxxx', method: 'POST', }) .then((res) => { if (res.code === '200') { return res.data.fileId }) .catch((err) => { console.log(err) }) }
當前端獲取到本地圖片後,利用 Blob.prototype.slice
方法(和數組的 slice
方法類似),將大文件按照沒小片 1M 進行切割,返回原文件的某個切片,再併發將各個分片上傳到服務端。算法
getCkunk (file, fileId) { let vm = this let chunkSize = 1024 * 1024 let totalSize = file.size let count = Math.ceil(totalSize / chunkSize) let chunkArr = [] for (let i = 0; i < count; i++) { if (i === count.length - 1) { chunkArr.push(file.slice(i * chunkSize, totalSize)) } else { chunkArr.push(file.slice(i * chunkSize, (i + 1) * chunkSize)) } for (let index = 0; index < count; index++) { let item = chunkArr[index] this.uploadChunk(item, index, fileId) } }
各個分片上傳到服務端的方法。此處省略 hash 值得獲取方式。npm
ploadChunk(item, index, fileId) { let formData = new FormData() formData.append('file', item) request({ headers: { 'Content-Type': 'application/octet-stream;', 'x-data': JSON.stringify({ fileId: fileId, partId: index + 1, hash: res, }) }, url: 'xxxxx', method: 'POST', data: formData, }) .then((res) => { return res.data.path }) .catch((err) => { console.log(err) }) }
因爲文件比較大,即便是採用分片上傳的方式也是須要必定的時間的,爲了更好的用戶體驗,前端最好是提示上傳的進度。這時候就須要後端在每一個分片的放回結果加上上傳的 100%字段。前端獲取到返回值就改變當前進度。
當最後一個分片上傳完成後,服務端返回文件的 url,前端獲取 url,同時將進度條狀態改變爲 100%。
上面說到的分片上傳,解決了大文件上傳超時和服務器的限制。可是對於更大的文件,上傳並非短期內就上傳完成,甚至有時候會面臨斷網或者手動暫停,難道就要從新將整個文件上傳了,咱們固然不但願。這時候斷點續傳就派上用場了。
下面說一下實現思路。
首先斷點續傳必須是基於分片上傳的基礎上的
文件下載有如下幾種方法
這是最原始的方法,爲一個下載按鈕添加 click 事件,點擊時動態生成一個表單,利用表單提交的功能來實現文件的下載(實際上表單的提交就是發送一個請求)。
function downloadFile(downloadUrl, fileName) { // 建立表單 let form = document.createElement('form') form.method = 'get' form.action = downloadUrl //form.target = '_blank'; // form新開頁面 document.body.appendChild(form) form.submit() document.body.removeChild(form) }
最簡單最直接的方式,實際上跟 a 標籤訪問下載連接同樣
window.open('downloadFile.zip') location.href = 'downloadFile.zip'
缺點
download 屬性是 HTML5 新增的屬性,兼容性能夠了解下 can i use download
。
<a href="xxxx" download>點擊下載</a> <!-- 重命名下載文件 --> <a href="xxxx" download="test">點擊下載</a>
優勢:能解決不能直接下載瀏覽器可瀏覽的文件。
缺點
此方法除了能利用已知文件地址路徑進行下載外,還能經過發送 ajax 請求 api 獲取文件流進行下載。利用 Blob 對象能夠將文件流轉化成 Blob 二進制對象。
進行下載的思路很簡單:發請求獲取二進制數據,轉化爲 Blob 對象,利用 URL.createObjectUrl 生成 url 地址,賦值在 a 標籤的 href 屬性上,結合 download 進行下載。
downdFile (path, name) { const xhr = new XMLHttpRequest(); xhr.open('get', path); xhr.responseType = 'blob'; xhr.send(); xhr.onload = function () { if (this.status === 200 || this.status === 304) { // const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') }); // const url = URL.createObjectURL(blob); const url = URL.createObjectURL(this.response); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } } }
你也許不知道的javascript高級函數
總結javascript處理異步的方法
總結移動端H5開發經常使用技巧(乾貨滿滿哦!)
從零開始構建一個webpack項目
總結幾個webpack打包優化的方法
總結前端性能優化的方法
總結vue知識體系之高級應用篇
總結vue知識體系之實用技巧
幾種常見的JS遞歸算法
封裝一個toast和dialog組件併發布到npm
關注的個人公衆號不按期分享前端知識,與您一塊兒進步!