一、檔案上傳幾種方式
- form表單上傳
- iframe
- FormData非同步上傳
1、from 表單上傳
首先要知道我們上傳檔案時需要修改form表單的 enctype='multipart/form-data'
產生問題:
form表單提交之後會重新整理頁面
form表單上傳大檔案時,很容易遇見伺服器超時
1.1 普通上傳
<form action="http:localhost:8080/uploadFile" method="POST" enctype="multipart/form-data">
<input type="file" name="myfile">
<input type="submit">
</form>
1.2非同步上傳
方案1:base64上傳
通過canvas講圖片裝成base64,然後在服務端進行解碼。
base64會將原本的體積轉成4/3的體積,so會增大請求體加,浪費頻寬,上傳和解析的時間會明顯增加。
<input type="file" id='file'>
<canvas id='canvas'></canvas>
<img src="" id='target-img'>
<script>
let canvas = document.getElementById("canvas"),
targetImg = document.getElementById('target-img'),
file = document.getElementById('file'),
context = canvas.getContext('2d')
file.onchange = function() {
let URL = window.URL || window.webkitURL
let dataURL = URL.createObjectURL(this.files[0]) // 建立URL物件
let img = new Image()
img.crossOrigin = "anonymous" // 只有伺服器模式開啟, 才有效
img.src = dataURL
img.onload = function() {
URL.revokeObjectURL(this.src) // img載入完成後,主動釋放URL物件
canvas.width = img.width
canvas.height = img.height
context.drawImage(img, 0, 0, img.width, img.height)
let dataBase64Url = canvas.toDataURL('img/png')
targetImg.src = dataBase64Url
}
}
</script>
方案2:二進位制形式
除了進行base64編碼,還可以在前端直接讀取檔案內容後以二進位制格式上傳
關鍵api:
參考
-
FileReader:物件允許Web應用程式非同步讀取儲存在使用者計算機上的檔案(或原始資料緩衝區)的內容,使用 File 或 Blob 物件指定要讀取的檔案或資料。
-
File:物件可以是來自使用者在一個
<input>
元素上選擇檔案後返回的files物件 -
readAsBinaryString: 方法會讀取指定的 Blob 或 File 物件,當讀取完成的時候,readyState 會變成DONE(已完成),並觸發 loadend (en-US) 事件,同時result 屬性將包含所讀取檔案原始二進位制格式
-
-
Blob: 前端的一個專門用於支援檔案操作的二進位制物件
-
ArrayBuffer:前端的一個通用的二進位制緩衝區,類似陣列,但在API和特性上卻有諸多不同
-
Buffer:Node.js提供的一個二進位制緩衝區,常用來處理I/O操作
-
Uint8Array:型別陣列表示的8位無符號整數陣列
二進位制上傳
檔案路徑格式轉二進位制
var reader = new FileReader();//①
reader.readAsBinaryString(file);// 把從input裡讀取的檔案內容,放到fileReader的result欄位裡
reader.onload = function(){
readBinary(this.result) // 讀取result或直接上傳
}
// 讀取二進位制檔案
function readBinary(text){
var data = new ArrayBuffer(text.length);//建立一個長度為text.length的二進位制快取區
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
console.log(ui8a)
}
二進位制下載
在向後端發起請求時,需要在請求頭中加上
responseType: 'blob'
這樣在返回data中可以得到一個瀏覽器可以解析的blob資料
const downURL = window.URL.createObjectURL(new Blob([data]));
// data 為獲取到的二進位制資料
const listNode = document.createElement("a");
// 這裡注意 : 非同源a標籤的download去命名沒有用
listNode.download = '合同公允價錯誤檔案下載.xlsx';
listNode.style.display = "none";
listNode.href = downURL;
2、frame上傳
低版本瀏覽器上,xhr請求不支援formdata上傳,只能form表單上傳。
form表單上傳,出現的問題上文已經提到,會本身進行頁面跳轉,產生原因為target屬性導致
target我們或多或少有些瞭解,a標籤也有改屬性:
_self:預設值,在相同的視窗中開啟響應頁面
_blank:在新視窗開啟
_parent:在父視窗開啟
_top:在最頂層的視窗開啟
實現方案
實現非同步上傳的感覺,自理我們就要用到framename去置頂名字的iframe中開啟,也就是<iframe name='formtarget'></iframe>
,<form target='formtarget'>
,這樣一來返回的資料會被iframe接收,就不會出現重新整理問題,而返回的內容可以通過iframe文字拿到。
問題:預覽圖片只有先傳給後臺,後臺再返回一個線上的地址
<iframe id="iframe1" name="formtarget" style="display: none"></iframe>
<form id="fm1" action="/app04/ajax1/" method="POST" target="formtarget" enctype="multipart/form-data">
<input type="file" name="k3"/>
<input type="submit">
</form>
<script>
file.onchange = function() {
let iframe = document.getElementById('iframe1')
iframe.addEventListener("load", function() {
var content = this.contents().
var data = JSON.parse(content)
})
}
</script>
3、FormData非同步上傳
利用FormData模擬表單資料,通過ajax進行提交,可以更加靈活地傳送Ajax請求。可以使用FormData來模擬表單提交。
let files = e.target.files // 獲取input的file物件
let formData = new FormData();
formData.append('file', file);
axios.post(url, formData);
二、大檔案上傳
在同一個請求中,要上傳大量的資料,導致整個過程會比較漫長,且失敗後需要重頭開始上傳。
大檔案上傳我們需要考慮三個方面:
- 切片:拆分上傳請求
- 斷點續傳
- 顯示上傳進度和暫停上傳
1、切片
識別切片來源
保證切片拼接順序
- 我們一般採用編碼的方式進行上傳,獲取檔案對應的二進位制內容。
- 計算出內容的總大小,根據檔案大小切成對應的分片。
- 上傳時標識出當前檔案,告訴後端上傳到了第幾個(可以用時間戳形式)。
- 不加表示的話後端在追加切片時,無法識別切片順序
- 介面異常的情況下無法正確拼接
實現
根據檔名、檔案長度等基本資訊進行拼接,為了避免多個使用者上傳相同的檔案,可以再額外拼接使用者資訊如uid等保證唯一性
根據檔案的二進位制內容計算檔案的hash,這樣只要檔案內容不一樣,則標識也會不一樣,缺點在於計算量比較大.
將檔案拆分成piece大小的分塊,然後每次請求只需要上傳這一個部分的分塊即可
let file = document.querySelector("[name=file]").files[0];
const LENGTH = 1024 * 1024 * 0.1;
let chunks = sliceFile(file, LENGTH); // 首先拆分切片
chunks.forEach((chunk,index) => {
let fd = new FormData();
fd.append("file", chunk);
// 傳遞context
fd.append("context", file.name + file.length);
// 傳遞切片索引值
fd.append("chunk", index + 1);
upload(fd)
})
function sliceFile(file, piece = 1024 * 1024 * 5) {
let totalSize = file.size; // 檔案總大小
let start = 0; // 每次上傳的開始位元組
let end = start + piece; // 每次上傳的結尾位元組
let chunks = []
while (start < totalSize) {
// 根據長度擷取每次需要上傳的資料
// File物件繼承自Blob物件,因此包含slice方法
let blob = file.slice(start, end);
chunks.push(blob)
start = end;
end = start + piece;
}
return chunks
}
請求
/**
* 檔案上傳
* @param {} params
*/
export function upload (params) {
const data = new FormData();
data.append('file', params.file);
data.append('type', params.type);
return $axios({
method: 'post',
url: "/api/Files/upload",
data: data,
headers: {
'Content-Type': 'multipart/form-data',
}
})
}
2、斷點續傳
我們在上傳或者下載檔案的時候,如果已經進行了一部分,這時候網路故障、頁面關閉的情況下,不需要從頭開始操作,而是從指定位置繼續進行操作,這種處理方式就是所說的“斷點續傳”
斷點:的由來是在下載過程中,將一個下載檔案分成了多個部分,同時進行多個部分一起的下載,當某個時間點,任務被暫停了,此時下載暫停的位置就是斷點了。
續傳:一個任務從暫停到開始時,會從上一次任務暫停處開始(可以每次傳輸成功後加一個表示為告訴前端傳輸進度)。
實現思路:
- 儲存已上傳的切片資訊
- 選擇未上傳的切片進行上傳
- 全部上傳成功後後端進行檔案合併
實現方案:
- 本地儲存:我們可以利用localstorage,cookie等方式儲存在瀏覽器內,這種情況下我們不依賴於後端,直接本地讀取就行。清理了本地檔案,會導致上傳記錄丟失。
- 其實伺服器知道我們已經傳輸到了哪些切片,那些進度,我們通過介面去傳輸為上傳的切片即可。
3、上傳進度和暫停
進度:我們可以利用xhr.upload.onprogress = Function方法做進度的監聽
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var percent = Math.floor( e.loaded / e.total * 100);//進度計算
if(percent == 100){
}else{
}
}
};
暫停:如果該請求已被髮出,XMLHttpRequest.abort() 方法將終止該請求,實現上傳暫停的效果。
繼續:和斷點繼傳類似,先獲取傳輸的列表,然後重新傳送未上傳的切片。