• JavaEE系列之(二)commonsfileupload实现文件上传、下载


    摘自互联网

    一、文件上传概述

        实现Web开发中的文件上传功能,需要两步操作:
        1、在Web页面中添加上传输入项
    1.  
             <form action="#" method="post" enctype="multipart/form-data">
                  <input type="file" name="filename1"/><br>
                  <input type="file" name="filename2"/><br>
                  <input type="submit" value="上传"/>
             <form>
             <!-- 1、表单方式必须是post
                  2、必须设置encType属性为 multipart/form-data.设置该值后,浏览器在上传文件时,将会把文件数据附带在http请求消息体中,
      并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。 3、必须要设置input的name属性,否则浏览器将不会发送上传文件的数据。 -->
       

           2、在Servlet中读取文件上传数据,并保存到服务器硬盘

                 Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在Servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。
                 比如下面是截取的浏览器上传文件时发送的请求的HTTP协议中的部分内容:         
     
    Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
    Content-Type: multipart/form-data; boundary=---------------------------7dfa01d1908a4
    UA-CPU: AMD64
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like Gecko
    Content-Length: 653
    Host: localhost:8080
    Connection: Keep-Alive
    Pragma: no-cache
    Cookie: JSESSIONID=11CEFF8E271AB62CE676B5A87B746B5F
    -----------------------------7dfa01d1908a4
    Content-Disposition: form-data; name="username"
    zhangsan
    -----------------------------7dfa01d1908a4
    Content-Disposition: form-data; name="userpass"
    1234
    -----------------------------7dfa01d1908a4
    Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload.txt"
    Content-Type: text/plain
    this is first file content!
    -----------------------------7dfa01d1908a4
    Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload2.txt"
    Content-Type: text/plain
    this is Second file content!
    hello
    -----------------------------7dfa01d1908a4--
     

                 从上面的数据中也可以看出,如果自己手工的去分割读取数据很难写出健壮稳定的程序。所以,为方便用户处理上传数据,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。

                 需要导入两个jar包:Commons-fileupload、commons-io
     
      1         response.setContentType("text/html;charset=utf-8");//设置响应编码
      2         request.setCharacterEncoding("utf-8");
      3         PrintWriter writer = response.getWriter();//获取响应输出流
      4         
      5         ServletInputStream inputStream = request.getInputStream();//获取请求输入流
      6         
      7         /*
      8          * 1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
      9          *    该类有两个构造方法一个是无参的构造方法,
     10          *    另一个是带两个参数的构造方法
     11          * @param  int sizeThreshold,该参数设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件
     12          * @param  java.io.File repository,该参数指定临时文件目录,默认值为System.getProperty("java.io.tmpdir");
     13          * 
     14          *    如果使用了无参的构造方法,则使用setSizeThreshold(int sizeThreshold),setRepository(java.io.File repository)
     15          *    方法手动进行设置 
     16          */
     17         DiskFileItemFactory factory = new DiskFileItemFactory();
     18         
     19         int sizeThreshold=1024*1024;
     20         factory.setSizeThreshold(sizeThreshold);
     21         
     22         File repository = new File(request.getSession().getServletContext().getRealPath("temp"));
     23 //        System.out.println(request.getSession().getServletContext().getRealPath("temp"));
     24 //        System.out.println(request.getRealPath("temp"));
     25         factory.setRepository(repository);
     26         
     27         /*
     28          * 2、使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小
     29          *    
     30          *    ServletFileUpload对象负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem
     31          *    该对象的常用方法有:
     32          *           boolean isMultipartContent(request);判断上传表单是否为multipart/form-data类型
     33          *           List parseRequest(request);解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合
     34          *           void setFileSizeMax(long filesizeMax);设置单个上传文件的最大值
     35          *           void setSizeMax(long sizeMax);设置上传温江总量的最大值
     36          *           void setHeaderEncoding();设置编码格式,解决上传文件名乱码问题
     37          */
     38         ServletFileUpload upload = new ServletFileUpload(factory);
     39         
     40         upload.setHeaderEncoding("utf-8");//设置编码格式,解决上传文件名乱码问题
     41         /*
     42          * 3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象
     43          */
     44         List<FileItem> parseRequest=null;
     45         try {
     46              parseRequest = upload.parseRequest(request);
     47         } catch (FileUploadException e) {
     48             e.printStackTrace();
     49         }
     50         /*
     51          * 4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是文件上传
     52          *    true表示是普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
     53          *    false为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据
     54          *    
     55          *    FileItem用来表示文件上传表单中的一个上传文件对象或者普通的表单对象
     56          *    该对象常用方法有:
     57          *         boolean isFormField();判断FileItem是一个文件上传对象还是普通表单对象
     58          *         true表示是普通表单字段,
     59          *                 则调用getFieldName、getString方法得到字段名和字段值
     60          *         false为上传文件,
     61          *                 则调用getName()获得上传文件的文件名,注意:有些浏览器会携带客户端路径,需要自己减除
     62          *                 调用getInputStream()方法得到数据输入流,从而读取上传数据
     63          *                 delete(); 表示在关闭FileItem输入流后,删除临时文件。
     64          */
     65         
     66         for (FileItem fileItem : parseRequest) {
     67             if (fileItem.isFormField()) {//表示普通字段
     68                 if ("username".equals(fileItem.getFieldName())) {
     69                     String username = fileItem.getString();
     70                     writer.write("您的用户名:"+username+"<br>");
     71                 }
     72                 if ("userpass".equals(fileItem.getFieldName())) {
     73                     String userpass = fileItem.getString();
     74                     writer.write("您的密码:"+userpass+"<br>");
     75                 }
     76                 
     77             }else {//表示是上传的文件
     78                 //不同浏览器上传的文件可能带有路径名,需要自己切割
     79                 String clientName = fileItem.getName();
     80                 String filename = "";
     81                 if (clientName.contains("\\")) {//如果包含"\"表示是一个带路径的名字,则截取最后的文件名
     82                     filename = clientName.substring(clientName.lastIndexOf("\\")).substring(1);
     83                 }else {
     84                     filename = clientName;
     85                 }
     86                 
     87                 UUID randomUUID = UUID.randomUUID();//生成一个128位长的全球唯一标识
     88                 
     89                 filename = randomUUID.toString()+filename;
     90                 
     91                 /*
     92                  * 设计一个目录生成算法,如果所用用户上传的文件总数是亿数量级的或更多,放在同一个目录下回导致文件索引非常慢,
     93                  * 所以,设计一个目录结构来分散存放文件是非常有必要,且合理的
     94                  * 将UUID取哈希算法,散列到更小的范围,
     95                  * 将UUID的hashcode转换为一个8位的8进制字符串,
     96                  * 从这个字符串的第一位开始,每一个字符代表一级目录,这样就构建了一个八级目录,每一级目录中最多有16个子目录
     97                  * 这无论对于服务器还是操作系统都是非常高效的目录结构
     98                  */
     99                 int hashUUID =randomUUID.hashCode();
    100                 String hexUUID = Integer.toHexString(hashUUID);
    101                 //System.out.println(hexUUID);
    102                 //获取将上传的文件存存储在哪个文件夹下的绝对路径
    103                 String filepath=request.getSession().getServletContext().getRealPath("upload");
    104                 for (char c : hexUUID.toCharArray()) {
    105                     filepath = filepath+"/"+c;
    106                 }
    107                 //如果目录不存在就生成八级目录
    108                 File filepathFile = new File(filepath);
    109                 if (!filepathFile.exists()) {
    110                     filepathFile.mkdirs();
    111                 }
    112                 //从Request输入流中读取文件,并写入到服务器
    113                 InputStream inputStream2 = fileItem.getInputStream();
    114                 //在服务器端创建文件
    115                 File file  = new File(filepath+"/"+filename);
    116                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
    117                 
    118                 byte[] buffer = new byte[10*1024];
    119                 int len = 0;
    120                 while ((len= inputStream2.read(buffer, 0, 10*1024))!=-1) {
    121                     bos.write(buffer, 0, len);
    122                 }
    123                 writer.write("您上传文件"+clientName+"成功<br>");
    124                 //关闭资源
    125                 bos.close();
    126                 inputStream2.close();
    127             }
    128         }
    129     //注意Eclipse的上传的文件是保存在项目的运行目录,而不是workspace中的工程目录里。
     

    二、文件上传需要特别注意的问题:(这些问题在上面的代码中都提供了简单的解决)

           1、文件存放的位置
              为保证服务器的安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录,如果用户上传一个带有可执行代码的文件,如jsp文件,根据拼接访问路径去访问的话,可以在服务器端做任何事情。
           2、为防止多用户上传形同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
              使用UUID + 用户上传文件名的方式重命名
              关于UUID:
                 UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。
                是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID。从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义上)3240年不重复。
                从JDK1.5开始,生成UUID变成了一件简单的事,以为JDK实现了UUID:
    java.util.UUID,直接调用即可.
    UUID uuid  =  UUID.randomUUID();
    String s = UUID.randomUUID().toString();//用来生成数据库的主键id非常不错。。  
     
    UUID是由一个十六位的数字组成,表现出来的形式例如
    550E8400-E29B-11D4-A716-446655440000 

           3、为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应该应根据可能的上传总量,选择合适的目录结构生成算法,将上传文件分散存储。如使用hashcode方法构建多级目录。

           4、如果不同用户都上传了相同的文件,那么在服务器端没有必要存储同一个文件的很多分拷贝,这样很浪费资源,应该设计算法解决这种重复文件的问题。
           5、JSP技术原理自动实现了多线程。所以开发者不需要考虑上传文件的多线程操作
     
    三、文件下载  
     
    <%
            ArrayList<String> fileNames = new ArrayList<String>();
            fileNames.add("file/aa.txt");
            fileNames.add("file/bb.jpg");
            for(String fileName : fileNames) {
          %>
         
            <form action="DownloadServlet" method="get">
                <input type="hidden" name="fileName" value="<%=fileName %>" />
                <input type="submit" value="下载:<%=fileName %>" />
            </form>
         <%
            }
         %>
     
     
            request.setCharacterEncoding("utf-8");
            
            String filename = request.getParameter("fileName");
            
            
            String urlname = URLEncoder.encode(filename, "utf-8");//防止文件名中有中文乱码
            response.setHeader("Content-Disposition","attachment;filename="+urlname);
            
            FileInputStream fis = new FileInputStream(new File(request.getSession().getServletContext().getRealPath(filename)));
            BufferedInputStream bis = new BufferedInputStream(fis);
            ServletOutputStream sos = response.getOutputStream();
            
            byte[] buffer = new byte[1024];
            int len=0;
            while((len=bis.read(buffer, 0, 1024))!=-1){
                sos.write(buffer, 0, len);
            }
            bis.close();
            fis.close();
     
    四、在SSH中使用smartUpload组件简化文件上传下载
  • 相关阅读:
    proto,生成指定包名的java类
    龋齿比较深了还能补吗
    Spring Security with JWT for REST API[转]
    ci-Model使用方法
    ci-crud增删改查基本操作
    openjdk11 下载地址
    System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
    Newtonsoft.Json高级用法之枚举中文转义
    java中文与unicode编码之间的转换
    SpringBoot参数校验-Validator
  • 原文地址:https://www.cnblogs.com/kitor/p/9727091.html
Copyright © 2020-2023  润新知