Spring Boot 下载文件名乱码

对于文件的下载功能来说,我归纳为 21 流。极其简单。

  • Content-Type 头,告诉客户端文件类型。
  • Content-Disposition 头,告诉客户端对于这个文件的处理方式(在浏览器中显示,还是下载)。
  • Output 流,写入文件内容到客户端。

文件名称乱码的问题

Content-Disposition 头有三个属性值,可以指定文件的名称。

  • name
  • filename
  • filename*

至于区别,我也说不大清楚。反正是有个规范,我感觉很乱。具体可以参考如下链接:

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition

这就是容易出问题的地方了,由于文件名称设置方法不正确。或者是浏览器不兼容问题,如果 文件名称包含中文,可能会导致 下载文件名称乱码

response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + "中文の文件.txt");

如上,直接用 + 拼接中文字符串,在谷歌浏览器中下载就是乱码。

下载文件名称乱码

常见的解决方案

以下我是在网上看到的一些解决方案。

  1. 对文件名称进行URI编码

    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode("中文の文件.txt", StandardCharsets.UTF_8));
    
  2. 修改字符编码

    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + new String("中文の文件.txt".getBytes("GBK"),"ISO-8859-1"));
    

经过测试,上面2种办法都OK。

比较优雅的方式

Spring 提供了一个工具类: org.springframework.http.ContentDisposition,专门用于处理文件下载中的 Content-Disposition Header,它会自动处理好中文文件名称的编码问题。

ContentDisposition 使用实在是太简单了,这里就不多说了,给一个完整的使用示例就能看明白。

package io.springboot.demo.web.controller;


import java.io.IOException;
import java.nio.charset.StandardCharsets;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/demo")
public class DemoController {
 
 @GetMapping
 public void demo (HttpServletResponse response) throws IOException {

  byte[] content = "Hello Spring".getBytes(StandardCharsets.UTF_8);
  
        // 文件类型
        response.setContentType("text/plain");
        // 文本类型文件的编码
        response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
        // 文件长度
        response.setContentLength(content.length);
        // 文件的处理方式。attachment 表示附件,filename 表示文件的名称
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition
               .attachment()    // 附件形式
               .filename("中文の文件.txt", StandardCharsets.UTF_8)  // 文件名称 & 编码
               .build()
               .toString());
        response.getOutputStream().write(content);
 }
}

以后文件下载接口,就不用自己去拼接、转码文件名称了,交给 ContentDisposition 吧。