搞一搞文件上传

1. 准备工作

文件上传下载是项目里面最常见的功能之一,一个优秀的项目不可能没有文件上传下载操作。所以今天就来搞一搞文件上传和下载。

前端页面准备

  • 重要:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;
  • 对表单中的 enctype 属性做个详细的说明:
    • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
    • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
    • text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<form action="${pageContext.request.contextPath}/upload2" method="post" enctype="multipart/form-data" >
    上传用户:<input type="text" name="username">
   <p><input type="file" name="uploadFile"></p>
   <p><input type="submit" value="上传">|<input type="reset" value="重置"></p>
</form>

就像上面一样,当然这只是最基本的样式,如果你有能力换样式也可以还样式。

推荐几款不错的前端框架
LayUI 一个非常不错的前端框架。

ElementUI 饿了么开发的前端框架,主要基于Vue实现。

这两个前端框架非常美观大方。

好的,言归正传。
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

文件上传

  • 工具:IDEA,Tomcat8.5

使用IDEA创建Maven项目。

导入依赖

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.5</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.5</version>
</dependency>
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.2</version>
</dependency>
  • mmons-fileupload是我们文件上传使用的,而commons-fileupload要依赖于commons-io进行开发。

前端页面代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%----%>
<form action="${pageContext.request.contextPath}/upload2" method="post" enctype="multipart/form-data" >
    上传用户:<input type="text" name="username">
   <p><input type="file" name="uploadFile"></p>
   <p><input type="submit" value="上传">|<input type="reset" value="重置"></p>
</form>
</body>
</html>

创建Servlet并继承HttpServlet ,重写doPost方法

public class FileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    }
 }

配置Servlet

在web.xml文件中添加映射

<servlet>
    <servlet-name>upload</servlet-name>
    <servlet-class>com.file.servlet.UpFileServlet</servlet-class>

</servlet>
<servlet-mapping>
    <servlet-name>upload</servlet-name>
    <url-pattern>/upload</url-pattern>
</servlet-mapping>

到这里准备工作就做好了

2. 编写代码

  1. 判断上传文件是否是普通文件。
  2. 创建上传文件的报存路径,建议在WEB-INF路径下。 安全,用户我无法直接访问上传的文件。
  3. 临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
  4. 判断上传文件一般都需要通过流来获取,我们可以使用request.getinputstream(),原生态的文件上传流获取,十分麻烦,但是我们都建议使用Apache的文件上传组件来进行上传,commons-fileupload,他需要依赖于commons-io组件。

文件前必须先了解几个类

核心API—DiskFileItemFactory

  • DiskFileItemFactory 是创建FileItem 对象的工厂,这个工厂类常用方法:
    • public void setSizeThreshold(int sizeThreshold) :设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件。
    • public void setRepository(Java.io.File repository) :指定临时文件目录,默认值为System.getProperty(“java.io.tmpdir”).
    • public DiskFileItemFactory(int sizeThreshold,java.io.File repository) :构造函数

核心API—-ServletFileUpload
ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem 对象中。常用方法有:

  1. boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型

  2. List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。

  3. setFileSizeMax(long fileSizeMax) :设置上传文件的最大值(单个文件),用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。

  4. setSizeMax(long sizeMax) :设置上传文件总量的最大值(所有上传文件),用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。

  5. setHeaderEncoding(java.lang.String encoding) :设置编码格式。在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,文件上传字段中的文件路径名也是文本,在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。

核心API—FileItem
FileItem类的常用方法:

  1. boolean isFormField(): isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。

  2. String getName():用于获得文件上传字段中的文件名。注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。

  3. **String getFieldName()**用于返回表单标签name属性的值。

  4. void write(File file):用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。

  5. **String getString():**用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:

public Java.lang.String getString()public java.lang.String getString(java.lang.String encoding)  throws java.io.UnsupportedEncodingException

前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。

  1. void delete():delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。

代码

实现步骤:
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录。
2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件:
4.1、 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。
4.2、为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。

doPost方法

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //判断上传文件是否是普通文件。
    if (!ServletFileUpload.isMultipartContent(req)) {
        return; //终止方法执行,说明这是
    }
    //创建上传文件的报存路径,建议在WEB-INF路径下。
    // 安全,用户我无法直接访问上传的文件;
    String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
    File file = new File(uploadPath);
    //判断文件是否存在
    if (!file.exists()) {
        file.mkdirs();//创建这个目录
    }
    try {
        //缓存,临时文件
        //临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
        String temPath = this.getServletContext().getRealPath("/WEB-INF/tem");
        File temFile = new File(temPath);
        //判断文件是否存在
        if (!temFile.exists()) {
            temFile.mkdirs();//创建这个 目录
        }

        //1.创建DiskFileItemFactory对象,处理文件上传路径或者大小限制;
        DiskFileItemFactory factory = getDiskFileItemFactory(temFile);
        //2.获取ServletFileUpload
        ServletFileUpload upload = getServletFileUpload(factory);
        //3.处理上传文件
        String message = null;
        message = uploadParseRequest(req, uploadPath, upload);
        req.setAttribute("message", message);
        req.getRequestDispatcher("message.jsp").forward(req, resp);

    } catch (FileUploadException e) {
        e.printStackTrace();
    }
}

getDiskFileItemFactory方法

public static DiskFileItemFactory getDiskFileItemFactory(File temFile) {
    DiskFileItemFactory factory = new DiskFileItemFactory();
    //通过这个工厂设置一个缓冲区,当上传文件大于这个缓冲区的时候,将他放入临时文件中。
    factory.setSizeThreshold(1024 * 1024);//缓冲区大小为1M
    factory.setRepository(temFile);//临时文件目录,需要一个File
    return factory;
}

getServletFileUpload方法

public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
    ServletFileUpload upload = new ServletFileUpload(factory);
    //监听文件上传进度
    upload.setProgressListener(new ProgressListener() {
        @Override
        // l:已读取到文件大小
        // l1:文件大小
        public void update(long l, long l1, int i) {
            System.out.println("总大小:" + l1 + "已上传:" + l);
        }
    });
    //处理乱码问题
    upload.setHeaderEncoding("UTF-8");
    //设置单个文件最大值
    upload.setFileSizeMax(1024 * 1024 * 10);
    //设置总共能够上传文件的大小
    upload.setSizeMax(1024 * 1024 * 10);
    return upload;
}

uploadParseRequest方法

public static String uploadParseRequest(HttpServletRequest req, String uploadPath, ServletFileUpload upload) throws IOException, FileUploadException {

    //把前端请求解析,封装成一个FileItem对象,需要从ServletFileUpload对象中获取
    List<FileItem> fileItems = upload.parseRequest(req);
    //fileItem ==>每个表单对象
    for (FileItem fileItem : fileItems) {
        //判断上传的文件是普通的表单还是带文件的表单
        if (fileItem.isFormField()) {
            String name = fileItem.getFieldName();
            String value = fileItem.getString("UTF-8");
            System.out.println(name + ":" + value);
        } else {//文件


            //*******************处理文件*******************//
            // 获取文件名
            String uploadFileName = fileItem.getName();
            //可能存在文件不合法的
            if (uploadFileName.trim().equals("") || uploadFileName == null) {
                continue;  //返回
            }
            //获取上传文件名,
            String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);

            //获取文件的后缀名
            String fileExtName = uploadFileName.substring(uploadFileName.indexOf(".") + 1);
            //可以使用UUID(唯一识别的通用码),保证文件名唯一。
            //UUID.randomUUID(),随机生成一个唯一识别的通用码。
            //private static final long serialVersionUID = -4856846361193249489L;
            //
            // implements Serializable :标记接口,JVM--->java栈 本地方法栈, native-->(调用)C++
            String uuidPath = UUID.randomUUID().toString();


            //*******************存放地址*******************//
            //存到哪?uploadPath
            //文件真是存在的路径 realpath
            String realPath = uploadPath + "/" + uuidPath;

            //给每个文件创建一个对应的文件夹。
            File realPathFile = new File(realPath);
            if (!realPathFile.exists()) {
                realPathFile.mkdirs();
            }


            //*******************文件传输*******************//
            //获得文件上传的流
            InputStream inputStream = fileItem.getInputStream();
            FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
            byte[] buffer = new byte[1024 * 1024];
            int len = 0;
            while ((len = inputStream.read(buffer)) > 0) {
                fos.write(buffer, 0, len);
            }
            fos.close();
            inputStream.close();


        }
    }
    return "上传成功!";
}

思路:上面代码把各个部分封装成一个可供使用的方法。
当然也可以写在一起。我认为这种不易理解。看着也费劲。哈哈,这只是我个人的观点。

package com.file.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;

public class FileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //判断上传文件是否是普通文件。
        if (!ServletFileUpload.isMultipartContent(req)) {
            return; //终止方法执行,说明这是
        }
        try {
            //创建上传文件的报存路径,建议在WEB-INF路径下。
            // 安全,用户我无法直接访问上传的文件;
            String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
            File file = new File(uploadPath);
            //判断文件是否存在
            if (!file.exists()) {
                file.mkdirs();//创建这个目录
            }

            //缓存,临时文件
            //临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
            String temPath = this.getServletContext().getRealPath("/WEB-INF/tem");
            File temFile = new File(temPath);
            //判断文件是否存在
            if (!temFile.exists()) {
                temFile.mkdirs();//创建这个 目录
            }

            //判断上传文件一般都需要通过流来获取,我们可以使用request.getinputstream(),原生态的文件上传流获取,
            //十分麻烦,但是我们都建议使用Apache的文件上传组件来进行上传,commons-fileupload,他需要依赖于commons-io组件。

            //1.创建DiskFileItemFactory对象,处理文件上传路径或者大小限制;
            DiskFileItemFactory factory = new DiskFileItemFactory();
            //通过这个工厂设置一个缓冲区,当上传文件大于这个缓冲区的时候,将他放入临时文件中。
            factory.setSizeThreshold(1024 * 1024);//缓冲区大小为1M
            factory.setRepository(temFile);//临时文件目录,需要一个File


            //2.获取ServletFileUpload
            ServletFileUpload upload = new ServletFileUpload(factory);

            //监听文件上传进度
            upload.setProgressListener(new ProgressListener() {
                @Override
                // l:已读取到文件大小
                // l1:文件大小
                public void update(long l, long l1, int i) {

                    System.out.println("总大小:" + l1 + "已上传:" + l);
                }
            });

            //处理乱码问题
            upload.setHeaderEncoding("UTF-8");

            //设置单个文件最大值
            upload.setFileSizeMax(1024 * 1024 * 10);

            //设置总共能够上传文件的大小
            upload.setSizeMax(1024 * 1024 * 10);

            //3.处理上传文件
            //把前端请求解析,封装成一个FileItem对象,需要从ServletFileUpload对象中获取

            List<FileItem> fileItems = upload.parseRequest(req);
            //fileItem ==>每个表单对象
            for (FileItem fileItem : fileItems) {
                //判断上传的文件是普通的表单还是带文件的表单
                if (fileItem.isFormField()) {
                    String name = fileItem.getFieldName();
                    String value = fileItem.getString("UTF-8");
                    System.out.println(name + ":" + value);
                } else {//文件
                    //*******************处理文件*******************//
                    // 获取文件名
                    String uploadFileName = fileItem.getName();
                    //可能存在文件不合法的
                    if (uploadFileName.trim().equals("") || uploadFileName == null) {
                        continue;  //返回
                    }
                    //获取上传文件名,
                    String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);

                    //获取文件的后缀名
                    String fileExtName = uploadFileName.substring(uploadFileName.indexOf(".") + 1);
                    //可以使用UUID(唯一识别的通用码),保证文件名唯一。
                    //UUID.randomUUID(),随机生成一个唯一识别的通用码。
                    //private static final long serialVersionUID = -4856846361193249489L;
                    //
                    // implements Serializable :标记接口,JVM--->java栈 本地方法栈, native-->(调用)C++
                    String uuidPath = UUID.randomUUID().toString();

                    //*******************存放地址*******************//
                    //存到哪?uploadPath
                    //文件真是存在的路径 realpath
                    String realPath = uploadPath + "/" + uuidPath;

                    //给每个文件创建一个对应的文件夹。
                    File realPathFile = new File(realPath);
                    if (!realPathFile.exists()) {
                        realPathFile.mkdirs();
                    }

                    //*******************文件传输*******************//
                    //获得文件上传的流
                    InputStream inputStream = fileItem.getInputStream();
                    FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
                    byte[] buffer = new byte[1024 * 1024];
                    int len = 0;
                    while ((len = inputStream.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                    fos.close();
                    inputStream.close();
                }
            }
            String message = "成功";
            req.setAttribute("message", message);
            req.getRequestDispatcher("message.jsp").forward(req, resp);

        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }
}

总结: 了解了commons-fileupload中的几个类,当然项目中不是完全这样写,还有很多改进的地方,主要是理解这种原理。

结束:欢迎各位留言交流,有帮助请点个赞👍再走吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值