服务器之家

服务器之家 > 正文

JavaEE组件commons-fileupload实现文件上传、下载

时间:2020-06-23 11:47     来源/作者:rocomp

一、文件上传概述

实现Web开发中的文件上传功能,需要两步操作:

1、在Web页面中添加上传输入项

?
1
2
3
4
5
6
7
8
9
10
<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协议中的部分内容:        

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

三、文件下载 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%
    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组件简化文件上传下载

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
沙雕群名称大全2019精选 今年最火的微信群名沙雕有创意
沙雕群名称大全2019精选 今年最火的微信群名沙雕有创意 2019-07-07
玄元剑仙肉身有什么用 玄元剑仙肉身境界等级划分
玄元剑仙肉身有什么用 玄元剑仙肉身境界等级划分 2019-06-21
男生常说24816是什么意思?女生说13579是什么意思?
男生常说24816是什么意思?女生说13579是什么意思? 2019-09-17
返回顶部