文件上传
文件上传需将表格的提交方式设为"POST",并且将enctype设为"multipart/form-data",以二进制的方式提交数据。
spring mvc中可通过MultipartResolver监听每个请求,如有上传的文件,则把请求封装为MultipartHttpServletRequest,通过封装的请求可以获取上传的文件信息和上传的文件。
实际使用可直接将MultipartFile作为控制器中请求处理方法的参数,MultipartFile是一个接口,其实现类为CommonsMultipartFile,通过MultipartFile封装的方法也可获取文件相关信息。
注意:spring mvc默认没有装配MultipartResolver,因此要使用文件上传功能,可在上下文中配置基于Apache CommonsFileUpload实现的CommonsMultipartResolver,同时要加入相关的commons-fileupload.jar包。
public interface MultipartFile extends InputStreamSource { String getName();//获得上传文件在表单中的域名 String getOriginalFilename();//获得上传文件的原名 String getContentType();//获得提交的内容类型 boolean isEmpty();//判断是否有上传的文件 long getSize();//上传文件的大小 byte[] getBytes() throws IOException;//获得上传文件的字节数组 @Override InputStream getInputStream() throws IOException; void transferTo(File dest) throws IOException, IllegalStateException;//获得上传文件的输入流 }
下面是封装的工具类:
package com.yc.utils; import static java.util.Calendar.MONTH; import static java.util.Calendar.YEAR; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.springframework.web.multipart.MultipartFile; /** * Spring MVC文件上传的帮助类,对基于Apache Commons * FileUpload实现的CommonsMultipartResolver得到的MultipartFile进行处理 * * @author 刘亚楼 * */ public class MVCFileUpload { /** * 访问的相对路径 */ public static final String RELATIVE_PATH = "RelativePath"; /** * 文件保存的绝对路径 */ public static final String ABSOLUTE_PATH = "AbsolutePath"; /** * 文件的原名 */ public static final String ORIGINAL_FILENAME = "OriginalFileName"; /** * 文件保存的前缀形式为yyyyMMddHHmmssSS */ public static final String PREFIX_DATE = "date"; /** * 文件保存的前缀形式为uuid编号 */ public static final String PREFIX_UUID = "uuid"; // 文件保存目录 private String saveDir = "images"; // 文件前缀形式可为date或uuid private String prefixForm = PREFIX_DATE; public MVCFileUpload() { super(); } public MVCFileUpload(String prefixForm) { super(); this.prefixForm = prefixForm; } public String getSaveDir() { return saveDir; } public void setSaveDir(String saveDir) { this.saveDir = saveDir; } public String getPrefixForm() { return prefixForm; } public void setPrefixForm(String prefixForm) { this.prefixForm = prefixForm; } /** * 处理表单域名相同的多个文件<br/> * 将上传的文件保存到服务器<br/> * 默认路径为 <b>D:\TomcatInstalling\apache-tomcat-7.0.79\webapps\images\年\月\日<b> * * @param request * @param files * @throws IllegalStateException * @throws IOException */ public List<Map<String, String>> transferMultiFilesWithSameField(HttpServletRequest request, MultipartFile[] files) throws IllegalStateException, IOException { List<Map<String, String>> fileList = new ArrayList<Map<String, String>>(); if (files != null && files.length > 0) { String basePath = getSaveBasePath(request); for (MultipartFile file : files) { if (!file.isEmpty()) { Map<String, String> fileInfo = new HashMap<String, String>(); saveToServer(fileInfo, basePath, file); fileList.add(fileInfo); } } } return fileList; } /** * 处理表单域名不同的多个文件,将上传的文件保存到服务器<br/> * 默认路径为 <b>D:\TomcatInstalling\apache-tomcat-7.0.79\webapps\images\年\月\日<b> * * @param request * @param files * @return * @throws IOException */ public Map<String, String> transferMultiFilesWithDistinctField(HttpServletRequest request, MultipartFile... files) throws IOException { Map<String, String> fileInfo = new HashMap<String, String>(); if (files != null && files.length > 0) { String basePath = getSaveBasePath(request); for (MultipartFile file : files) { if (!file.isEmpty()) { saveToServer(fileInfo, basePath, file); } } } return fileInfo; } // 获得文件存储的基础路径 private String getSaveBasePath(HttpServletRequest request) { Calendar c = Calendar.getInstance(); String path = request.getServletContext().getRealPath("/"); File rootPath = new File(path).getParentFile(); File basePath = new File(rootPath.getAbsolutePath(), saveDir + File.separator + c.get(YEAR) + File.separator + (c.get(MONTH) + 1)); if (!basePath.exists()) { basePath.mkdirs(); } return basePath.getAbsolutePath(); } /** * 将上传的文件存到服务器并将文件相关信息记录到map中,可通过表单中文件的域名加MVCFileUpload的静态变量访问 * * @param fileInfo * @param basePath * @param file * @throws IOException */ private void saveToServer(Map<String, String> fileInfo, String basePath, MultipartFile file) throws IOException { if (!file.isEmpty()) { // 表单中文件域名 String fieldName = file.getName(); // 文件前缀 String prefix = generateFilePrefix(); // 文件原名 String fileName = file.getOriginalFilename(); // 文件后缀 String suffix = fileName.substring(fileName.indexOf(".")); File dest = new File(basePath, prefix + suffix); // 存入服务器 file.transferTo(dest); Calendar c = Calendar.getInstance(); // 访问的相对路径 String relativePath = "../" + saveDir + "/" + c.get(YEAR) + "/" + (c.get(MONTH) + 1) + "/" + prefix + suffix; // 访问的绝对路径 String absolutePath = dest.getAbsolutePath(); fileInfo.put(fieldName + RELATIVE_PATH, relativePath); fileInfo.put(fieldName + ABSOLUTE_PATH, absolutePath); fileInfo.put(fieldName + ORIGINAL_FILENAME, fileName); } } // 生成保存的文件前缀 private String generateFilePrefix() { String prefix = ""; if (prefixForm.equalsIgnoreCase(PREFIX_DATE)) { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS"); prefix = sdf.format(new Date()); } else if (prefixForm.equalsIgnoreCase(PREFIX_UUID)) { UUID uuid = UUID.randomUUID(); prefix = uuid.toString().replaceAll("-", ""); } else { throw new IllegalArgumentException("前缀模式不支持"); } return prefix; } }
不通过表单提交也可通过Ajax实现文件上传,这里要借助于ajaxfileupload.js,通过一个隐藏的iframe创建表单实现文件上传
jQuery .extend({ createUploadIframe : function(id, uri) { // create frame var frameId = 'jUploadFrame' + id; var iframeHtml = '<iframe id="' + frameId + '" name="' + frameId + '" style="position:absolute; top:-9999px; left:-9999px"'; if (window.ActiveXObject) { if (typeof uri == 'boolean') { iframeHtml += ' src="' + 'javascript:false' + '"'; } else if (typeof uri == 'string') { iframeHtml += ' src="' + uri + '"'; } } iframeHtml += ' />'; jQuery(iframeHtml).appendTo(document.body); return jQuery('#' + frameId).get(0); }, createUploadForm : function(id, fileElementId, data) { // create form var formId = 'jUploadForm' + id; var fileId = 'jUploadFile' + id; var form = jQuery('<form action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>'); if (fileElementId != null && fileElementId.length > 0) { for(var i=0;i<fileElementId.length;i++){ var feid=fileElementId[i];//'pic','pic2','pic3' var oldElement = jQuery('#' + feid); var newElement = jQuery(oldElement).clone(); jQuery(oldElement).attr('id', "jUploadFile"+new Date().getTime()); jQuery(oldElement).before(newElement); jQuery(oldElement).appendTo(form); } } /** *** 增加参数的支持 **** */ if (data) { for ( var i in data) { $( '<input type="hidden" name="' + i + '" value="' + data[i] + '" />').appendTo(form); } } // set attributes jQuery(form).css('position', 'absolute'); jQuery(form).css('top', '-1200px'); jQuery(form).css('left', '-1200px'); jQuery(form).appendTo('body'); return form; }, ajaxFileUpload : function(s) { // TODO introduce global settings, allowing the client to modify // them for all requests, not only timeout s = jQuery.extend({}, jQuery.ajaxSettings, s); var id = new Date().getTime(); // ADD s.data var form = jQuery.createUploadForm(id, s.fileElementId, s.data); var io = jQuery.createUploadIframe(id, s.secureuri); var frameId = 'jUploadFrame' + id; var formId = 'jUploadForm' + id; // Watch for a new set of requests if (s.global && !jQuery.active++) { jQuery.event.trigger("ajaxStart"); } var requestDone = false; // Create the request object var xml = {} if (s.global) jQuery.event.trigger("ajaxSend", [ xml, s ]); // Wait for a response to come back var uploadCallback = function(isTimeout) { var io = document.getElementById(frameId); try { if (io.contentWindow) { xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null; xml.responseXML = io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument : io.contentWindow.document; } else if (io.contentDocument) { xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null; xml.responseXML = io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument : io.contentDocument.document; } } catch (e) { jQuery.handleError(s, xml, null, e); } if (xml || isTimeout == "timeout") { requestDone = true; var status; try { status = isTimeout != "timeout" ? "success" : "error"; // Make sure that the request was successful or // notmodified if (status != "error") { // process the data (runs the xml through // httpData regardless of callback) var data = jQuery.uploadHttpData(xml, s.dataType); // If a local callback was specified, fire it // and pass it the data if (s.success) s.success(data, status); // Fire the global callback if (s.global) jQuery.event.trigger("ajaxSuccess", [ xml, s ]); } else jQuery.handleError(s, xml, status); } catch (e) { status = "error"; jQuery.handleError(s, xml, status, e); } // The request was completed if (s.global) jQuery.event.trigger("ajaxComplete", [ xml, s ]); // Handle the global AJAX counter if (s.global && !--jQuery.active) jQuery.event.trigger("ajaxStop"); // Process result if (s.complete) s.complete(xml, status); jQuery(io).unbind() setTimeout(function() { try { jQuery(io).remove(); jQuery(form).remove(); } catch (e) { jQuery.handleError(s, xml, null, e); } }, 100) xml = null } } // Timeout checker if (s.timeout > 0) { setTimeout(function() { // Check to see if the request is still happening if (!requestDone) uploadCallback("timeout"); }, s.timeout); } try { var form = jQuery('#' + formId); jQuery(form).attr('action', s.url); jQuery(form).attr('method', 'POST'); jQuery(form).attr('target', frameId); if (form.encoding) { jQuery(form).attr('encoding', 'multipart/form-data'); } else { jQuery(form).attr('enctype', 'multipart/form-data'); } jQuery(form).submit(); } catch (e) { jQuery.handleError(s, xml, null, e); } jQuery('#' + frameId).load(uploadCallback); return { abort : function() { } }; }, /** handleError 方法在jquery1.4.2之后移除了,此处重写改方法 * */ handleError : function(s, xhr, status, e) { // If a local callback was specified, fire it if (s.error) { s.error.call(s.context || s, xhr, status, e); } // Fire the global callback if (s.global) { (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [ xhr, s, e ]); } }, uploadHttpData : function(r, type) { var data = !type; data = type == "xml" || data ? r.responseXML : r.responseText; // If the type is "script", eval it in global context if (type == "script") { jQuery.globalEval(data); } // Get the JavaScript object, if JSON is used. if (type == "json") { var result = data.substring(data.indexOf("{"), data .indexOf("}") + 1); data = eval("(" + result + ")"); } // evaluate scripts within html if (type == "html") { jQuery("<div>").html(data).evalScripts(); } return data; } })
下面是上传的js代码
var person = { uname : $("#uname").val(), upass : $("#upass").val(), diploma : $("#diploma").val(), }; $.ajaxFileUpload({ type : "POST", secureuri : true, fileElementId : ["pic","pic2","pic3"],//上传文件的表单域名 url : "test.jsp", data : person, dataType : "json", success : function(data, status) { } });
文件下载
文件下载比较简单,直接在页面给出一个超链接,该链接href属性等于要下载文件的文件名,就可以实现下载了。但是如果该文件的文件名为中文名,在某些早期的浏览器上就会导致下载失败。如果使用最新的Firefox、Opera、Chrome、Safari则都可以正常下载了。但对于可打开的文件如文本、图片等等,浏览器会直接打开。因此这里我们使用ResponseEntity对象作为请求处理方法的返回值。
在Spring MVC中可使用ResponseEntity<T>(继承自HttpEntity)代表响应的实体,HttpEntity可以访问请求和响应头。下面是请求处理方法:
@RequestMapping("/download.action") public ResponseEntity<byte[]> download(String fileName) throws IOException { String path = System.getProperty("user.home"); File file = new File(path, "Pictures" + File.separator + fileName); // 实现了MultiValueMap,代表请求头或者响应头 HttpHeaders responseHeader = new HttpHeaders(); // 浏览器对可打开的文件如:图片或文本文件,会自行打开。通过设置Content-Disposition属性为attachment,浏览器会弹出对话框提示打开还是保存,不会默认打开。 // 即使打开也会使用其它程序,如记事本。 responseHeader.setContentDispositionFormData("attachment", fileName); // 设置响应类型为application/octet-stream(二进制流数据,最常见的文件下载) responseHeader.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 使用FileUtils读取文件,返回一个ResponseEntity对象(由内容,响应头信息,状态码组成),HttpStatus.CREATED代表创建新资源 return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), responseHeader, HttpStatus.CREATED); }