首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

文件下載中文亂碼終極解決方案:跨瀏覽器兼容工具類

     編輯:Java 程式設計入門

問題根因

HTTP 標準 Content-Disposition 頭僅支援 ASCII,中文文件名需編碼。
各瀏覽器實現差異巨大:

  • IE / Edge (舊):需 URLEncoder.encode(fileName, "UTF-8")
  • Firefox / Chrome / Safari / 新 Edge:需 RFC 5987 格式 filename*=UTF-8''encodedName,或 ISO-8859-1 偽裝

通用工具類 (生產級)


public class DownloadFilenameEncoder {
    /**
     * 根據 User-Agent 自動選擇編碼策略,返回可直接用於 Content-Disposition 的文件名片段
     * 使用示例:response.setHeader("Content-Disposition", "attachment; filename=\"" + encode(request, name) + "\"");
     */
    public static String encode(HttpServletRequest request, String originalFilename) {
        String userAgent = request.getHeader("User-Agent");
        boolean isIE = userAgent != null && (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge"));
        
        try {
            if (isIE) {
                // IE/舊 Edge: URLEncoder 編碼,空格變 + 需改為 %20
                String encoded = URLEncoder.encode(originalFilename, StandardCharsets.UTF_8.name());
                return encoded.replace("+", "%20");
            } else {
                // 現代瀏覽器: RFC 5987 標準格式,完美支援中文及特殊字符
                // 格式: filename*=UTF-8''encodedValue
                String encoded = URLEncoder.encode(originalFilename, StandardCharsets.UTF_8.name())
                                    .replace("+", "%20")
                                    .replace("%21", "!").replace("%27", "'")
                                    .replace("%28", "(").replace("%29", ")")
                                    .replace("%7E", "~"); // RFC 3986 保留字符不編碼
                return "filename*=UTF-8''" + encoded;
            }
        } catch (UnsupportedEncodingException e) {
            // 理論不發生,降級處理
            return originalFilename.replace("\"", "");
        }
    }
}

完整下載 Servlet 示例


@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String fileName = "產品報表_2024_Q1.xlsx";
        String filePath = "/data/reports/" + fileName;
        
        File file = new File(filePath);
        if (!file.exists()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        // 1. 設置響應頭
        resp.setContentType("application/octet-stream");
        resp.setHeader("Content-Disposition", "attachment; " + DownloadFilenameEncoder.encode(req, fileName));
        resp.setContentLengthLong(file.length());
        
        // 2. 流式傳輸 (NIO 零拷貝更高效)
        try (FileChannel in = FileChannel.open(file.toPath(), StandardOpenOption.READ);
             WritableByteChannel out = Channels.newChannel(resp.getOutputStream())) {
            in.transferTo(0, file.length(), out);
        }
    }
}

Spring MVC Controller 寫法


@GetMapping("/download")
public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String fileName = "用戶導出_" + LocalDate.now() + ".csv";
    File file = new File("/tmp/exports/" + fileName);
    
    response.setContentType("text/csv;charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment; " + DownloadFilenameEncoder.encode(request, fileName));
    
    Files.copy(file.toPath(), response.getOutputStream());
}

測試驗證清單

瀏覽器User-Agent 關鍵字預期結果
Chrome 90+Chrome/RFC 5987 完美中文
Firefox 88+Firefox/RFC 5987 完美中文
Edge 85+ (Chromium)Edg/RFC 5987 完美中文
IE 11Trident/7.0URLEncoder 正常
舊 Edge (EdgeHTML)Edge/ (非 Edg/)URLEncoder 正常
SafariSafari/ (無 Chrome)RFC 5987 正常
移動端微信/釘釘內嵌MicroMessenger/DingTalk按內核策略 (多為 Chrome/WebKit)

進階:Spring Boot 全局配置


@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 可選:統一響應頭處理
    }
}

// 或使用 ResponseEntity
@GetMapping("/download")
public ResponseEntity<Resource> download(HttpServletRequest request) {
    String fileName = "報表.pdf";
    Resource resource = new FileSystemResource("/data/" + fileName);
    String encoded = DownloadFilenameEncoder.encode(request, fileName);
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; " + encoded)
        .contentType(MediaType.APPLICATION_PDF)
        .body(resource);
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved