文件上传 分片上传-初步实现

时间:2025-01-18 07:01:12

后端代码

/**
 * 分片上传
 *
 * @param file 上传的文件
 * @param start 文件开始上传的位置
 * @param fileName 文件名称
 * @return  上传结果
 */
@PostMapping("/fragmentUpload")
@ResponseBody
public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {
    try {
        // 检查上传目录是否存在,如果不存在则创建
        File directory = new File(uploadPath);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 设置上传文件的目标路径
        File targetFile = new File(uploadPath +File.separator+ fileName);
        // 创建 RandomAccessFile 对象以便进行文件的随机读写操作
        RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
        // 获取 RandomAccessFile 对应的 FileChannel
        FileChannel channel = randomAccessFile.getChannel();
        // 设置文件通道的位置,即从哪里开始写入文件内容
        channel.position(start);
        // 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置
        channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());

        // 关闭文件通道和 RandomAccessFile 对象
        channel.close();
        randomAccessFile.close();

        // 返回上传成功的响应
        return AjaxResult.success("上传成功");
    } catch (Exception e) {
        // 捕获异常并返回上传失败的响应
        return AjaxResult.error("上传失败");
    }
}

/**
 * 检测文件是否存在
 * 如果文件存在,则返回已经存在的文件大小。
 * 如果文件不存在,则返回 0,表示前端从头开始上传该文件。
 * @param filename
 * @return
 */
@GetMapping("/checkFile")
@ResponseBody
public AjaxResult checkFile(@RequestParam("filename") String filename) {
    File file = new File(uploadPath+File.separator + filename);
    if (file.exists()) {
        return AjaxResult.success(file.length());
    } else {
        return AjaxResult.success(0L);
    }
}

前端

var prefix = ctx + "/kuroshiro/file-upload";

// 每次上传大小
const chunkSize = 1 * 1024 * 1024;

/**
 * 开始上传
 */
function startUpload(type) {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];
    if (!file) {
        alert("请选择文件");
        return;
    }
    if(type == 1){
	    checkFile(filename).then(start => {
	         uploadFile(file, start,Math.min(start + chunkSize, file.size));
	     })
	 }
}

/**
 * 检查是否上传过
 * @param filename
 * @returns {Promise<unknown>}
 */
function checkFile(filename) {
    return $fetch(prefix+`/checkFile?filename=${filename}`);
}

/**
 * 开始分片上传
 * @param file 文件
 * @param start 开始位置
 * @param end 结束位置
 */
function uploadFile(file, start,end) {
    if(start < end){
        const chunk = file.slice(start, end);
        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('start', start);
        formData.append('fileName', file.name);

        $fetch(prefix+'/fragmentUpload', {
            method: 'POST',
            body: formData
        }).then(response => {
            console.log(`分片 ${start} - ${end} 上传成功`);
            // 递归调用
            uploadFile(file,end,Math.min(end + chunkSize, file.size))
        })
    }

}

function $fetch(url,requestInit){
        return new Promise((resolve, reject) => {
            fetch(url,requestInit).then(response => {
                if (!response.ok) {
                    throw new Error('请求失败');
                }
                return response.json();
            }).then(data => {
            if (data.code === 0) {
                resolve(data.data);
            } else {
                console.error(data.msg);
                reject(data.msg)
            }
            }).catch(error => {
                console.error(error);
                reject(error)
            });
        });
    }

以上虽然实现的分片上传,但是它是某种意义上来说还是与整体上传差不多,它是一段一段的上传,某段上传失败后,后续的就不会再继续上传;不过比起整体上传来说,它会保存之前上传的内容,下一个上传时,从之前上传的位置接着上传。不用整体上传。下面进行优化。