web项目文件上传下载演变总结

  背景:文件上传下载对于在互联网开发中是一个比较常见的功能,本文将分别对上传和下载进行阐述,只关心核心功能,其他边缘功能不在叙述,每个功能采用演变的思想,给出几种方案,当然每种方案都能完成需求,希望大家根据项目的需求以及上传文件大小挑选出适合自己的方案。

1 文件上传

    以下所有上传方案只针对单文件上传做优化,通过简单的封装和多线程的改写,可以支持多线程上传。另外在上传过程中边缘功能,例如文件大小限制,格式判断,文件摘要,进度条等不在此次方案中赘述。

 1> 文件上传精简版

   本方案是所有开发者首先想到的方案,也是不愿意折腾的方案,适合小文件上传,具体流程为:

   1 前端发送文件流  2 后端顺序接受 3 操作文件流保存为文件

前端表单

<form action="http://localhost:8081/upload" method="post" enctype="multipart/form-data" accept-charset="utf-8">
     <input type="file" name="file" value="选择文件"/>
     <input id="submit_form" type="submit"  value="提交"/>
</form>

备注:accept-chaset主要是为了解决中文文件名称乱码问题。

后端代码如下:

@Override
	public ServiceResult upload(HttpServletRequest req) {
		// TODO Auto-generated method stub
		// TODO Auto-generated method stub
		ServiceResult result = null;
		try {
			/*
			 * CommonsMultipartResolver resolver = new
			 * CommonsMultipartResolver(req.getSession().getServletContext());
			 * resolver.setDefaultEncoding("utf-8");
			 */
			MultipartHttpServletRequest params = ((MultipartHttpServletRequest) req);
			List<MultipartFile> files = params.getFiles("file"); // 多文件的上传
			if (files != null && files.size() > 0) {
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
				long time = System.currentTimeMillis();
				String prefix = "logs" + File.separator;
				String fullPath = uploadPath + prefix;
				File file = new File(fullPath);
				if (!file.exists()) {
					file.mkdirs();
				}
				// 某一个上传错误,全部中断
				List<String> fileNames = new ArrayList<String>();
				for (MultipartFile multipartFile : files) {
					if (multipartFile != null) {
						String fileName = multipartFile.getOriginalFilename();
						multipartFile.transferTo(new File(fullPath + fileName));
						fileNames.add(fileName);
					}
				}
				result = new ServiceResult<>();
				result.setData(fileNames);
			} else {
				result = new ServiceResult(HttpResultEnum.CLIENT_UNABLE_OBTAIN);
			}

		} catch (Exception e) {
			e.printStackTrace();
			result = new ServiceResult(HttpResultEnum.FAIL);
		}

		return result;

	}

备注:使用springmvc 上传,支持多文件上传。
 

扫描二维码关注公众号,回复: 4844622 查看本文章

 2> 分块上传

       当文件比较大,需要严格控制上传的时间时,那么我们就需要充分利用网络资源,压榨服务器性能。回想上一个方案中,上传文件作为一个整体被发送到服务端,服务端也按照顺序一个字节一个字节的读取,按照目前的服务器配置大多都是双cpu,多核心,那么我们可以同时让其处理一个文件的多个部分,从而缩短上传时间。

  分块上传需要前后端配合完成,对于分块上传前端的编程思路为:

     1 确定分块的大小   2 异步的发送数据块(根据情况,不要并发太多,3-5个足以)  

    前端js分段代码

<script>
        function Upload() {
            var files = document.getElementById('myFile').files;
            var file = files[0];
            var totalSize = file.size;//文件大小
            var blockSize = 1024 * 1024 * 2;//块大小
            var count = Math.ceil(totalSize / blockSize);//总块数

            for(var i =0;i<count;i++){
            
            //创建FormData对象
            var formData = new FormData();
            formData.append('fileName', file.name);//文件名
            formData.append('total', blockCount);//总块数
            formData.append('index', index);//当前上传的块下标
            var start = index * blockSize;
            var end = Math.min(totalSize, start + blockSize);
            var block = file.slice(start, end);
            formData.set('data', block);
            $.ajax({
                url: 'localhost/upload',
                type: 'post',
                data: formData,
                processData: false,
                contentType: false,
                success: function (res) {
                   
                }
            });
         }
        }
    </script>

备注:前端计算好每块的md5值,用md5+index作为文件名,为断点续传做准备。

后端处理的步骤为:

 1  处理每块上传的数据  2 把每块上传的信息保存到数据库  3  每次上传完成后,查看当前所有分块是否上传完成,当上传完成后进行合并。

 代码如下:

        MultipartHttpServletRequest params = ((MultipartHttpServletRequest) req);
            //获取所有前端信息
			List<MultipartFile> files = params.getFiles("file"); // 多文件的上传
			String oleFileName = params.getParameter("oleFileName"); //文件名称md5
			String fileName = params.getParameter("fileName"); //文件名称md5
			String total = params.getParameter("total");   //总共有几块
			String index = params.getParameter("index");   //当前是第几块
            //保存块文件
		    multipartFile.transferTo(new File(fullPath + fileName));
            //把块相关信息插入数据库
			saveInfo(FileChunkForm);	
            //判断是否已经上传完成,当所有的都上传完成进行合并
			if(checkFinish(oleFileName)==Integer.parseInt(total)){
				mergeFile(oleFileName);
			}

 3> 断点续传

  此方案是一个优化的策略,可以和前两个方案结合,按照字面意思理解断点续传,最重要的是要断点在哪,所以就需要服务端做记录,保存上次客户端上传之前先获取该文件在服务端的状态。 web前端可以选择插件类似WebUploader等完成断点续传,也可以采用分块的思想自行编写代码。

private void upload(long[] startPos, long[] endPos, File tmpfile) {
	        RandomAccessFile rantmpfile = null;
	        try {
	            if (tmpfile.exists()) {
	                rantmpfile = new RandomAccessFile(tmpfile, "rw");
	                for (int i = 0; i < threadNum; i++) {
	                    rantmpfile.seek(8 * i + 8);
	                    startPos[i] = rantmpfile.readLong();

	                    rantmpfile.seek(8 * (i + 1000) + 16);
	                    endPos[i] = rantmpfile.readLong();
	                }
	            } 
	        } catch (FileNotFoundException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        } finally {
	            try {
	                if (rantmpfile != null) {
	                    rantmpfile.close();
	                }
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	    }

备注:方案1中改造后端主要通过RandomAccessFile 完成断点续传,方案2中因为已经拆分为块,续传只需要控制到块级别就行就行(已经完成的块不在上传,没有完成的块重头开始上传)。

2 文件下载

     未完待续

猜你喜欢

转载自blog.csdn.net/sky1988818/article/details/86074996