中文文件名乱码非框架bug,而是http协议限制filename字段仅支持ascii;正确解法是遵循rfc 5987使用filename*=utf-8''%e4%ba%a7%e5%93%81%e6%89%8b%e5%86%8c.pdf格式,并配合access-control-expose-headers暴露响应头供前端解析。

中文文件名直接传给 c.FileAttachment() 会乱码
不是框架 bug,是 HTTP 协议对 filename 字段的编码限制:它只支持 ASCII,不支持 UTF-8 原始字节。浏览器收到 filename=产品手册.pdf 这种含中文的值,会按 Latin-1 解码,结果就是一堆 或乱码。
正确做法是用 RFC 5987 规范的 filename*= 格式,让浏览器明确知道这是 UTF-8 编码:
-
url.PathEscape()比url.QueryEscape()更合适——它专为路径/文件名设计,不会把/转成%2F(避免干扰路径校验) - 构造头时必须写成
attachment; filename="fallback.pdf"; filename*=utf-8''%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C.pdf,其中filename=是兼容旧浏览器的降级字段,filename*=才是真正起作用的 - 不要手动拼接字符串,用
fmt.Sprintf("attachment; filename=\"%s\"; filename*=utf-8''%s", fallbackName, encodedName)
c.FileAttachment() 和手动设头的区别在哪
c.FileAttachment() 内部其实只是调用了 c.Header("Content-Disposition", ...) 并做了基础编码,但它默认用的是简单 filename= 形式,不带 filename*=。所以你传入中文名,它照样乱码。
换句话说:这个方法省了你设头的步骤,但没解决编码问题。生产环境必须绕过它,自己控制头:
- 删掉
c.FileAttachment(filepath, "中文名.pdf") - 改用
c.Header("Content-Disposition", ...)+c.File(filepath) - 确保
filepath是安全路径(如前缀校验),否则c.File()本身就会 404 或读错文件
Linux 服务器上文件名本身就是 GBK 编码怎么办
这不是 Gin 的问题,是文件系统层遗留问题。用 ls -b 看真实字节:\243\306.txt 就是 GBK,\346\226\207.txt 才是 UTF-8。
如果文件名确实是 GBK 字节,又不能重命名,那就得在 Go 里做一次转码:
- 用
golang.org/x/text/encoding/simplifiedchinese.GBK.NewDecoder()包装原始文件名字节 - 再用
url.PathEscape()编码转换后的 UTF-8 字符串 - 注意:这种方案只适用于文件名固定、可控的场景;动态用户上传的 GBK 名,应强制前端用 UTF-8 编码上传
前端用 fetch + blob 下载时仍乱码
这时候问题不在 Gin,而在前端没正确读取响应头里的 Content-Disposition。Gin 设置了 filename*=,但 fetch 默认不暴露自定义响应头,需要后端额外加 Access-Control-Expose-Headers: Content-Disposition。
否则前端拿到的 response.headers.get("Content-Disposition") 是 null,只能硬编码文件名,自然无法还原中文。
- 后端加一行:
c.Header("Access-Control-Expose-Headers", "Content-Disposition") - 前端解析逻辑要能提取
filename*=后面的值,不能只看filename= - 别依赖
response.headers["fileName"]这种非标字段——那是某些人自己加的,标准协议不认
filename*= 设头 → 前端显式读取并解码该字段。任何一环妥协,都会在某个浏览器或某次部署后突然失效。











