Web笔记-上传下载

文件的上传
  1. 文件上传的条件和原理

文件上传页面书写必须符合以下条件:

  • Form表单的请求方式必须为post
  • Form表单中提供type=”file”类型的上传输入框
  • Form表单enctype属性必须是multipart/form-data,enctype默认值为application/x-www-urlencoded

文件上传原理:
使用request.getInputStream();通过流来取得用户上传的数据,关键问题是流的解析
图1

图2
2、借助三方组件实现文件上传
使用Apache提供的commons-fileupload组件进行上传功能开发(内部核心封装的为文件流解析方法)
commons-io为fileupload组件在1.1开始就依赖的jar包,用于辅助文件的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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package com.gaojinze.web.upload.servlet;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
/**
* 使用第三方组件进行上传
* apache的
* commons-fileupload.jar:核心内容解析请求正文实现上传
* commons-io.jar:是apache对jdk对java.io.*对扩展和增强,fileupload从1.1就依赖该包
* @author gaopengfei
*
*/
public class UploadServlet3 extends HttpServlet {
private static final long serialVersionUID = 1L;
public UploadServlet3() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//检验表单enctype属性
boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
if (!isMultipartContent) {
throw new RuntimeException("Please check your form enctype attribute,the value msut be multipart/form-data");
}
//创建核心解析器实例
DiskFileItemFactory factory = new DiskFileItemFactory();//创建工厂实例,该工厂可以创建FileItem对象
ServletFileUpload upload = new ServletFileUpload(factory);//利用工厂创建解析器实例
upload.setFileSizeMax(4 * 1024 * 1024);//设置单个文件的上传大小为4M
upload.setSizeMax(8 * 1024 * 1024);//设置总上传文件的大小为8M
List<FileItem> fileItems = new ArrayList<>();
try {
fileItems = upload.parseRequest(request);
} catch (FileUploadBase.FileSizeLimitExceededException e) {
response.getWriter().write("单个文件上传大小不得超过4M");
}catch (FileUploadBase.SizeLimitExceededException e) {
response.getWriter().write("多文件上传总大小不得超过8M");
}catch (FileUploadException e) {
e.printStackTrace();
}
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
//非上传字段
processFormField(fileItem);
}else {
//上传字段
processUploadField(fileItem);
}
}
response.getWriter().write("上传成功!");
response.setHeader("refresh", "1;url=" + request.getContextPath() + "/upload.jsp");
// response.sendRedirect(request.getContextPath() + "/upload.jsp");
}
/**
* 处理非上传字段
* @param fileItem
*/
private void processFormField(FileItem fileItem) {
String fieldName = fileItem.getFieldName();
String fieldValue = null;
try {
fieldValue = fileItem.getString("UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(fieldName + ":" + fieldValue);
}
/**
* 处理上传文件
* @param fileItem
*/
private void processUploadField(FileItem fileItem) {
try {
//获取文件名(因为浏览器不同导致在选择完文件后的name也不同,如IE选择后,文件名为:D:\XXX\a.txt)
String fileName = fileItem.getName();
//判断是否有未选择的空项
if ("".equals(fileName) || null == fileName) {
return;
}
//通过扩展名来限制上传文件类型
String extension = FilenameUtils.getExtension(fileName);
//获取上传文件的MIME类型
String contentType = fileItem.getContentType();
if (!extension.equalsIgnoreCase("jpg"))
return;
if (!contentType.startsWith("image")) {
return;
}
fileName = FilenameUtils.getName(fileName);
String uuidFileName = UUID.randomUUID().toString() + "_" + fileName;
//得到存放文件的真实路径
String uploadBasePath = getServletContext().getRealPath("/WEB-INF/files");
// String childUploadDir = getChildUploadDir(uploadBasePath);
String childUploadDir = getChildUploadDir2(uploadBasePath, uuidFileName);
//保存文件,并清理缓存
fileItem.write(new File(uploadBasePath + File.separator +childUploadDir, uuidFileName));
} catch (Exception e) {
throw new RuntimeException("上传失败!");
}
}
/**
* 按照日期进行子目录创建
* @param uploadPath
* @return
*/
private String getChildUploadDir(String uploadBasePath) {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String childUploadDir = dateFormat.format(date);
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目录不存在则创建
childDir.mkdirs();
}
return childUploadDir;
}
/**
* 通过uuid的hascode分散目录
* @param uploadBasePath
* @param uuidFileName
* @return
*/
private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
int hashCode = uuidFileName.hashCode();
//作为一级目录名
int dir1 = hashCode&0xf;
//作为二级目录名
int dir2 = (hashCode&0xf0)>>4;
String childUploadDir = dir1 + File.separator + dir2;
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目录不存在则创建
childDir.mkdirs();
}
return childUploadDir;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

3、三方组件核心类介绍以及实际开发中需要注意的细节

  • 如何保证服务器安全?
    将文件上传目录放置在WEB-INF目录下即可
  • 如何避免文件重名而导致上传后文件覆盖?
    创建唯一文件名,使用UUID.randomUUID().toString() + “_” + fileName;即可
  • 如何避免同一个目录文件过多而查找难?
    解决方法:
    a)以日期分类目录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 按照日期进行子目录创建
    * @param uploadPath
    * @return
    */
    private String getChildUploadDir(String uploadBasePath) {
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    String childUploadDir = dateFormat.format(date);
    File childDir = new File(uploadBasePath, childUploadDir);
    if (!childDir.exists()) {
    //若目录不存在则创建
    childDir.mkdirs();
    }
    return childUploadDir;
    }

b)利用UUID文件名的hash码进行分散存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 通过uuid的hascode分散目录
* @param uploadBasePath
* @param uuidFileName
* @return
*/
private String getChildUploadDir2(String uploadBasePath, String uuidFileName){
int hashCode = uuidFileName.hashCode();
//作为一级目录名
int dir1 = hashCode&0xf;
//作为二级目录名
int dir2 = (hashCode&0xf0)>>4;
String childUploadDir = dir1 + File.separator + dir2;
File childDir = new File(uploadBasePath, childUploadDir);
if (!childDir.exists()) {
//若目录不存在则创建
childDir.mkdirs();
}
return childUploadDir;
}

hashcode

  1. 文件的限制
  • 限制文件上传大小
    Web上传不适合上传太大的文件,一般为2M

    1
    2
    3
    4
    5
    /创建核心解析器实例
    DiskFileItemFactory factory = new DiskFileItemFactory();//创建工厂实例,该工厂可以创建FileItem对象
    ServletFileUpload upload = new ServletFileUpload(factory);//利用工厂创建解析器实例
    upload.setFileSizeMax(4 * 1024 * 1024);//设置单个文件的上传大小为4M
    upload.setSizeMax(8 * 1024 * 1024);//设置总上传文件的大小为8M
  • 限制上传文件的类型
    通过MIME类型
    操作系统是根据扩展名来区分文件类型的
    信息传输的数据通过MIME来区分类型,详细的对应关系,可以查看tomcat目录下的web.xml有记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //通过扩展名来限制上传文件类型,该方式不靠谱
    String extension = FilenameUtils.getExtension(fileName);
    if (!extension.equalsIgnoreCase("jpg")) {
    return;
    }
    ------------------------------------------------------------
    //获取上传文件的MIME类型
    String contentType = fileItem.getContentType();
    if (!extension.equalsIgnoreCase("jpg"))
    return;
    if (!contentType.startsWith("image")) {
    return;
    }
    ------------------------------------------------------------
    //判断是否有未选择的空项
    if ("".equals(fileName) || null == fileName) {
    return;
    }
  1. 上传时的临时文件处理
    DiskFileItemFactory创建FileItem对象时会使用缓存,默认10kb,若上传文件大小超过10kb,则会采用磁盘进行缓存(磁盘缓存即为垃圾文件
    1
    2
    3
    4
    5
    6
    //创建核心解析器实例
    DiskFileItemFactory factory = new DiskFileItemFactory();//创建工厂实例,该工厂可以创建FileItem对象
    //设置缓存文件大小
    factory.setSizeThreshold(100 * 1024);
    //设置缓存文件的存放目录,默认是系统的临时文件目录
    factory.setRepository(new File("f:/"));

若使用原生io流进行文件读写存储,则在关闭流后使用fileItem.delete();方法进行缓存清理
若使用fileItem.write();进行读写存储,则无需处理,其内部已经进行流缓存清理

  1. 中文乱码问题
  • 普通字段中文乱码解决
    String fieldValue = fileItem.getString("UTF-8");
  • 中文文件名乱码解决
    request.setCharacterEncoding("UTF-8");

文件的下载
  • FilelistServlet(文件列表获取)

    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
    package com.gaojinze.web.upload.servlet.download;
    import java.io.File;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    /**
    * 文件列表Servlet
    * @author gaopengfei
    */
    public class FilelistServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public FilelistServlet() {
    super();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //创建存储key为文件在服务器中的名称,value为用户上传文件名
    Map<String, String> fileMap = new HashMap<>();
    String fileBasePath = getServletContext().getRealPath("/WEB-INF/files");
    File uploadRootDir = new File(fileBasePath);
    findAllFiles(uploadRootDir, fileMap);
    request.setAttribute("fileMap", fileMap);
    request.getRequestDispatcher("/filelist.jsp").forward(request, response);
    }
    /**
    * 遍历服务器所有文件并将文件信息存储到map中
    * @param file
    * @param fileMap
    */
    private void findAllFiles(File file, Map<String, String> fileMap) {
    if (file.isFile()) {
    String fileUUIDName = file.getName();
    String fileRealName = fileUUIDName.substring(fileUUIDName.indexOf("_") + 1);
    fileMap.put(fileUUIDName, fileRealName);
    }else {
    //如果是目录则递归进行遍历
    File[] listFiles = file.listFiles();
    if (listFiles == null || listFiles.length == 0) {
    return;
    }
    for (File f : listFiles) {
    findAllFiles(f, fileMap);
    }
    }
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doGet(request, response);
    }
    }
  • filelist.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
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>文件列表</title>
    </head>
    <body>
    <h3 align="center">文件列表</h3>
    <table align="center" border="1">
    <tr>
    <th>文件名</th>
    <th>操作</th>
    </tr>
    <c:forEach items="${ fileMap }" var="mk">
    <!-- 以下标签用于构建一个URL /Web-Upload/servlet/Download?filename=经过url编码的中文 -->
    <c:url var="url" value="/servlet/DownloadServlet">
    <c:param name="filename" value="${ mk.key }"></c:param>
    </c:url>
    <tr>
    <td>${ mk.value }</td>
    <!-- 中文名跟在URL后需要进行URL编码 -->
    <!-- ${pageContext.request.contextPath }"/servlet/DownloadServlet?filename=${ mk.key } -->
    <td><a href="${ url }">下载</a></td>
    </tr>
    </c:forEach>
    </table>
    </body>
    </html>
  • DownloadServlet(文件下载)

    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
    package com.gaoshiyi.web.upload.servlet.download;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.URLEncoder;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    /**
    * 文件下载
    * @author gaopengfei
    *
    */
    public class DownloadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public DownloadServlet() {
    super();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    String uuidName = request.getParameter("filename");
    String realName = uuidName.substring(uuidName.indexOf("_") + 1);
    // 上传文件跟目录
    String uploadRootPath = getServletContext().getRealPath("/WEB-INF/files");
    // 获取对应uuidname文件的子目录
    String childDirPath = getChildDirPathByUUIDName(uploadRootPath, uuidName);
    // 构建文件读取流
    InputStream in = new FileInputStream(new File(uploadRootPath + File.separator + childDirPath, uuidName));
    //处理下载时的文件名称中文问题
    String userAgent = request.getHeader("User-Agent");
    if (userAgent.contains("Firefox")) {
    //单独处理火狐浏览器
    realName = new String(realName.getBytes("UTF-8"), "ISO-8859-1");
    }else {
    //其他浏览器
    realName = URLEncoder.encode(realName, "UTF-8");
    }
    // 告知浏览器以下载方式下载
    response.setHeader("Content-Disposition", "attachment;filename=" + realName);
    // 设置头信息告知客户端文件大小
    response.setContentLength(in.available());
    // 设置头信息告知客户端文件MIME类型
    response.setHeader("Content-Type", "application/octet-stream");
    // 输出
    OutputStream out = response.getOutputStream();
    int len = 0;
    byte[] buff = new byte[1024];
    while ((len = in.read(buff)) != 1) {
    out.write(buff, 0, len);
    }
    in.close();
    out.close();
    }
    /**
    * 获取子目录
    *
    * @param uploadRootPath
    * @param uuidName
    * @return
    */
    private String getChildDirPathByUUIDName(String uploadRootPath, String uuidName) {
    int hashCode = uuidName.hashCode();
    int dir1 = hashCode&0xf;
    int dir2 = (hashCode&0xf0) >> 4;
    String cDir = dir1 + File.separator + dir2;
    File childDir = new File(uploadRootPath, cDir);
    if (!childDir.exists()) {
    childDir.mkdirs();
    }
    return cDir;
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    doGet(request, response);
    }
    }
如果帮到了你,想打赏支持,喏~