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

9 篇文章 0 订阅
3 篇文章 0 订阅

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>

 

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值