zoukankan      html  css  js  c++  java
  • 使用fileupload实现文件上传

    一. fileupload组件工作原理

    先来张图片, 帮助大家理解

    fileupload核心API

    1. DiskFileItemFactory
    构造器
    1) DiskFileItemFactory() // 使用默认配置
    2) DiskFileItemFactory(int sizeThreshold, File repository)
      sizeThreshold 内存缓冲区, 不能设置太大, 否则会导致JVM崩溃
      repository 临时文件目录

    2. ServletFileUpload
    1) isMutipartContent(request) // 判断上传表单是否为multipart/form-data类型 true/false
    2) parseRequest(request) // 解析request, 返回值为List<FileItem>类型
    3) setFileSizeMax(long) // 上传文件单个最大值 fileupload内部通过抛出异常的形式处理, 处理文件大小超出限制, 可以通过捕获这个异常, 提示给用户
    4) setSizeMax(long) // 上传文件总量最大值
    5) setHeaderEncoding(String) // 设置编码格式
    6) setProgressListener(ProgressListener) // 设置监听器, 可以用于制作进度条

    二. 使用fileupload实现文件上传

    1. 编写JSP

     1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     2 <html>
     3 <head>
     4     <title>演示文件上传</title>
     5 </head>
     6 <body>
     7     <form action="${pageContext.request.contextPath}/servlet/FileUpload1" method="post" enctype="multipart/form-data">
     8         用户名: <input type="text" name="username"/><br/>
     9         文件1: <input type="file" name="file1"/><br/>
    10         文件2: <input type="file" name="file2"/><br/>
    11         <input type="submit"/>
    12     </form>
    13 </body>
    14 </html>

    要点:

    1) 表单包含file类型输入项时, enctype属性必须设置为multipart/form-data

    2) input:file必须指定name属性

    3) 表单提交方式为post, 因为get请求无法携带大量数据

    4) 若表单的提交方式为multipart/form-data, 那么在Servlet就无法使用getParameter方法获取表单数据, 可以通过获取客户机提交数据的输入流来获取所有上传数据, 然后进行解析.

    1 // 获取客户机提交数据的输入流
    2 request.getInputStream();

    5) 解析数据难度较大, 一般不自己编写程序, 可以使用开源项目解析数据

    2. 编写Servlet

     1 public class FileUpload1 extends HttpServlet {
     2     @Override
     3     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     4 
     5         InputStream in = null;
     6         OutputStream out = null;
     7 
     8         try {
     9             // 使用默认配置创建解析器工厂
    10             DiskFileItemFactory factory = new DiskFileItemFactory();
    11             // 获取解析器
    12             ServletFileUpload upload = new ServletFileUpload(factory);
    13             // 上传表单是否为multipart/form-data类型
    14             if (!upload.isMultipartContent(request)) {
    15                 return;
    16             }
    17             // 解析request的输入流
    18             List<FileItem> fileItemList = upload.parseRequest(request);
    19             // 迭代list集合
    20             for (FileItem fileItem : fileItemList) {
    21                 if (fileItem.isFormField()) {
    22                     // 普通字段
    23                     String name = fileItem.getFieldName();
    24                     String value = fileItem.getString();
    25                     System.out.println(name + "=" + value);
    26                 } else {
    27                     // 上传文件
    28                     // 获取上传文件名
    29                     String fileName = fileItem.getName();
    30                     fileName = fileName.substring(fileName.lastIndexOf("\")+1);
    31                     // 获取输入流
    32                     in = fileItem.getInputStream();
    33 
    34                     // 获取上传文件目录
    35                     String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
    36                     // 上传文件名若不存在, 则先创建
    37                     File savePathDir = new File(savePath);
    38                     if (!savePathDir.exists()) {
    39                         savePathDir.mkdir();
    40                     }
    41 
    42                     // 获取输出流
    43                     out = new FileOutputStream(savePath + "\" + fileName);
    44                     int len = 0;
    45                     byte[] buffer = new byte[1024];
    46                     while((len=in.read(buffer)) > 0) {
    47                         out.write(buffer, 0, len);
    48                     }
    49                 }
    50             }
    51         } catch (Exception e) {
    52             e.printStackTrace();
    53         } finally {
    54             if (in != null) {
    55                 in.close();
    56             }
    57             if (out != null) {
    58                 out.close();
    59             }
    60         }
    61 
    62     }
    63 
    64     @Override
    65     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    66         doGet(req, resp);
    67     }
    68 }

    1) 在WEB-INF中创建upload文件夹时. IDEA不会在out目录的WEB-INF中创建upload文件夹, 需要手动创建, 所以上面先检查upload文件夹是否存在

    2) 在finally中关闭流时, 应该先检查流是否为null, 否则当上传表单不为multipart/form-data类型时, 执行return后再执行finally, 程序就会出现NPE

    3) 记得在web.xml中配置servlet的映射路径

    3. 测试

    4. 使用浏览器抓包

    三. 禁止别人访问上传文件目录

    上传文件目录应该放在WEB-INF目录下, 禁止别人访问上传文件目录, 否则黑客可能通过上传脚本, 然后访问该脚本, 对网站发起攻击

    举例:

    1. 黑客上传一个JSP文件

    test.jsp
    1 <%
    2     Runtime.getRuntime().exec("shutdown -s -t 200")  // 执行Windows命令
    3 %>

    2. 通过访问该文件, 关闭服务器

    http://localhost:8080/upload/test.jsp

    备注:

    1) Runtime类  // 调用Windows程序

    2) Window命令:

      shutdown -a
      format c:

    四. 待解决的问题

    1. 解决上传文件名的中文乱码问题

    upload.setHeaderEncoding("UTF-8");

    2. 解决上传数据的中文乱码问题

    1) 表单为文件上传时, 设置request的编码无效

    request.setCharacterEncoding("UTF-8"); 

    2) 只能手工转化

    value = new String(value.getBytes("iso8859-1"), "UTF-8");

    3) 调用upload组件的getString的重载方法实现的效果是相同的

    value = upload.getString("UTF-8");

    3. 上传文件夹存储在WEB-INF中, 防止用户直接访问上传文件

    4. 文件名重复问题

    使用UUID作为上传文件的名称

    1 public String makeFileName(String fileName) {
    2     return UUID.randomUUID().toString() + "_" + fileName;
    3 }

    5. 使用hash算法产生图片上传的随机目录

    为了防止一个目录中出现太多文件, 使用算法打散存储

     1 public String makePath(String savePath, String fileName) {
     2     // 根据文件名产生int型hashcode, 32位二进制
     3     int hashcode = fileName.hashCode();
     4     // 获取第4位 0-15
     5     int dir1 = hashcode&0xf;
     6     // 获取第5-8位 0-15
     7     int dir2 = (hashcode&0xf0)>>4;
     8     // 凭借随机目录
     9     String dir = savePath + "\" + dir1 + "\" + dir2;  // upload24
    10     // 若目录不存在时, 创建目录
    11     File file = new File(dir);
    12     if(!file.exists()) {
    13         file.mkdirs();
    14     }
    15     return dir;
    16 }

    这里放张图片, 方便大家食用...

    5. 限制上传文件的最大值

    ServletFileUpload.setFileSizeMax(1024);方法实现,并通过捕获FileUploadBase.FileSizeLimitExceededException异常以给用户友好提示

    6. 确保临时文件被删除

    在处理完上传文件后,调用item.delete方法

    7. 限制上传文件的类型

    在收到上传文件名时,判断后缀名是否合法

    8. 监听文件上传进度

    1 ServletFileUpload upload = new ServletFileUpload(factory);
    2 upload.setProgressListener(new ProgressListener(){
    3     // pBytesRead 当前处理
    4     // pContentLength 文件总大小
    5     // arg2 当前解析的item
    6     public void update(long pBytesRead, long pContentLength, int arg2) {
    7         System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
    8     }
    9 });

    备注:

    1) 可以配合ajax+div/css生成进度条
    2) 监听器在request解析之前设置

    附: 改造后的Servlet

      1 public class FileUpload1 extends HttpServlet {
      2     @Override
      3     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      4 
      5         InputStream in = null;
      6         OutputStream out = null;
      7 
      8         // 获取上传文件目录
      9         String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
     10 
     11         try {
     12             // 使用默认配置创建解析器工厂
     13             DiskFileItemFactory factory = new DiskFileItemFactory();
     14             // 获取解析器
     15             ServletFileUpload upload = new ServletFileUpload(factory);
     16             upload.setProgressListener(new ProgressListener() {
     17                 @Override
     18                 public void update(long l, long l1, int i) {
     19                     System.out.println("文件大小为:" + l1 + ",当前已处理:" + l);
     20                 }
     21             });
     22             // 解决上传文件名的中文乱码问题
     23             upload.setHeaderEncoding("UTF-8");
     24             // 上传表单是否为multipart/form-data类型
     25             if (!upload.isMultipartContent(request)) {
     26                 return;
     27             }
     28             // 解析request的输入流
     29             List<FileItem> fileItemList = upload.parseRequest(request);
     30             // 迭代list集合
     31             for (FileItem fileItem : fileItemList) {
     32                 if (fileItem.isFormField()) {
     33                     // 普通字段
     34                     String name = fileItem.getFieldName();
     35                     // 调用getString重载方法, 解决上传数据的中文乱码问题
     36                     String value = fileItem.getString("UTF-8");
     37                     System.out.println(name + "=" + value);
     38                 } else {
     39                     // 上传文件
     40                     // 获取上传文件名
     41                     String fileName = fileItem.getName();
     42                     // input:file没有指定上传文件时, 结束本次循环并继续下一次循环
     43                     if(fileName == null && fileName.trim().equals("")) {
     44                         continue;
     45                     }
     46                     fileName = fileName.substring(fileName.lastIndexOf("\")+1);
     47                     // 使用UUID作为上传文件的名称
     48                     fileName = makeFileName(fileName);
     49                     // 获取输入流
     50                     in = fileItem.getInputStream();
     51 
     52                     // 上传文件名若不存在, 则先创建
     53                     File savePathDir = new File(savePath);
     54                     if (!savePathDir.exists()) {
     55                         savePathDir.mkdir();
     56                     }
     57 
     58                     // 使用hash算法产生当前上传图片的随机目录
     59                     String currentFileSavePath = makePath(savePath, fileName);
     60 
     61                     // 获取输出流
     62                     out = new FileOutputStream(currentFileSavePath + "\" + fileName);
     63                     int len = 0;
     64                     byte[] buffer = new byte[1024];
     65                     while((len=in.read(buffer)) > 0) {
     66                         out.write(buffer, 0, len);
     67                     }
     68                 }
     69             }
     70         } catch (Exception e) {
     71             e.printStackTrace();
     72         } finally {
     73             if (in != null) {
     74                 in.close();
     75             }
     76             if (out != null) {
     77                 out.close();
     78             }
     79         }
     80 
     81     }
     82     public String makeFileName(String fileName) {
     83         return UUID.randomUUID().toString() + "_" + fileName;
     84     }
     85     public String makePath(String savePath, String fileName) {
     86         // 根据文件名产生int型hashcode, 32位二进制
     87         int hashcode = fileName.hashCode();
     88         // 获取第4位 0-15
     89         int dir1 = hashcode&0xf;
     90         // 获取第5-8位 0-15
     91         int dir2 = (hashcode&0xf0)>>4;
     92         // 凭借随机目录
     93         String dir = savePath + "\" + dir1 + "\" + dir2;  // upload24
     94         // 若目录不存在时, 创建目录
     95         File file = new File(dir);
     96         if(!file.exists()) {
     97             file.mkdirs();
     98         }
     99         return dir;
    100     }
    101 
    102     @Override
    103     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    104         doGet(req, resp);
    105     }
    106 }

     效果预览:

  • 相关阅读:
    Winform中实现ZedGraph曲线图缩放后复原功能
    MySQL Workbench 安装失败 Mysql workbench requires the visual C++ 2019 redistributable package
    MySQL Workbench 8.0提示SSL connection error: SSL is required but the server doesn‘t support it
    域名证书有效,但是访问提示不安全连接
    图片Base64编码
    centos系统设置防火墙
    《中有成就秘笈》之中央密严刹土
    Arweave
    去中心化身份聚合器
    区块链跨链技术
  • 原文地址:https://www.cnblogs.com/shaohsiung/p/9536901.html
Copyright © 2011-2022 走看看