您當前的位置:首頁 > 收藏

大檔案上傳:秒傳、斷點續傳、分片上傳

作者:由 小知 發表于 收藏時間:2021-01-14

前言

檔案上傳是一個老生常談的話題了,在檔案相對比較小的情況下,可以直接把檔案轉化為位元組流上傳到伺服器,但在檔案比較大的情況下,用普通的方式進行上傳,這可不是一個好的辦法,畢竟很少有人會忍受,當檔案上傳到一半中斷後,繼續上傳卻只能重頭開始上傳,這種讓人不爽的體驗。那有沒有比較好的上傳體驗呢,答案有的,就是下邊要介紹的幾種上傳方式

詳細教程

秒傳

1、什麼是秒傳

通俗的說,你把要上傳的東西上傳,伺服器會先做MD5校驗,如果伺服器上有一樣的東西,它就直接給你個新地址,其實你下載的都是伺服器上的同一個檔案,想要不秒傳,其實只要讓MD5改變,就是對檔案本身做一下修改(改名字不行),例如一個

文字檔案

,你多加幾個字,MD5就變了,就不會秒傳了。

2、本文實現的秒傳核心邏輯

a、利用redis的set方法存放檔案上傳狀態,其中key為檔案上傳的md5,value為是否上傳完成的標誌位,

b、當標誌位true為上傳已經完成,此時如果有相同檔案上傳,則進入秒傳邏輯。如果標誌位為false,則說明還沒上傳完成,此時需要在呼叫set的方法,儲存塊號檔案記錄的路徑,其中key為上傳檔案md5加一個固定字首,value為塊號檔案記錄路徑

分片上傳

1.什麼是分片上傳

分片上傳,就是將所要上傳的檔案,按照一定的大小,將整個檔案分隔成多個數據塊(我們稱之為Part)來進行分別上傳,上傳完之後再由

服務端

對所有上傳的檔案進行彙總整合成原始的檔案。

2.分片上傳的場景

1。大檔案上傳

2。網路環境環境不好,存在需要重傳風險的場景

斷點續傳

1、什麼是斷點續傳

斷點續傳是在下載或上傳時,將下載或上傳任務(一個檔案或一個壓縮包)人為的劃分為幾個部分,每一個部分採用一個執行緒進行上傳或下載,如果碰到網路故障,可以從已經上傳或

下載

的部分開始繼續上傳或者下載未完成的部分,而沒有必要從頭開始上傳或者下載。本文的斷點續傳主要是針對斷點上傳場景。

2、應用場景

斷點續傳可以看成是分片上傳的一個衍生,因此可以使用分片上傳的場景,都可以使用斷點續傳。

3、實現斷點續傳的核心邏輯

在分片上傳的過程中,如果因為系統崩潰或者網路中斷等異常因素導致上傳中斷,這時候

客戶端

需要記錄上傳的進度。在之後支援再次上傳時,可以繼續從上次上傳中斷的地方進行繼續上傳。

為了避免客戶端在上傳之後的進度資料被刪除而導致重新開始從頭上傳的問題,服務端也可以提供相應的介面便於客戶端對已經上傳的分片資料進行查詢,從而使客戶端知道已經上傳的分片資料,從而從下一個分片資料開始繼續上傳。

4、實現流程步驟

a、方案一,常規步驟

將需要上傳的檔案按照一定的分割規則,分割成相同大小的資料塊;

初始化一個分片上傳任務,返回本次分片上傳唯一標識;

按照一定的策略(序列或並行)傳送各個分片資料塊;

傳送完成後,服務端根據判斷資料上傳是否完整,如果完整,則進行資料塊合成得到原始檔案。

b、方案二、本文實現的步驟

前端(客戶端)需要根據固定大小對檔案進行分片,請求後端(服務端)時要帶上分片序號和大小

服務端建立

conf檔案

用來記錄分塊位置,conf檔案長度為總分片數,每上傳一個分塊即向conf檔案中寫入一個127,那麼沒上傳的位置就是預設的0,已上傳的就是Byte。MAX_VALUE 127(這步是實現斷點續傳和秒傳的核心步驟)

伺服器按照請求資料中給的分片序號和每片

分塊大小

(分片大小是固定且一樣的)算出開始位置,與讀取到的檔案片段資料,寫入檔案。

5、分片上傳/斷點上傳程式碼實現

a、前端採用百度提供的webuploader的外掛,進行分片。因本文主要介紹服務端程式碼實現,webuploader如何進行分片,具體實現可以檢視如下連結:

http://

fex。baidu。com/webupload

er/getting-started。html

b、後端用兩種方式實現檔案寫入,一種是用RandomAccessFile,如果對RandomAccessFile不熟悉的朋友,可以檢視如下連結:

https://

blog。csdn。net/dimudan20

15/article/details/81910690

另一種是使用MappedByteBuffer,對MappedByteBuffer不熟悉的朋友,可以檢視如下連結進行了解:

https://www。

jianshu。com/p/f90866dcb

ffc

後端進行寫入操作的核心程式碼

a、RandomAccessFile實現方式

@UploadMode

mode

=

UploadModeEnum

RANDOM_ACCESS

@Slf4j

public

class

RandomAccessUploadStrategy

extends

SliceUploadTemplate

{

@Autowired

private

FilePathUtil

filePathUtil

@Value

“${upload。chunkSize}”

private

long

defaultChunkSize

@Override

public

boolean

upload

FileUploadRequestDTO

param

{

RandomAccessFile

accessTmpFile

=

null

try

{

String

uploadDirPath

=

filePathUtil

getPath

param

);

File

tmpFile

=

super

createTmpFile

param

);

accessTmpFile

=

new

RandomAccessFile

tmpFile

“rw”

);

//這個必須與前端設定的值一致

long

chunkSize

=

Objects

isNull

param

getChunkSize

())

defaultChunkSize

*

1024

*

1024

param

getChunkSize

();

long

offset

=

chunkSize

*

param

getChunk

();

//定位到該分片的

偏移量

accessTmpFile

seek

offset

);

//寫入該分片資料

accessTmpFile

write

param

getFile

()。

getBytes

());

boolean

isOk

=

super

checkAndSetUploadProgress

param

uploadDirPath

);

return

isOk

}

catch

IOException

e

{

log

error

e

getMessage

(),

e

);

}

finally

{

FileUtil

close

accessTmpFile

);

}

return

false

}

}

b、MappedByteBuffer實現方式

@UploadMode

mode

=

UploadModeEnum

MAPPED_BYTEBUFFER

@Slf4j

public

class

MappedByteBufferUploadStrategy

extends

SliceUploadTemplate

{

@Autowired

private

FilePathUtil

filePathUtil

@Value

“${upload。chunkSize}”

private

long

defaultChunkSize

@Override

public

boolean

upload

FileUploadRequestDTO

param

{

RandomAccessFile

tempRaf

=

null

FileChannel

fileChannel

=

null

MappedByteBuffer

mappedByteBuffer

=

null

try

{

String

uploadDirPath

=

filePathUtil

getPath

param

);

File

tmpFile

=

super

createTmpFile

param

);

tempRaf

=

new

RandomAccessFile

tmpFile

“rw”

);

fileChannel

=

tempRaf

getChannel

();

long

chunkSize

=

Objects

isNull

param

getChunkSize

())

defaultChunkSize

*

1024

*

1024

param

getChunkSize

();

//寫入該分片資料

long

offset

=

chunkSize

*

param

getChunk

();

byte

[]

fileData

=

param

getFile

()。

getBytes

();

mappedByteBuffer

=

fileChannel

map

FileChannel

MapMode

READ_WRITE

offset

fileData

length

);

mappedByteBuffer

put

fileData

);

boolean

isOk

=

super

checkAndSetUploadProgress

param

uploadDirPath

);

return

isOk

}

catch

IOException

e

{

log

error

e

getMessage

(),

e

);

}

finally

{

FileUtil

freedMappedByteBuffer

mappedByteBuffer

);

FileUtil

close

fileChannel

);

FileUtil

close

tempRaf

);

}

return

false

}

}

c、檔案操作核心模板類程式碼

@Slf4j

public

abstract

class

SliceUploadTemplate

implements

SliceUploadStrategy

{

public

abstract

boolean

upload

FileUploadRequestDTO

param

);

protected

File

createTmpFile

FileUploadRequestDTO

param

{

FilePathUtil

filePathUtil

=

SpringContextHolder

getBean

FilePathUtil

class

);

param

setPath

FileUtil

withoutHeadAndTailDiagonal

param

getPath

()));

String

fileName

=

param

getFile

()。

getOriginalFilename

();

String

uploadDirPath

=

filePathUtil

getPath

param

);

String

tempFileName

=

fileName

+

“_tmp”

File

tmpDir

=

new

File

uploadDirPath

);

File

tmpFile

=

new

File

uploadDirPath

tempFileName

);

if

(!

tmpDir

exists

())

{

tmpDir

mkdirs

();

}

return

tmpFile

}

@Override

public

FileUploadDTO

sliceUpload

FileUploadRequestDTO

param

{

boolean

isOk

=

this

upload

param

);

if

isOk

{

File

tmpFile

=

this

createTmpFile

param

);

FileUploadDTO

fileUploadDTO

=

this

saveAndFileUploadDTO

param

getFile

()。

getOriginalFilename

(),

tmpFile

);

return

fileUploadDTO

}

String

md5

=

FileMD5Util

getFileMD5

param

getFile

());

Map

<

Integer

String

>

map

=

new

HashMap

<>();

map

put

param

getChunk

(),

md5

);

return

FileUploadDTO

builder

()。

chunkMd5Info

map

)。

build

();

}

/**

* 檢查並修改檔案上傳進度

*/

public

boolean

checkAndSetUploadProgress

FileUploadRequestDTO

param

String

uploadDirPath

{

String

fileName

=

param

getFile

()。

getOriginalFilename

();

File

confFile

=

new

File

uploadDirPath

fileName

+

“。conf”

);

byte

isComplete

=

0

RandomAccessFile

accessConfFile

=

null

try

{

accessConfFile

=

new

RandomAccessFile

confFile

“rw”

);

//把該分段標記為 true 表示完成

System

out

println

“set part ”

+

param

getChunk

()

+

“ complete”

);

//建立conf檔案檔案長度為總分片數,每上傳一個分塊即向conf檔案中寫入一個127,那麼沒上傳的位置就是預設0,已上傳的就是Byte。MAX_VALUE 127

accessConfFile

setLength

param

getChunks

());

accessConfFile

seek

param

getChunk

());

accessConfFile

write

Byte

MAX_VALUE

);

//completeList 檢查是否全部完成,如果數組裡是否全部都是127(全部分片都成功上傳)

byte

[]

completeList

=

FileUtils

readFileToByteArray

confFile

);

isComplete

=

Byte

MAX_VALUE

for

int

i

=

0

i

<

completeList

length

&&

isComplete

==

Byte

MAX_VALUE

i

++)

{

//與運算, 如果有部分沒有完成則 isComplete 不是 Byte。MAX_VALUE

isComplete

=

byte

isComplete

&

completeList

i

]);

System

out

println

“check part ”

+

i

+

“ complete?:”

+

completeList

i

]);

}

}

catch

IOException

e

{

log

error

e

getMessage

(),

e

);

}

finally

{

FileUtil

close

accessConfFile

);

}

boolean

isOk

=

setUploadProgress2Redis

param

uploadDirPath

fileName

confFile

isComplete

);

return

isOk

}

/**

* 把上傳進度資訊存進redis

*/

private

boolean

setUploadProgress2Redis

FileUploadRequestDTO

param

String

uploadDirPath

String

fileName

File

confFile

byte

isComplete

{

RedisUtil

redisUtil

=

SpringContextHolder

getBean

RedisUtil

class

);

if

isComplete

==

Byte

MAX_VALUE

{

redisUtil

hset

FileConstant

FILE_UPLOAD_STATUS

param

getMd5

(),

“true”

);

redisUtil

del

FileConstant

FILE_MD5_KEY

+

param

getMd5

());

confFile

delete

();

return

true

}

else

{

if

(!

redisUtil

hHasKey

FileConstant

FILE_UPLOAD_STATUS

param

getMd5

()))

{

redisUtil

hset

FileConstant

FILE_UPLOAD_STATUS

param

getMd5

(),

“false”

);

redisUtil

set

FileConstant

FILE_MD5_KEY

+

param

getMd5

(),

uploadDirPath

+

FileConstant

FILE_SEPARATORCHAR

+

fileName

+

“。conf”

);

}

return

false

}

}

/**

* 儲存檔案操作

*/

public

FileUploadDTO

saveAndFileUploadDTO

String

fileName

File

tmpFile

{

FileUploadDTO

fileUploadDTO

=

null

try

{

fileUploadDTO

=

renameFile

tmpFile

fileName

);

if

fileUploadDTO

isUploadComplete

())

{

System

out

println

“upload complete !!”

+

fileUploadDTO

isUploadComplete

()

+

“ name=”

+

fileName

);

//TODO 儲存檔案資訊到資料庫

}

}

catch

Exception

e

{

log

error

e

getMessage

(),

e

);

}

finally

{

}

return

fileUploadDTO

}

/**

* 檔案重新命名

*

* @param toBeRenamed 將要修改名字的檔案

* @param toFileNewName 新的名字

*/

private

FileUploadDTO

renameFile

File

toBeRenamed

String

toFileNewName

{

//檢查要重新命名的檔案是否存在,是否是檔案

FileUploadDTO

fileUploadDTO

=

new

FileUploadDTO

();

if

(!

toBeRenamed

exists

()

||

toBeRenamed

isDirectory

())

{

log

info

“File does not exist: {}”

toBeRenamed

getName

());

fileUploadDTO

setUploadComplete

false

);

return

fileUploadDTO

}

String

ext

=

FileUtil

getExtension

toFileNewName

);

String

p

=

toBeRenamed

getParent

();

String

filePath

=

p

+

FileConstant

FILE_SEPARATORCHAR

+

toFileNewName

File

newFile

=

new

File

filePath

);

//修改檔名

boolean

uploadFlag

=

toBeRenamed

renameTo

newFile

);

fileUploadDTO

setMtime

DateUtil

getCurrentTimeStamp

());

fileUploadDTO

setUploadComplete

uploadFlag

);

fileUploadDTO

setPath

filePath

);

fileUploadDTO

setSize

newFile

length

());

fileUploadDTO

setFileExt

ext

);

fileUploadDTO

setFileId

toFileNewName

);

return

fileUploadDTO

}

}

總結

在實現分片上傳的過程,需要前端和後端配合,比如前後端的上傳塊號的檔案大小,前後端必須得要一致,否則上傳就會有問題。其次檔案相關操作正常都是要搭建一個

檔案伺服器

的,比如使用fastdfs、hdfs等。

本示例程式碼在電腦配置為4核記憶體8G情況下,上傳24G大小的檔案,上傳時間需要30多分鐘,主要時間耗費在前端的md5值計算,後端寫入的速度還是比較快。如果專案組覺得自建檔案伺服器太花費時間,且專案的需求僅僅只是上傳下載,那麼推薦使用阿里的

oss伺服器

,其介紹可以檢視官網:

https://

help。aliyun。com/product

/31815。html

阿里的oss它本質是一個物件儲存伺服器,而非檔案伺服器,因此如果有涉及到大量刪除或者修改檔案的需求,oss可能就不是一個好的選擇。

文末提供一個oss表單上傳的連結demo,透過oss表單上傳,可以直接從前端把檔案上傳到oss伺服器,把上傳的壓力都推給oss伺服器:

https://www。

cnblogs。com/ossteam/p/4

942227。html

大檔案上傳:秒傳、斷點續傳、分片上傳-Java知音

推薦

強大,10k+點讚的 SpringBoot 後臺管理系統竟然出了詳細教程!

分享一套基於SpringBoot和Vue的企業級中後臺開源專案,程式碼很規範!

能掙錢的,開源 SpringBoot 商城系統,功能超全,超漂亮!

標簽: 上傳  param  分片  檔案  fileUploadDTO