JavaWeb世界(九):文件上传与下载

一、文件上传

先创建工程,写一个简易的JSP:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>文件上传</h3>
	<form action="" method="POST">
		账号:<input type="text" name="username"/><br/>
		邮箱:<input type="text" name="email"/><br/>
		头像:<input type="file" name="pic" accept="image/*"/><br/>
		<input type="submit" value=" 注册 "/><br/>
	</form>
</body>
</html>

在上传的头像那里,类型改为 file ,后面的 accept 可以用来更改选择文件的类型(为图片类型),效果如下:
文件上传
在这里插入图片描述
要点:

  1. 上传控件所在的表单method必须为 POST,因为GET方式传入的数据大小不能超过2KB,而POST大小没有限制。
  2. 上传控件的类型为 file.

使用一个Servlet来接收参数,并打印表单中的内容,如下:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		req.setCharacterEncoding("UTF-8");
		System.out.println(req.getParameter("username"));
		System.out.println(req.getParameter("email"));
		System.out.println(req.getParameter("pic"));
	}
}

在这里插入图片描述
显然这里的图片是字符串。

因此表单的编码必须是二进制编码
因此我们需要用到表单中的 enctype 属性中的 multipart/form-data
在这里插入图片描述
再提交一遍:
在这里插入图片描述
全部为 NULL 了。

这是因为在使用二进制编码 multipart/form-data 后,在Servlet中再也不能通过request.getParameter 方法来获得参数了,设置编码都没有效果了。

没有设置二进制编码时,POST为:
未设置二进制编码前
在设置了二进制编码后的请求头:
在这里插入图片描述
红色框框里的就是图片的二维码信息。
我们打印一下:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Scanner sc = new Scanner(req.getInputStream());
		while (sc.hasNextLine()) {
			System.out.println(sc.nextLine());
		}
	}
}

在这里插入图片描述

基于 Apache FileUpload 组件

Apache commons-fileupload 的jar包下载
Apache commons-io 的jar包下载
我们按照官方文档的提示来进行文件上传操作:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		//检查是否有文件上传请求:请求方法为POST,请求编码为multipart/form-data
		boolean isMultipart = ServletFileUpload.isMultipartContent(req);
		if (!isMultipart) {
			return;
		}

		try {
			//1.创建FileItemFactory对象
			//FileItemFactory是用来创建FileItem对象的
			//FileItem对象相当于form表单中的表单控件的封装
			DiskFileItemFactory factory = new DiskFileItemFactory();

			//2.创建一个新的文件上传处理程序(文件上传处理器)
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//3.解析请求
			List<FileItem> items = upload.parseRequest(req);
			System.out.println(items);
		} catch (FileUploadException e) {
			e.printStackTrace();
		}
	}
}

运行发现报错:找不到类:
ClassNotFoundException
缺少IO包,添加之后,运行结果如下:
在这里插入图片描述
我们参照文档,将表单里面的内容提取并将文件内容写到磁盘中的文件里:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		//检查是否有文件上传请求:请求方法为POST,请求编码为multipart/form-data
		boolean isMultipart = ServletFileUpload.isMultipartContent(req);
		if (!isMultipart) {
			return;
		}
		try {
			//1.创建FileItemFactory对象
			//FileItemFactory是用来创建FileItem对象的
			//FileItem对象相当于form表单中的表单控件的封装
			DiskFileItemFactory factory = new DiskFileItemFactory();

			//2.创建一个新的文件上传处理程序(文件上传处理器)
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//3.解析请求
			List<FileItem> items = upload.parseRequest(req);
			for (int i = 0; i < items.size(); i++) {
				System.out.println(items.get(i));
			}
			//4.迭代出每一个FileItem
			for (FileItem item : items) {
				//获取表单属性名称
				String fieldName = item.getFieldName();
				if (item.isFormField()) {
					//普通表单控件
					String value = item.getString("UTF-8");//获取当前普通表单控件的参数值
					System.out.println(fieldName + "-" + value);
				} else {
					//上传文件控件
					System.out.println(fieldName + "-" + item.getName());//字段名-上传的文件名
					item.write(new File("E:/", item.getName()));//把二进制文件写到哪个文件中
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

最终结果如下:
在这里插入图片描述
在这里插入图片描述

文件上传的文件名处理

文件名

IE6 中,通过 FileItem.getName 方法获取到的文件名会带有完整路径,因此要进行处理。
我们可以用 FilenameUtils.getName(item.getName())进行转化
解释如下:

public class FileNameUtilsTest {
	public static void main(String[] args) {
		String path = "E:/Judy.png";
		//获取文件名称
		System.out.println(FilenameUtils.getName(path));
		//获取文件名称,不包括拓展名
		System.out.println(FilenameUtils.getBaseName(path));
		//获取文件拓展名
		System.out.println(FilenameUtils.getExtension(path));
	}
}

在这里插入图片描述

上传的文件名问题

我们要给上传的文件起一个唯一的名称:通过UUID

String fileName = UUID.randomUUID().toString() + "." +
					FilenameUtils.getExtension(item.getName());
item.write(new File("E:/", fileName));
上传文件的保存路径

如果我们想将文件保存在应用中,可以用super.getServletContext().getRealPath()函数获得真实路径:

String dir = super.getServletContext().getRealPath("/upload");
System.out.println(dir);
item.write(new File(dir, fileName));

这样就可以在浏览器中访问项目中的文件了,就不必从磁盘中访问了。
注意:在写文件的时候只能写到文件中,不能写到文件夹中。

缓存大小和临时目录

当文件大小超过 10KB 就不能放在缓存中了。
文件不放在内存中,其实放在了所谓的 “临时目录”,在 Tomcat 根目录中的 temp 文件夹下。

DiskFileItemFactory factory = new DiskFileItemFactory();
//设置缓存大小
factory.setSizeThreshold(20 * 1024);
//设置临时目录
factory.setRepository(repository);
文件类型约束
//允许接收的图片类型
private static final String ALLOWED_IMAGE_TYPE = "jpg;png;jpeg;gif";
String[] allowedImageType = ALLOWED_IMAGE_TYPE.split(";");
String ext = FilenameUtils.getExtension(item.getName());
List<String> list = Arrays.asList(allowedImageType);
//上传文件类型不合法
if (!list.contains(ext)) {
	req.setAttribute("errorMsg", "上传文件类型错误");
	req.getRequestDispatcher("/input.jsp").forward(req, resp);
	System.out.println("Invalid Type!");
	return;
}	

jsp中:

<span style="color: red">${errorMsg}</span>

或者更通用的办法是通过上传文件的 mime-type 进行判断是否为 image 类型:

String mimeType = super.getServletContext().getMimeType(FilenameUtils.getName(item.getName()));

抽取FileUtil工具类

在工具类中,因为要复用代码,因此应该具有一般性,并尽可能简洁,我们可以删掉方法中响应参数,并且通过抛出自定义异常来让调用者接收:

if (!list.contains(ext)) {
throw new LogicException("上传文件类型错误,请上传图片文件...");
}
...
catch (LogicException e) {
	throw e;
}

调用类 UploadServlet:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			FileUtil.upload(req);
		} catch (LogicException e) {
			String errorMsg = e.getMessage();
			req.setAttribute("errorMsg", errorMsg);
			req.getRequestDispatcher("/input.jsp").forward(req, resp);
		}
	}
}

这样就重构了 FileUtil

文件大小约束问题

情况一:单个文件超过指定大小;

upload.setFileSizeMax(2*1024*1024);	//2M

否则会报出 FileUploadBase$FileSizeLimitExceededException 的异常
异常
因为是异常,我们要再次捕捉这个异常:

catch (FileSizeLimitExceededException e) {
	throw new LogicException("文件大小超过2M,请重试", e);
}

情况二:该次请求的全部数据超过指定大小

upload.setSizeMax(10 * 1024 * 1024); //10M

否则会报出 SizeLimitExceededException 异常
继续接受这个异常并抛出:

catch (SizeLimitExceededException e) {
	throw new LogicException("该次请求信息量超过10M,请重试", e);
}

效果如下:
效果图

使用Map封装请求信息

我们想要将表单中的值封装到一个对象中,例如 User 类,我们可以在 upload 方法中加入 User 参数,将 Servlet 中创建的类的实例对象作为参数传递进来。:
User类

public class User {
	private String username;
	private String email;
	private String imageUrl;//图片路径
	private String imageName;//图片原始名称
}
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			User user = new User();	//新建User实例	
			FileUtil.upload(req, user);//作为参数传递
			System.out.println(user);
		} catch (LogicException e) {
			e.printStackTrace();
			String errorMsg = e.getMessage();
			req.setAttribute("errorMsg", errorMsg);
			req.getRequestDispatcher("/input.jsp").forward(req, resp);
		}
	}
}

将User赋值

if ("username".equals(fieldName)) {
	user.setUsername(value);
} else if ("email".equals(fieldName)) {
	user.setEmail(value);
}

但是这样并不能处理通用情况,因此我们可以考虑设置一个Map进行传递,而我们知道类和接口对象都是引用传递,类似于C中的指针,因此得到Map中的值后再设置到User中。但是Map不能封装图片、复选框等需要有多个信息的控件。
如果还要用Map进行封装的话,我们可以将图片的两个信息(imageUrl和imageName)再用一个类进行封装,例如 CFile 类,然后作为 Map 的参数传递进去:
CFile类:

@Data
public class CFile {
	private String imageUrl;//图片路径
	private String imageName;//图片原始名称
	
	CFile(String imageUrl, String imageName) {
		this.imageName = imageName;
		this.imageUrl = imageUrl;
	};
}
Map<String, CFile> binaryMap = new HashMap<>();
FileUtil.upload(req, fieldMap, binaryMap);
binaryMap.put(fileName, new CFile("/upload" + fileName, FilenameUtils.getName(item.getName())));

设置值到 User 中,并共享到 show.jsp

user.setUsername(fieldMap.get("username"));
user.setEmail(fieldMap.get("email"));
user.setImageName(binaryMap.get("pic").getImageName());
user.setImageUrl(binaryMap.get("pic").getImageUrl());			
req.setAttribute("user", user);
req.getRequestDispatcher("/show.jsp").forward(req, resp);

然后我们在网页中显示:

<body>
	注册名称:${user.username}</br>
	注册邮箱:${user.email}</br>
	头像原始名称:${user.imageName}</br>
	头像:<img src="${user.imageUrl}">
</body>

结果如下:
在这里插入图片描述

完整代码

UploadServlet.java:

package com.cherry.upload;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

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 javax.xml.transform.Templates;

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 org.apache.commons.io.FilenameUtils;

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

	private static final long serialVersionUID = 3067915922682828536L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			User user = new User();			
			//封装普通表单的数据
			//key:参数名称
			//value:参数的值
			Map<String, String> fieldMap = new HashMap<>();
			Map<String, CFile> binaryMap = new HashMap<>();
			FileUtil.upload(req, fieldMap, binaryMap);
			//再从fieldMap中设置到User中
			System.out.println(fieldMap);
			System.out.println(binaryMap);
			
			user.setUsername(fieldMap.get("username"));
			user.setEmail(fieldMap.get("email"));
			user.setImageName(binaryMap.get("pic").getImageName());
			user.setImageUrl(binaryMap.get("pic").getImageUrl());
			
			req.setAttribute("user", user);
			req.getRequestDispatcher("/show.jsp").forward(req, resp);
			System.out.println(user);
		} catch (LogicException e) {
			e.printStackTrace();
			String errorMsg = e.getMessage();
			req.setAttribute("errorMsg", errorMsg);
			req.getRequestDispatcher("/input.jsp").forward(req, resp);
		}
	}
}

FileUtil.java

package com.cherry.upload;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;

import lombok.Value;

public class FileUtil {
	//允许接收的图片类型
	private static final String ALLOWED_IMAGE_TYPE = "jpg;png;jpeg;gif;pdf";

	public static void upload(HttpServletRequest req, Map<String, String> fieldMap,
			Map<String, CFile> binaryMap) {
		//检查是否有文件上传请求:请求方法为POST,请求编码为multipart/form-data
		boolean isMultipart = ServletFileUpload.isMultipartContent(req);
		if (!isMultipart) {
			return;
		}

		try {
			//1.创建FileItemFactory对象
			//FileItemFactory是用来创建FileItem对象的
			//FileItem对象相当于form表单中的表单控件的封装
			DiskFileItemFactory factory = new DiskFileItemFactory();
			//设置缓存大小
			//factory.setSizeThreshold(20 * 1024);
			//设置临时目录
			//factory.setRepository(repository);

			//2.创建一个新的文件上传处理程序(文件上传处理器)
			ServletFileUpload upload = new ServletFileUpload(factory);
			//设置单个文件上传的大小限制
			upload.setFileSizeMax(2 * 1024 * 1024); //2M
			//设置所有请求的总数据的大小
			upload.setSizeMax(4 * 1024 * 1024); //10M

			//3.解析请求
			List<FileItem> items = upload.parseRequest(req);
			/*for (int i = 0; i < items.size(); i++) {
				System.out.println(items.get(i));
			}*/
			//4.迭代出每一个FileItem
			for (FileItem item : items) {
				//获取表单属性名称
				String fieldName = item.getFieldName();
				if (item.isFormField()) {
					//普通表单控件
					String value = item.getString("UTF-8");//获取当前普通表单控件的参数值
					System.out.println(fieldName + "-" + value);
					fieldMap.put(fieldName, value);
				} else {
					//当前上传文件的mime类型
					/*String mimeType = req.getServletContext()
							.getMimeType(FilenameUtils.getName(item.getName()));
					System.out.println(mimeType);*/

					String[] allowedImageType = ALLOWED_IMAGE_TYPE.split(";");
					String ext = FilenameUtils.getExtension(item.getName()).toLowerCase();
					List<String> list = Arrays.asList(allowedImageType);
					//上传文件类型不合法
					if (!list.contains(ext)) {
						throw new LogicException("上传文件类型错误,请上传图片文件...");
					}
					//上传文件控件
					System.out.println(fieldName + "-" + FilenameUtils.getName(item.getName()));//字段名-上传的文件名
					String fileName = UUID.randomUUID().toString() + "."
							+ FilenameUtils.getExtension(item.getName());
					String dir = req.getServletContext().getRealPath("/upload");
					item.write(new File(dir, fileName));//把二进制文件写到哪个文件中
					//是否存储在内存中
					System.out.println(item.isInMemory());

					binaryMap.put(fieldName, new CFile("/upload/" + fileName,
							FilenameUtils.getName(item.getName())));
				}
			}
		} catch (FileSizeLimitExceededException e) {
			throw new LogicException("文件大小超过2M,请重试", e);
		} catch (SizeLimitExceededException e) {
			throw new LogicException("该次请求信息量超过10M,请重试", e);
		} catch (LogicException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

User.java:

package com.cherry.upload;

import lombok.Data;

@Data
public class User {
	private String username;
	private String email;
	private String imageUrl;//图片路径
	private String imageName;//图片原始名称
}

CFile.java:

package com.cherry.upload;

import lombok.Data;

@Data
public class CFile {
	private String imageUrl;//图片路径
	private String imageName;//图片原始名称
	
	CFile(String imageUrl, String imageName) {
		this.imageName = imageName;
		this.imageUrl = imageUrl;
	};
}

input.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>文件上传</h3>
	<span style="color: red">${errorMsg}</span>
	<form action="/upload" method="POST" enctype="multipart/form-data">
		账号:<input type="text" name="username"/><br/>
		邮箱:<input type="text" name="email"/><br/>
		头像:<input type="file" name="pic" accept="image/*"/><br/>
		<input type="submit" value=" 注册 "/><br/>
	</form>
</body>
</html>

show.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	注册名称:${user.username}</br>
	注册邮箱:${user.email}</br>
	头像原始名称:${user.imageName}</br>
	头像:<img src="${user.imageUrl}">
</body>
</html>

二、文件下载

没什么好说的,需要注意的是,下载资源不能暴露在 WEB-INF 外面,必须通过 Servlet 响应下载,直接上代码:

DownloadServlet.java

package com.cherry._02_download;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/down")
public class DownloadServlet extends HttpServlet {

	private static final long serialVersionUID = -6406195121839970110L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		//权限检查等
		//1.获取被下载的资源文件名
		String fileName = req.getParameter("filename");
		System.out.println(fileName);
		if (hasLength(fileName)) {
			fileName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
		}
		//2.从服务器中找到被下载资源的绝对路径
		String realPath = super.getServletContext().getRealPath("/WEB-INF/download/" + fileName);
		System.out.println(realPath);

		//①不要让浏览器打开文件,而是要弹出下载框并保存文件
		resp.setContentType("application/x-msdownload");
		//②设置下载文件的建议文件名
		String userAgent = req.getHeader("User-Agent");
		if (userAgent.contains("MSIE")) {
			//IE
			fileName = URLEncoder.encode(fileName, "UTF-8");
		} else {
			//非IE
			fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
		}
		resp.setHeader("Content-Disposition", "attachment; filename =" + fileName);
		//3.通过文件输出流将磁盘中的文件读取到程序中,然后输出响应到浏览器
		Files.copy(Paths.get(realPath), resp.getOutputStream());
	}

	private boolean hasLength(String str) {
		return str != null && "".equals(str.trim());
	}
}

download.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>下载资源列表</h3>
	<a href="/down?filename=Judy.zip">Judy.zip</a></br>
	<a href="/down?filename=Tom猫.zip">Tom猫.zip</a></br>	
</body>
</html>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值