【归纳总结】Fileupload——向服务器上传文件

一、如何向服务器上传一个本地图片?

首先,Fileupload的使用场景:比如说提交作业,更换头像。

需要一个form表单,表单有如下三个要求

  1. input type = file
  2. method = post
  3. enctype=multipart/form-data
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //response给我们封装现存的响应输出流
        //response.getOutputStream
        //request给我们封装好现存的输入流
        ServletInputStream inputStream = request.getInputStream();
        String realPath = getServletContext().getRealPath("upload/1.jpg");
        File file = new File(realPath);
        //防止父目录不存在,引发异常,如果父目录不存在,则创建
        if(!file.getParentFile().exists()){
    
    
           file.getParentFile().mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        int length = 0;
        byte[] bytes = new byte[1024];
        while ((length = inputStream.read(bytes)) != -1){
    
    
            fileOutputStream.write(bytes, 0, length);
        }
        fileOutputStream.close();
        //inputStream可以关也可以不关,不关的话tomcat会帮助我们关闭
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

    }
}

二、为什么文件会损坏?

用上述代码上传一个图片,去部署根目录下找到文件,发现文件已损坏

上传一个txt文件发现文件平白无故多出了一些WebKitFormBoundary分隔符在这里插入图片描述
对于一个二进制文件,图片、音频、视频、exe文件,多出来这一部分会直接导致文件损坏。

并且普通form表单请求参数也无法获取,因为请求体中的数据结构发生了变更,之前的数据key=value&key=value拼接的,getParameter通过key可以获取到数据value,那么如果数据结构发生了变更,getParameter理应不能再用了。

所有一切的原因都是因为添加了enctype=multipart/form-data。但是如果不添加,就不会上传文件。

三、使用组件上传文件

实现步骤:

  1. 创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
  2. 使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
  3. 关键)调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。list中的每一个元素,就是每个input的封装。
  4. 对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
  5. true表示为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
  6. false 为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据
@WebServlet("/upload2")
public class UploadServlet2 extends HttpServlet {
    
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //判断是否含有上传的文件
        response.setContentType("text/html;charset=utf-8");
        boolean result = ServletFileUpload.isMultipartContent(request);
        if(!result){
    
    
            response.getWriter().println("没有包含上传的文件");
            return;
        }
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //设置一个仓库、缓冲区,目的是为了传输大型文件时,会利用缓冲区边中转边传输
        ServletContext servletContext = getServletContext();
        File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(repository);
        // 拿到真正去处理请求的处理器
        ServletFileUpload handler = new ServletFileUpload(factory);
        try {
    
    
            //这一步是最为关键的,它会对请求进行解析,会将每个input封装成为一个FileItem
            List<FileItem> items = handler.parseRequest(request);
            //遍历出每一个input,
            Iterator<FileItem> iterator = items.iterator();
            while (iterator.hasNext()){
    
    
                //item有两种可能性,一种是普通的form表单,一种是上传的文件
                FileItem item = iterator.next();
                if(item.isFormField()){
    
    
                    processFormField(item);
                }else {
    
    
                    processUploadedFile(item);
                }
            }

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

    private void processUploadedFile(FileItem item) {
    
    
        //属性
        String fieldName = item.getFieldName();
        String fileName = item.getName();
        String contentType = item.getContentType();
        boolean isInMemory = item.isInMemory();
        long sizeInBytes = item.getSize();
        System.out.println("file:" + fieldName + ":" + fileName + ":" + contentType + ":" + isInMemory + ":" + sizeInBytes);
        //保存再本地硬盘上  硬盘的绝对路径
        String realPath = getServletContext().getRealPath("upload/" + fileName);
        File file = new File(realPath);
        if(!file.getParentFile().exists()){
    
    
            file.getParentFile().mkdirs();
        }
        try {
    
    
            //执行该行代码时,它会将文件的数据写入到file
            //这一步也是对磁盘IO的封装
            item.write(file);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    private void processFormField(FileItem item) {
    
    
        String name = item.getFieldName();
        String value = item.getString();
        System.out.println(name + ":" + value);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

    }
}

四、中文乱码问题

表单里面还是有乱码问题的,且之前可以解决乱码的API,此时也是无法解决的。
那是因为:请求体数据已经发生变化,所以之前设置编码格式的API此时也不再适用了。

把刚刚代码中的processFormField方法改成如下形式:

private void processFormField(FileItem item) {
    
    
    String name = item.getFieldName();
    String value = null;
    try {
    
    
        value = item.getString("utf-8");
    } catch (UnsupportedEncodingException e) {
    
    
        e.printStackTrace();
    }
    System.out.println(name + ":" + value);
}

如果上传文件的名字中有中文,则添加代码:

ServletFileUpload handler = new ServletFileUpload(factory);
//可以解决上传文件中文名乱码问题
handler.setHeaderEncoding("utf-8");

五、将数据封装到对象中

使用刚刚的代码,我们只是将图片上传到了服务器,将表单数据输出到了控制台。
接下来需要将这些数据封装到一个bean中(对于文件,封装它的路径),然后再保存到数据库。

扫描二维码关注公众号,回复: 12755094 查看本文章

我们可以写一个FileUploadUtils类把前端页面输入的所有数据整理放进一个map里

public class FileUploadUtils {
    
    

    public static Map parseRequest(HttpServletRequest request){
    
    
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletContext servletContext = request.getServletContext();
        File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(repository);

        // Create a new file upload handler
        ServletFileUpload upload = new ServletFileUpload(factory);
        //如果上传的文件名中文乱码,可以这么解决
        //upload.setHeaderEncoding("utf-8");
        // bytes  1024 bytes
        //upload.setFileSizeMax(1024);
        Map map = new HashMap();
        try {
    
    
            List<FileItem> items = upload.parseRequest(request);
            //对items进行迭代
            Iterator<FileItem> iterator = items.iterator();
            while (iterator.hasNext()){
    
    
                //每一个item其实就是一个input框的封装
                FileItem item = iterator.next();
                if(item.isFormField()){
    
    
                    //当前是普通的form表单数据
                    processFormField(item, map);
                }else{
    
    
                    //当前是上传的文件
                    processUploadedFile(item, map, request);
                }
            }

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

    /**
     * 处理上传的文件逻辑
     * @param item
     * @param map
     * @param request
     */
    private static void processUploadedFile(FileItem item, Map map, HttpServletRequest request) {
    
    
        String fieldName = item.getFieldName();
        String fileName = item.getName();
        String UPLOAD_BASE = "upload";
        //解决文件重名问题
        fileName =  UUID.randomUUID().toString() + "-" + fileName;
        //解决目录内文件数过多的问题
        int hashCode = fileName.hashCode();
        String hexString = Integer.toHexString(hashCode);
        char[] chars = hexString.toCharArray();
        for (char aChar : chars) {
    
    
            UPLOAD_BASE = UPLOAD_BASE + "/" + aChar;
        }
        System.out.println(fieldName + " === " + fileName);
        UPLOAD_BASE = UPLOAD_BASE + "/" + fileName;
        String realPath = request.getServletContext().getRealPath(UPLOAD_BASE);
        File file = new File(realPath);
        if(!file.getParentFile().exists()){
    
    
            file.getParentFile().mkdirs();
        }
        try {
    
    
            //保存文件到硬盘中
            item.write(file);
            //保存什么路径呢?
            //img src='/应用名/资源名'
            map.put(fieldName, UPLOAD_BASE);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 处理普通的form表单数据
     * 只需要获取name和value值即可
     * 上传文件之后,不要再用getParameter去获取请求参数了
     * 有没有中文乱码问题呢?
     * @param item
     * @param map
     */
    private static void processFormField(FileItem item, Map map) {
    
    
        String name = item.getFieldName();
        String value = null;
        try {
    
    
            value = item.getString("utf-8");
            //如果传入进来的是checkbox,那么下面需要变更一下
            map.put(name, value);
        } catch (UnsupportedEncodingException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(name + " = " + value);
    }

}

这样,在主类中我们只需要调用BeanUtils类的populate()方法就可以将map中的数据全部放进一个对象中。

@WebServlet("/upload3")
public class UploadServlet3 extends HttpServlet {
    
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //因为提交的是multipart/form-data,所以响应正文的数据结构发生改变,无法通过以下代码解决乱码问题。
        //request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //post里面写代码
        //返回的是true表示有Multipart
        boolean result = ServletFileUpload.isMultipartContent(request);
        if (!result) {
    
    
            response.getWriter().println("不包含上传的文件");
            return;
        }
        //通过FileUploadUtils获取map,map中存取的是name-value键值对
        Map map = FileUploadUtils.parseRequest(request);
        User user = new User();
        //将map中的数据封装成一个user对象
        try {
    
    
            BeanUtils.populate(user, map);
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(user);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

    }
}

另外,在刚刚的FileUploadUtils类中,我们解决了两个问题:

  1. 文件重名问题:
//解决文件重名问题
        fileName =  UUID.randomUUID().toString() + "-" + fileName;
  1. 目录内文件数目过多问题:
//解决目录内文件数过多的问题
		int hashCode = fileName.hashCode();
        String hexString = Integer.toHexString(hashCode);
        char[] chars = hexString.toCharArray();
        for (char aChar : chars) {
    
    
            UPLOAD_BASE = UPLOAD_BASE + "/" + aChar;
        }
        System.out.println(fieldName + " === " + fileName);
        UPLOAD_BASE = UPLOAD_BASE + "/" + fileName;

六、回显操作

指到数据库里面去查询这条数据,然后封装到bean中,接下来,将它在页面显示出来。

在这里,我们注册成功之后,封装到bean,我们不去保存到数据库,而是通过转发交给另外一个servlet,该servlet在本页面中将之前选择的数据进行回显。

首先,在UploadServlet3中加如下代码:

 //转发给另一个Servlet,将其内容显示出来
        request.setAttribute("user", user);
        request.getRequestDispatcher("/view").forward(request, response);

编写刚刚在代码中提到的ViewServlet类

@WebServlet("/view")
public class ViewServlet extends HttpServlet {
    
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        User user = (User) request.getAttribute("user");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println("<div>用户名:" + user.getUsername() + "</div>");
        response.getWriter().println("<div>密码:" + user.getPassword() + "</div>");
        response.getWriter().println("<div>头像:<img src='" + request.getContextPath() + "/" + user.getImage() + "'></div>");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

    }
}

这样写后发现打断点后能显示头像,直接运行无法显示头像。这是因为FileUploadUtils中的代码:item.write(file);该方法是非阻塞方法。文件可能还在传输的过程中,而此时viewServlet就调用
这个问题是可以忽略的,如果从数据库中取数据然后再显示是不会出现这种问题的。

猜你喜欢

转载自blog.csdn.net/Octavius_/article/details/114341612
今日推荐