导出 Excel,点了下载,文件打开是网页源码,还夹着一堆乱码。检查响应头,Content-Disposition 写的是 inline,浏览器就当成页面直接渲染了。改成 attachment 之后,文件名又成了百分号编码的一长串。
这个头的作用就一句话:告诉浏览器这段内容是展示还是下载,以及存成什么名字。格式是 Content-Disposition 加 attachment 再加 filename。attachment 触发下载,inline 尽量在浏览器里打开,PDF 和图片常这么用。
中文文件名为什么乱
HTTP 头历史上只允许 ASCII,直接塞中文,不同浏览器解码方式不一样,结果就是乱码或者截断。RFC 5987 给了标准写法:保留一个 filename,再补一个 filename*,值用 UTF-8 加百分号编码,形如 UTF-8 两个单引号加编码后的名字。两个都写,老浏览器读前者,新的读后者。
Content-Disposition: attachment; filename=report.xlsx; filename*=UTF-8''baobiao.xlsx编码时有个细节,URLEncoder 会把空格变成加号,而加号在文件名里是合法字符,不会被还原成空格,得再替换回去。还有分号、逗号在头里是分隔符,文件名里出现必须编码或者直接去掉。
Nginx 和框架层会覆盖
- Nginx:如果开了 gzip 或者用了 proxy_pass,某些配置会重写头,先看实际响应而不是只看代码
- Spring:用 ResponseEntity 设 ContentDisposition,别自己拼字符串,builder 会处理编码
- 前端:a 标签的 download 属性对同源地址有效,跨域基本被忽略,别指望它改文件名
- 缓存:下载接口记得加 Cache-Control 的 no-store,否则重复下载拿到旧文件
还有一种情况,前端用 fetch 拿 blob 再触发下载。这时跨域场景读不到响应头里的文件名,得让后端把名字放进自定义头返回,并且配好 Access-Control-Expose-Headers,少一个都取不到。
文件内容也要对,Excel 的 MIME 建议用 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,写成 text/html 会让某些客户端直接预览。头写对了,前端怎么写都省事;头写错,前端补一堆兼容代码也只是打补丁。