需求
下载文件时,需要保留原始中文文件名称
实现
- 文件名:
中文文件名.txt
- 使用URLEncoder.encode之后的文件名:
%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.txt
- 代码实现:
fun downloadFile(
bytes: ByteArray,
fileName: String,
mediaType: String = MediaTypes.APPLICATION_BINARY,
) {
val response = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes).response!!
response.contentType = mediaType
// 先将文件名编码
val encodeFileName = URLEncoder.encode(fileName, "UTF-8")
// 再传入响应头中,主要是:fileName*=UTF-8''$encodeFileName,接下来的事情交给浏览器
// UTF-8 指定你使用的编码方式,encodeFileName为你编码后的文件名
response.setHeader(
FileUploadBase.CONTENT_DISPOSITION, "attachment;fileName*=UTF-8''$encodeFileName"
)
response.outputStream.write(bytes)
}
原理
filename* 是 Content-Disposition 响应头域的一个扩展,用于在 HTTP 响应中指定一个 Unicode 文件名,并支持使用 RFC 5987 规定的方式来指定文件名,并支持 UTF-8 编码的中文字符。相比之下,filename 字段只支持 ASCII 字符,因此无法直接在其中指定中文文件名。
在 HTTP/1.1 中,filename* 字段的语法定义如下:
Content-Disposition: attachment; filename*=charset'lang'encoded-value
其中,charset 是指定编码的字符集,lang 是指定语言的 ISO 639-1 编码,encoded-value 是指定文件名的编码值。RFC 5987 规定将 encoded-value 编码为 ASCII 字符集的百分比编码,然后将其嵌入到 filename* 字段中。
在实际应用中,通常使用 UTF-8 编码和语言编码为英语(en)来指定文件名。例如,以下是一个 filename* 字段的示例:
Content-Disposition: attachment; filename*=UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.txt
在这个示例中,文件名为 “中文文件名.txt”,使用 UTF-8 编码进行 URL 编码,然后将编码后的值 %E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.txt 嵌入到 filename* 字段中。
需要注意的是,filename* 字段只是 Content-Disposition 响应头的一个扩展,因此并不是所有的浏览器和下载工具都支持它。如果浏览器或下载工具不支持 filename* 字段,则会尝试使用 filename 字段来保存文件,此时需要将文件名进行 URL 编码,以确保兼容性。