zoukankan      html  css  js  c++  java
  • JavaWeb 之文件上传

    1. 文件上传的要求

    1.1 上传对表单的限制

    • method="post";
    • enctype="multipart/form-data";
    • 表单中需要添加文件表单项: <input type="file" name="xxx"/>.

    1.2 上传对 Servlet 的限制

    • request.getParameter("xxx") 方法返回的是字符串类型, 所以在表单中有
      enctype="multipart/form-data"时, 该方法作废了, 因为它永远都返回 null.
    • 应该使用 ServletInputStream request.getInputStream(), 返回结果中包含整个请求体.
    • 上传不能使用 BaseServlet,因为BaseServlet 内部调用了 getParameter() 方法.

    2. 多部件表单体

    1. 一个表单被分割出多个部件,即一个表单项一个部件;
    2. 一个部件中自己包含请求头和空行, 以及请求体;
    3. 普通表单项
      • 包含一个请求头: Content-Disposition:xxx; name="表单项名称";
      • 请求体就是表单项的值.
    4. 文件表单项
      • 包含两个请求头:
      • Content-Disposition:xxx; name="表单项名称"; filename="上传文件的名称";
      • Content-Type: 上传文件的 MIME 类型;
      • 请求体就是上传文件的内容.

    3. 相关 jar 包

    • commons-fileupload.jar
    • commons-io.jar
    • 这个组件会解析 request 中的上传数据, 解析后的结果是,一个表单项数据封装到一个 FileItem 对象中.
      我们只需要调用 FileItem 的方法即可.

    4. 上传三步

    4.1 上传涉及的相关类

    • 工厂类: DiskFileItemFactory;
    • 解析器类: ServletFileUpload;
    • 表单项类: FileItem;

    4.2 具体步骤

    • 创建工厂: DiskFileItemFactory factory = new DiskFileItemFactory();
    • 创建解析器: ServletFileUpload sfu = new ServletFileUpload(factory);
    • 使用解析器来解析 request, 得到FileItem集合: List<FileItem> fileItemList = sfu.parseRequest(request);

    4.3 FileItem 对象中的方法 (commons-fileupload API)

    • boolean isFormField(): 是否为普通表单项. true,表示为普通表单项; false,表示为文件表单项;
    • String getFieldName(): 返回当前表单项的名称;
    • String getString(String charset): 返回表单项的值, charset 默认值为 "utf-8";
    • String getName(): 返回上传文件的名称;
    • long getSize(): 返回上传文件的字节数;
    • InputStream getInputStream(): 返回上传文件对应的输入流;
    • void write(File destFile): 把上传文件的内容保存到指定的文件中;
    • String getContentType(): 获取上传文件的 MIME 类型;

    5. 上传的细节

    5.1 文件必须保存到 WEB-INF 下!

    • 目的是不让浏览器直接访问到.

    5.2 文件名称相关问题

    1. IE6 浏览器上传的文件名称是绝对路径(包含磁盘的路径),需要将磁盘部分切割, 例如: "c:filesa.jpg";
    2. 文件名乱码或普通表单项乱码:
      • request.setCharacterEncoding("utf-8"), fileupload 内部会调用 request.getCharacterEncoding()方法;
      • servletFileUpload.setHeaderEncoding("utf-8"), 这种方式的优先级高于前一种.
    3. 文件同名问题: 需要为每个文件添加名称前缀, 为了保证不重复, 可以使用 uuid
      • filename = CommonUtils.uuid()+"_"+filename;

    5.3 目录打散

    1. 不能在一个目录下存放过多文件
      • 首字母打散: 使用文件的首字母作为目录名称; 不方便操作中文的文件名
      • 时间打散: 使用当前日期作为目录;
      • 哈希打散:
        • 通过文件名称获得 int 值, 即调用 hashCode();
        • 把 int 值转换成十六进制 "0~9 和 A ~ F";
        • 获取十六进制的前两位用来生成目录, 目录为两层! 例如: "1B2C3D4E5F", /1/B 保存文件.

    5.4 上传文件的大小限制

    1. 单个文件的大小限制

      • sfu.setFileSizeMax(100 * 1024): 表示限制单个文件大小为 100K;
      • 必须在 parseRequest() 方法之前调用;
      • 如果上传的文件超出限制, 在 parseRequest() 方法执行时, 会抛出异常!!
        FileUploadBase.FileSizeLimitExceedeException.
    2. 整个请求所有数据大小限制

      • sfu.setSizeMax(1024 * 1024): 表示限制整个表单大小为 1M;
      • 必须在 parseRequest() 方法之前调用;
      • 如果上传的文件超出限制, 在 parseRequest() 方法执行时, 会抛出异常!!
        FileUploadBase.SizeLimitExceededException.

    5.5 缓存大小与临时目录

    • 缓存大小: 上传文件超出多大时, 才向硬盘保存! 默认 10KB;
    • 临时目录: 向硬盘的什么目录保存;
    • 设置缓存大小和临时目录: new DiskFileItemFactory(20 * 1024, new File("F:/temp"))
    // 目录打散
        public void UploadServlet extends HttpServlet{
    
            public void doPost(HttpServletRequest request, HttpServletResponse resp)
                    throws ServletException, IOException{
    
                request.setCharacterEncoding("UTF-8");
                response.setContentType("text/html;utf-8");
    
                // 文件上传三步
                DiskFileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload sfu = new ServletFileUpload(factory);
    
                try{
                    List<FileItem> fileItemList = sfu.parseRequest(request);
    
                    // 获取照片文件表单项
                    FileItem fi = fileItemList.get(1);
    
                    // 得到保存上传文件的根路径
                    String root = this.getServletContext().getRealPath("/WEB-INF/files/");
    
                    // 得到文件名
                    String filename = fi.getName();
    
                    //处理文件名的绝对路径问题
                    int index = filename.lastIndexOf("\");
                    if(index != -1){
                        filename = filename.substring(index+1);
                    }
    
                    // 给文件名添加 uuid 前缀, 处理文件同名问题
                    String savename = CommonUtils.uuid()+"_"+filename;
    
                    // 得到文件名的 hashCode, 生成两层目录
                    int hCode = filename.hashCode();
                    String hex = Integer.toHexString(hCode);
    
                    File dirFile = new File(root,hex.charAt(0)+"/"+hex.charAt(1));
    
                    // 如果目录不存在, 创建目录链
                    dirFile.mkdirs();
    
                    // 创建目标文件
                    File destFile = new File(dirFile,savename);
    
                    // 保存
                    fi.save(destFile);
    
                }catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
    

    参考资料:

  • 相关阅读:
    Codeforces Round #251 (Div. 2) A
    topcoder SRM 623 DIV2 CatAndRat
    topcoder SRM 623 DIV2 CatchTheBeatEasy
    topcoder SRM 622 DIV2 FibonacciDiv2
    topcoder SRM 622 DIV2 BoxesDiv2
    Leetcode Linked List Cycle II
    leetcode Linked List Cycle
    Leetcode Search Insert Position
    关于vim插件
    Codeforces Round #248 (Div. 2) B. Kuriyama Mirai's Stones
  • 原文地址:https://www.cnblogs.com/linkworld/p/7636559.html
Copyright © 2011-2022 走看看