文件上传与下载

一、文件上传

进行文件上传时需要做如下前提准备:

  1. 网页表单的请求方式必须为POST请求,并且form项的enctype属性要设置为multipart/form-data(表示以二进制方式提交请求信息)
  2. 使用file的表单域:<input type="file" name="file"/>(注意name属性必须设置,不然浏览器不会发送上传的数据)

当表单请求的编码方式变成以二进制的方式传输信息时,通过String request.getParameter方法是获取不到请求信息的。我们需要通过request对象获得输入流,然后通过输入流来读取到信息。这样后端就拿到上传的文件了,只不过有时候我们还需要解析等操作,用此方式是比较麻烦的。Apache组织提供了一个开源的组件commons-fileupload。该组件可将“multipart/form-data”类型请求的各种表单域解析出来,并实现一个或多个文件上传,同时也可以限制上传文件的大小等内容。

首先需要导入两个jar包:commons-fileupload和commons-io。

commons-fileupload可以解析请求,得到一个 FileItem对象组成的List,它把所有的请求信息都解析为FileItem对象,无论是一个普通的文本域还是一个文件域。(对于这一点可以利用FileItem对象的isFormField()方法来判断是否是一个表单域(若不是,则说明是一个文件域))。如下是得到这个List<FileItem>的简单方式:

FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);

当然在需要的时候我们可以增加一些设置,如下:

DiskFileItemFactory factory = new DiskFileItemFactory();
//用于设置临时文件的的临界值100KB如不指定则系统默认为10KB
factory.setSizeThreshold(1024*100);
//创建临时存放文件夹
File tempDirectory = new File("f:\\tempDirectory");
//当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以临时文件形式保存在磁盘上。
//如果不设置则默认采用系统默认的临时文件路径,该路径可以通过Stsyem.getProperty("java.io.tmpdir")来获取。
factory.setRepository(tempDirectory);

ServletFileUpload upload = new ServletFileUpload(factory);
//设置编码方式为UTF-8,注意这个解决的是上传的文件路径的乱码
upload.setHeaderEncoding("UTF-8");
//设置上传文件的总大小
upload.setSizeMax(1024 * 1024 * 10);
//设置单个上传文件的大小
upload.setFileSizeMax(1024 * 1024 * 3);

List<FileItem> items = upload.parseRequest(request);

 Apahce文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析的数据,以便在后面进行数据的进一步处理。由于java虚拟机默认可以使用的内存空间是有限的,超出限制则会抛出java.lang.OutOfMemoryError错误。若上传的文件很大,在内存中将无法保存该文件内容,所以采用临时文件来保存这些数据,但如果文件很小,则直接加载到内存中性能会好一些。

当拿到这个List<FileItem>之后,就可以遍历它针对普通的表单域和文件域做出不同的处理。示例如下:

List<FileItem> items = upload.parseRequest(request);
int size = items.size();
for(int index = 0; index < size; index++) {
    FileItem item = (FileItem) items.get(index);
    //下面只是介绍FileItem的一些常用方法。
    //如果是一个普通的表单域
    if(item.isFormField()) {
        //获取参数名
        String name = item.getFieldName();
        //获取参数值(以UTF-8的编码方式获取)
        String value = item.getString("UTF-8");
    }else {
	//这个是<input>标签的name属性值
	String fieldName = item.getFieldName();
	//获取上传文件的文件名(带绝对路径)
	String fileName = item.getName();
	//获取文件真实大小(单位B)
	long sizeInBytes = item.getSize();

	//这里可以使用自定义方法writeFile(file, item)将上传的文件写到目标文件中去,但是建议直接使用FileItem对象的write方法。
        item.write(file);
        //用于删除临时文件。务必调用。
        item.delete();        
    }
}

private void writeFile(File file, FileItem item) throws IOException {
    //获得该文件的输入流
    InputStream in = item.getInputStream();
    byte[] buff = new byte[1024];
    int len = 0;
    OutputStream out = new FileOutputStream(file);
		
    while((len = in.read(buff)) != -1) {
        out.write(buff, 0, len);
    }
		
    out.close();
    in.close();

}

综上就是文件上传组件的基本使用。这里需要注意的是

中文乱码问题:

  • upload.setHeaderEncoding("UTF-8"):这个解决的是上传的中文路径的乱码。
  • item.getString("UTF-8"):这个解决的是普通表单项的乱码。

拷贝文件时直接调用FileItem对象的write(File file)即可。

上传完成时务必删除临时文件。

文件上传实战案例

需求:

  1. 在upload.jsp 页面上使用jQuery实现“新增一个附件",“删除附件"。但至少需要保留一个.。
  2. 对文件的扩展名和文件的大小进行验证。以下的规则是可配置的:
    1. 文件的扩展名必须为-pptx, docx, doc
    2. 每个文件的大小不能超过1M
    3. 总的文件大小不能超过5M.

jsp定义如下:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/scripts/jQuery.js"></script>
<script type="text/javascript">
	$(function(){
		var i = 2;
		//获取#addFile
		$("#addFile").click(function(){
			$(this).parent().parent().before("<tr class='file' ><td>File" 
					+ i + ":</td><td><input type='file' name='file"
					+ i + "'/></td></tr>"
					+ "<tr class='desc'><td>Desc"
					+ i + ":</td><td><input type='text' name='desc"
					+ i + "'/><button type='button' id='delete" 
					+ i + "'>删除</button></td></tr>");
			i++;
			
			//获取新添加的按钮
			$("#delete" + (i - 1)).click(function(){
				var $tr = $(this).parent().parent();
				$tr.prev("tr").remove();
				$tr.remove();
				
				//对i重写排序
				$(".file").each(function(index){
					var n = index + 1;
					$(this).find("td:first").text("File" + n);
					$(this).find("td:last input").attr("name","file" + n);
				});
				
				$(".desc").each(function(index){
					var n = index + 1;
					$(this).find("td:first").text("Desc" + n);
					$(this).find("td:last input").attr("name","desc" + n);
				});
				
				i--;
			});
			
		});
		
	});
</script>
</head>
<body>
        <c:if test="${!empty requestScope.sizeLimitExceeded}">
		${requestScope.sizeLimitExceeded} 	
	</c:if>
	<c:if test="${!empty requestScope.fileSizeLimitExeeded}">
		${requestScope.fileSizeLimitExeeded}
	</c:if>
	<form action="fileUploadServlet" method="post" enctype="multipart/form-data">
		<table>
			<tr class="file">
				<td>File1:</td>
				<td><input type="file" name="file1" /></td>
			</tr>
			
			<tr class="desc">
				<td>Desc1:</td>
				<td><input type="text" name="desc1" /></td>
			</tr>
			
			<tr>
				<td> <input type="submit" value="提交" /> </td>
<!--button的type 属性,IE的默认是 “button”,非IE默认是 “submit”。 所以如果不指定type则点击button会提交表单-->
				<td><button type="button" id="addFile">新增一个附件</button></td>
			</tr>
		</table>
	</form>
</body>
</html>

这里需要注意的是:File和Desc是一一对应的,所以为了后端能顺利映射成功,将JSP页面中的这两个输入项的name需要对应起来(比如:file1对应desc1) 

后端的处理,可以配置扩展名,可以配置单个文件和总文件大小,我们利用properties文件进行配置,配置方式如下:

exts=pptx,docx,doc
file.max.size=1048576
total.file.max.size=5242880

工具类PropertiesParseUtil负责解析properties文件,将其键值对加载至内存:

public class PropertiesParseUtil {
	private static final Map<String, String> map = new ConcurrentHashMap<String, String>();
	
	private PropertiesParseUtil() {};
	
	public static void parseProperties(String filepPath) {
		InputStream in = PropertiesParseUtil.class.getClassLoader().getResourceAsStream(filepPath);
		Properties properties = new Properties();
		try {
			properties.load(in);
			Enumeration<Object> keys = properties.keys();
			while(keys.hasMoreElements()) {
				String key = (String) keys.nextElement();
				map.put(key, properties.getProperty(key));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static String getProperty(String propertyName) {
		return map.get(propertyName);
	}
}

配合监听器,当web服务启动时就解析properties文件:

public class FileUploadListener implements ServletContextListener {
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		PropertiesParseUtil.parseProperties("/upload.properties");
	}
}

定义JavaBean,用来封装:文件名,文件存放路径,文件描述信息。(用于映射数据库中对应的三个字段)

public class FileUploadBean {
	private String fileName;
	private String filePath;
	private String fileDesc;

	public FileUploadBean(){}
	
	public FileUploadBean(String fileName, String filePath, String fileDesc) {
		this.fileName = fileName;
		this.filePath = filePath;
		this.fileDesc = fileDesc;
	}
//省略getter和setter方法
}

核心类FileUploadServlet

1.首先我们需要获得FileItem的集合,并对配置的参数进行设置,如下:

@SuppressWarnings("unchecked")
private List<FileItem> getFileItemList(HttpServletRequest request) throws FileUploadException {
    String fileMaxSizeValue = PropertiesParseUtil.getProperty("file.max.size");
    String totalFileMaxSizeValue = PropertiesParseUtil.getProperty("total.file.max.size");

    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setSizeThreshold(1024 * 1024 * 5);
		
    File tempDirectory = new File(DEFAULT_DIRECTORY);
    factory.setRepository(tempDirectory);
		
    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setSizeMax(Integer.parseInt(totalFileMaxSizeValue)); 
    upload.setFileSizeMax(Integer.parseInt(fileMaxSizeValue));
    upload.setHeaderEncoding("UTF-8");

    return upload.parseRequest(request);
}

2.拿到List<FileItem>之后,需要将每个FileItem解析为FileUploadBean,即解析出FileUploadBean集合;其次将扩展名合法的文件上传至后端,并且收集那些扩展名不合法的文件。

//这里IllegalExtNameList用于收集扩展名不合规格的文件
private List<FileUploadBean> buildFileUploadBeans(List<FileItem> fileItems, List<String> IllegalExtNameList) throws Exception {
    Map<String, String> descMap = dealFormField(fileItems);
    return dealFileField(fileItems, descMap, IllegalExtNameList);
}

这里需要分两步做的原因是:文件描述项本身是普通域而文件是文件域,这两者获取到的内容是不一样的,所以我先获取了每个文件的描述项,将其封装进HashMap中(键:每个文件的desc对应标签的name属性值,值:描述内容) ,由于将JSP页面中的这两个输入项的name是对应的(比如:file1对应desc1),所以在解析文件生成FileUploadBean对象时可以正确构建其中的文件描述项。 

private Map<String, String> dealFormField(List<FileItem> fileItems){
    Map<String, String> descMap = new HashMap<String, String>();
    try {
        for(FileItem item : fileItems) {
            if(item.isFormField()) {
	        String key = item.getFieldName();
                String value = item.getString("UTF-8");
		descMap.put(key, value);
	    }
	}
    } catch (UnsupportedEncodingException e) {
        //这个异常时getString抛出的,此处UTF-8是支持的
        e.printStackTrace();
    }
    return descMap;
}

dealFileField用于生成FileUploadBean集合,并处理用户上传的文件(存储扩展名合法的文件,收集扩展名不合法的文件名用于反馈给用户)。

private List<FileUploadBean> dealFileField(List<FileItem> fileItems, Map<String,String> descMap, List<String> IllegalExtNameList) throws Exception {
    String exts = PropertiesParseUtil.getProperty("exts");
    List<String> legalExtNameList = Arrays.asList(exts.split(","));
    List<FileUploadBean> beans = new ArrayList<FileUploadBean>();	
    for(FileItem item : fileItems) {
        if(!item.isFormField()) {
	    String fieldName = item.getFieldName();
            String index = fieldName.substring(fieldName.length() - 1);		
	    //截取文件名
	    String fileName = item.getName();		
	    int i = fileName.lastIndexOf('\\');
	    fileName = fileName.substring(i + 1);
	    //截取文件扩展名
	    int j = fileName.lastIndexOf('.');
	    String extName = fileName.substring(j + 1);
	    //效验文件扩展名
	    if(!legalExtNameList.contains(extName)) {
	        IllegalExtNameList.add(fileName);
	        continue;
	    }
	    //随机生成目标文件存放路径
	    String targetFilePath = createTargetFilePath(extName);	
	    item.write(new File(targetFilePath));
            item.delete();
				
	    beans.add(new FileUploadBean(fileName, targetFilePath, descMap.get("desc" + index)));
	}
    }
    return beans;
}

在存储上传的文件时,并没有用原来的文件名,而是随机生成了文件名,并将其存放至默认目录中。如下:

private String createTargetFilePath(String extName) {
    Random random = new Random();
    int randomNumber = random.nextInt(1000000);
    return TARGET_FILE_PATH + "\\" + System.currentTimeMillis() + randomNumber + "." + extName;
}

3.当请求过来时,我们只需要按照上面的思路处理就可以了,如下:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String path  = null;
    try {
        List<FileItem> fileItems = getFileItemList(request);
        List<String> IllegalExtNameList = new ArrayList<String>();
			
        List<FileUploadBean> beans = buildFileUploadBeans(fileItems, IllegalExtNameList);
        saveBeans(beans);		                        //将信息保存至数据库
	FileUtils.deleteDirectory(new File(DEFAULT_DIRECTORY));	//删除临时文件夹
        path = "/app/sucess.jsp";
	//设置扩展名不合法异常
	setIllegalExtNameException(request, IllegalExtNameList);
    } catch (Exception e) {
        path = "/app/upload.jsp";
        //设置上传文件大小越界异常
        setFileUploadException(request, e);
    }
    request.getRequestDispatcher(path).forward(request, response);
}

4.向用户反馈信息,当文件大小越界,或者扩展名不合法时,都应该将该消息告知给用户,所以首先将这些消息设置进request域中,并通过请求转发的方式跳转页面(如果出现异常则所有文件都不能成功上传,因为这个异常是在ServletFileUpload对象歇息request的时候就出现的)。

设置扩展名不合法消息:

private void setIllegalExtNameException(HttpServletRequest request, List<String> IllegalExtNameList) {
    if(IllegalExtNameList.size() > 0) {
        StringBuilder extException = new StringBuilder();
	extException.append("当前支持的扩展名有:" + PropertiesParseUtil.getProperty("exts") + "\n");
	extException.append("下列文件扩展名不合法:\n");
	for(String fileName : IllegalExtNameList) {
	    extException.append("\t" + fileName + "\n");
	}
	request.setAttribute("extException", extException.toString());
    }
}

设置文件大小越界消息:

private void setFileUploadException(HttpServletRequest request, Exception e) {
    //总文件大小超出异常
    if(e instanceof FileUploadBase.SizeLimitExceededException) {
	Pattern pattern = Pattern.compile("\\D*(\\d+)\\D*(\\d+)\\)");
	Matcher matcher = pattern.matcher(e.getMessage());
	if(matcher.matches()) {
	    request.setAttribute("sizeLimitExceeded", "您的总文件大小" + matcher.group(1) + "超过了规定大小" + matcher.group(2));
	}
    }
    //单个文件大小超出异常
    if(e instanceof FileUploadBase.FileSizeLimitExceededException) {
	request.setAttribute("fileSizeLimitExeeded", "文件大小超出限制,允许上传的单个文件的最大大小为:" + PropertiesParseUtil.getProperty("file.max.size"));
    }
}

 最终的成功页面:

<body>
    <c:if test="${!empty requestScope.extException}">
        <h3>只有部分文件上传成功,${requestScope.extException }</h3> 
    </c:if>
    <c:if test="${empty requestScope.extException}">
	<h3>文件上传成功</h3>
    </c:if>
    <br>
    <a href="${pageContext.request.contextPath }/app/upload.jsp">Return...</a>
</body>

下面是这几种情况的运行结果:

二、文件下载

比如我现在有一个txt文件在浏览器端展示,如果用户需要下载这个txt文件可以这样操作:将这个文件的路径包裹在超链接标签内,然后访问那个txt文件,然后点“另存为”就可以下载,如下:

当然上面的方式也是一种下载方式,是静态下载。如果我们直接点击超链接就会发现网页直接显示了txt文件中的内容。这是因为:浏览器可以自行识别解析txt格式的文件,你再直接点击超链接访问mp4文件它也是直接可以播放的。如果直接访问的是浏览器不能识别解析的文件格式,那么它才会下载。我们现在需要做的就是直接访问txt文件不让浏览器自行解析,而是让其将文件发送传输给客户端浏览器,也就是下载。具体做法很简单,如下:

前端页面:

<body>
    <a href="./downloadServlet">下载mp4文件</a>
</body>

downloadServlet如下:

public class DownloadServlet extends HttpServlet{
    private static final long serialVersionUID = 2730011332467612775L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.通知客户端浏览器:这是一个需要下载的文件,不能再按普通的html文件的方式打开。
        response.setContentType("application/x-msdownload");
        String fileName = "视频文件.mp4";
	/*
	 * 2.通知浏览器,不再有浏览器处理该文件,而是交由用户自行处理
	 * 注意该文件名是中文,将该文件名按照UTF-8格式编码,以解决下载文件名称乱码问题
	 */
	response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
		
	OutputStream out = response.getOutputStream();
	String realPath = getServletContext().getRealPath("resources/" + fileName);
	InputStream in = new FileInputStream(realPath);
		
	byte[] buffer = new byte[1024];
	int len = 0;
	while((len = in.read(buffer)) != -1) {
	    out.write(buffer, 0, len);
	}
	in.close();
	//输出流是不用关的,还需要将响应信息交给浏览器端
    }
}

这样我们直接访问视频文件就会下载下来:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值