【SpringBoot】文件分片上传、合并

简介: 【SpringBoot】文件分片上传、合并

背景
在上传大型文件时,一般采用的都是分片、断点续传等技术,这样不会导致因文件过大而造成系统超时或者过压等情况。

6666.jpeg

接下来开始教学。

一、实际效果图

整个前端网页的效果图:
image.png

二、后端代码

1.开发环境
开发工具:idea ,开发语言:java ,框架:springboot

2.创建文件上传工具类
创建工具类:FileUtils


import com.caozhen.common.constant.HttpStatusConstant;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;


/**
 * @author caozhen
 * @ClassName FileUtils
 * @description: 文件上传
 * @date 2023年10月07日
 * @version: 1.0
 */
@Component
public class FileUtils {
   
   
    //文件路径,可以统一配置
    private static final String UPLOAD_DIR = "d:\\uploads";

    /***
     * 断点续传(上传文件)
     * @param file
     * @return
     */
    public ResultAjax resumeFile(MultipartFile file,
                                 int chunkNumber,
                                 int totalChunks,
                                 String identifier) {
   
   
        try {
   
   
            File uploadDir = new File(UPLOAD_DIR);
            if (!uploadDir.exists()) {
   
   
                uploadDir.mkdirs();
            }

            String fileName = identifier + "-chunk-" + chunkNumber;
            File chunkFile = new File(uploadDir, fileName);

            try (InputStream inputStream = file.getInputStream();
                 FileOutputStream outputStream = new FileOutputStream(chunkFile)) {
   
   
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
   
   
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

            if (chunkNumber == totalChunks - 1) {
   
   
                File finalFile = new File(UPLOAD_DIR + File.separator + identifier);
                for (int i = 0; i < totalChunks; i++) {
   
   
                    File part = new File(uploadDir, identifier + "-chunk-" + i);
                    try (FileInputStream partStream = new FileInputStream(part);
                         FileOutputStream finalStream = new FileOutputStream(finalFile, true)) {
   
   
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        while ((bytesRead = partStream.read(buffer)) != -1) {
   
   
                            finalStream.write(buffer, 0, bytesRead);
                        }
                    }
                    part.delete();
                }
                uploadDir.delete();
            }

            return ResultAjax.ok("Chunk " + chunkNumber + " uploaded");
        } catch (IOException e) {
   
   
            e.printStackTrace();
            return ResultAjax.error(HttpStatusConstant.HTTP_CODE_500, "上传失败:" + e.getMessage());
        }
    }
}

3.创建controller类

@RestController
@Slf4j
@Api(tags = "系统服务")
@RequestMapping("/sys/test/")
public class SystemController {
   
   

    @Autowired
    private FileUtils fileUtils;


    @ApiOperation("测试文件断点续传-上传")
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public ResultAjax fileUploads(@RequestParam("file") MultipartFile file,
                                  @RequestParam("chunkNumber") int chunkNumber,
                                  @RequestParam("totalChunks") int totalChunks,
                                  @RequestParam("identifier") String identifier) {
   
   
        return fileUtils.resumeFile(file,chunkNumber,totalChunks,identifier);
    }

}

4.自定义封装类ResultAjax


import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author caozhen
 * @ClassName ResultAjax
 * @description: 全局封装统一返回实体类
 * @date 2023年07月26日
 * @version: 1.0
 */
public class ResultAjax extends LinkedHashMap<String, Object> {
   
   
    private static final long serialVersionUID = 1L;

    public ResultAjax() {
   
   
        put("success", true);
        put("code", HttpStatusConstant.HTTP_CODE_200);
        put("msg", "操作成功!");
        put("data", null);
    }

    public ResultAjax(Integer code) {
   
   
        put("success", true);
        put("code", code);
        put("msg", "操作成功!");
        put("data", null);
    }

    public ResultAjax(Integer code, String msg) {
   
   
        put("success", true);
        put("code", code);
        put("msg", msg);
        put("data", null);
    }

    public static ResultAjax error() {
   
   
        return error(HttpStatusConstant.HTTP_CODE_500, "未知异常,请联系管理员");
    }

    public static ResultAjax errorDebug(String message) {
   
   
        return error(HttpStatusConstant.HTTP_CODE_500, "未知异常 " + message + ",请联系管理员");
    }

    public static ResultAjax error(String msg) {
   
   
        return error(HttpStatusConstant.HTTP_CODE_500, msg);
    }


    public static ResultAjax error(int code, String msg) {
   
   
        ResultAjax r = new ResultAjax();
        r.put("success", false);
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public ResultAjax errorInfo(String msg) {
   
   
        this.put("errorMsg", msg);
        return this;
    }

    public static ResultAjax ok(Object data) {
   
   
        ResultAjax r = new ResultAjax();
        r.put("success", true);
        r.put("code", HttpStatusConstant.HTTP_CODE_200);
        r.put("msg", "操作成功!");
        r.put("data", data);
        return r;
    }

    public static ResultAjax ok(Map<String, Object> map) {
   
   
        ResultAjax r = new ResultAjax();
        r.putAll(map);
        r.put("data", null);
        return r;
    }

    public static ResultAjax ok(String msg, Integer code) {
   
   
        ResultAjax r = new ResultAjax();
        r.put("success", true);
        r.put("code", code);
        r.put("msg", msg);
        r.put("data", null);
        return r;
    }

    public static ResultAjax ok() {
   
   
        return new ResultAjax().put("msg", "操作成功!").put("data", null);
    }

    public static ResultAjax ok(Integer size) {
   
   
        return new ResultAjax().put("data", null);
    }

    @Override
    public ResultAjax put(String key, Object value) {
   
   
        super.put(key, value);
        return this;
    }

    /**
     * 添加返回结果数据
     *
     * @param key
     * @param value
     * @return
     */
    public ResultAjax putData(String key, Object value) {
   
   
        Map<String, Object> map = (HashMap<String, Object>) this.get("data");
        map.put(key, value);
        return this;
    }
}
public class HttpStatusConstant {
   
   
    //目前常用的,200,500
    public final static Integer HTTP_CODE_200 = 200;
    public final static Integer HTTP_CODE_500 = 500;
    //拦截
    public final static Integer HTTP_CODE_403 = 403;
    public final static Integer HTTP_CODE_401 = 401;
}

三、前端代码

<!DOCTYPE html>
<html>
<head>
    <title>文件分片上传</title>
</head>
<body>
    <input type="file" id="fileInput">
    <button id="startUpload">开始上传</button>
    <button id="pauseResumeUpload">暂停上传</button>
    <progress id="progress" max="100" value="0"></progress>
    <span id="progressText">0%</span>

    <script>
        const fileInput = document.getElementById('fileInput');
        const startUpload = document.getElementById('startUpload');
        const pauseResumeUpload = document.getElementById('pauseResumeUpload');
        const progress = document.getElementById('progress');
        const progressText = document.getElementById('progressText');
        let file;
        let uploading = false;
        let currentChunk = 0;
        let uploadPaused = false;
        let xhr;

        fileInput.addEventListener('change', (event) => {
   
   
            file = event.target.files[0];
        });

        async function uploadFile() {
   
   
            const chunkSize = 1 * 1024 * 1024; // 1MB
            const totalChunks = Math.ceil(file.size / chunkSize);

            for (currentChunk; currentChunk < totalChunks; currentChunk++) {
   
   
                if (uploadPaused) {
   
   
                    break;
                }

                const startByte = currentChunk * chunkSize;
                const endByte = Math.min(startByte + chunkSize, file.size);
                const chunk = file.slice(startByte, endByte);

                const formData = new FormData();
                formData.append('file', chunk);
                formData.append('chunkNumber', currentChunk);
                formData.append('totalChunks', totalChunks);
                formData.append('identifier', file.name);

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

                if (response.status !== 200) {
   
   
                    console.error('Error uploading chunk: ', response.statusText);
                    break;
                }

                const percent = ((currentChunk + 1) / totalChunks) * 100;
                progress.value = percent;
                progressText.textContent = `${
     
     Math.round(percent)}%`;
            }

            if (currentChunk >= totalChunks) {
   
   
                alert('文件上传成功!');
                uploading = false;
                currentChunk = 0;
                progress.value = 100;
                progressText.textContent = '100%';
            }
        }

        startUpload.addEventListener('click', () => {
   
   
            if (file && !uploading) {
   
   
                uploading = true;
                uploadFile();
            }
        });

        pauseResumeUpload.addEventListener('click', () => {
   
   
            if (uploading) {
   
   
                if (!uploadPaused) {
   
   
                    uploadPaused = true;
                    pauseResumeUpload.textContent = '继续上传';
                } else {
   
   
                    uploadPaused = false;
                    pauseResumeUpload.textContent = '暂停上传';
                    uploadFile();
                }
            }
        });
    </script>
</body>
</html>

四、接下来我们演示下

image.png

相关文章
|
2天前
|
前端开发 关系型数据库 MySQL
【MySQL × SpringBoot 突发奇想】全面实现流程 · 数据库导出Excel表格文件的接口
【MySQL × SpringBoot 突发奇想】全面实现流程 · 数据库导出Excel表格文件的接口
24 0
|
2天前
|
Java 关系型数据库 MySQL
【MySQL × SpringBoot 突发奇想】全面实现流程 · xlsx文件,Excel表格导入数据库的接口(下)
【MySQL × SpringBoot 突发奇想】全面实现流程 · xlsx文件,Excel表格导入数据库的接口
10 0
|
2天前
|
Java 关系型数据库 MySQL
【MySQL × SpringBoot 突发奇想】全面实现流程 · xlsx文件,Excel表格导入数据库的接口(上)
【MySQL × SpringBoot 突发奇想】全面实现流程 · xlsx文件,Excel表格导入数据库的接口
15 0
|
2天前
|
JavaScript Java API
【JavaEE】Spring Boot - 日志文件
【JavaEE】Spring Boot - 日志文件
6 0
|
2天前
|
安全 JavaScript Java
springboot实现文件防盗链设计
`shigen`,一位专注于Java、Python、Vue和Shell的博主,分享成长和技术。近期将探讨SpringBoot实现图片防盗链,通过限制`Referer`防止资源被盗用。基础版通过`WebMvcConfigurer`配置静态资源,升级版添加拦截器检查`Referer`,确保请求来源合法性。详细代码实现和案例可在文中链接找到。一起学习,每天进步!
46 13
springboot实现文件防盗链设计
|
2天前
|
Java 数据安全/隐私保护 开发者
【SpringBoot】讲清楚日志文件&&lombok
【SpringBoot】讲清楚日志文件&&lombok
23 5
|
2天前
|
JSON 前端开发 JavaScript
Vue+Axios+SpringBoot后端同时接收文件和json作为请求参数
Vue+Axios+SpringBoot后端同时接收文件和json作为请求参数
39 0
|
存储 前端开发 Java
SpringBoot文件上传和下载
SpringBoot文件上传和下载
SpringBoot文件上传和下载
|
前端开发 Java Spring
SpringBoot文件上传下载
SpringBoot文件上传下载
227 0
SpringBoot文件上传下载
|
Java
SpringBoot文件上传下载
项目中经常会有上传和下载的需求,这篇文章简述一下springboot项目中实现简单的上传和下载。 新建springboot项目,前台页面使用的thymeleaf模板,其余的没有特别的配置,pom代码如下: 4.
4119 0