Skip to content

设置下载时的默认文件名

🏷️ EasyExcel

今天用 EasyExcel 导出 Excel 文件,通过 ApiFox 调用接口保存时默认文件名为 response.zip ,于是查了下如何在响应中设置默认文件名。

经调查,默认的文件名是通过 Header 中 Content-Disposition 的值决定的,其格式为

http
Content-Disposition: <disposition-type>; <parameter-name>="<parameter-value>"; ...

其中 <parameter-value> 的值通常需要 URL 编码。

如果是下载文件,则 <disposition-type> 值为 attachment

http
Content-Disposition: attachment; filename="example.txt"

如果文件名中含有中文等特殊字符,则需要改成如下格式:

http
Content-Disposition: attachment; filename*=UTF-8''%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt
  • filename*= 参数指定编码类型和编码值。
  • UTF-8 表示使用 UTF-8 编码
  • '' 表示空格分隔符
  • %E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt 是文件名的编码值

在 Java 中可以通过 ContentDisposition 对象构建这个字符串:

java
ContentDisposition disposition = ContentDisposition.builder("attachment")
        .filename("测试文件.txt", StandardCharsets.UTF_8)
        .build();
System.out.println(disposition.toString());

奇怪的是,在 ApiFox 中不支持这种,反而支持不做 URL 编码的格式:

http
Content-Disposition: attachment; filename="测试文件.txt"

为了兼容,最后将其修改为了如下格式:

http
Content-Disposition: attachment; filename="测试文件.txt"; filename*=UTF-8''%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt

另外每种文件对应的 Content-Type 是不一样的,比如 xls 文件对应的是 application/vnd.ms-excelpdf 对应的是 application/pdf

可以参考下 org.springframework.boot.web.server.MimeMappings.DEFAULT 中的值,不过这里的并不全。

最终的代码如下:

java
import cn.hutool.core.io.file.FileNameUtil;
import org.springframework.boot.web.server.MimeMappings;
import org.springframework.http.HttpHeaders;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

public class HttpUtil {

    public static void setDownloadFileName(HttpServletResponse response, String filename) throws UnsupportedEncodingException {
        // 设置 Content-Disposition
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString())
                // 替换空格(+)为 %20
                .replace("+", "%20");
        String disposition = "attachment; "
                + "filename=\"" + encodedFilename + "\"; "
                + "filename*=UTF-8''" + encodedFilename;
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, disposition);
        response.setHeader(HttpHeaders.CONTENT_TYPE, getContentTypeByFilename(filename));
    }

    public static String getContentTypeByFilename(String filename) {
        return Optional.ofNullable(MimeMappings.DEFAULT.get(FileNameUtil.getSuffix(filename)))
                // 默认二进制流
                .orElse("application/octet-stream");
    }
}

在 EasyExcel 的导出处理前调用上面的 setDownloadFileName 方法:

java
// 设置文件名
String sheetName = "消费明细";
String fileName = String.format("%s_%s.xls", sheetName, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
HttpUtil.setDownloadFileName(response, fileName);

// 导出文件
EasyExcel.write(response.getOutputStream(), BrandBillDetailVO.class)
        .excelType(ExcelTypeEnum.XLS)
        .sheet(sheetName)
        .doWrite(bills);