MInIO入门-03 秒传+大文件分片上传

MInIO 文件分片上传

项目传送门

1、前端页面

image-20220507111614983

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript" src="/js/jquery.js" th:src="@{/js/jquery.js}"></script>
<script type="text/javascript" src="/js/spark-md5.min.js" th:src="@{/js/spark-md5.min.js}"></script>

<input type="file" name="file" id="file">
<script>
    const baseUrl = "http://localhost:18002";

    /**
     * 分块计算文件的md5值
     * @param file 文件
     * @param chunkSize 分片大小
     * @returns Promise
     */
    function calculateFileMd5(file, chunkSize) {
     
     
        return new Promise((resolve, reject) => {
     
     
            let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
            let chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            let spark = new SparkMD5.ArrayBuffer();
            let fileReader = new FileReader();

            fileReader.onload = function (e) {
     
     
                spark.append(e.target.result);
                currentChunk++;
                if (currentChunk < chunks) {
     
     
                    loadNext();
                } else {
     
     
                    let md5 = spark.end();
                    resolve(md5);
                }
            };

            fileReader.onerror = function (e) {
     
     
                reject(e);
            };

            function loadNext() {
     
     
                let start = currentChunk * chunkSize;
                let end = start + chunkSize;
                if (end > file.size) {
     
     
                    end = file.size;
                }
                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            }

            loadNext();
        });
    }

    /**
     * 分块计算文件的md5值,默认分片大小为2097152(2M)
     * @param file 文件
     * @returns Promise
     */
    function calculateFileMd5ByDefaultChunkSize(file) {
     
     
        return calculateFileMd5(file, 2097152);
    }

    /**
     * 获取文件的后缀名
     */
    function getFileType(fileName) {
     
     
        return fileName.substr(fileName.lastIndexOf(".") + 1).toLowerCase();
    }

    // 文件选择之后就计算文件的md5值
    document.getElementById("file").addEventListener("change", function () {
     
     
        let file = this.files[0];
        calculateFileMd5ByDefaultChunkSize(file).then(e => {
     
     
            // 获取到文件的md5
            let md5 = e;
            checkMd5(md5, file)
        }).catch(e => {
     
     
            // 处理异常
            console.error(e);
        });
    });

    /**
     * 根据文件的md5值判断文件是否已经上传过了
     *
     * @param md5 文件的md5
     * @param file 准备上传的文件
     */
    function checkMd5(md5, file) {
     
     
        // 请求数据库,查询md5是否存在
        $.ajax({
     
     
            url: baseUrl + "/file/check",
            type: "GET",
            data: {
     
     
                md5: md5
            },
            async: true, //异步
            dataType: "json",
            success: function (msg) {
     
     
                console.log(msg);
                // 文件已经存在了,无需上传
                if (msg.status === 20000) {
     
     
                    console.log("文件已经存在了,无需上传")
                } else if (msg.status === 40004) {
     
     
                    // 文件不存在需要上传
                    console.log("文件不存在需要上传")
                    PostFile(file, 0, md5);
                } else {
     
     
                    console.log('未知错误');
                }
            }
        })
    }

    /**
     * 执行分片上传
     * @param file 上传的文件
     * @param i 第几分片,从0开始
     * @param md5 文件的md5值
     */
    function PostFile(file, i, md5) {
     
     
        let name = file.name,                           //文件名
            size = file.size,                           //总大小shardSize = 2 * 1024 * 1024,
            shardSize = 5 * 1024 * 1024,                //以5MB为一个分片,每个分片的大小
            shardCount = Math.ceil(size / shardSize);   //总片数
        if (i >= shardCount) {
     
     
            return;
        }

        let start = i * shardSize;
        let end = start + shardSize;
        let packet = file.slice(start, end);  //将文件进行切片
        /*  构建form表单进行提交  */
        let form = new FormData();
        form.append("md5", md5);// 前端生成uuid作为标识符传个后台每个文件都是一个uuid防止文件串了
        form.append("data", packet); //slice方法用于切出文件的一部分
        form.append("name", name);
        form.append("totalSize", size);
        form.append("total", shardCount); //总片数
        form.append("index", i + 1); //当前是第几片
        $.ajax({
     
     
            url: baseUrl + "/file/upload",
            type: "POST",
            data: form,
            //timeout:"10000",  //超时10秒
            async: true, //异步
            dataType: "json",
            processData: false, //很重要,告诉jquery不要对form进行处理
            contentType: false, //很重要,指定为false才能形成正确的Content-Type
            success: function (msg) {
     
     
                console.log(msg);
                /*  表示上一块文件上传成功,继续下一次  */
                if (msg.status === 20001) {
     
     
                    form = '';
                    i++;
                    PostFile(file, i, md5);
                } else if (msg.status === 50000) {
     
     
                    form = '';
                    /*  失败后,每2秒继续传一次分片文件  */
                    setInterval(function () {
     
     
                        PostFile(file, i, md5)
                    }, 2000);
                } else if (msg.status === 20002) {
     
     
                    merge(shardCount, name, md5, getFileType(file.name), file.size)
                    console.log("上传成功");
                } else {
     
     
                    console.log('未知错误');
                }
            }
        })
    }

    /**
     * 合并文件
     * @param shardCount 分片数
     * @param fileName 文件名
     * @param md5 文件md值
     * @param fileType 文件类型
     * @param fileSize 文件大小
     */
    function merge(shardCount, fileName, md5, fileType, fileSize) {
     
     
        $.ajax({
     
     
            url: baseUrl + "/file/merge",
            type: "GET",
            data: {
     
     
                shardCount: shardCount,
                fileName: fileName,
                md5: md5,
                fileType: fileType,
                fileSize: fileSize
            },
            // timeout:"10000",  //超时10秒
            async: true, //异步
            dataType: "json",
            success: function (msg) {
     
     
                console.log(msg);
            }
        })
    }
</script>

</body>
</html>

2、文件md5校验

/**
     * 根据文件大小和文件的md5校验文件是否存在
     * 暂时使用Redis实现,后续需要存入数据库
     * 实现秒传接口
     *
     * @param md5 文件的md5
     * @return 操作是否成功
     */
    @GetMapping(value = "/check")
    public Map<String, Object> checkFileExists(String md5) {
   
   
        Map<String, Object> resultMap = new HashMap<>();
        if (ObjectUtils.isEmpty(md5)) {
   
   
            resultMap.put("status", StatusCode.PARAM_ERROR.getCode());
            return resultMap;
        }
        // 先从Redis中查询
        String url = (String) jsonRedisTemplate.boundHashOps(MD5_KEY).get(md5);

        // 文件不存在
        if (ObjectUtils.isEmpty(url)) {
   
   
            resultMap.put("status", StatusCode.NOT_FOUND.getCode());
            return resultMap;
        }

        resultMap.put("status", StatusCode.SUCCESS.getCode());
        resultMap.put("url", url);
        // 文件已经存在了
        return resultMap;
    }

3、文件分片上传

/**
     * 文件上传,适合大文件,集成了分片上传
     */
    @PostMapping(value = "/upload")
    public Map<String, Object> upload(HttpServletRequest req) {
   
   
        Map<String, Object> map = new HashMap<>();

        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;

        // 获得文件分片数据
        MultipartFile file = multipartRequest.getFile("data");

        // 上传过程中出现异常,状态码设置为50000
        if (file == null) {
   
   
            map.put("status", StatusCode.FAILURE.getCode());
            return map;
        }
        // 分片第几片
        int index = Integer.parseInt(multipartRequest.getParameter("index"));
        // 总片数
        int total = Integer.parseInt(multipartRequest.getParameter("total"));
        // 获取文件名
        String fileName = multipartRequest.getParameter("name");

        String md5 = multipartRequest.getParameter("md5");

        // 创建文件桶
        minioTemplate.makeBucket(md5);
        String objectName = String.valueOf(index);

        log.info("index: {}, total:{}, fileName:{}, md5:{}, objectName:{}", index, total, fileName, md5, objectName);

        // 当不是最后一片时,上传返回的状态码为20001
        if (index < total) {
   
   
            try {
   
   
                // 上传文件
                OssFile ossFile = minioTemplate.putChunkObject(file.getInputStream(), md5, objectName);
                log.info("{} upload success {}", objectName, ossFile);

                // 设置上传分片的状态
                map.put("status", StatusCode.ALONE_CHUNK_UPLOAD_SUCCESS.getCode());
                return map;
            } catch (Exception e) {
   
   
                e.printStackTrace();
                map.put("status", StatusCode.FAILURE.getCode());
                return map;
            }
        } else {
   
   
            // 为最后一片时状态码为20002
            try {
   
   
                // 上传文件
                minioTemplate.putChunkObject(file.getInputStream(), md5, objectName);

                // 设置上传分片的状态
                map.put("status", StatusCode.ALL_CHUNK_UPLOAD_SUCCESS.getCode());
                return map;
            } catch (Exception e) {
   
   
                e.printStackTrace();
                map.put("status", StatusCode.FAILURE.getCode());
                return map;
            }
        }

    }

4、文件合并

 /**
     * 文件合并
     *
     * @param shardCount 分片总数
     * @param fileName   文件名
     * @param md5        文件的md5
     * @param fileType   文件类型
     * @param fileSize   文件大小
     * @return 分片合并的状态
     */
    @GetMapping(value = "/merge")
    public Map<String, Object> merge(Integer shardCount, String fileName, String md5, String fileType,
                                     Long fileSize) {
   
   
        Map<String, Object> retMap = new HashMap<>();

        try {
   
   
            // 查询片数据
            List<String> objectNameList = minioTemplate.listObjectNames(md5);
            if (shardCount != objectNameList.size()) {
   
   
                // 失败
                retMap.put("status", StatusCode.FAILURE.getCode());
            } else {
   
   
                // 开始合并请求
                String targetBucketName =
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值