FileInputStreamresponse.getOutputStream()IOUtils.copy 或手寫循環public class DownloadUtils {
public static void download(HttpServletRequest req, HttpServletResponse resp,
String filePath, String fileName) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream())) {
long len = new File(filePath).length();
resp.setCharacterEncoding("UTF-8");
resp.setContentType("multipart/form-data");
resp.setHeader("Content-Length", String.valueOf(len));
// 核心:多瀏覽器中文文件名編碼
String ua = req.getHeader("User-Agent");
byte[] nameBytes = ua.contains("MSIE") || ua.contains("Trident")
? fileName.getBytes() // IE/Edge
: fileName.getBytes("UTF-8"); // Chrome/Firefox/Safari
String encodedName = new String(nameBytes, "ISO-8859-1");
resp.setHeader("Content-Disposition",
String.format("attachment; filename=\"%s\"", encodedName));
byte[] buf = new byte[2048];
int read;
while ((read = bis.read(buf)) != -1) bos.write(buf, 0, read);
}
}
// 重載:不指定文件名,取原文件名
public static void download(HttpServletRequest req, HttpServletResponse resp, String filePath) throws IOException {
download(req, resp, filePath, new File(filePath).getName());
}
}
// 雙重 encodeURI 確保參數傳遞不亂碼
function download(fileName, dir) {
window.location.href = basePath + "/download?dir=" + dir
+ "&fileName=" + encodeURI(encodeURI(fileName));
}
@GetMapping("/download")
public void download(@RequestParam String dir, @RequestParam String fileName,
HttpServletRequest req, HttpServletResponse resp) throws Exception {
String decoded = URLDecoder.decode(fileName, "UTF-8");
String base = req.getSession().getServletContext().getRealPath("/WEB-INF/");
String folder = "upload".equals(dir) ? "upload" : "download";
DownloadUtils.download(req, resp, base + folder + "/" + decoded, decoded);
}
multipart/form-data 為通用二進制流,瀏覽器會彈出下載框而非嘗試打開。若需精確類型,可用 MimetypesFileTypeMap 自動檢測。
encodeURI + 後端 URLDecoder.decodeContent-Disposition 針對 UA 區分編碼策略