根據部門的業務需求,需要在網絡狀態不良的情況下上傳很大的文件(1G+)。
其中會遇到的問題:
1,文件過大,超出服務端的請求大小限制;
2,請求時間過長,請求超時;
3,傳輸中斷,必須重新上傳導致前功盡棄。
解決方案實現思路,拿到文件,保存文件唯一性標識,切割文件、分片上傳、文件MD5驗證、斷點續傳、手動重試上傳。
鑒於過往有使用過webupload文件上傳組件的經驗,於是此次采用的是Plupload作為替換。Plupload是一款由著名的web編輯器TinyMCE團隊開發的上傳組件,簡單易用且功能強大。
Plupload有以下功能和特點
- 擁有多種上傳方式:HTML5、flash、silverlight以及傳統的
<input type=”file” />
。Plupload會自動偵測當前的環境,選擇最合適的上傳方式,並且會優先使用HTML5的方式。所以你完全不用去操心當前的瀏覽器支持哪些上傳方式,Plupload會自動為你選擇最合適的方式。 - 支持以拖拽的方式來選取要上傳的文件
- 支持在前端壓縮圖片,即在圖片文件還未上傳之前就對它進行壓縮
- 可以直接讀取原生的文件數據,這樣的好處就是例如可以在圖片文件還未上傳之前就能把它顯示在頁面上預覽
- 支持把大文件切割成小片進行上傳,因為有些瀏覽器對很大的文件比如幾G的一些文件無法上傳。
環境
- vue2.x
- webpack3.x
- axios
代碼
npm安裝plupload,文件引入組件,
1
2
3
4
5
6
7
8
9
10
11
12
|
<uploader browse_button=
"upload_area"
:max_retries=
"3"
:url=
"action"
:headers=
"headers"
chunk_size=
"10MB"
drop_element=
"upload_area"
@disableBrowse=
"!loading"
:BeforeUpload=
"beforeUpload"
:ChunkUploaded=
"chunkUploaded"
:FilesAdded=
"filesAdded"
:StateChanged=
"stateChanged"
@inputUploader=
"inputUploader"
/>
|
初始化方法filesAdded(),每次上傳前清空隊列的其他文件,保證上傳的一致性。其次對文件類型進行判斷過濾fileType(),文件進入時進行總md5一次fileMd5(),然后進入文件分片chunkCheckStatus(),每個分片都要進行md5並與后台進行校驗fileMd5(),確保文件在中斷后繼續上傳的准確性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
filesAdded (up, files) {
// 刪除上傳隊列中其他文件,只保留最近上傳的文件
let
fileLen = files.length, that =
this
if
(fileLen > 1) {
files = files.splice(0, fileLen - 1)
// 清空上傳隊列
}
files.forEach((f) => {
f.status = -1
that.dataForm.file = f
that.fileType(f.getNative())
if
(that.loading) {
that.computeStatus =
true
that.progress = 0
// 文件分片
let
chunkSize = 2097152,
// Read in chunks of 2MB
chunks = Math.ceil(f.size / chunkSize)
that.fileMd5(f.getNative(), (e, md5) => {
that.dataForm.md5 = md5
if
(that.loading ==
true
) {
that.count = 0
that.chunkCheckStatus(md5, that.dataForm.fileName, (uploader, dataList) => {
that.uploading = uploader
if
(that.uploading ==
true
) {
for
(
let
chunk = 1; chunk <= chunks; chunk++) {
that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
that.fileMd5(chunkFile, (e, blockMd5) => {
that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
})
})
}
}
else
{
// 去重
that.progress = 0
for
(
let
chunk = 1; chunk <= chunks; chunk++) {
let
status = 0
dataList.some((item) => {
if
(item.chunk == chunk) {
status = 1
return
false
}
})
if
(status == 0) {
that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
that.fileMd5(chunkFile, (e, blockMd5) => {
that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
})
})
}
}
}
})
}
})
}
})
}
|
文件md5方法,這里使用了SparkMD5,import SparkMD5 from 'spark-md5'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
fileMd5 (file, callback) {
let
that =
this
var
blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
file = file,
chunkSize = 2097152,
// Read in chunks of 2MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark =
new
SparkMD5.ArrayBuffer(),
fileReader =
new
FileReader()
fileReader.onload =
function
(e) {
console.log(
'read chunk nr'
, currentChunk + 1,
'of'
, chunks)
spark.append(e.target.result)
// Append array buffer
currentChunk++
if
(currentChunk < chunks) {
loadNext()
}
else
{
let
blockMd5 =
''
blockMd5 = spark.end()
callback(
null
, blockMd5)
}
}
fileReader.onerror =
function
() {
callback(
'oops, something went wrong.'
)
}
function
loadNext () {
var
start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
}
loadNext()
}
|
文件分片上傳方法,驗證總分片信息后,把每個分片進行md5加密並上傳校驗,這里有寫進度條相關的控制,不一一展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
chunkCheckStatus (md5, fileName, callback) {
this
.$http({
url:
this
.$http.adornUrl(
'/biz/upload/getFileBlockStatus'
),
method:
'get'
,
params:
this
.$http.adornParams({
md5: md5,
fileName: fileName
})
}).then(({ data }) => {
if
(data && data.code === 0) {
if
(data.list !=
null
) {
this
.uploading =
false
this
.chunkCheckData = []
data.list.map((item, index) => {
if
(item.isUpload ==
true
) {
this
.count++
this
.chunkCheckData.push(item)
}
})
callback(
this
.uploading,
this
.chunkCheckData)
return
}
this
.uploading =
true
callback(
this
.uploading)
}
else
{
this
.$message.error(data.msg)
this
.loading =
false
this
.computeStatus =
false
return
false
}
})
}
|
詳細的配置信息可以參考我寫的這篇文章:http://blog.ncmem.com/wordpress/2019/08/09/vue%e5%a4%a7%e6%96%87%e4%bb%b6%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/