0%

chunked-upload

前端分片上传(vue-simple-uploader实现)

vue-simple-uploader 是一个基于 Vue.js 的文件上传组件,支持分片上传、断点续传、秒传、拖拽上传等功能。

1. 在项目中安装 vue-simple-uploader
1
npm install vue-simple-uploader --save
2. 在 Vue 项目中引入并注册 vue-simple-uploader:
1
2
3
4
import Vue from 'vue';
import uploader from 'vue-simple-uploader';

Vue.use(uploader);
3. 前端分片上传vue-simple-uploader

配置<uploader> 标签

1
2
3
4
5
6
7
8
9
10
<template>
<div>
<uploader
:options="uploaderOptions"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-error="onFileError"
></uploader>
</div>
</template>

并配置相关选项,分片大小、目标地址、检查分片是否已上传、token

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
<script>
export default {
data() {
return {
uploaderOptions: {
target: `${this.$config.baseContext}/file/uploadfile`, // 上传文件-目标 URL
chunkSize: 1024 * 1024, // 每个分片的大小
fileParameterName: 'file', // 上传文件时文件的参数名,默认 file
maxChunkRetries: 3, // 并发上传数,默认 3
testChunks: true, // 是否开启分片已存在于服务器的校验
// 其他配置项...
headers: {
token: this.$common.getCookies(this.$config.tokenKeyName)
},
},
};
},
methods: {
onFileAdded(file) {
console.log('文件添加:', file);
// 可以在这里进行文件校验
},
onFileSuccess(rootFile, file, response) {
console.log('上传成功:', response);
// 处理上传成功的逻辑
},
onFileError(rootFile, file, response) {
console.error('上传失败:', response);
// 处理上传失败的逻辑
},
},
};
</script>
4.请求的表单数据

vue-simple-uploader在上传文件时会自动生成以下数据

  1. 分片相关数据
    • chunkNumber:当前分片的次序,从1开始。
    • totalChunks:文件被分成的总分片数。
    • chunkSize:每个分片的大小。
    • currentChunkSize:当前分片的实际大小。
    • totalSize:文件的总大小。
  2. 文件标识与路径
    • identifier:文件的唯一标识,通常通过计算文件的 MD5 值生成。
    • filename:文件名。
    • relativePath:文件的相对路径,用于文件夹上传。
  3. 其他数据
    • fileParameterName:上传文件时文件的参数名,默认为 file
    • headers:额外的请求头,例如用于传递认证 token。

如图

image-20250301185947681

后端处理

1.DTO接收请求,HttpServletRequest接收文件流(再将HttpServletRequest 转换为 MultipartFile)

DTO接收发送的请求中的数据

image-20250301192517185
2.通过Identifier和ConcurrentHashMap获取tmp文件名
1
2
3
4
5
6
String fileUrl = FileUtils.getUploadFileUrl(uploadFile.getIdentifier(), MultipartFile.getExtendName());
if (StringUtils.isNotEmpty(FILE_URL_MAP.get(uploadFile.getIdentifier()))) {
fileUrl = FILE_URL_MAP.get(uploadFile.getIdentifier());
} else {
FILE_URL_MAP.put(uploadFile.getIdentifier(), fileUrl);
}
3.通过RandomAccessFile 和 FileChannel随机访问写文件(不同分片写的偏移不同互不干扰)
1
2
3
4
5
6
7
FileChannel fileChannel = raf.getChannel();
long position = (long)(uploadFile.getChunkNumber() - 1) * uploadFile.getChunkSize();
byte[] fileData = qiwenMultipartFile.getUploadBytes();
fileChannel.position(position);
fileChannel.write(ByteBuffer.wrap(fileData));
fileChannel.force(true);
fileChannel.close();
4.判断是否写完(通过数组记录所有分片是否写完,写完后进行加密等可能需要进行的操作
1
2
3
4
5
6
7
8
9
10
boolean isComplete = this.checkUploadStatus(uploadFile, confFile);
if (isComplete) {
tempFile.renameTo(file);
try {
Sm2Util.encryptFile(file, publicKey);
} catch (IOException e) {
throw new CxException("加密失败");
}
....
}
5.返回上传结果给前端

前端可能设置了检查分片是否成功上传,故后端需要返回每个分片的接收结果,对应的对象赋值返回。

image-20250301210743705