<p>PHP 8.1 文件下载中文名乱码的根本原因是 Content-Disposition 的 filename 字段未遵循 RFC 5987 规范;正确做法是同时提供 ASCII 版 filename 和 UTF-8 编码的 filename=,前者用 preg_replace 过滤非字母数字字符,后者用 rawurlencode 编码,并在前端用 decodeURIComponent 解析 filename 值。</p>

PHP 8.1 下文件下载时中文名乱码,本质是 HTTP Content-Disposition 响应头中的 filename 字段不满足 RFC 6266 / RFC 5987 规范——它要求非 ASCII 文件名必须用特定格式编码,否则 Chrome、Firefox、Edge 会解析失败或显示为乱码,Safari 更可能直接忽略而用默认名(如 download.bin)。
核心原则:不要直接拼接中文到 filename= 后面
错误写法:header('Content-Disposition: attachment; filename="订单报表-张三.xlsx"');
这种写法在所有现代浏览器中都不可靠,尤其在 Linux/macOS 服务器环境几乎必乱。
推荐方案:使用 RFC 5987 兼容格式(多浏览器通用)
正确做法是同时提供两个 filename 参数:一个 ASCII 兜底(兼容老浏览器),一个 UTF-8 编码的 filename*=(供现代浏览器解析):
- 先对原始中文名做
rawurlencode()(不是urlencode()),确保空格变%20、中文转%E4%BD%A0等,并以UTF-8''前缀声明编码 - ASCII 版本用
basename()提取安全文件名,仅保留字母、数字、下划线、短横线,后缀不变 - 组合成标准 header
示例代码:
$origName = '销售统计-2026年9月.xlsx';
$asciiName = preg_replace('/[^\w\-.]+/', '_', $origName); // 如:销售统计-2026年9月.xlsx → _-2026_9_.xlsx
$utf8Name = rawurlencode($origName); // 如:销售统计-2026年9月.xlsx → %E9%94%80%E5%94%AE%E7%BB%9F%E8%AE%A1-2026%E5%B9%B49%E6%9C%88.xlsx
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Length: ' . filesize($filePath));
header('Content-Disposition: attachment; filename="' . $asciiName . '"; filename*=UTF-8\'\'' . $utf8Name);
readfile($filePath);
前端下载时也要配合处理(尤其 Vue/React 场景)
如果后端已按 RFC 5987 输出,但前端用 fetch + Blob 下载仍乱码,常见原因是未正确解析响应头中的 Content-Disposition。可手动提取并解码:
立即学习“PHP免费学习笔记(深入)”;
- 用正则匹配
filename\*=(.+?);?部分 - 对匹配值调用
decodeURIComponent()(注意不是decodeURI()) - 若无
filename*,再 fallback 到filename="..."并去除引号
JavaScript 示例:
const contentDisposition = response.headers.get('content-disposition');
let filename = 'download.xlsx';
if (contentDisposition) {
const utf8Match = contentDisposition.match(/filename\*=(?:[^']*)'([^']*)'([^;]+)/i);
if (utf8Match && utf8Match[2]) {
filename = decodeURIComponent(utf8Match[2]);
} else {
const basicMatch = contentDisposition.match(/filename="([^"]+)"/i);
filename = basicMatch ? basicMatch[1] : filename;
}
}
避坑提醒
- 不要用
iconv()或mb_convert_encoding()转换文件名再塞进 header——这违反协议,且 Windows/Linux 系统默认编码不同,极易出错 - 避免在 PHP 文件里硬写中文名(如
$name = "报表.xlsx"),确保 PHP 源文件本身保存为 UTF-8 无 BOM 格式 - 若用 Nginx,确认未开启
underscores_in_headers on;干扰 header 解析(极少见但存在) - 测试务必覆盖 Chrome、Firefox、Edge、Safari —— Safari 对
filename*支持最严格,最容易暴露问题



















