文件上传详解

    xiaoxiao2021-08-23  143

    一、文件上传 1.文件上传的原理分析 1.1文件上传的必要前提: a.表单的method必须是post b.表单的enctype属性必须是multipart/form-data类型的  enctype默认值:application/x- www-form-urlencoded   作用:告知服务器,请求正文的MIME类型  urlencoded的正文的格式为: application/x- www-form-urlencoded  : username = abc &passwoard = 123, String name = request.getParameter("name");  这种获取参数的方法,仅适用于该种类型 1.2将表单设置为enctype = multipart/form-data类型之后,内容的展现类型如下所示 文件头和正文之间用空行隔开,上面是指定的分界符号,用来区分不同的表单提交内容。 1.3文件上传的原理:   解析请求正文的内容 c.表单中提供type="file"类型的上传组件  2.借助第三方组件实现文件上传

    拷贝jar包:commons-fileupload.jar   commons-io.jar 具体参考

    http://commons.apache.org/proper/commons-fileupload/using.html

    2.2How it works

    fileupload组件的工作流程如下:

     

    借助第三方组件,Commons-fileupload组件来实现文件的上传。 package com.itheima.servlet; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.List; 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.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FilenameUtils; import com.sun.corba.se.impl.orb.ParserTable; //借助commons-fileupload 实现文件上传的简单案例 public class UploadServlet2 extends HttpServlet {  public void doGet(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {   //设置响应正文的编码格式,防止中文乱码   response.setContentType("text/html;charset=UTF-8");   PrintWriter out = response.getWriter();     //判断用户提交的正文类型是不是multipart/form-data类型   boolean isMultipart = ServletFileUpload.isMultipartContent(request);   if(!isMultipart){    throw new RuntimeException("请检查您的表单的enctype属性,确定是multipart/form-data");   }     //创建DiskFileItemFactory   DiskFileItemFactory  dfif = new DiskFileItemFactory();   ServletFileUpload parser = new ServletFileUpload(dfif);     List<FileItem> items = null ;     //解析内容   try {    items = parser.parseRequest(request);   } catch (FileUploadException e) {    throw new RuntimeException("解析上传内容失败,请重新试一下");   }     //处理请求内容   if(items != null){    for(FileItem  item:items){     if(item.isFormField()){      //普通表单字段      processFormField(item);     }     else{       //上传的文件字段      processUploadField(item);     }    }        out.write("上传成功");   }      }    //上传字段  private void processUploadField(FileItem item) {   try { // InputStream  in = item.getInputStream(); //输入流    String fileName = item.getName(); //上传文件的名称  C:\Users\zhangjianbo\Desktop\a.txt    a.txt        //截取文件名 // fileName = fileName.substring(fileName.lastIndexOf("\\")+1);    if(fileName!=null){     fileName = FilenameUtils.getName(fileName);    }        //把文件存到服务器的某个目录中    String storeDirectoryPath = getServletContext().getRealPath("/files");        File storeDirectory = new File(storeDirectoryPath);    if(!storeDirectory.exists()){     storeDirectory.mkdirs();    }     // //构建输出流,输出流需要指定文件写出的路径   // OutputStream out  = new FileOutputStream(storeDirectoryPath +File.separator+ fileName); // int len = -1; // byte b[] = new byte[1024]; // while((len = in.read(b))!=-1){ // out.write(b,0,len); // } // // in.close(); // out.close(); //item.delete();  //传递大文件的时候,会自动产生临时文件         item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法   } catch (Exception e) {    throw new RuntimeException("上传失败,请重试");   }        }  //普通表单字段  private void processFormField(FileItem item) {   String filedName = item.getFieldName(); //字段名    String filedValue = item.getString(); //字段值      System.out.println(filedName + "=" + filedValue);    }  public void doPost(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {   doGet(request, response);  } } 3.文件上传详解

    3.1.  DiskFileItemFactory

    该类主要是对磁盘文件的操作,对于上传文件分为两部分: 1)如果文件的大小小于10KB,那么默认存放到缓存中。 2)如果文件的大小大于10KB,那么该文件将存放到临时文件的存放目录中,默认是当前登录用户的临时文件目录。

     

    public void setRepository(File repository):设置临时文件的存放目录

    public void setSizeThreshold(int sizeThreshold):设置缓存的大小

    专题:关于临时文件

    虚拟内存的概念:当内存中的空间不够用的时候,这个时候,会在磁盘上划分一部分区域作为虚拟内存,将内存中不经常使用的文件先保存到虚拟内存中。所以虚拟内存是磁盘上的一部分存储空间。

    为什么启用缓存呢?减少与硬盘的交互次数,最终还是要存放到磁盘上的 ,存放到内存中,速度更快。 所以上传的文件首先是存放到内存中的缓存中的,

    如果是一个100M大小的文件,那么每次获取10Kb,到内存中,然后存放到临时文件中,当所有的100M都存放到临时文件中后,然后再把临时文件中的文件全部放到最终文件中,最后把临时文件进行删除。 

    临时文件需要在代码中指定来实现,调用item.delete()来删除。  FileItem item  =  new DiskFileItemFactory()

    临时文件的删除问题:

    文件上传时,自己用IO流处理的文件,一定要在流关闭的时候删除临时文件,FileItem.delete()

    建议使用:FileItem.writer(File f)会自动删除临时文件

    3.2乱码问题 

    1) 普通字段乱码

    filedValue = item.getString("UTF-8");  如果提交的是输出框的值,那么需要通过FileItem.getString(String charset);编码需要和客户端一致

    2)文件名乱码 

    可以通过requset.setCharacterEncoding("UTF-8");仅能够解决报文中的请求头的问题,不能解决正文的编码问题 

    3)解决文件名重名的问题 

    通过UUID来命名文件名fileName = UUID.randomUUID()+"_"+   FilenameUtils.getName(fileName);

    4)保证服务器的安全

    为了防止用户上传的文件,通过路径可以直接访问到该文件,造成服务器的漏洞,bat文件等,需要将用户上传的文件存放到用户不能直接访问的地方,如:

    String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files"); 将文件放到WEB-INF目录下面。 

    5)避免一个文件夹中的文件过多 

    解决方法:分目录存储

    参考实现:

    1)按照日期分目录存储 

    2)通过文件名的HashCode来设定文件夹

     //上传字段

     private void processUploadField(FileItem item) {

      try {

       String fileName = item.getName(); //上传文件的名称  C:\Users\zhangjianbo\Desktop\a.txt    a.txt

       

       //截取文件名

       if(fileName!=null){

        fileName = UUID.randomUUID()+"_"+   FilenameUtils.getName(fileName);

       }

       

    // //分目录存储:日期解决方案

    // Date now = new Date();

    // DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

       

    // String time = df.format(now);

       

       //通过文件名的hashCode来实现

       String childDiretory = makeChildDirectory(getServletContext().getRealPath("/WEB-INF/files/"),fileName);

       //把文件存到服务器的某个目录中

       String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files/"+childDiretory);

       

       File storeDirectory = new File(storeDirectoryPath);

       if(!storeDirectory.exists()){

        storeDirectory.mkdirs();

       }

       item.delete();//表示删除临时文件

       item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法

      } catch (Exception e) {

       throw new RuntimeException("上传失败,请重试");

      }

     

     

     

     }

     private String makeChildDirectory(String realPath, String fileName) {

     

      int hashCode = fileName.hashCode();

      int dir1 = hashCode&0xf;//取文件名的二进制的1~4位

      int dir2 = (hashCode&0xf0)>>4; //获取5~8

     

      String directory = "" + dir1 + File.separator +dir2;

      File file = new File(realPath,directory);

     

      if (!file.exists()){

       file.mkdirs();

      }

      return directory;

     

     

     

     }

    3.6 限制文件上传的大小,并给出友好提示 

    Web方式不适合传输较大的文件

    1)单文件大小(一般限制4M)

     //创建DiskFileItemFactory

      DiskFileItemFactory  dfif = new DiskFileItemFactory();

      ServletFileUpload parser = new ServletFileUpload(dfif);

     

      parser.setFileSizeMax(4*1024*1024); //设置单个文件上传的大小

      parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制

     

     

     

      List<FileItem> items = null ;

     

      //解析内容

      try {

       items = parser.parseRequest(request);

      }catch(FileUploadBase.FileSizeLimitExceededException e){  //该异常为一个静态内部类

       out.write("上传的文件超出了4M");

       return;

      }

      catch(FileUploadBase.SizeLimitExceededException e){  //该异常为一个静态内部类

       out.write("总文件大小超出了6M");

       return;

      }

      catch (FileUploadException e) {

       throw new RuntimeException("解析上传内容失败,请重新试一下");

      }

    2)总文件大小(多文件上传)

      parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制 

    3.7 限制上传文件的类型 

    通过扩展名+ 文件的MIME类型来进行限制 

    // 扩展名

        String extension = FilenameUtils.getExtension(fileName);

        // MIME类型 -- 判断MIME类型仅限于IE浏览器

        String contentType = item.getContentType();

        if (contentType.startsWith("image/")) {

    MIME类型:Web请求文件中的Content-Type类型,就表示MIME类型

    大类型/小类型组成,我们只需要判断大类型即可。

    4.文件的下载   public void doGet(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {     String uuidfilename = request.getParameter("filename");//get方式提交的   uuidfilename = new String(uuidfilename.getBytes("ISO-8895-1"),"UTF-8");//UUID的文件名     String storeDirectory = getServletContext().getRealPath("/WEB-INF/files");   //得到存放的子目录   String childDirecotry = makeChildDirectory(storeDirectory,uuidfilename);     //构建输出流   InputStream in  = new FileInputStream(storeDirectory + File.separator + childDirecotry + File.separator + uuidfilename);     //下载   String oldfilename = uuidfilename.substring(uuidfilename.indexOf("_") + 1);     //通知客户端以下载的方式打开   response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(oldfilename,"UTF-8"));     OutputStream  out = response.getOutputStream();     int len = -1;   byte b[] = new byte[1024];   while((len = in.read(b))!= - 1){    out.write(b,0,len);   }       in.close();   out.close();    }    private String makeChildDirectory(String realPath, String fileName) {   int hashCode = fileName.hashCode();   int dir1 = hashCode & 0xf;// 取文件名的二进制的1~4位   int dir2 = (hashCode & 0xf0) >> 4; // 获取5~8   String directory = "" + dir1 + File.separator + dir2;   File file = new File(realPath, directory);   if (!file.exists()) {    file.mkdirs();   }   return directory;  }      public void doPost(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {   doGet(request, response);  } } =================jstl url ==================                        <h1>jstl中的url的编码方式,使用url编码,因为路径中有中文</h1>        <c:forEach items="${map}" var = "me">         <c:url  value = "/servlet/DownLoadServlet" var = "url">          <c:param name="filename" value="${me.key}">           </c:param>                             </c:url>        ${me.value}  <a href="${url}"></a>下载<br/> ==============showallfiles============= public class ShowAllFilesServlet extends HttpServlet {  public void doGet(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {   String storeDirectory =  getServletContext().getRealPath("/WEB-INF/files");   File root = new File(storeDirectory);       //使用Map保存递归的文件名,key:UUID文件名  value:老文件名   Map<String,String> map = new HashMap<String, String>();   treeWalker(root,map);     request.setAttribute("map", map);   request.getRequestDispatcher("/listFiles.jsp").forward(request, response);  }  //递归,将文件名存放到Map中  private void treeWalker(File root, Map<String, String> map) {   if(root.isFile()){    String fileName = root.getName(); //获取当前的文件名   234322_a_a.bmp    String oldFileName = fileName.substring(fileName.indexOf("_") + 1);    map.put(fileName, oldFileName);           }   else{    File fs[]  = root.listFiles();    for(File file:fs){     treeWalker(file, map);    }   }      } 二、Servlet规范中的监听器 1.观察者设计模式 2.Servlet规范中提供的八个监听器 3.监听器案例:查看在线登录的人数,踢人
    转载请注明原文地址: https://ju.6miu.com/read-676980.html

    最新回复(0)