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);
                }
            }
        }
    

    参考资料:

  • 相关阅读:
    c#中value是什么意思
    javascript json转为 go struct 小工具代码
    android greendao的外部封装不太友好。
    redis 内存泄露
    Robolectric 配置
    android studio 代理配置
    python 写文件,utf-8问题
    go 的 time ticker 设置定时器
    FQ记(nexus7 2代 恢复出厂设置,然后重启,因为被墙,卡住了!)
    lua https request 调用
  • 原文地址:https://www.cnblogs.com/linkworld/p/7636559.html
Copyright © 2011-2022 走看看