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 = "模式二:分片上传文件")@OperateLogpublic 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 chunklet 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 neededfor (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 barupdateProgressBar((i + 1) / totalChunks * 100);} catch (error) {console.error('Error uploading chunk:', error);}}// Merge chunks after all are uploadedtry {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>