FileController
@GetMapping("/file-url")
@Operation(summary = "获取文件地址")
@ApiResponse(description = "0文件不存在")
@OperateLog(enable = false)
public CommonResult<String> getFileUrl(String identifier) {
String url0 = fileService.getFirstUrlByName(identifier);
if (url0 != null) {
return success(url0);
}
return success("0");
}
@PostMapping("/chunks-upload")
@Operation(summary = "上传文件", description = "模式二:分片上传文件")
@ApiResponse(description = "0:上传失败,1:上传成功")
@OperateLog(enable = false)
public CommonResult<String> uploadChunks(FileChunkUploadReqVO chunkUploadReqVO) throws Exception {
MultipartFile file = chunkUploadReqVO.getFile();
String identifier = chunkUploadReqVO.getIdentifier();
Long number = chunkUploadReqVO.getNumber();
Long total = chunkUploadReqVO.getTotal();
String filename = StrUtil.format("{}_{}", total, number);
String filepath = getSrcDir(identifier) + "/" + filename;
if (!FileUtil.exist(filepath)) {
// 1、创建分片文件
File chunking = FileUtil.touch(filepath + ".chunking");
FileUtil.writeBytes(file.getBytes(), chunking);
// 2、上传成功后删除后缀
FileUtil.rename(chunking, filename, true);
}
return success("1");
}
@GetMapping("/chunks-merge")
@Operation(summary = "合并文件", description = "模式二:分片上传文件")
@OperateLog
public CommonResult<String> mergeChunks(String identifier, String path) throws Exception {
// 1、创建文件
String url = fileService.createFile(identifier, path,
new byte[0]);
File tarFile = fileService.getLocalFile(url);
if (tarFile == null) {
throw new ServiceException("文件不存在,暂只支持本地分片文件的合并");
}
// 2、合并至文件
String srcDir = getSrcDir(identifier);
mergeChunks(srcDir, tarFile);
// 3、删除临时目录
FileUtil.del(srcDir);
return success(url);
}
private String getSrcDir(String identifier) {
return "~/temp/" + identifier;
}
private void mergeChunks(String srcDir, File tarFile) {
if (FileUtil.exist(srcDir)) {
File[] files = FileUtil.ls(srcDir);
Arrays.sort(files, (o1, o2) -> {
Integer n1 = Integer.valueOf(o1.getName().replace("_", ""));
Integer n2 = Integer.valueOf(o2.getName().replace("_", ""));
return n1 - n2;
});
// 合并
for (File file : files) {
if (file.getName().endsWith(".chunking")) {
continue;
}
byte[] content = FileUtil.readBytes(file);
FileUtil.writeBytes(content, tarFile, 0, content.length, true);
}
}
}
FileService
/**
* 通过url获取文件的实际存储对象
* @param url 文件url
* @return 文件绝对路径
*/
File getLocalFile(String url);
/**
* 获取url,该方法的前提是使用name存储md5或其他算法生成的文件唯一标识,从而保证打文件秒传
* @param name 文件name
* @return 文件url
*/
String getFirstUrlByName(String name);
tstup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chunked File Upload</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<style>
#progressContainer {
width: 100%;
background-color: #f3f3f3;
}
#progressBar {
width: 0%;
height: 30px;
background-color: #4caf50;
}
</style>
</head>
<body>
<h1>Chunked File Upload</h1>
<input type="file" id="fileInput"/>
<button onclick="uploadFile()">Upload</button>
<div id="progressContainer">
<div id="progressBar"></div>
</div>
<script>
const chunkSize = 2 * 1024 * 1024; // 2MB per chunk
let fileInput = document.getElementById('fileInput');
async function uploadFile() {
const file = fileInput.files[0];
if (!file) {
alert('Please select a file');
return;
}
const totalChunks = Math.ceil(file.size / chunkSize);
const identifier = generateIdentifier(file);
const path = file.name; // adjust as needed
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('path', path);
formData.append('identifier', identifier);
formData.append('number', i + 1);
formData.append('total', totalChunks);
try {
const response = await fetch('http://localhost:20611/admin-api/infra/file/chunks-upload', {
method: 'POST',
body: formData,
headers: {
'Authorization': 'Bearer 6ec7f94e414943d893fa5a63d4ad1bad'
}
});
const result = await response.json();
if (result.data !== '1' && result.data !== '0') i = totalChunks;
console.log('Chunk upload response:', result);
// Update the progress bar
updateProgressBar((i + 1) / totalChunks * 100);
} catch (error) {
console.error('Error uploading chunk:', error);
}
}
// Merge chunks after all are uploaded
try {
const response = await fetch(`http://localhost:20611/admin-api/infra/file/chunks-merge?identifier=${identifier}&path=${path}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer 6ec7f94e414943d893fa5a63d4ad1bad'
}
});
const result = await response.json();
console.log('Merge response:', result);
alert('File uploaded and merged successfully');
} catch (error) {
console.error('Error merging chunks:', error);
}
}
function updateProgressBar(percentage) {
const progressBar = document.getElementById('progressBar');
progressBar.style.width = percentage + '%';
}
function generateIdentifier(file) {
return CryptoJS.MD5(CryptoJS.enc.Latin1.parse(file)).toString();
}
</script>
</body>
</html>