一、概述
文件上传时,http请求头Content-Type须为multipart/form-data,有两种实现方式:
1、基于FormData对象,该方式简单灵活
2、基于<form>表单元素,method设为POST,enctype设置为multipart/form-data,在form表单上提交
web容器收到该请求时,须根据请求头将字节流解析为文件对象,spring mvc 提供了MultipartResolver、MultipartFile两个接口用于支持文件上传功能
二、MultipartResolver & MultipartFile
1、MultipartResolver接口提供了文件解析功能,其定义如下:
public interface MultipartResolver { boolean isMultipart(HttpServletRequest request); MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; void cleanupMultipart(MultipartHttpServletRequest request); }
Spring MVC使用Apache Commons fileupload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver
2、MultipartFile接口代表上传的文件,提供了文件操作的相关功能,其定义如下:
public interface MultipartFile { String getName(); String getOriginalFilename(); // 原文件名 String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; InputStream getInputStream() throws IOException; // 获取文件流 void transferTo(File dest) throws IOException, IllegalStateException; // 保存文件 }
三、使用示例
1、添加pom依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
2、spring-mvc.xml中配置MultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 上传文件大小限制,单位为字节-10Mb --> <property name="maxUploadSize"> <value>10485760</value> </property> <!-- 请求的编码格式 --> <property name="defaultEncoding"> <value>UTF-8</value> </property> </bean>
3、controller
@Controller public class FileController { /** * 文件存储目录 */ private static final String uploadFolder = "d:/upload-file/"; /** * 上传 */ @ResponseBody @RequestMapping(value = "/upload", method = RequestMethod.POST) public Map<String, String> fileUpload(@RequestParam("file") MultipartFile file, @RequestParam("fileType") String fileType) throws Exception { // 创建文件目录 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); String dateStr = sdf.format(new Date()); String dirPath = uploadFolder + dateStr; File dir = new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } // 保存文件 String fileName = UUID.randomUUID() + this.getFileNameSuffix(file.getOriginalFilename()); File savedFile = new File(dir + "/" + fileName); file.transferTo(savedFile); Map<String, String> result = new HashMap<String, String>(); result.put("fileName", file.getOriginalFilename()); result.put("fileType", fileType); result.put("filePath", dateStr + "/" + fileName); return result; } /** * 下载 */ @RequestMapping("/download") public void downloadFile(@RequestParam(value = "fileName", required = false) String fileName, @RequestParam("filePath") String filePath, HttpServletResponse response) throws Exception { File file = new File(uploadFolder + filePath); if (!file.exists()) { return; } // 读取字节流到缓存 InputStream in = new BufferedInputStream(new FileInputStream(file)); byte[] buffer = new byte[in.available()]; in.read(buffer); in.close(); // 设置ContentType String suff = this.getFileNameSuffix(file.getName()); if (suff != null) { suff = suff.toLowerCase(); } switch (suff) { case ".jpg": case ".jpeg": response.setContentType(MediaType.IMAGE_JPEG_VALUE); break; case ".png": response.setContentType(MediaType.IMAGE_PNG_VALUE); break; case ".gif": response.setContentType(MediaType.IMAGE_GIF_VALUE); break; default: response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); break; } // 输出 response.addHeader("Content-Disposition", "attachment;filename="" + (StringUtils.isEmpty(fileName) ? file.getName() : fileName)); response.addHeader("Content-Length", "" + file.length()); response.setContentType("application/x-msdownload"); OutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(buffer); out.flush(); } /** * 获取文件名后缀*/ private String getFileNameSuffix(String fileName) { if (StringUtils.isEmpty(fileName)) { return null; } String suffix; int index = fileName.lastIndexOf("."); if (index == -1) { suffix = null; } else { suffix = fileName.substring(index); } return suffix; } }
4、前端页面(test.html)
<!DOCTYPE html> <html> <head> <title>test page</title> </head> <body> <div> <input id="upload" type="file" onchange="uploadFile(event)" /> </div> <div> <input id="download" type="button" onclick="downloadFile()" value="下载" /> </div> </body> <script> var context = '/test' function uploadFile(event) { // 文件类型过滤 var file = event.currentTarget.files[0]; var fileName = file.name; if (/.(jpg|jpeg|png|gif)$/i.test(fileName) == false) { alert('提示', '不支持的图片类型,头像只支持.jpg,.jpeg,.png,.gif'); return; } var formData = new FormData(); formData.append('fileType', 'portrait'); formData.append('file', event.currentTarget.files[0]); // 发送请求 var xhr = new XMLHttpRequest(); xhr.open('POST', context + '/upload', true); xhr.onreadystatechange = function() { if (xhr.readyState==4 && xhr.status==200) { document.getElementById('download').fileData = JSON.parse(xhr.responseText); alert("上传成功"); } } xhr.send(formData); // 重置文件上传控件,使得重复选择同一个文件时,onchange依旧触发 event.target.value = null; } function downloadFile() { var fileData = document.getElementById('download').fileData; location.href = context + '/download?filePath=' + decodeURIComponent(fileData.filePath) + '&fileName=' + decodeURIComponent(fileData.fileName); } </script> </html>
访问http://file-test/test.html,可测试文件的上传下载功能
补充:示例使用@ResponseBody输出json数据,因此还需添加相关配置,详细可参考Spring MVC 使用介绍(五)—— 注解式控制器(一):基本介绍
另外,文件下载除了示例中文件流方式,还可以基于spring对静态文件的支持功能,详细可参考Spring MVC 使用介绍(十一)—— 跨域与静态资源访问
参考: