乐于分享
好东西不私藏

Spring Boot 文件下载:这6个坑让你的下载接口报 500

Spring Boot 文件下载:这6个坑让你的下载接口报 500

用户点下载弹乱码、”跨域”报错就是下不了、500M 文件直接 OOM——文件下载看着简单,一行 OutputStream 的事,6 个细节漏一个就炸锅。
01
1. 文件名中文乱码
// ❌ 直接传中文名,浏览器 URL 编码不统一response.setHeader("Content-Disposition","attachment; filename=" + fileName);
// ✅ URLEncoder 统一编码 + UTF-8 声明String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8)        .replace("+""+");response.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + encoded);

02
 大文件一次读进内存
// ❌ 500M 文件一把 readAllBytes → OOMbyte[] bytes = Files.readAllBytes(file.toPath());response.getOutputStream().write(bytes);
// ✅ 流式边读边写,内存恒定try (InputStream in = new FileInputStream(file);     OutputStream out = response.getOutputStream()) {byte[] buf = newbyte[4096];int len;while ((len = in.read(buf)) != -1) {        out.write(buf, 0, len);    }    out.flush();}

03
 Content-Type 忘写
// ❌ 没设 Content-Type → 浏览器当文本渲染、下载触发不了response.setHeader("Content-Disposition","attachment; filename=" + fileName);
// ✅ 明确告诉浏览器这是二进制流response.setContentType("application/octet-stream");response.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + encoded);

04
 路径穿越漏洞
// ❌ 前端传 fileName=../../etc/passwd,直接拼路径File file = new File(basePath + fileName);
// ✅ 校验 realPath 必须在 basePath 下File file = new File(basePath, fileName);if (!file.getCanonicalPath()        .startsWith(new File(basePath).getCanonicalPath())) {thrownew SecurityException("非法路径");}

05
response 写入后未 flush/close
// ❌ 先设置 header 再写 body 没问题,但忘 flushOutputStream out = response.getOutputStream();Files.copy(file.toPath(), out);// 浏览器拿到截断文件
// ✅ flush 关键:强迫缓冲区写出OutputStream out = response.getOutputStream();Files.copy(file.toPath(), out);out.flush();

06
Nginx 代理缓存大文件爆盘
# ❌ proxy_buffering on → 大文件全部缓存到 Nginx 磁盘proxy_pass http://backend;
# ✅ 大文件关缓冲 + 限制速率location /download/ {    proxy_pass http://backend;    proxy_buffering off;    proxy_request_buffering off;    limit_rate 2m;}

避坑速查表
# 坑点 快速修复
1
中文乱码
URLEncoder

 + filename*=UTF-8''
2
大文件 OOM
流式 while read write 4KB buf
3
Content-Type 缺
application/octet-stream
4
路径穿越
getCanonicalPath()

 + startsWith
5
忘 flush
out.flush()
6
Nginx 爆盘
proxy_buffering off

一句话总结:文件下载核心三条——流式输出别读内存、Content-Disposition 用 UTF-8 编码、路径拼接先校验