一、文件的上传

HTML代码需要一个 <form> 表单,注意如下

  1. 要有一个 <form> 标签,method="post"
  2. <form> 标签的 encType 属性值必须为 "multipart/form-data"
  3. <form> 标签中的 <input> 标签,type="file"
  4. 编写服务器代码,接收上传的数据

说明encType="multipart/form-data" 表示提交的数据以多段(每一个input标签为一个段)的形式进行拼接,然后以二进制流的形式发送给Servlet。

HTML代码的一个样例如下:

<html>
	<head>
		<title>上传文件</title>
	</head>
	<body>
		<!-- 核心的form表单 -->
		<form action="uploadServlet" method="post" encType="multipart/form-data">
			用户名:<input type="text" name="username"> <br>
			选择头像:<input type="file" name="photo"> <br>
			<input type="submit" value="上传信息">
		</form>
	</body>
</html>

那么,当我们将encType设置为multipart/form-data之后,HTTP协议是如何发送数据的呢?参考下面这个图,数据是以二进制流的方式分段发送的。

java ResponseEntity下载文件 java download file_文件下载


那么服务端如何接收数据呢?因为客户端是以二进制流的形式发送的,那么服务端就不能再通过req.getParameter("username")这种形式获得参数了,只能通过二进制输入流来接收。

public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 获取服务端发送过来的输入流
		ServletInputStream inputStream = req.getInputStream();
		// 定义一个缓冲区
		byte[] buf = new buf[1024];
		int readLen = 0;
		while ((readLen = inputStream.read(buf)) != -1) {
			System.out.println(new String(buf, 0, readLen));
		}
    }
}

运行结果如下,可以发现服务端接收到的数据格式和客户端发过来的一致。

java ResponseEntity下载文件 java download file_文件下载_02


下面需要做的工作就是,如何解析我们得到的数据流的内容?

这个解析的工作,commons-fileupload.jarcommons-io.jar两个包已经帮我们封装好了解析的功能,我们只需要掌握如何使用他们即可。

这里我们使用的版本是 commons-fileupload-1.2.1.jarcommons-io-1.4.jar,引入到需要的项目工程中即可使用。

下面介绍一些常用的方法:

// 判断当前上传的数据格式是否是多段的格式
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
// 解析上传的数据,每一个FileItem表示每一个表单项,对应每一个input标签
public List<FileItem> parseRequest(HttpServletRequest request);
// 判断当前这个FileItem 是否是普通的表单项
// true表示普通类型的表单项,false表示文件
boolean FileItem.isFormField();
// 获取当前FileItem表单项的name属性值(普通表单项)
String FileItem.getFieldName();
// 获取当前FileItem表单项的值(普通表单项)
String FileItem.getString();
// 获取当前FileItem文件名(文件表单项)
String FileItem.getName();
// 将上传的文件,写到 filePath 指定的磁盘位置
void FileItem.write(String filePath);

我们将这些方法应用到实际项目中去:

public class UploadServlet extends HttpServlet {

     @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 先判断上传的数据是否是多段数据
        if (ServletFileUpload.isMultipartContent(req)) {
            // 创建FileItemFactory 工厂实现类
            FileItemFactory fileItemFactory = new DiskFileItemFactory();
            // 创建用于解析上传数据的实现类ServletFileUpload 类
            ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
            // 这里解决上传的文件名的中文乱码问题
            servletFileUpload.setHeaderEncoding("UTF-8");

            try {
            	// 解析上传的二进制流数据,得到每一个表单项FileItem
                List<FileItem> list = servletFileUpload.parseRequest(req);
				// 循环判断,每一个表单项是普通类型还是文件类型
                for (FileItem fileItem : list) {
                    if (fileItem.isFormField()) {
                        // 代表普通表单项
                        System.out.println("表单项的name 属性值:" + fileItem.getFieldName());
                        // 参数UFT-8 解决普通表单项的中文乱码问题
                        System.out.println("表单项的value 属性值:" + fileItem.getString("UTF-8"));
                    } else {
                        // 代表上传的文件
                        System.out.println("表单项的name 属性值:" + fileItem.getFieldName());
                        System.out.println("上传的文件名:" + fileItem.getName());
                        // 保存到服务器的指定目录下
                        fileItem.write(new File("e:\\" + fileItem.getName()));
                    }

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

二、文件的下载

实现文件下载的逻辑,如下图。

java ResponseEntity下载文件 java download file_文件下载_03


直接上项目的实战代码:

public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 1. 获取客户端请求的文件名
        String downloadFileName = req.getParameter("downloadFileName");
		// 2. 读取要下载的文件内容(通过ServletContext对象实现)
		ServletContext servletContext = getServletContext();
		
		// 3. 在回传前,通过响应头告诉客户端返回的文件类型
		// 第一个斜杠/ 表示地址为 http://ip:port/工程名/ 映射到代码的web目录
        String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
        resp.setContentType(mimeType);
        
        // 4. 告诉客户端,通过响应头告诉客户端收到的数据是用于下载的
        // Content-Disposition响应头:表示设置收到的数据的处理方式
        // attachment:表示附件,告知浏览器用下载的方式接收文件
        // filename:表示客户端收到的文件名
        // 如果不写下面这行,文件(如图片)会直接写在页面上,而不是以一个下载的方式
        resp.setHeader("Content-Disposition", "attachment; filename=" + downloadFileName);
		
		// 5. 获取对应文件二进制流和response的输出流
        InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
        ServletOutputStream outputStream = resp.getOutputStream();
        
        // 6. IOUtils.copy(InputStream, OutputStream) 将输入流的内容拷贝给输出流
        IOUtils.copy(resourceAsStream, outputStream);
    }
}

现在,客户端虽然能够获得正确的文件,但是如果文件的名称包含中文,可能会导致中文乱码的问题。我们可以通过下面的方式解决:

public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 1. 获取客户端请求的文件名
        String downloadFileName = req.getParameter("downloadFileName");
		// 2. 读取要下载的文件内容(通过ServletContext对象实现)
		ServletContext servletContext = getServletContext();
		
		// 3. 在回传前,通过响应头告诉客户端返回的文件类型
		// 第一个斜杠/ 表示地址为 http://ip:port/工程名/ 映射到代码的web目录
        String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
        resp.setContentType(mimeType);
        
        // 4. 告诉客户端,通过响应头告诉客户端收到的数据是用于下载的
        // Content-Disposition响应头:表示设置收到的数据的处理方式
        // attachment:表示附件,告知浏览器用下载的方式接收文件
        // filename:表示客户端收到的文件名
		// 下面根据不同浏览器,设置不同的中文乱码处理方式
		if (req.getHeader("User-Agent").contains("Firefox")) {
			// 处理火狐浏览器中文乱码问题,使用Base64编码
			resp.setHeader("Content-Disposition", "attachment; filename==?UTF-8?B?" + new BASE64Encoder().encode(downloadFileName, "UTF-8"));
		} else {
			// 处理IE 和 Chrome浏览器中文乱码问题,使用URL编码
			resp.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("downloadFileName", "UTF-8"));
		}
        
		// 5. 获取对应文件二进制流和response的输出流
        InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
        ServletOutputStream outputStream = resp.getOutputStream();
        
        // 6. IOUtils.copy(InputStream, OutputStream) 将输入流的内容拷贝给输出流
        IOUtils.copy(resourceAsStream, outputStream);
    }
}