一、文件上傳幾種方式
- 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() 方法將終止該請求,實現上傳暫停的效果。
繼續:和斷點繼傳類似,先獲取傳輸的列表,然后重新發送未上傳的切片。