O upload de arquivos grandes, como vídeos, tem muitas desvantagens:
1. Pode haver um limite de tempo de solicitação definido em segundo plano. Se for muito longo, o upload falhará.
2.NGINX pode ter definido um limite máximo para upload de arquivos, causando falha
vantagem:
1. Se o arquivo for muito grande, o upload em partes pode acelerar o upload e melhorar a experiência do usuário.
2. Capaz de retomar o upload em pontos de interrupção. Se o último upload falhar ou sair no meio do caminho, você não precisará recomeçar a partir do próximo upload.
3. Os arquivos que foram carregados são carregados diretamente e instantaneamente com base na consulta HASH.
Processo de implementação
Apenas o código principal é postado aqui
estrutura de dados:
const fileInfoRef = useRef<FileInfoType>({
HASH: '', // 生成的文件hash值
fileSuffix: '', // 文件后缀
alreadyUploadedHashes: [], // 服务器存在的切片hash
uploadedCount: 0, // 上传成功切片个数
url: '', // 上传的url
chunks: [], // 所有切片集合
file: null, // 当前上传的文件
totalSize: 0, // 总大小
currentChunk: null, // 当前上传的切片
})
1. O front-end usa sparkmd5 para gerar um identificador exclusivo HASH baseado no arquivo. Primeiro converta o arquivo em um ArrayBuffer e depois gere o HASH.
fio adicionar spark-md5
export const useFileToArrayBuffer =
() =>
(file: File): Promise<ArrayBuffer> =>
new Promise((resolve, reject) => {
try {
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onload = (ev: ProgressEvent<FileReader>) => {
resolve(ev.target!.result as ArrayBuffer)
}
} catch (error) {
reject(error)
}
})
// 将文件转arraybuffer
const arrayBuffer = await fileToArrayBuffer(file)
// 生成文件的HASH值
const spark = new SparkMD5.ArrayBuffer()
spark.append(arrayBuffer)
fileInfoRef.current.HASH = spark.end()
fileInfoRef.current.fileSuffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)![1]
2. Fatiamento de arquivo
As fatias de arquivo têm um tamanho fixo e um número fixo que pode ser definido dinamicamente de acordo com o tamanho do arquivo.
Use o método Blob.prototype.slice para fatiar o arquivo
Use HASH_chunkIndex.fileSuffix como nome do arquivo
// 文件切片
function slice(file: File) {
fileInfoRef.current.file = file
let chunkIndex = 0,
singleSize = file.size > 1024 * 1024 * 5 ? 1024 * 1024 * 5 : file.size, // 默认一个切片,超过5M的话5M一个切片
splitCount = Math.ceil(file.size / singleSize), // 默认按照固定大小切 向上取整多切一个保证文件完整不丢字节
chunks = []
// 如果固定大小数量超出了最大切片数量 则按照固定数量切
if (splitCount > 100) {
splitCount = 100
singleSize = Math.ceil(file.size / 100)
}
while (chunkIndex < splitCount) {
// 切好和文件名存起来 后续请求上传的参数
chunks.push({
file: file.slice(chunkIndex * singleSize, ++chunkIndex * singleSize),
filename: `${fileInfoRef.current.HASH}_${chunkIndex}.${fileInfoRef.current.fileSuffix}`,
})
}
fileInfoRef.current.chunks = chunks
}
3. Carregar fatias
Percorra todas as fatias para fazer upload
async function uploadChunks() {
// 上传每一个切片
for (const chunk of fileInfoRef.current.chunks) {
fileInfoRef.current.currentChunk = chunk.file
// 服务器已经存在的切片不再上传
if (fileInfoRef.current.alreadyUploadedHashes.includes(chunk.filename)) {
// 更新上传进度
notifyUpdate()
continue
}
const formData = new FormData()
formData.append('file', chunk.file)
formData.append('filename', `${chunk.filename}`)
await axios.post<UploadSliceResp>(
'http://127.0.0.1:8888/uploadSlice',
formData,
{
onUploadProgress(progressEvent) {
// 更新进度
setUploadPercent(
Number(
((progressEvent.loaded + fileInfoRef.current.totalSize) /
fileInfoRef.current.file!.size) *
100
).toFixed(2)
)
},
}
)
await notifyUpdate()
}
}
4. Atualize o progresso do upload da fatia durante o upload
async function notifyUpdate() {
fileInfoRef.current.uploadedCount++ // 已上传+1
// 更新当前总上传大小
fileInfoRef.current.totalSize += fileInfoRef.current.currentChunk!.size
// 全部上传完成就合并
if (
fileInfoRef.current.uploadedCount === fileInfoRef.current.chunks.length
) {
fileInfoRef.current.url = await mergeChunks()
}
}
5. Se todos os uploads forem bem-sucedidos, envie uma solicitação de mesclagem de fatia
async function mergeChunks() {
const response = await axios.post<UploadSliceResp>(
'http://127.0.0.1:8888/mergeSlice',
{
HASH: fileInfoRef.current.HASH,
count: fileInfoRef.current.chunks.length,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
)
return response.data.servicePath
}