欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

week12_day03_Fileupload

程序员文章站 2022-05-09 14:05:37
...

1.FileUpload

1.1.文件上传的准备工作
请求报文。文件的二进制信息应当放在哪?请求体。
form表单 method=post
input type=file

Upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/app/upload" method="post">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <input type="file" name="image"><br>
        <input type="submit">
    </form>
</body>
</html>

正文长度为12,很明显不正常
week12_day03_Fileupload
此时发现,仅会上传文件名,不会上传真实的文件数据。
解决方案:form表单需要添加enctype=multipart/form-data
这次的正文长度就比较正常了
week12_day03_Fileupload
接下来,编写servlet来接收上传的文件。

UploadServlet:

package com.cskaoyan.upload;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //逻辑
        //response给我们提供好了现存的响应输出流
        //request里面有没有给我们封装好现存的输入流呢?
        ServletInputStream inputStream = request.getInputStream();
        String realPath = getServletContext().getRealPath("upload/1.txt");
        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 {

    }
}

上传完成后,开发目录中没有发现上传的文件,应当到部署目录中去查看上传的文件。
week12_day03_Fileupload
发现最终文件损坏?代码有问题吗????没有问题的。
可以更换一个文件,1.txt来看一下。
上传前:
week12_day03_Fileupload
上传后:
week12_day03_Fileupload
文件平白无故多出了一些WebKitFormBoundary分隔符,
对于一个普通的文本,多出一些内容
如果对于一个二进制文件,图片、音频、视频、exe文件,多出来这一部分会直接导致文件损坏。

1.2.文件上传遇到的第一个问题

1.2.1.会多出来一部分分隔符,导致文件损坏
在文件上传的同时,还想上传form表单数据
week12_day03_Fileupload
会发现获取到的数据都是null
此时上传的文件打开,会发现
week12_day03_Fileupload
普通的form表单数据也进入到文件中了。

1.2.2.无法获取到普通form表单请求参数,且表单数据会进入到上传的文件中
为什么呢?
所有一切的原因都是因为添加了enctype=multipart/form-data。但是如果不添加,就不会上传文件。
week12_day03_Fileupload
week12_day03_Fileupload

请求体中的数据结构发生了变更,之前的数据key=value&key=value拼接的,getParameter通过key可以获取到数据value,那么如果数据结构发生了变更,getParameter理应不能再用了。
为什么整个表单中的数据都会进入文件中呢?
因为这些数据都是位于请求体中的,request.getInputSream()相当于把整个请求体全部放入文件中。同样的,分隔符也是位于请求体中的,所以也会被写入文件中。

1.3.总结
问题的话,主要有两大类,
1.文件会有分隔符,导致文件损坏
2.获取请求参数的API不再适用,无法获取到普通form表单数据
原因都是因为添加了enctype=multipart/form-data,因为添加这个属性,使得请求体的数据结构发生了变更,所以获取请求参数的API不在适用,同时添加之后,会多出很多分隔符,所以文件中会包含有分隔符。

1.4.使用组件来进行文件上传
接下来的内容大家会觉得代码太多,而且不理解,记不住。不用去深究。
以后学习到Spring。Spring会帮助你把这些步骤全部封装好,所以今后啥也不用干。

Commons-FileUpload组件
http://commons.apache.org/proper/commons-fileupload/using.html
这个网站会有Commons-FileUpload的一些使用案例。

// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
这句代码的作用就是用来检查request是否包含enctype=multipart/form-data.

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

Tip:核心API—DiskFileItemFactory
DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:
public DiskFileItemFactory(int sizeThreshold, java.io.File repository)
构造函数

public void setSizeThreshold(int sizeThreshold)
设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。

public void setRepository(java.io.File repository)
指定临时文件目录,默认值为System.getProperty(“java.io.tmpdir”).

Tip:核心API—ServletFileUpload
ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
boolean isMultipartContent(HttpServletRequest request)
判断上传表单是否为multipart/form-data类型
List parseRequest(HttpServletRequest request)
解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
setFileSizeMax(long fileSizeMax)
设置单个上传文件的最大值bytes
setSizeMax(long sizeMax)
设置上传文件总量的最大值(多个文件同时上传时文件最大值的总和)
setHeaderEncoding(java.lang.String encoding)
设置编码格式,解决上传文件名乱码问题

Tip:核心API—FileItem
FileItem 用来表示文件上传表单中的一个上传文件对象或者普通表单对象
boolean isFormField() 判断FileItem是一个文件上传对象还是普通表单对象
如果判断是一个普通表单对象
String getFieldName() 获得普通表单对象的name属性
String getString(String encoding) 获得普通表单对象的value属性
如果判断是一个文件上传对象
String getName() 获得上传文件的文件名(有些浏览器会携带客户端路径)
InputStream getInputStream() 获得上传文件的输入流
delete() 在关闭FileItem输入流后,删除临时文件

Tip:上传文件的存放问题
文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。

UploadServlet2:

package com.cskaoyan.upload;

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 javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;

@WebServlet("/upload2")
public class UploadServlet2 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;
        }
        //result=true
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletContext servletContext = 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");
        // 允许上传文件的最大大小为1024 bytes
        //upload.setFileSizeMax(1024);
        try {
            List<FileItem> items = upload.parseRequest(request);
            //对items进行迭代
            Iterator<FileItem> iterator = items.iterator();
            while (iterator.hasNext()){
                //每一个item其实就是一个有name属性的input框的封装
                FileItem item = iterator.next();
                if(item.isFormField()){
                    //当前是普通的form表单数据
                    processFormField(item);
                }else{
                   //当前是上传的文件
                   processUploadedFile(item);
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理上传的文件逻辑
     * @param item
     */
    private void processUploadedFile(FileItem item) {
        String fieldName = item.getFieldName();
        String fileName = item.getName();
        System.out.println(fieldName + " === " + fileName);
        String realPath = getServletContext().getRealPath("upload/" + fileName);
        File file = new File(realPath);
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        try {
            //保存文件到硬盘中
            item.write(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理普通的form表单数据
     * 只需要获取name和value值即可
     * 上传文件之后,不要再用getParameter去获取请求参数了
     * 有没有中文乱码问题呢?
     * @param item
     */
    private void processFormField(FileItem item) {
        String name = item.getFieldName();
        String value = null;
        try {
            //表示value用utf-8编码
            value = item.getString("utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println(name + " = " + value);
    }

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

    }
}

1.4.1.form表单数据中文乱码
week12_day03_Fileupload
week12_day03_Fileupload

问题:
比如,还是像之前获取form表单数据一样,接下来需要将这些数据封装到一个bean中,然后保存到数据库,怎么操作简便一些???????


作业1: 在之前的注册页面的基础上,增加注册时传头像(可以想一想如何更加简便)
form.html

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
</head>
<body>
<!--action就表示你需要向哪个地址发起一个请求-->
<!--submit一个表单一个,就构建一个请求报文,由于post请求方式,所以在请求体中,-->
<!--参数的组织形式是这样的:username=xx&password=xx&gender=xx&-->
<!--所以我们就可以通过name取到用户所输入的值-->
<form action="/app/register" enctype="multipart/form-data" method="post">
   用户名:<input type="text" name="username"><br>
   上传头像:<input type="file" name="img"><br>
   密码:<input type="password" name="password"><br>
   性别:男<input type="radio" name="gender" value="male"><input type="radio" name="gender" value="female"><br>
   爱好:java<input type="checkbox" name="hobby" value="java">
   c++<input type="checkbox" name="hobby" value="c++">
   python<input type="checkbox" name="hobby" value="python"><br>
   简介<textarea name="description"></textarea><br>
   <input type="submit">
</form>
</body>
</html>

HomeworkServlet01 :

package com.cskaoyan.Homework;

import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
* @author shihao
* @create 2020-06-25 16:35
*/
@WebServlet("/register")
public class HomeworkServlet01 extends HttpServlet {
   /**
    * 需要注意的是,当引入文件上传之后,form表单中的数据通过request.getParameter方法获取不到数据
    * @param request
    * @param response
    * @throws ServletException
    * @throws IOException
    */
   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       Map<String, Object> map = FileUploadUtils.parseRequest(request);
       User user = new User();
       try {
           BeanUtils.populate(user,map);
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
       //将user保存到数据库中
       System.out.println(user);
       //hobby里面的数据直接将多个数据以,隔开的形式保存在数据库中即可
   }

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

   }
}

作业2: 实现多文件上传功能

uploadMultiFile.html:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
</head>
<body>
   <form action="/app/multi" enctype="multipart/form-data" method="post">
       <input type="file" name="headImage"><br>
       <input type="file" name="detailImage"><br>
       <input type="submit">
   </form>
</body>
</html>

FileUploadUtils:

package com.cskaoyan.Homework;

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 javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
* @author shihao
* @create 2020-06-25 17:03
*/
public class FileUploadUtils {

   private static String UPLOAD_PATH = "/upload";

   public static Map<String, Object> parseRequest(HttpServletRequest request) {
       // Create a factory for disk-based file items
       DiskFileItemFactory factory = new DiskFileItemFactory();

       // Configure a repository (to ensure a secure temp location is used)
       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);
       HashMap<String, Object> result = new HashMap<>();

       //如果上传的文件名中文乱码,可以这么解决
       upload.setHeaderEncoding("utf-8");

       // Parse the request
       try {
           List<FileItem> items = upload.parseRequest(request);
           Iterator<FileItem> iterator = items.iterator();
           while (iterator.hasNext()) {
               FileItem item = iterator.next();
               if (item.isFormField()) {
                   processFormField(item, result);
               } else {
                   processUploadField(item, result, servletContext);
               }
           }
       } catch (FileUploadException e) {
           e.printStackTrace();
       } catch (UnsupportedEncodingException e) {
           e.printStackTrace();
       }

       return result;
   }

   /**
    * 处理上传文件的具体逻辑
    *
    * @param item
    * @param result         文件上传成功后路径保存在map中
    * @param servletContext
    */
   private static void processUploadField(FileItem item, HashMap<String, Object> result, ServletContext servletContext) {
       String fieldName = item.getFieldName();
       String fileName = item.getName();
       System.out.println(fieldName + " === " + fileName);
       String realPath = servletContext.getRealPath("upload/" + fileName);
       File file = new File(realPath);
       if (!file.getParentFile().exists()) {
           file.getParentFile().mkdirs();
       }
       try {
           //保存文件到硬盘中
           item.write(file);
       } catch (Exception e) {
           e.printStackTrace();
       }
       result.put(fileName, realPath);
   }

   /**
    * 处理普通的表单数据
    * 需要考虑到hobby是一个组合,需要考虑到后面覆盖前面的情况
    *
    * @param item
    * @param result 处理表单的数据保存再map中
    */
   private static void processFormField(FileItem item, HashMap<String, Object> result) throws UnsupportedEncodingException {
       String fieldName = item.getFieldName();
       if (result.get(fieldName) == null) {
           result.put(fieldName, item.getString("utf-8"));
           return;
       }
       result.put(fieldName, result.get(fieldName) + "," + item.getString("utf-8"));
   }
}

HomeworkServlet02:

package com.cskaoyan.Homework;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
* @author shihao
* @create 2020-06-25 17:52
*/
@WebServlet("/multi")
public class HomeworkServlet02 extends HttpServlet {
   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       Map<String, Object> map = FileUploadUtils.parseRequest(request);
       System.out.println(map);
   }

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

   }
}
相关标签: JavaEE