上传文件慢,SpringBoot分片上传文件

Posted killer-leon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了上传文件慢,SpringBoot分片上传文件相关的知识,希望对你有一定的参考价值。

Java上传文件慢,大文件上传卡顿,请求超时怎么办?

话不多说直接上代码,代码复制过去可以直接使用

第一步:创建后端代码

package cn.leon.demo.rest;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * 分片上传文件相关接口
 *
 * @author leon
 * @date 2021/03/19 17:40:06
 */
@Slf4j
@RequestMapping("/chunk-upload")
@RestController
public class UploadFileController 


    /**
     * 文件上传路径,配置文件配置或者这里写死也行
     * ##fileUploadPath
     * file.upload.path=/Users/leon/Desktop
     **/
    @Value("$file.upload.path")
    private String fileUploadPath;


    /**
     * 分片上传小文件
     *
     * @param clientId 客户端ID,每个客户端每次上传时生成,保持唯一
     * @param chunkId  分片ID,从0开始累加,每次上保持传唯一
     * @param chunks   分片总数
     * @param file
     * @return java.lang.String
     * @author leon
     * @date 2021/04/07 17:16:59
     */
    @CrossOrigin
    @PostMapping("/part")
    public Result bigFile(
            MultipartFile file, 
            @RequestParam(name = "clientId", required = true) String clientId, 
            @RequestParam(name = "chunks", required = true) Integer chunks, 
            @RequestParam(name = "chunkId", required = true) Integer chunkId) throws Exception 
        log.info("开始上传分片文件,客户端ID:,总分片数:,分片ID:", clientId, chunks, chunkId);
        // 文件存放目录:临时目录用来存放所有分片文件
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
        String dateStr = sdf.format(new Date());
        //临时文件目录
        String tempFileDir = fileUploadPath + File.separator + dateStr + clientId;
        File parentFileDir = new File(tempFileDir);
        if (!parentFileDir.exists()) 
            parentFileDir.mkdirs();
        
        // 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
        File tempPartFile = new File(parentFileDir, clientId + "_" + chunkId + ".part");
        FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);
        log.info("分片文件上传成功,分片ID:", chunkId);
        return "ok";
    

    /**
     * 上传分片文件完成后合并成一个大文件
     *
     * @param clientId 客户端ID,每次上传时生成和分片上传时参数保持一致
     * @param fileName 原文件名
     * @return java.lang.String 返回最终保存文件路径
     * @author leon
     * @date 2021/04/07 17:13:46
     */
    @CrossOrigin
    @PostMapping("/merge")
    public String mergeFile(
            @RequestParam(name = "clientId", required = true) String clientId, 
            @RequestParam(name = "fileName", required = true) String fileName) throws Exception 
        log.info("开始合并文件,客户端ID:,文件名:", clientId, fileName);
        // 文件存放目录
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
        String dateStr = sdf.format(new Date());
        //最终文件上传目录
        String fileSavePath = fileUploadPath + File.separator + dateStr;
        //临时文件目录
        String tmpFileSavePath = fileSavePath + clientId;
        //最终文件上传文件名
        String newFileName = UUID.randomUUID().toString();
        if (fileName.indexOf(".") != -1) 
            newFileName += fileName.substring(fileName.lastIndexOf("."));
        
        //创建父文件夹
        File parentFileDir = new File(tmpFileSavePath);
        if (parentFileDir.isDirectory()) 
            File destNewFile = new File(fileSavePath, newFileName);
            if (!destNewFile.exists()) 
                //先得到文件的上级目录,并创建上级目录,再创建文件
                destNewFile.getParentFile().mkdir();
                destNewFile.createNewFile();
            
            //遍历"所有分片文件"到"最终文件"中,此处一定要按照顺序合并文件,不然会导致文件合并错乱不可用
            for (int i=0;i<parentFileDir.listFiles().length;i++) 
                FileOutputStream destNewFileFos = new FileOutputStream(destNewFile, true);
                FileUtils.copyFile(new File(parentFileDir, clientId + "_" + i + ".part"), destNewFileFos);
                destNewFileFos.close();
            
            // 删除临时目录中的分片文件
            FileUtils.deleteDirectory(parentFileDir);
        
        log.info("合并文件完成,客户端ID:,文件名:", clientId, fileName);
        return fileSavePath + newFileName;
    



第二步:创建前端代码测试

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>分片上传文件测试</title>
    <script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<div id="uploader">
    <div class="btns">
        <input id="file" name="file" type="file"/>
        <br>
        <br>
        <button id="startBtn">
            开始上传
        </button>
        </br>
        </br>
    </div>
    <div id="output">
    </div>
</div>
</body>
<script type="text/javascript">
    var status = 0;//上传状态
    var startDate;
    var page = 
        init: function()
            $("#startBtn").click($.proxy(this.upload, this));
        ,
        upload: function()
            startDate = (new Date()).getTime();
            console.log("开始上传文件......");
            status = 0;
            var clientId = this.generateClientId();
            var file = $("#file")[0].files[0],  //文件对象
                fileName = file.name,        //文件名
                size = file.size;        //总大小
            var shardSize = 1024 * 1024,    //以1MB为一个分片
            // var shardSize = 500 * 1024,    //以500kb为一个分片
            shardCount = Math.ceil(size / shardSize);  //总片数
            console.log("每个分片文件1MB,总分片数:"+shardCount);
            // console.log("每个分片文件500kb,总分片数:"+shardCount);
            for(var i = 0;i < shardCount;++i)
                //计算每一片的起始与结束位置
                var start = i * shardSize,
                    end = Math.min(size, start + shardSize);
                var partFile = file.slice(start,end);
                this.partUpload(clientId,partFile,fileName,shardCount,i);
                console.log("第"+i+"个分片文件上传成功");
            
            var endDate = (new Date()).getTime();
            console.log("所有分片文件上传请求发送成功,总耗时:"+(endDate-startDate)+"毫秒");
        ,
        partUpload:function(clientId,partFile,fileName,chunks,chunkId)
            //构造一个表单,FormData是HTML5新增的
            var  now = this;
            var form = new FormData();
            form.append("clientId", clientId);
            form.append("file", partFile);  //slice方法用于切出文件的一部分
            form.append("fileName", fileName);
            form.append("chunks", chunks);  //总片数
            form.append("chunkId", chunkId);        //当前是第几片
            //Ajax提交
            $.ajax(
                url: "http://localhost:8080/chunk-upload/part",
                type: "POST",
                data: form,
                async: true,        //异步
                processData: false,  //很重要,告诉jquery不要对form进行处理
                contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                success: function(data)
                    status++;
                    if(data.code == 0)
                        $("#output").html(status+ " / " + chunks);
                    else
                        alert('出现异常:'+data.message);
                    
                    if(status==chunks)
                        var endDate = (new Date()).getTime();
                        console.log("所有分片文件上传成功,总耗时:"+(endDate-startDate)+"毫秒")
                        now.mergeFile(clientId,fileName);
                    
                
            );
        ,
        mergeFile:function(clientId,fileName)
            var formMerge = new FormData();
            formMerge.append("clientId", clientId);
            formMerge.append("fileName", fileName);
            $.ajax(
                url: "http://localhost:8080/chunk-upload/merge",
                type: "POST",
                data: formMerge,
                processData: false,  //很重要,告诉jquery不要对form进行处理
                contentType: false,  //很重要,指定为false才能形成正确的Content-Types
                success: function(data)
                    if(data.code == 0)
                        var endDate = (new Date()).getTime();
                        console.log("上传文件成功,总耗时:"+(endDate-startDate)+"毫秒");
                        alert('上传成功!');
                    else
                        alert('出现异常:'+data.message);
                    
                
            );
        ,
        generateClientId:function()
            var counter = 0;
            var clientId = (+new Date()).toString( 32 ),
                i = 0;
            for ( ; i < 5; i++ ) 
                clientId += Math.floor( Math.random() * 65535 ).toString( 32 );
            
            return clientId + (counter++).toString( 32 );
        
    ;

    $(function()
        page.init();
    );
</script>
</html>

 

以上是关于上传文件慢,SpringBoot分片上传文件的主要内容,如果未能解决你的问题,请参考以下文章

上传文件慢,SpringBoot分片上传文件

SpringBoot超大文件上传如何实现?

SpringBoot文件分片上传

SpringBoot WebUploader大文件分片上传

SpringBoot实现浏览器端大文件分片上传

springboot2.0结合webuploader实现文件分片上传