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

    时间:2016-12-17 18:07

    ——文件上传概述

    上传不能使用BaseServlet


    1、文件上传的作用
        例如网络硬盘,就是用来上传和下载文件的。

    2、文件上传对表单的限制
        1)必须使用表单,而不能是超链接
        2)表单的method必须是POST,而不能是GET
        3)表单的enctype必须是multipart/form-data
        4)在表单中添加type="file",即<input type="file" name="" />

        <form action="xxx" method="post" enctype="multipart/form-data">
            用户名:<input type="text" name="username" /><br/>
            照    片:<input type="file" name="photo" /><br/>
            <input type="submit" value="上传" />
        </form>

    3、文件上传对Servlet的限制
        1)当表单为enctype="multipart/form-data"时,不能使用request.getParameter()方法,该方法永远返回null。
        2)使用ServletInputStream  request.getInputStream()来获取完整的请求体。

    4、多部件表单结构
        1)分割出多个部件,即一个表单项生成一个部件。
        2)一个部件中会自己包含请求头和空行以及请求体。
        3)普通表单项:
            >   一个头:
                *   Content-Disposition,包含name="xxx",即表单项名称。
            >   体就是表单项的值。
        4)文件表单项:
            >   两个头:
                *   Content-Disposition,包含name="xxx",即表单项名称,还有一个filename="xxx",表示上传文件的名称。
                *   Content-Type:它是上传文件的MIME类型,例如:image/jpeg,表示上传的是图片。
            >   体就是上传文件的内容。


    图片




    ——commons-fileupload

        *   commins-fileupload.jar
        *   依赖包:commons-io.jar

    这个组件会解析request中的上传数据,解析后的结果是一个表单项数据封装到一个FileItem对象中,只需要调用FIleItem对象的方法即可。(多个表单项会封装多个FileItem,即List<FileItem>)

    1、上传三个步骤

    相关类:
        *   工厂类:DiskFileItemFactory
        *   解析器:ServletFileUpload
        *   表单项:FileItem(该类对应一个表单项)

        1)创建工厂:
            DiskFileItemFactory factory = new DiskFileItemFactory();

        2)创建解析器:
            ServletFileUpload sfu = new ServletFileUpload(factory);

        3)使用解析器来解析request,得到FileItem集合:
            List<FileItem> fileItemList = sfu.parseRequest(request);

    2、FileItem类

    普通表单项相关方法:

        *   boolean isFormField()
            是否为普通表单项。
            true为普通表单项。
            false为文件表单项。

        *   String getFieldName()
            返回当前表单项的名称。

        *   String getString(String charset)
            通过指定编码获取表单项的值。
            该方法不适用于文件表单项。
            必须指定编码。 

    文件表单项相关方法:

        *   String getName()
            返回上传的文件名称。

        *   long getSize()
            返回上传文件的字节数。

         *   String getContentType()
            获取MIME类型。

        *   InputStream getInputStream()
            返回上传文件对应的输入流。

        *   void write(File file)
            将上传的文件写入指定文件中。
            如果file已存在,则会替换目标文件,如果file不存在,则会创建该文件。


    ——文件上传简单示例

    ===============================================================================

    com.wyc.servlet.UploadServlet

    import java.io.File;
    import java.io.IOException;
    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.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
     
    public class Upload2Servlet extends HttpServlet {
     
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
     
            /*
             * 上传三步
             * 1、得到工厂
             * 2、通过工厂创建解析器
             * 3、解析request,得到FileItem集合
             * 4、遍历FileItem集合,遍历其API完成文件的保存
             */
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload sfu = new ServletFileUpload(factory);
            try {
                List<FileItem> fileItemList = sfu.parseRequest(request);
                for(FileItem f : fileItemList){
                    if(f.isFormField()){
                        System.out.println("普通表单项:" + f.getFieldName() + "=" + f.getString("utf-8"));
                    } else {
                        System.out.println("文件表单项:");

                        // 获取文件MIME类型
                        String mime = f.getContentType();
                        System.out.println("Content-Type:" + mime);

                        // 获取文件大小(字节数)
                        long size = f.getSize();

                        System.out.println("文件大小:" + size);

                        // 获取文件名
                        String fileName = f.getName();
                        System.out.println("文件名称:" + fileName);
     
                        // 保存文件
                        String filePath = request.getServletContext().getRealPath("") + "/file/" + fileName;
                        File destFile = new File(filePath);
                        f.write(destFile);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }


    ------------------------------------------------------------------------------------------------------------------------------

    form.jsp

    <body>
        <h1>上传</h1>
        <form action="<c:url value='/Upload2Servlet'/>" method="post" enctype="multipart/form-data">
            用户名:<input type="text" name="username" value="张三" /><br/>
            照    片:<input type="file" name="photo" /><br/>
            <input type="submit" value="上传" />
        </form>
    </body>

    ===============================================================================

    ——文件上传细节

    1、把上传的文件放到WEB-INF目录下
        如果没有把用户上传的文件放到WEB-INF目录下,那么用户就可以通过浏览器直接访问上传的文件,这是非常危险的。

        假如说用户上传了一个JSP文件,然后用户再通过浏览器去访问这个JSP文件,那么服务器就会执行JSP文件中的内容,如果在JSP文件中有如下语句:Runtime.getRuntime().exec("shutdown -s -t 1");.........

        通常我们会在WEB-INF目录下创建一个upload目录来存放上传的文件,而在Servlet中找到这个目录需要使用ServletContext()的getRealPath(String)方法,例如在项目中有如下语句:
            ServletContext servletContext = this.getServletContext();
            String uploadPath = servletContext.getRealPath("/WEB-INF/uploads");
        其中uploadPath为:G: omcatwebappsFileDemoWEB-INFuploads

    2、文件名称(完整路径)
        上传的文件名称可能是完整路径。
        IE6获取的上传文件名称是完整路径,而在其他浏览器获取的上传文件名称只是文件名称而已,浏览器差异的问题还需要处理一下。
        例如:C:file哈哈.jpg
        可以通过分割字符串来获取文件名。
        int index = fileName.lastIndexOf("\");
        if(index != -1)
            fileName = fileName.substring(index+1);

    3、处理文件名称乱码和普通表单项乱码
        当上传的文件名称中包含中文时,需要设置编码,commons-fileupload组件为我们提供了两种设置编码的方式:
            request.setCharacterEncoding("utf-8");
            fileUpload.setHeaderEncoding(String charset);    // 这种方式的优先级高于前一种。
        commons-fileupload内部会调用request.getCharacterEncoding();来指定编码。

        上传文件的文件内容中包含中文:
            通常我们不需要关心上传文件的内容,因为我们会把上传文件保存到硬盘上,也就是说,原文件是什么样子,保存到服务器之后还是什么样子。
            如果想要在控制台显示上传文件的内容,那么可以使用fileItem.getString(String encoding)来使用指定编码处理字符串。

            文本内容和普通表单项内容都是用FileItem类的getString(String encoding)来处理编码。

    4、文件重名问题
            应该为每个文件名添加前缀名称,这个前缀要保证不能重复,可以使用UUID。
                fileName = CommonUtils.getUuid() + "_" + fileName;

    ===============================================================================

    5、一个目录不能存放过多文件(存放目录打散)
        一个目录下不应该存放过多的文件,一般一个目录存放1000个文件就是上限了,如果再多,那么打开目录时就会很“卡”。
        也就是说,我们需要把上传的文件存放到不同的目录中,但是也不能为每个上传的文件都新建一个目录,这样会导致目录过多,所以我们应该采用某种算法来“打散”存放文件的目录。
        方法有很多,例如使用日期来打散,每天生成一个目录,也可以使用文件名的首字母来生成目录,相同首字母的文件放到同一目录下。

        日期打散算法:如果某一天上传的文件过多,那么也会出现一个目录文件过多的情况。
        首字母打散算法:
            例如abc.txt,那么可以把文件保存到a目录下,如果a目录不存在,则创建a目录。
            如果文件名是中文的,因为中文过多,所以会导致目录过多的情况。

        哈希打散算法:
            *   通过文件名称得到一个int值,即调用hashCode()获取哈希值。
            *   将int值转换成16进制。
            *   获取16进制的前两位用来生成目录,目录为两层:例如1B2C3D4E5F,使用/1/B/ 来保存文件。
            该存储方式非常不直观,一般会将文件存放的目录保存起来,以后访问下载时可以直接获取。

    import java.io.File;
    import java.io.IOException;
    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.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
     
    import com.wyc.bean.CommonUtils;
     
    public class Upload3Servlet extends HttpServlet {
     
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
            /*
             * 上传三步
             */
            // 得到工厂
            DiskFileItemFactory factory = new DiskFileItemFactory();
     
            // 得到解析器
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
     
            // 解析request
            try {
                List<FileItem> fileItemList = fileUpload.parseRequest(request);
     
                // 判断表单项
                for(FileItem f : fileItemList){
                    if(f.isFormField()){
                        System.out.println(f.getFieldName() + "=" + f.getSize());
                    } else {
                        /*
                         * 1、得到文件保存的根路径,在此路径下创建多级目录
                         */
                        String realPath = this.getServletContext().getRealPath("/WEB-INF/files");
                        /*
                         * 2、生成二级目录
                         *   1)得到文件名称
                         *   2)得到hashCode
                         *   3)转成16进制
                         *   4)获取前两个字符用来生成目录
                         */
     
                        /*
                         * 获取文件名称,并处理文件名的绝对路径问题
                         */
                        String fileName = f.getName();
                        int index = fileName.lastIndexOf("\");
                        if(index != -1){
                            fileName = fileName.substring(index + 1);
                        }
     
                        /*
                         * 处理文件重名问题,给文件名称添加UUID前缀
                         */
                        String saveName = CommonUtils.getUuid() + "_" + fileName;
     
                        /*
                         * 得到hashCode,并转换成16进制
                         */
                        int hashCode = fileName.hashCode();
                        String hexCode = Integer.toHexString(hashCode);
     
                        /*
                         * 获取hexCode的前两个字符,与根路径连接在一起,生成一个完整的路径
                         * 将此抽象路径传递给File类的构造方法
                         */
                        File dirFile = new File(realPath, hexCode.charAt(0) + "/" + hexCode.charAt(1));
     
                        /*
                         * 创建目录
                         * mkdirs():创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
                         */
                         dirFile.mkdirs();
     
                        /*
                         * 创建目标文件
                         * 根据parent抽象路径名和child路径名字符串创建一个新 File 实例。
                         */
                        File destFile = new File(dirFile, saveName);
     
                        /*
                         * 写入硬盘(保存)
                         */
                        f.write(destFile);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }


    ===============================================================================

    6、上传的单个文件、整个表单的大小限制
        限制上传文件的大小很简单,ServletFileUpload类的setFileSizeMax(long)就可以实现了,参数就是上传文件的上限字节数,例如:servletFileUpload.setFileSizeMax(1024 * 10),表示上限为10kb。
        该方法一定要在parseRequest()方法之前调用。
        一旦上传的文件大小超出了上限,那么就会在执行parseRequest()方法时抛出FileUploadBase.FileSizeLimitExceededException异常。
        我们可以在Servlet中获取这个异常,然后想页面输出“上传的文件大小超出限制”。

        限制整个表单的大小:
            fileUpload.setSizeMax(long size);
        该方法必须在parseRequest()方法之前调用。
        如果大小超出限制,会抛出FileUploadBase.SizeLimitExceededException异常。

    public class Upload4Servlet extends HttpServlet {
     
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
     
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
     
            // 限制单个文件大小为10M
            fileUpload.setFileSizeMax(1024 * 10000);
     
            // 限制整个表单大小不能超过100M
            fileUpload.setSizeMax(1024 * 100000);
     
            List<FileItem> fileItemList;
            try {
                fileItemList = fileUpload.parseRequest(request);
                FileItem f = fileItemList.get(1);
            } catch (FileUploadException e) {
                if (e instanceof FileUploadBase.FileSizeLimitExceededException)
                    throw new RuntimeException("上传单个文件大小超出限制");
                else if (e instanceof FileUploadBase.SizeLimitExceededException)
                    throw new RuntimeException("表单大小超出限制");
            }
        }
    }


    ===============================================================================

    7、设置缓存大小与临时目录
        *   缓存大小:
                默认为10kb,当上传文件超出缓存大小时,先将文件保存到硬盘,当文件上传完毕后,再将文件保存。
        *   临时目录:
                缓存的保存目录。

        *   可以通过DiskFileItemFactory的构造方法来指定缓存大小和临时目录
                DiskFileItemFactory(int sizeThreshold, File repository)
                    >   默认的临时目录为:System.getProperty("java.io.tmpdir");,打印后:G:apache-tomcat-7.0.72 emp
                    >   例如:new DiskFileItemFactory(1024 * 10, new File("F:/temp"));

    public class Upload5Servlet extends HttpServlet {
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");

            // 打印默认临时目录 
            System.out.println(System.getProperty("java.io.tmpdir"));
     
            // 设置缓存大小与临时目录
            DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 10, new File("F:/temp"));
     
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
            try {
                List<FileItem> fileItemList = fileUpload.parseRequest(request);
                FileItem f = fileItemList.get(1);
                File file = new File("F:/temp/" + f.getName());
                f.write(file);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
     

    ===============================================================================


    ——文件下载

    被下载的资源必须放到WEB-INF目录下,然后通过Servlet完成下载。
    在JSP页面中给出超链接,链接到DownloadServlet,并提供要下载的文件名称,然后在DownloadServlet获取文件的真实路径,然后把文件写入到response.getOutputStream()流中。


    1、概述
        下载就是向客户端响应字节数据。
        之前响应的都是HTML的字符数据。
        其实就是把一个文件编程字节数组,使用response.getOutputStream()来将文件响应给浏览器。

    2、下载的要求
        两个头、一个流
            *   Content-Type:表示传递给客户端文件的MIME类型。
                >   通过文件名称调用ServletContext的getMimeType()方法得到MIME类型。
            *   Content-Disposition:
                >   它的默认值为inline,表示在浏览器窗口中打开(当打不开时会弹框)。
                >   attachment;filename=xxx:表示类型为附件,并在弹框的界面中显示下载的文件名。
            *   流:要下载的文件数据。
                >   自己new 一个输入流即可,然后通过IOUtils完成流的数据写入。

    ——下载示例

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
     
    import javax.servlet.ServletException;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.apache.commons.io.IOUtils;
     
    public class DownloadServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
     
            /*
             * 准备两个头,一个流
             * 1、Content-Type
             * 2、Content-Disposition
             * 3、流:下载文件的数据
             */
     
            /*
             * 如何获取文件的MIME类型
             */
            String fileName = "F:/Acacia - Valder Fields.mp3";

            // 通过文件名获取文件MIME类型
            String contentType = this.getServletContext().getMimeType(fileName);
     
            String contentDisposition = "attachment;filename=Acacia - Valder Fields.mp3";
     
            // 设置响应头
            response.setHeader("Content-Type", contentType);
            response.setHeader("Content-Disposition", contentDisposition);
     
            // 获取绑定了客户端的响应流
            ServletOutputStream output = response.getOutputStream();
     
            // 获取要下载的文件
            FileInputStream input = new FileInputStream(new File("F:/Acacia - Valder Fields.mp3"));
     
            // 将输入流中的数据复制到输出流中
            IOUtils.copy(input, output);
     
            input.close();
        }
    }



    ——DownloadUtils(下载文件名乱码问题)

    显示在下载框中包含中文名称时,会出现乱码。
        *   FireFox,使用Base64编码。
        *   其他大部分浏览器,使用URL编码。

        通用方法:filename = new String(filename.getBytes("gbk"), "iso-8859-1");
        浏览器能读懂ISO-8859-1编码 

    public class DownloadServlet extends HttpServlet {
     
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
            /*
              * 准备两个头,一个流
              * 1、Content-Type
              * 2、Content-Disposition
              * 3、流:下载文件的数据
              */
     
            /*
              * 如何获取文件的MIME类型
              */
            String fileName = "F:/哈哈.mp3";
     
            // 解决乱码问题
            // String frameName = new String(fileName.substring(fileName.lastIndexOf("/") + 1).getBytes("GBK"), "ISO-8859-1");
     
            // 使用DownloadUtils解决乱码问题
            String frameName;
            try {
                frameName = fileNameEncoding("哈哈.mp3", request);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
     
            // 通过文件名获取文件MIME类型
            String contentType = this.getServletContext().getMimeType(fileName);
     
            String contentDisposition = "attachment;filename=" + frameName;
     
            // 设置响应头
            response.setHeader("Content-Type", contentType);
            response.setHeader("Content-Disposition", contentDisposition);
     
            // 获取绑定了客户端的响应流
            ServletOutputStream output = response.getOutputStream();
     
            // 获取要下载的文件
            FileInputStream input = new FileInputStream(new File("F:/哈哈.mp3"));
     
            // 将输入流中的数据复制到输出流中
            IOUtils.copy(input, output);
     
            input.close();
        }

        public static String fileNameEncoding(String fileName, HttpServletRequest request) throws Exception{
            String agent = request.getHeader("User-Agent");
            if(agent.contains("Firefox")){
                BASE64Encoder base64Encoder = new BASE64Encoder();
                fileName = "=?utf-8?B?" + base64Encoder.encode(fileName.getBytes("utf-8")) + "?=";
            } else if (agent.contains("<SIE")) {
                fileName = URLEncoder.encode(fileName, "utf-8");
            } else {
                fileName = URLEncoder.encode(fileName, "utf-8");
            }
            return fileName;
        }
    }


    注意:
        Base64Encoder并不属于JDK标准库范畴,但是又包含在了JDK中,解决方法:
            1、按照如下方法设置Eclipse导入%JAVA_HOME%jrelib目录下的rt.jar包即可,Project->Properties,选择Java Build Path设置项,再选择Libraries标签,Add External Jars添加%JAVA_HOME%jrelib t.jar就可以使用了。
     
            2、MyEclipse —— Preferences —— Java —— Compiler —— Errors/Warnings —— Deprecated and restricted API —— Forbidden reference(access rules): Ignore

    ——BASE64Encoder、BASE64Decoder

    public void fun() throws Exception {
        // BASE64编码
        String s = "haha";
        BASE64Encoder encoder = new BASE64Encoder();
        s = encoder.encode(s.getBytes("utf-8"));
        System.out.println(s);
     
        // BASE64解码
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] b = decoder.decodeBuffer(s);
        System.out.println(new String(b, "utf-8"));
    }
  • 相关阅读:
    rails中输出excel
    Rails IDE 有很多选择,但是具体到ubuntu 64bit 选择的余地就不多了,这里选择Aptana Studio 3 Beta
    linux中查看系统资源占用情况的命令
    GIT GUI使用
    linux下的c 环境配置vim
    oracle11 忘记密码
    Aptana_Studio 介绍和应用
    linux root命令忘记以及挂载U盘
    程序员创业生死一线 最后归宿在哪里?
    如何使用Log4j? .
  • 原文地址:https://www.cnblogs.com/wwwwyc/p/6375346.html
Copyright © 2011-2022 走看看