导出大文件应绕过前端Blob方案,改用后端生成+轮询状态+静态路径下载;需校验磁盘空间、权限、并发安全、流式读写、Nginx超时与缓冲配置。
导出大文件时浏览器卡死,response.download 不顶用了
浏览器对内存和响应体大小有硬限制,前端直接生成并触发 download 的 blob url 方式,在文件超过 ~500mb 时极易卡死或崩溃——这不是代码写得不对,是浏览器本身不支持。这时候必须绕过前端拼接、流式传输、内存缓存这些环节。
实操建议:
- 彻底放弃在前端用
fetch+Blob+URL.createObjectURL导出大文件 - 后端不再返回文件内容,只返回一个唯一任务 ID 或预签名路径
- 前端轮询
/api/export/status?id=xxx,等服务端写完再跳转下载链接 - 下载链接必须是服务端静态文件路径(如
/exports/report_20241105_abc123.xlsx),由 Nginx / Caddy 直接托管,不经过应用层
保存到服务器目录前,必须检查磁盘空间和路径权限
很多线上问题不是逻辑错,而是 /var/www/exports 目录没写权限,或磁盘只剩 200MB 却试图导出 800MB 的 CSV。服务端保存失败时,前端轮询永远收不到完成状态,用户就卡在“加载中”。
实操建议:
- 导出前调用
df -h /var/www(Linux)或os.statvfs()(Python)校验剩余空间是否 > 预估文件大小 × 1.2 - 确保导出目录属主是运行 Web 服务的用户(如
www-data),且权限为755 - 文件名强制加哈希后缀(如
report_abc123_9f8a7b.xlsx),避免并发写入覆盖 - 设置定时清理:用
find /var/www/exports -name "*.xlsx" -mmin +60 -delete清理 1 小时前的临时文件
用 stream=True + 分块写入,避免内存爆掉
如果导出逻辑涉及数据库查大量数据(比如 1000 万行订单),用 cursor.fetchall() 一次性加载进内存,Python 进程直接 OOM;Node.js 里 JSON.stringify(allRows) 同样危险。
实操建议:
- Python(SQLAlchemy)用
yield_per(1000)或原生游标fetchmany(5000)流式读取 - 写入文件用
open(..., "wb")+write()分块,别用StringIO拼接全量字符串 - Node.js 用
pg.Cursor或mysql2的stream()方法,配合fs.createWriteStream() - 导出格式优先选
.csv或.parquet,别用.xlsx(xlsxwriter 内存开销是 CSV 的 5–8 倍)
Nginx 配置里漏掉 client_max_body_size 和超时项,会导致上传配置失败
虽然这次是“导出”,但有些场景需要先上传参数(比如筛选条件 JSON 文件),或者导出接口本身接收一个大型配置 POST。Nginx 默认 client_max_body_size 是 1MB,超了就直接返回 413,而前端可能只看到“网络错误”,根本看不到真实原因。
实操建议:
- 在对应 server 块里显式加:
client_max_body_size 2G; - 调大超时:
proxy_read_timeout 1200;(20 分钟)、proxy_send_timeout 1200; - 禁用缓冲:
proxy_buffering off;,防止 Nginx 把整个响应体缓存在内存里再吐给客户端 - 确认后端框架(如 Django、Express)也关掉了响应体压缩(
GZIP = False或compression(false)),否则流式写入会被压缩中间件阻塞
真正麻烦的从来不是“怎么把文件写到磁盘”,而是磁盘满了没人告警、权限错了日志没打、Nginx 缓冲吃光内存却以为是代码慢——这些点不提前卡住,上线后只能靠用户报“页面卡住”来反推问题。

















