SpringBoot实现大文件断点续传

92 阅读1分钟

方案

前端对文件进行分片,对每个分片按序上传,上传前先发送一个请求,检查上传的文件是否已经存在,如果存在,目前大小是多少,前端根据已经存在的大小,继续上传文件即可

前端js代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>断点续传示例</title>
</head>
<body>
<input type="file" id="fileInput"/>
<button onclick="startUpload()">开始上传</button>

<script>
    async function startUpload() {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];
        if (!file) {
            alert("请选择文件");
            return;
        }

        const filename = file.name;
        let start = await checkFile(filename);

        uploadFile(file, start);
    }

    // 查看文件是否存在,如果存在返回当前文件大小
    async function checkFile(filename) {
        const response = await fetch(`http://localhost:8585/check?filename=${filename}`);
        const start = await response.json();
        return start;
    }

    // 上传文件
    async function uploadFile(file, start) {
        const chunkSize = 1024 * 1024; // 每个分片1MB
        const total = Math.ceil((file.size - start) / chunkSize);

        for (let i = 0; i < total; i++) {
            const chunkStart = start + i * chunkSize;
            const chunkEnd = Math.min(chunkStart + chunkSize, file.size);
            const chunk = file.slice(chunkStart, chunkEnd);

            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('start', chunkStart);
            formData.append('fileName', file.name);

            const response = await fetch('http://localhost:8585/upload', {
                method: 'POST',
                body: formData
            });

            const result = await response.text();
            if (response.ok) {
                console.log(`分片 ${i + 1}/${total} 上传成功`);
            } else {
                console.error(`分片 ${i + 1}/${total} 上传失败: ${result}`);
                break;
            }
        }
    }
</script>
</body>
</html>

后端java

@CrossOrigin(origins = "*")
@RestController
public class Demo {

    private static final String UPLOAD_DIR = "E:\\Code\\springboot\\src\\main\\java\\cn\\ccb\\file";
    /**
     * 上传文件到指定位置
     *
     * @param file 上传的文件
     * @param start 文件开始上传的位置
     * @return ResponseEntity<String> 上传结果
     */
    @PostMapping("/upload")
    public ResponseEntity<String> resumeUpload(@RequestParam("file") MultipartFile file,
                                               @RequestParam("start") long start,
                                               @RequestParam("fileName") String fileName) {
        try {
            File directory = new File(UPLOAD_DIR);
            if (!directory.exists()) {
                directory.mkdirs();
            }
            File targetFile = new File(UPLOAD_DIR + fileName);
            RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
            FileChannel channel = randomAccessFile.getChannel();
            channel.position(start);
            channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());
            channel.close();
            randomAccessFile.close();
            return ResponseEntity.ok("上传成功");
        } catch (Exception e) {
            System.out.println("上传失败: "+e.getMessage());
            return ResponseEntity.status(500).body("上传失败");
        }
    }

    /**
     * 查看文件是否存在,如果存在返回文件大小
     */
    @GetMapping("/check")
    public ResponseEntity<Long> checkFile(@RequestParam("filename") String filename) {
        File file = new File(UPLOAD_DIR + filename);
        if (file.exists()) {
            return ResponseEntity.ok(file.length());
        } else {
            return ResponseEntity.ok(0L);
        }
    }

}

相关文章