前言
之前出过一篇【大文件分片下载】,想到要有始有终,但是最近确实有点忙,所以简单写下分片上传,后面有时间再捣鼓完整点
节约时间 直接上代码
前端
上传页面
<template>
<div id="app">
<input type="file" @change="handleFile"/>
<button @click="upload">上传</button>
</div>
</template>
<script>
import {handleFile,uploadByBlock} from '@/utils/upload.js'
export default {
name: 'App',
data(){
return {
chunks: []
}
},
methods:{
async handleFile(e){
this.chunks = await handleFile(e)
},
async upload(){
await uploadByBlock(this.chunks)
}
}
}
</script>
创建worker.js ,实现多线程
// 上传
self.onmessage = function (e) {
let json = e.data;
const formData = new FormData();
formData.append('file', json.file);
formData.append('fileName', json.fileName);
formData.append('chunkFileName', json.chunkFileName);
formData.append('fileSize', json.fileSize);
formData.append('chunkNum', json.chunkNum);
fetch("/api/upload/block", {
method: 'POST',
body: formData
})
.then(data => {
self.postMessage({response: "success" });
})
.catch(error => {
self.postMessage({ error: "error" });
});
};
创建upload.js
// 每片文件大小,1024*1024*200=200M
const chunkSize = 1024 * 1024 * 200;
// 处理文件
export async function handleFile(e) {
let file = e.target.files[0];
const chunks = [],
fileSize = file.size,
chunkNum = Math.ceil(fileSize / chunkSize)
for (let i = 0; i < fileSize; i += chunkSize) {
chunks.push({
fileSize,
chunkNum,
fileName:file.name,
chunkFileName:i,
blob: file.slice(i, i + chunkSize),
})
}
return chunks
}
// 分片上传
export async function uploadByBlock(chunks) {
const worker = new Worker('worker.js')
for (let i = 0; i < chunks.length; i++) {
// 利用worker 多线程处理上传请求
worker.postMessage(
{
'file': chunks[i].blob, // 分片文件
'fileName': chunks[i].fileName, // 文件名
'chunkFileName': chunks[i].chunkFileName, // 分片文件名
'fileSize': chunks[i].fileSize, // 文件大小
'chunkNum': chunks[i].chunkNum // 分片数量
}
);
}
// worker 回传消息
worker.onmessage = e => {
const { response, error } = e.data;
if (error) {
console.error('Chunk upload failed:', error);
} else {
console.log(`uploaded successfully:`, response);
}
};
worker.onerror = e => {
console.error('Worker error:', e.message);
};
}
后端
controller.java
package com.leezijin.controller;
import com.leezijin.util.DownloadUtil;
import com.leezijin.util.UploadUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("upload")
public class UploadController {
/**
*
* @param file 分片文件
* @param fileName 完整文件名
* @param chunkFileName 分片文件名
* @param fileSize 完整文件大小
* @param chunkNum 总的分片数量
* @param request
* @param response
* @throws IOException
*/
@PostMapping("/block")
public void upload(
@RequestParam("file") MultipartFile file,
@RequestParam("fileName") String fileName,
@RequestParam("chunkFileName") String chunkFileName,
@RequestParam("fileSize") long fileSize,
@RequestParam("chunkNum") int chunkNum,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
UploadUtil.uploadByBlock(file,fileName,chunkFileName,fileSize,chunkNum);
}
}
upload工具类
package com.leezijin.util;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class UploadUtil {
/**存放上传的分片数据**/
private static Map<String, List> files = new HashMap<>();
private static final String BASE_URL = "D:/temp/";
/**
* 上传文件
* @param file
* @param fileName
* @param chunkFileName
* @param fileSize
* @param chunkNum
*/
public static void uploadByBlock(MultipartFile file,String fileName,String chunkFileName,long fileSize,int chunkNum) throws IOException {
String filePath = saveFile(file,fileName,chunkFileName);
List<String> list = files.get(fileName);
if (list==null){
list=new ArrayList<>();
}
list.add(filePath);
files.put(fileName,list);
// 最后一个分片文件
if(list.size()==chunkNum){
// 合并文件
System.out.println("开始合并:"+new Date());
// 排序
Collections.sort(list);
mergeFiles(list, BASE_URL+fileName+"/"+fileName);
System.out.println("合并完成:"+new Date());
// 删除数据
files.remove(fileName);
}
}
public static String saveFile(MultipartFile file, String fileName, String chunkFileName) throws IOException {
String tem = BASE_URL+fileName;
File te = new File(tem);
if(!te.exists()){
te.mkdirs();
}
String filePath = tem+"/"+chunkFileName;
try {
byte[] bytes = file.getBytes();
Path path = Paths.get(filePath);
Files.write(path, bytes);
} catch (IOException e) {
e.printStackTrace();
}
return filePath;
}
/**
* 合并分片文件
* @param fileParts
* @param mergedFile
* @throws IOException
*/
public static void mergeFiles(List<String> fileParts, String mergedFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
for (String filePart : fileParts) {
File file = new File(filePart);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
// 删除已经读取的分片文件(可选)
file.delete();
}
}
}
}
结尾
时间太紧,图也就不上了,自己拿到代码试吧