博客详情页背景图

前端传参 + 后端处理文件名乱码

记录解决浏览器下载文件时文件名称乱码问题

2025-12-22 · 后端

1.前端传文件名时,统一使用 encodeURIComponent():

fetch(`/download?filename=${encodeURIComponent('测试文件.txt')}`)
  .then(response => response.blob())
  .then(blob => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = '测试文件.txt'; // 默认文件名(可选)
      a.click();
      URL.revokeObjectURL(url);
  });

2.后端根据不同浏览器设置对应浏览器的编码规则

@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(
        @RequestParam String filename,
        HttpServletRequest request) throws UnsupportedEncodingException {

    String userAgent = request.getHeader("User-Agent");
    String contentDisposition;

    // Safari 特殊处理
    if (userAgent != null && userAgent.contains("Safari") && !userAgent.contains("Chrome")) {
        String safariFixedName = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        contentDisposition = "attachment; filename=\"" + safariFixedName + "\"";
    } 
    // 其他浏览器(RFC 5987)
    else {
        contentDisposition = "attachment; filename=\"" + filename + "\"; filename*=utf-8''" + filename;
    }

    return ResponseEntity.ok()
            .header("Content-Disposition", contentDisposition)
            .body(fileResource);
}

3.测试

1.代码测试
import org.springframework.mock.web.MockHttpServletRequest;

@Test
void testDownloadWithDifferentBrowsers() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    
    // 1. Chrome 测试
    request.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
    testFilenameHandling(request); // Chrome 应使用 filename*=utf-8'' 格式

    // 2. Safari 测试(无 Chrome 字符串)
    request.removeHeader("User-Agent");
    request.addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15");
    testFilenameHandling(request); // Safari 应使用 ISO-8859-1 解码

    // 3. Firefox 测试
    request.removeHeader("User-Agent");
    request.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0");
    testFilenameHandling(request); // Firefox 应支持 filename*

    // 4. Edge 测试
    request.removeHeader("User-Agent");
    request.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
    testFilenameHandling(request); // Edge 同 Chrome
}

private void testFilenameHandling(HttpServletRequest request) {
    String userAgent = request.getHeader("User-Agent");
    String filename = "测试文件.txt";
    String contentDisposition;

    if (userAgent != null && userAgent.contains("Safari") && !userAgent.contains("Chrome")) {
        // Safari 特殊处理
        contentDisposition = "attachment; filename=\"" + 
            new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + "\"";
    } else {
        // Chrome/Firefox/Edge 标准处理
        contentDisposition = "attachment; filename=\"" + filename + "\"; filename*=utf-8''" + filename;
    }

    System.out.println("UA: " + userAgent);
    System.out.println("Content-Disposition: " + contentDisposition);
}
2.postman设置Headers
Key: User-Agent
Value: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15

Chrome:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

Firefox:
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0

根据不同浏览器头设置
注:
  1.查看前端传递的参数编码次数
	2.根据浏览器返回不同的编码规则
	3.测试请求头根据不同浏览器设置,测试名称是否正常