SpringMVC 文件上传下载

@

文件上传

文件上传是项目开发中最常见的功能。为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。
Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

MultipartFile对象

Spring MVC会将上传的文件绑定到MultipartFile对象中。MultipartFile提供了获取上传文件内容、文件名等方法。通过transferTo()方法还可以将文件存储到硬件中,MultipartFile对象中的常用方法如下:

  • byte[] getBytes():获取文件数据
  • String getContentType[]:获取文件MIME类型,如image/jpeg等
  • InputStream getInputStream():获取文件流
  • String getName():获取表单中文件组件的名字
  • String getOriginalFilename():获取上传文件的原名
  • Long getSize():获取文件的字节大小,单位为byte
  • boolean isEmpty():是否有上传文件
  • void transferTo(File dest):将上传文件保存到一个目录文件中

文件下载

在页面给出了一个超链接,该链接href的属性等于要下载文件的文件名,就可以实现文件下载了。

接收页面传递的文件名filename后,使用Apache Commons FileUpload组件的FileUtils读取项目的上传文件,并将其构建成ResponseEntity对象返回客户端下载。

使用ResponseEntity对象,可以很方便的定义返回的HttpHeaders和HttpStatus。上面代码中的MediaType,代表的是Internet Media Type,即互联网媒体类型,也叫做MIME类型。在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。HttpStatus类型代表的是Http协议中的状态。有关MediaType和HttpStatus类可以参考Spring MVC的API文档。

上传下载示例

pom.xml增加

<!-- 文件上传组件 -->
<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.3</version>
</dependency>

创建uploadForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="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>文件上传</title>
</head>
<body>
	<h2>文件上传</h2>
	<form action="${pageContext.request.contextPath}/mvc/upload" enctype="multipart/form-data" method="post">
		<table>
			<tr>
				<td>文件描述:</td>
				<td><input type="text" name="description"></td>
			</tr>
			<tr>
				<td>请选择文件:</td>
				<td><input type="file" name="file"></td>
			</tr>
			<tr>
				<td><input type="submit" value="上传"></td>
			</tr>
		</table>
	</form>
</body>
</html>

创建uploadForm2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="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>用户注册</title>
</head>
<body>
	<h2>用户注册</h2>
	<form action="${pageContext.request.contextPath}/mvc/upload2" enctype="multipart/form-data" method="post">
		<table>
			<tr>
				<td>用户名:</td>
				<td><input type="text" name="name"></td>
			</tr>
			<tr>
				<td>请上传头像:</td>
				<td><input type="file" name="image"></td>
			</tr>
			<tr>
				<td><input type="submit" value="注册"></td>
			</tr>
		</table>
	</form>
</body>
</html>

创建userInfo.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="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>文件下载</title>
</head>
<body>
	<h3>文件下载</h3>
	<a href="download?filename=${requestScope.user.image.originalFilename}"> ${requestScope.user.image.originalFilename }</a>
	<a href="download?filename=${user.name}"> ${user.name}</a>
</body>
</html>

springmvc-servlet.xml添加

SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver

<!-- 文件上传的配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="maxUploadSize" value="1048576" /><!-- 上传文件大小上限,单位为字节(1MB) -->
	<property name="defaultEncoding" value="UTF-8" /><!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
</bean>

创建User2.java对象

使用对象接受接收上传文件,必须要实现序列化接口

package com.xc.entity;

import java.io.Serializable;

import org.springframework.web.multipart.MultipartFile;

public class User2 implements Serializable {

	private static final long serialVersionUID = 1L;
	private String name;
	private MultipartFile image;

	public User2() {
		super();
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public MultipartFile getImage() {
		return image;
	}

	public void setImage(MultipartFile image) {
		this.image = image;
	}

}

创建UploadController.java

package com.xc.controller;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import com.xc.entity.User2;

@Controller
@RequestMapping("/mvc")
public class UploadController {

	// 上传文件会自动绑定到MultipartFile中
	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	public String upload(HttpServletRequest request, @RequestParam("description") String description,
			@RequestParam("file") MultipartFile multipartFile) throws Exception {

		System.out.println(description);
		if (!multipartFile.isEmpty()) {// 是否有上传文件
			// 上传文件路径
			// String path = request.getServletContext().getRealPath("/upload/");
			// 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
			String path = request.getServletContext().getRealPath("/WEB-INF/upload");
			System.out.println(path);
			String filename = multipartFile.getOriginalFilename();// 获取上传文件的原名
			File filepath = new File(path, filename);
			System.out.println(filepath);
			// 判断路径是否存在,如果不存在就创建一个
			if (!filepath.getParentFile().exists()) {
				filepath.getParentFile().mkdirs();
			}
			multipartFile.transferTo(new File(path + File.separator + filename));// 将上传文件保存到一个目标文件当中
			return "success";
		} else {
			return "error";
		}
	}

	@RequestMapping(value = "/upload2")
	public String register(HttpServletRequest request, @ModelAttribute User2 user, Model model) throws Exception {
		System.out.println(user.getName());
		// 如果文件不为空,写入上传路径
		if (!user.getImage().isEmpty()) {
			// 上传文件路径
			String path = request.getServletContext().getRealPath("/WEB-INF/upload");
			// 上传文件名
			String filename = user.getImage().getOriginalFilename();

			String fileExtName = filename.substring(filename.lastIndexOf(".") + 1);// 得到上传文件的扩展名
			System.out.println("上传文件的扩展名:" + fileExtName);
			String filenamePre = filename.substring(0, filename.lastIndexOf("."));
			System.out.println("上传文件的文件名:" + filenamePre);

			String saveFilename = makeFileName(filenamePre, fileExtName);
			System.out.println("saveFilename:" + saveFilename);
			Map<String, Object> pathMap = makePath(saveFilename, path);
			String dir1 = pathMap.get("dir1").toString();
			String dir2 = pathMap.get("dir2").toString();
			System.out.println("pathMap:" + pathMap);
			String realSavePath = pathMap.get("dir").toString();
			System.out.println("realSavePath:" + realSavePath);
			File filepath = new File(realSavePath, saveFilename);
			// 判断路径是否存在,如果不存在就创建一个
			if (!filepath.getParentFile().exists()) {
				filepath.getParentFile().mkdirs();
			}

			// 将上传文件保存到一个目标文件当中
			user.getImage().transferTo(new File(realSavePath + File.separator + saveFilename));
			user.setName("" + dir1 + "/" + dir2 + "/" + saveFilename);
			// 将用户添加到model
			model.addAttribute("user", user);
			return "userInfo";
		} else {
			return "error";
		}
	}

	/**
	 * @Method: makeFileName
	 * @Description: 生成上传文件的文件名,文件的原始名称+"_"+uuid
	 * @param filename
	 *            文件的原始名称
	 * @param fileExtName
	 * @return 文件的原始名称+"_"+uuid
	 */
	private String makeFileName(String filenamePre, String fileExtName) { // 2.jpg
		// 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
		return filenamePre + "_" + UUID.randomUUID().toString() + "."+ fileExtName;
	}

	/**
	 * 为防止一个目录下面出现太多文件,要使用hash算法打散存储
	 * 
	 * @Method: makePath
	 * @param filename
	 *            文件名,要根据文件名生成存储目录
	 * @param savePath
	 *            文件存储路径
	 * @return 新的存储目录
	 */
	private Map<String, Object> makePath(String filename, String savePath) {
		// 得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
		int hashcode = filename.hashCode();
		int dir1 = hashcode & 0xf; // 0--15
		int dir2 = (hashcode & 0xf0) >> 4; // 0-15
		// 构造新的保存目录
		String dir = savePath + "\" + dir1 + "\" + dir2; // upload23 upload35
		// File既可以代表文件也可以代表目录
		File file = new File(dir);
		// 如果目录不存在
		if (!file.exists()) {
			// 创建目录
			file.mkdirs();
		}

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("dir", dir);
		map.put("dir1", dir1);
		map.put("dir2", dir2);
		return map;
	}

	@RequestMapping(value = "/download")
	public ResponseEntity<byte[]> download(HttpServletRequest request, @RequestParam("filename") String filename, Model model) throws Exception {
		// 下载文件路径
		String path = request.getServletContext().getRealPath("/WEB-INF/upload/");
		File file = new File(path + File.separator + filename);
		HttpHeaders headers = new HttpHeaders();
		// 下载显示的文件名,解决中文名称乱码问题
		String downloadFielName = new String(filename.getBytes("UTF-8"), "iso-8859-1");
		// 通知浏览器以attachment(下载方式)打开图片
		headers.setContentDispositionFormData("attachment", downloadFielName);
		// application/octet-stream : 二进制流数据(最常见的文件下载)。
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
	}

}

文件上传的细节

上述的代码虽然可以成功将文件上传到服务器上面的指定目录当中,但是文件上传功能有许多需要注意的小细节问题,以下列出的几点需要特别注意的

  1. 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
  2. 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
  3. 为防止一个目录下面出现太多文件,要使用hash算法打散存储。
  4. 要限制上传文件的最大值。
  5. 要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。

参考文章:
Spring MVC 实现文件的上传和下载

原文地址:https://www.cnblogs.com/ooo0/p/10371188.html