zoukankan      html  css  js  c++  java
  • java大文件(百M以上)的上传下载分享

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。

    这次项目的需求:

    支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,ie8,ie9,Chrome,Firefox,360安全浏览器,并且刷新浏览器后仍然能够续传,重启浏览器(关闭浏览器后再打开)仍然能够继续上传,重启电脑后仍然能够上传

    支持文件夹的上传,要求服务端能够保留层级结构,并且能够续传。需要支持10万个以上的文件夹上传。

    支持低版本的系统和浏览器,因为这个项目的最终运行环境在政府,政府的配置都一般,职员都是办公用,内存都不大,基本上以Windows XP的系统为主。

    1、介绍enctype

    enctype 属性规定发送到服务器之前应该如何对表单数据进行编码。

    enctype作用是告知服务器请求正文的MIME类型(请求消息头content-type的作用一样)

    1、1 enctype的取值有三种

    描述

    application/x-www-form-urlencoded

    在发送前编码所有字符(默认)

    multipart/form-data

    不对字符编码。每一个表单项分割为一个部件

    text/plain

    空格转换为 “+” 加号,但不对特殊字符编码。

    1. 当enctype=’application/x-www-form-urlencoded’

    2.当enctype=’multipart/form-data’

    通过观察发现这个的请求体就发生了变化。这种请求体被称之为多部件请求体。

    什么是多部件请求体:就是把每一个表单项分割为一个部件。

    以请求头的content-type的boundary后面的一串随机字符串作为分割标识

    普通表单项:

    //name的意思是文本框里面name的属性值,而admin是我们输入的文本值

    Content-Disposition: form-data; name="username"

    admin

    文件表单项

    //filename的意思是:我们上传的文件名称,content-Type的意思是:MIME类型,asdasdas的意思是:文件里面的内容

    Content-Disposition: form-data; name="upload"; filename="a.txt"

    Content-Type: text/plain

    asdasdas

    3. 当enctype=’text/plain’

     

    w3c称:空格会变成”+”加号,但是我这里没有发现,只有当get请求的时候,空格会变成”+”号

     

    进入正题

    完成上传需要满足3个必要的条件

    提供form表单,method必须是post,因为get请求的传输数据一般为2kb,不同浏览器不一样。

    form表单属性enctype的必须是multipart/form-data

    提供input type=”file”类的上传输入域

    大致实现原理:当enctype的值是multipart/form-data时,浏览器会把每个表单项进行分割,分割成不同的部件,以boundary的值为分割标识,这个标识的字符串是随机生成的,最后一个表单项的分割标识字符串末尾会多两个”- -“,代表结束。服务端用request.getHeader(“content-type”)获取分割字符串,然后进行解析。

     

    代码实现

    一、开发环境搭建

    准备两个第三方jar包

    commons-io包

    commons-upload包

    所有依赖包

     

    代码实现

    <%@ page language="java" import="up6.DBFile" pageEncoding="UTF-8"%>

    <%@ page contentType="text/html;charset=UTF-8"%>

    <%@ page import="up6.FileBlockWriter" %>

    <%@ page import="up6.XDebug" %>

    <%@ page import="up6.*" %>

    <%@ page import="up6.biz.*" %>

    <%@ page import="org.apache.commons.fileupload.FileItem" %>

    <%@ page import="org.apache.commons.fileupload.FileItemFactory" %>

    <%@ page import="org.apache.commons.fileupload.FileUploadException" %>

    <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>

    <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>

    <%@ page import="org.apache.commons.lang.*" %>

    <%@ page import="java.net.URLDecoder"%>

    <%@ page import="java.util.Iterator"%>

    <%@ page import="net.sf.json.JSONObject"%>

    <%@ page import="java.util.List"%>

    <%

    out.clear();

    String uid             = request.getHeader("uid");//

    String id              = request.getHeader("id");

    String lenSvr      = request.getHeader("lenSvr");

    String lenLoc      = request.getHeader("lenLoc");

    String blockOffset = request.getHeader("blockOffset");

    String blockSize   = request.getHeader("blockSize");

    String blockIndex  = request.getHeader("blockIndex");

    String blockMd5        = request.getHeader("blockMd5");

    String complete        = request.getHeader("complete");

    String pathSvr         = "";

    //参数为空

    if(  StringUtils.isBlank( uid )

         || StringUtils.isBlank( id )

         || StringUtils.isBlank( blockOffset ))

    {

         XDebug.Output("param is null");return;

    }

    boolean isMultipart = ServletFileUpload.isMultipartContent(request);

    FileItemFactory factory = new DiskFileItemFactory();  

    ServletFileUpload upload = new ServletFileUpload(factory);

    List files = null;

    try {files = upload.parseRequest(request);}

    catch (FileUploadException e)

    {out.println("read file data error:" + e.toString());return;}

    FileItem rangeFile = null;

    Iterator fileItr = files.iterator();

    while (fileItr.hasNext())

    {

         rangeFile = (FileItem) fileItr.next();

         if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr"))

         {

             pathSvr = rangeFile.getString();

             pathSvr = PathTool.url_decode(pathSvr);

         }

    }

    boolean verify = false;

    String msg = "";

    String md5Svr = "";

    long blockSizeSvr = rangeFile.getSize();

    if(!StringUtils.isBlank(blockMd5)){md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream());}

    verify = Integer.parseInt(blockSize) == blockSizeSvr;

    if(!verify){ msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize;}

    if(verify && !StringUtils.isBlank(blockMd5))

    {

         verify = md5Svr.equals(blockMd5); if(!verify) msg = "block md5 error";

    }

    if(verify)

    {

         FileBlockWriter res = new FileBlockWriter();

         if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc));

         res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);

         up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex));

        

         JSONObject o = new JSONObject();

         o.put("msg", "ok");

         o.put("md5", md5Svr); 

         o.put("offset", blockOffset);

         msg = o.toString();

    }

    rangeFile.delete();

    out.write(msg);

    %>

    下载的必须条件

    两个头一个流

    content-type

    Content-Type是返回消息中非常重要的内容,表示文档内容属于什么MIME类型。

    浏览器会根据Content-Type来决定如何显示返回的消息体内容。

    默认值是text/html

    可以使用request.getServletContext().getMimeType(“文件名”)获取MIME类型。

    Content-Disposition

    Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。

    默认值是inline,表示在浏览器窗口中打开。

    服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器 中显示。

    如果需要提示用户保存,利用Content-Disposition进行一下处理,关键在于一定要加上attachment。

    例如:Content-Disposition:attachment;filename=xxx,浏览器就会激活下载框对话框, attachment 表示附件, filname 后面跟随的是显示在下载框中的文件名称。

    下载就是向客户端响应字节数据! 将一个文件变成字节数组, 使用 response.getOutputStream()

    来响应给浏览器。

    代码如下,此代码已经实现了断点续传功能,用户在下载过程可以暂停,和继续下载,对服务器造成的压力也比较小。

    String fid             = request.getHeader("id");

    String blockIndex = request.getHeader("blockIndex");//基于1

    String blockOffset     = request.getHeader("blockOffset");//块偏移,相对于整个文件

    String blockSize   = request.getHeader("blockSize");//块大小(当前需要下载的大小)

    String pathSvr         = request.getHeader("pathSvr");//文件在服务器的位置

    pathSvr            = PathTool.url_decode(pathSvr);

    if (  StringUtils.isBlank(fid)

         ||StringUtils.isBlank(blockIndex)

         ||StringUtils.isEmpty(blockOffset)

         ||StringUtils.isBlank(blockSize)

         ||StringUtils.isBlank(pathSvr))

    {

         response.setStatus(500);

         response.setHeader("err","参数为空");

         return;

    }

    File f = new File(pathSvr);

    //文件不存在

    if(!f.exists())

    {

         response.setStatus(500);

         OutputStream os = response.getOutputStream();

         System.out.println(String.format("%s 文件不存在",pathSvr));

         os.close();

         return;

    }

    long fileLen = f.length();

    response.setContentType("application/x-download");

    response.setHeader("Pragma","No-cache"); 

    response.setHeader("Cache-Control","no-cache");

    response.addHeader("Content-Length",blockSize); 

    response.setDateHeader("Expires", 0);

    OutputStream os = response.getOutputStream();

    try

    {

         RandomAccessFile raf = new RandomAccessFile(pathSvr,"r");

        

         int readToLen = Integer.parseInt(blockSize);

         int readLen = 0;

         raf.seek( Long.parseLong(blockOffset) );//定位索引

         byte[] data = new byte[1048576];

        

         while( readToLen > 0 )

         {

             readLen = raf.read(data,0,Math.min(1048576,readToLen) );

             readToLen -= readLen;

             os.write(data, 0, readLen);

            

         }

         os.flush();

         os.close();  

         raf.close();

         os = null;

         response.flushBuffer();

        

         out.clear();

         out = pageContext.pushBody();

    }

    catch(Exception e)

    {

         response.setStatus(500);

         os.close();

         out.close();

         e.printStackTrace();

    }

    finally

    {   

         if(os != null)

         {

             os.close();       

             os = null;

         }

         out.clear();

         out = pageContext.pushBody();

    }%>

    加载文件列表,在下载列表中显示出来

    后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/

    欢迎入群一起讨论“374992201”

  • 相关阅读:
    [ Algorithm ] N次方算法 N Square 动态规划解决
    [ Algorithm ] LCS 算法 动态规划解决
    sql server全文索引使用中的小坑
    关于join时显示no join predicate的那点事
    使用scvmm 2012的动态优化管理群集资源
    附加数据库后无法创建发布,error 2812 解决
    浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法
    windows 2012 r2下安装sharepoint 2013错误解决
    sql server 2012 数据引擎任务调度算法解析(下)
    sql server 2012 数据引擎任务调度算法解析(上)
  • 原文地址:https://www.cnblogs.com/songsu/p/13686604.html
Copyright © 2011-2022 走看看