文本轉載:http://blog.lifw.org/post/24251622。
本文介紹在 spring mvc 中如何進行文件的下載,以及關於下載文件名亂碼這個令人頭疼的問題的一些探討。
一、在 spring mvc 中進行文件下載,主要有以下步驟
1. 設置響應內容類型 Content-Type
調用 URLConnection.guessContentTypeFromName(String fileName) 方法簡單判斷一下文件的類型,如果不能識別文件類型,則使用默認類型 application/octet-stream。調用 response.setContentType(mimeType); 設置 Content-Type。
2. 設置響應長度 Content-Length
調用 response.setContentLengthLong(file.length()); 設置 Content-Length。
3. 設置 Content-Disposition 響應頭
調用 response.setHeader("Content-Disposition", "attachment;fileName=\"" + encodedFileName + "\""); 設置 Content-Disposition 響應頭。Content-Disposition 響應頭有兩個作用,一是使用 attachment 用來告訴瀏覽器進行文件下載,而不是嘗試打開文件,二是使用 fileName 來指定文件下載名。
這裏有一個十分噁心的問題,就是 fileName 只支持 ASCII 字符,如果是中文則爲亂碼,而且不同瀏覽器編碼也不同,下面將專門討論一下該問題的解決方案。
4. 使用 FileCopyUtils 進行文件下載
spring 爲我們提供了 FileCopyUtils 來進行文件流相關操作,使用該方法能大大簡化文件下載代碼。
二、文件下載名亂碼問題的一些探討
1.如果使用 URLEncoder.encode(fileName) 對文件名進行編碼,可以解決 chrome 和 ie 瀏覽器文件名亂碼問題。但是有個小問題,該方法會將空格轉換成 + ,如果文件名爲 "壁 紙.jpg",則下載後文件名就變成了 "壁+紙.jpg",因此需要將編碼後的文件名中的 + 替換成 utf-8 中空格的編碼 "%20"。
2.使用 MimeUtility.encodeWord(fileName) 方法對文件名進行編碼,可以解決 Firefox 文件名亂碼問題,但是注意將 fileName 用雙引號引起來,否則如果文件名中有空格將會導致文件名截斷問題,例如文件名爲"壁 紙.jpg",則下載後文件名變成 "壁",丟失了空格後的內容。
3.Safari 亂碼問題找了很多資料也沒解決,貌似無解。
通過以上探討,形成了以下思路解決亂碼問題,首先使用 eu.bitwalker.UserAgentUtils 工具包通過 User-Agent 來判斷瀏覽器類型,如果是 ie、chrome、Safari,則使用 URLEncoder 對文件名進行編碼,並將加號替換爲 "%20" ,如果是 firefox ,則使用 MimeUtility 對文件名進行編碼,並將文件名使用雙引號引起來。
三、參考代碼
加入 eu.bitwalker.UserAgentUtils 依賴
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.19</version>
</dependency>
文件下載代碼
方法一:
@RequestMapping(value = "/Download", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> downloadFile(HttpServletRequest request) throws IOException {
String filePath = "E:\\test\\測 試.txt";
FileSystemResource file = new FileSystemResource(filePath);
//文件名編碼,解決亂碼問題
String fileName = file.getFilename();
//解決文件名亂碼問題
// String fileName = URLEncoder.encode(file.getFilename(), StandardCharsets.UTF_8.toString());
String userAgentString = request.getHeader("User-Agent");
String browser = UserAgent.parseUserAgentString(userAgentString).getBrowser().getGroup().getName();
// String t=browser;
if(browser.equals("Chrome") || browser.equals("Internet Exploer") || browser.equals("Safari")) {
fileName = URLEncoder.encode(fileName,"utf-8").replaceAll("\\+", "%20");
} else {
fileName = MimeUtility.decodeText(fileName) ; //encodeWord(fileName);
}
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.contentLength())
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
方法二:@Controller
public class DownloadController {
@RequestMapping("/download")
public void download(HttpServletResponse response,HttpServletRequest request) throws IOException {
File file = new File("/Music/1901.m4a");
//判斷文件是否存在
if(!file.exists()) {
return;
}
//判斷文件類型
String mimeType = URLConnection.guessContentTypeFromName(file.getName());
if(mimeType == null) {
mimeType = "application/octet-stream";
}
response.setContentType(mimeType);
//設置文件響應大小
response.setContentLengthLong(file.length());
//文件名編碼,解決亂碼問題
String fileName = file.getName();
String encodedFileName = null;
String userAgentString = request.getHeader("User-Agent");
String browser = UserAgent.parseUserAgentString(userAgentString).getBrowser().getGroup().getName();
if(browser.equals("Chrome") || browser.equals("Internet Exploer") || browser.equals("Safari")) {
encodedFileName = URLEncoder.encode(fileName,"utf-8").replaceAll("\\+", "%20");
} else {
encodedFileName = MimeUtility.encodeWord(fileName);
}
//設置Content-Disposition響應頭,一方面可以指定下載的文件名,另一方面可以引導瀏覽器彈出文件下載窗口
response.setHeader("Content-Disposition", "attachment;fileName=\"" + encodedFileName + "\"");
//文件下載
InputStream in = new BufferedInputStream(new FileInputStream(file));
FileCopyUtils.copy(in, response.getOutputStream());
}