1. 程式人生 > >Spring MVC中檔案上傳和下載

Spring MVC中檔案上傳和下載

檔案上傳

檔案上傳需將表格的提交方式設為"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);
}