前言
做毕设时需要将大文件进行上传到后端进行保存,结合多方面的资料搜集完成了这个功能,但是还是没有做到文件分片上传时统计总上传进度。
前端部分
实现需要得到一个文件对象,这里不作出叙述了。先上总代码,因为很多部分涉及到了我的具体业务,很多都是多余的,后面慢慢解释重要的片段。
uploadFile(file){
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const chunkSize = 10 * 1024 * 1024; // 每片10MB
// 创建一个FileReader实例
const reader = new FileReader();
reader.onload = (e) => {
const arrayBuffer = e.target.result;
const spark = new SparkMD5.ArrayBuffer();
spark.append(arrayBuffer);
const md5Hash = spark.end();
var uploadItem={
file_name:file.name,
percentage:0,
state:0
}
this.uploadListState.push(uploadItem)
if(md5Hash){
uploadItem.state=1
}
const param1 = this.$route.query.path;
let file_pid='root'
if(param1!=null){
file_pid=param1
}
apis.uploadPreview({
user_id:this.UserInfo.user_id,
file_pid:file_pid,
file_md5:md5Hash
})
.then(res=>{
if(res.data.code==0){
if (file.size > MAX_FILE_SIZE) {
// 文件大小超过10MB,执行分片上传
let file_name=file.name
let file_type=file.type
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
this.chunkUpLoad(chunk,i,totalChunks,file_name,uploadItem,file_type,md5Hash)
}
} else {
this.minFileUpLoad(file,uploadItem,md5Hash)
}
}else{
uploadItem.percentage=100
uploadItem.state=3
this.$message({
message: '上传成功',
type: 'success'
})
console.info('已经完成上传')
}
})
.catch(error=>{
console.info(error)
})
};
// 读取文件为ArrayBuffer
reader.readAsArrayBuffer(file);
},
当选取到的文件大小超过了限制进入分片逻辑时。
let file_name=file.name
let file_type=file.type
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
this.chunkUpLoad(chunk,i,totalChunks,file_name,uploadItem,file_type,md5Hash)
}
在这里将文件分成chunkSize个字节大小,并且通过chunkUpLoad将分片后的文件chunk上传到后端。
后端部分
后端在控制层用MultipartFile 类型将该文件接收,在fileService类中进行具体处理。
@PostMapping("/chunkUpLoad")
public ResponseResult chunkUpLoad(@RequestParam("file") MultipartFile file,
@RequestParam String user_id,
@RequestParam String file_pid,
@RequestParam String file_type,
@RequestParam Integer index,
@RequestParam Integer chunkNum,
@RequestParam String file_name,
@RequestParam String file_md5,
@RequestParam Integer file_category){
String Extension=UUIDUtils.getFileExtension(file_name);
if ("exe".equals(Extension)||"bat".equals(Extension)){
ResponseResult result=new ResponseResult();
result.setCode(-1);
result.setErrorMessage("该文件类型不允许上传");
}
return fileService.chunkUpLoad(file,user_id,file_pid,file_type,index,chunkNum,file_name,file_md5,file_category);
}
在后端的处理逻辑为这样的,即用户将带有总分片和该文件的分片号传来后,将该MultipartFile文件作为字节文件存储在一个临时文件夹并将存储的文件路径存储在chunkStoreList列表中,当最后一片文件成功完成后,检查之前的文件是否上传成功,没有成功则循环等待1秒,反之则将已经上传的分片文件拼接得到一个临时文件并获取该文件的InputStream,在我的业务中需要用InputStream存入minio中。
代码部分:
public ResponseResult chunkUpLoad(MultipartFile file,
String user_id,
String file_pid,
String file_type,
Integer index,
Integer chunkNum,
String file_name,
String file_md5,
Integer file_category) {
ResponseResult result=new ResponseResult();
java.io.File dest = new java.io.File(diskAddress+"\\"+file_name+"_"+index+"_"+chunkNum);
// 确保目标文件夹存在
java.io.File parentDir = dest.getParentFile();
if (!parentDir.exists()) {
try {
Files.createDirectories(Paths.get(parentDir.toURI()));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
try {
file.transferTo(dest);
System.out.println("File has been saved to " + dest.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
result.setCode(1002);
result.setErrorMessage(e.getMessage());
//读取文件
return result;
}
List<String> chunkStoreList=new ArrayList<>();
//这是最后一片了
if (index+1==chunkNum){
//读取该文件的全部分片文件
for (int i=0;i<chunkNum;i++){
chunkStoreList.add(diskAddress+"\\"+file_name+"_"+i+"_"+chunkNum);
}
for (String s:chunkStoreList){
while (!isFileExist(s)){
try {
System.out.println(s+",未完成传输,等待1.5s");
Thread.sleep(1500);
}catch (Exception e){
}
}
}
// 创建一个临时文件来存储合并后的内容
try {
Path tempFile = Files.createTempFile("merged-", null);
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile().getAbsolutePath());
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
for (String chunkPath : chunkStoreList) {
java.io.File chunkFile = new java.io.File(chunkPath);
if (chunkFile.exists() && chunkFile.isFile()) {
try (FileInputStream fis = new FileInputStream(chunkFile);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
}
}
}
long fileSize = Files.size(tempFile);
InputStream mergedInputStream =new FileInputStream(tempFile.toFile());
String path=System.currentTimeMillis()+"_"+ file_name;
//在这里保存
int res=minioUtils.uploadChunkFile(mergedInputStream,path,file_type);
//保存到数据库中
if (res<1){
System.out.println("分片保存失败");
}else {
System.out.println("分片保存成功");
}
File fileVo=new File();
fileVo.setFile_id(UUIDUtils.getUUIDString());
fileVo.setUser_id(user_id);
fileVo.setFile_md5(file_md5);
fileVo.setFile_pid(file_pid);
fileVo.setFile_size(fileSize);
fileVo.setFile_name(file_name);
fileVo.setFile_path(path);
String time=UUIDUtils.getCurrentTime();
fileVo.setCreate_time(time);
fileVo.setLast_update_time(time);
fileVo.setFolder_type(0);
String fileExtension = getFileExtension(file_name);
if (File_TYPE_MAP.get(fileExtension)==null){
fileVo.setFile_type(10);
}else {
fileVo.setFile_type(File_TYPE_MAP.get(fileExtension));
}
if (file_category==0){
if (File_TYPE_MAP.get(getFileExtension(file_name))==null){
fileVo.setFile_category(5);
}else {
fileVo.setFile_category(File_CATEGORY.get(fileVo.getFile_type()));
}
}else {
fileVo.setFile_category(file_category);
}
fileVo.setFile_cover(COVER_MAP.get(fileVo.getFile_type()));//封面
if (fileVo.getFile_type()==3){//设置图片封面
String file_cover=minioUtils.saveImageCover(mergedInputStream,path,0.7f,file_type);
fileVo.setFile_cover(file_cover);
}
fileVo.setRecovery_time(null);
fileVo.setDel_flag(3);
fileMapper.insert(fileVo);//保存到数据库中
mergedInputStream.close();
}catch (Exception e){
e.printStackTrace();
result.setCode(1002);
result.setErrorMessage(e.getMessage());
//读取文件
return result;
}
//删除该文件的全部分片文件
for (String deletePath:chunkStoreList){
try {
// 创建Path对象
Path path = Paths.get(deletePath);
// 检查文件是否存在
if (Files.exists(path)) {
// 删除文件
Files.delete(path);
System.out.println("文件已成功删除: " + deletePath);
} else {
System.out.println("文件不存在: " + deletePath);
}
} catch (IOException e) {
// 处理可能的异常,如文件不存在、没有权限等
e.printStackTrace();
result.setCode(1002);
result.setErrorMessage(e.getMessage());
//读取文件
return result;
}
}
result.setCode(1000);
//读取文件
return result;
}
result.setCode(1001);
//读取文件
return result;
}
希望能够帮助到你。