大文件分片上传、断点上传

因为一个政府项目需要上传影像tif文件,文件大小超过20g,以往的上传肯定是不行的,一旦失败就要从头开始,所以了解了分片上传。

分片上传:

分片上传就是将文件分成一个个小块去上传,上传完成之后再将这些文件按顺序合成起来。这里我用来进行分片的组件是百度开源的webuploader,这个组件依赖于jquery

断点上传:

断点上传就是上传分片之前检查分片文件是否存在,如果存在就不再上传。

上后端代码:

`private static final String tifPath = “D:/uploadTiff”;
public static Map<String, Integer> riskmap = new HashMap<String, Integer>();
public static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(6);
@RequestMapping(value = “uploadTiff”)
@Transactional(rollbackFor = Exception.class)
public ResResult uploadTiff(@RequestParam(“file”) MultipartFile file, HttpServletRequest request,
HttpServletResponse response) throws IOException {

    //判断文件是否为空
    if(file.isEmpty()){
        return new ResResult(StatusCode.REQUEST_CODE_BAD,"空文件");
    }

    //接收request参数
    Integer chunk = MyUtil.convertToInt(request.getParameter("chunk"),0);//当前分片
    Integer chunks = MyUtil.convertToInt(request.getParameter("chunks"),0);//总分片数
    String name = MyUtil.convertToString(request.getParameter("name"));//文件名字

    System.out.println("请求:"+chunk);
    System.out.println("总分片:"+chunks);

    try {
        String uploadDir = tifPath;
        File fileDir = new File(uploadDir);

        //创建目录
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }

        //获取上次失败位置
        Integer hechengnum = MyUtil.convertToInt(redisTemplate.opsForValue().get("uploadfile"+name),0);
        System.out.println("上次合成到:"+hechengnum);




        File fileCache = new File(uploadDir,name);
        if(fileCache.exists()){//文件存在

            if (hechengnum>0&&hechengnum>chunk){
                System.out.println("已经合成过");

                return new AjaxResult("","文件已上传");
            }
        }

        //分片文件名称  以 0_xxxx.xxx   1_xxxx.xxx的形式
        String tempName = name;
        if(chunks>0){
            tempName = chunk+"_"+name;
        }


        File tempFile = new File(uploadDir,tempName);
        //存储文件路径
        String uploadFilePath = uploadDir+tempName;

        if (!tempFile.exists()){//文件不存在(分片没上传),则上传分片


            File uploadFile = new File(uploadFilePath);
            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(uploadFile));
            out.write(file.getBytes());
            out.flush();
        }


        //开启线程去合成
        //riskmap.get(name) 手动简单锁一下,保证只有一个线程在合成
        if(riskmap.get(name)==null){
            riskmap.put(name,1);
            fixedThreadPool.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    try{
                        File addFile = new File(uploadDir,name);
                        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(addFile));
                        List<File> flist = new ArrayList<>();

                        //循环每一个分片
                        for (int i = 0; i < chunks; i++) {

                            //上次合成的位置  已合成的则不再合成
                            if (hechengnum >0&& hechengnum >i){
                                continue;
                            }

                            File file1 = new File(uploadDir,i+"_"+name);
                            System.out.println(i);
                            System.out.println(file1.exists());



                            Integer returnnum = 0;
                            while (!file1.exists()){//分片未下载完,等他下载完,每秒查一次,300秒都没有则失败

                                System.out.println("等待");
                                if(returnnum>300){//考虑文件比较大,所以等的时间比较长

                                    //记录失败位置
                                    redisTemplate.opsForValue().set("uploadfile"+name,i+"");
                                }
                                returnnum++;
                                Thread.sleep(1000);
                            }
                            //将字节写入目标文件
                            byte[] bytes = FileUtils.readFileToByteArray(file1);
                            os.write(bytes);
                            os.flush();
                            flist.add(file1);
                        }
                        os.flush();
                        System.gc();//gc,回收掉堆内存的垃圾file对象,不然删不掉
                        Thread.sleep(1000);

                        //合成完删除分片文件
                        for (File f:flist) {
                            f.delete();
                        }

                    }finally {
                        riskmap.put(name,null);
                    }

                }
            });
        }

    } catch (Exception e) {

        e.printStackTrace();
    } finally {
    }
    return new AjaxResult("","上传成功");
}`

开始我是在最后一个分片上传完后,再进行合成,但是因为文件本身比较大,非常的耗时,前端就timeout了,所以就改成开始就合成

前端代码:

这里用的是webupload组件,只是做了一个简单的演示测试功能。不了解的兄弟可以去官网查看文档http://fex.baidu.com/webuploader/
需要下载他的依赖引入项目,下载他官网那个压缩版本即可。在这里插入图片描述
测试目录如上图,他是需要jq的。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<!--引入JS-->
		<script type="text/javascript" src="./jq.js"></script>
		
		<!--引入CSS-->
		<link rel="stylesheet" type="text/css" href="./webuploader-0.1.5/webuploader.css">
		
		<!--引入JS-->
		<script type="text/javascript" src="./webuploader-0.1.5/webuploader.js"></script>
		
		
		<div id="uploader" class="wu-example">
		    <!--用来存放文件信息-->
		    <div id="thelist" class="uploader-list"></div>
		    <div class="btns">
				<div id="upload-container"><span>点击选择文件</span></div>
				
		        <button id="picker" class="btn btn-default">开始上传</button>
		    </div>
		</div>
		
		

		<!-- <div id="upload-container"><span>点击选择文件</span></div>
		<div id="upload-list"></div>
		<button id="picker">点击上传</button> -->
		
		<script type="text/javascript">
			$("#upload-container").click(function(event){
				$("#picker").find("input").click();
			});
			   
			// 初始化Web Uploader
			var uploader = WebUploader.create({
			
			    // 选完文件后,是否自动上传。
			    auto: true,
			
			    // swf文件路径
			    swf: './webuploader-0.1.5/webuploader-0.1.5/Uploader.swf',
			
			    // 文件接收服务端。
			    server: 'http://localhost:8088/examine/geoServer/uploadTiff',
				dnd: "#upload-container",
			    // 选择文件的按钮。可选。
			    // 内部根据当前运行是创建,可能是input元素,也可能是flash.
			    pick: '#picker',
				multiple: true,
				chunked: true,
				chunkSize:100 * 1024 * 1024, //每片100M 
				threads: 10,  //开启十个线程上传
				method: "POST",
				fileVal: "file"//文件参数名称 与@RequestParam("file")一致
				
			  /*  // 只允许选择图片文件。
			    accept: {
			        title: 'Images',
			        extensions: 'tif',
			        mimeTypes: 'image/*'
			    } */
			});
			
			// 当有文件被添加进队列的时候
			uploader.on( 'fileQueued', function( file ) {
			    $("#thelist").append( '<div id="' + file.id + '" class="item">' +
			        '<h4 class="info">' + file.name + '</h4>' +
			        '<p class="state">等待上传...</p>' +
			    '</div>' );
				
				uploader.md5File( file )
				
				        // 及时显示进度
				        .progress(function(percentage) {
				            console.log('Percentage:', percentage);
				        })
				
				        // 完成
				        .then(function(val) {
				            console.log('md5 result:', val);
				        });
				
			});
			
			// 文件上传过程中创建进度条实时显示。
			uploader.on( 'uploadProgress', function( file, percentage ) {
			    var $li = $( '#'+file.id ),
			        $percent = $li.find('.progress .progress-bar');
			
			    // 避免重复创建
			    if ( !$percent.length ) {
			        $percent = $('<div class="progress progress-striped active">' +
			          '<div class="progress-bar" role="progressbar" style="width: 0%">' +
			          '</div>' +
			        '</div>').appendTo( $li ).find('.progress-bar');
			    }
			
			    $li.find('p.state').text('上传中');
			
			    $percent.css( 'width', percentage * 100 + '%' );
			});
			
			uploader.on( 'uploadSuccess', function( file ) {
			    $( '#'+file.id ).find('p.state').text('已上传');
			});
			
			uploader.on( 'uploadError', function( file ) {
			    $( '#'+file.id ).find('p.state').text('上传出错');
			});
			
			uploader.on( 'uploadComplete', function( file ) {
			    $( '#'+file.id ).find('.progress').fadeOut();
			});
		
		</script>
	</body>
</html>

秒传:

我这里没有集成秒传功能,其实也很简单,就是上传之前拿文件的md5值去服务端查一下有没有,展示的html代码里也有一个打印了md5值的,在上传分片之前带上md5值调一下接口查一下。然后后端上传完文件可以将md5值和文件路径存起来,redis、mysql都可以,找到有一样的md5值直接算上传完成

结尾:

到这我的分享就结束了,可能代码有些纰漏,有什么问题欢迎大家留言。谢谢大家的阅读。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值