背景
做博客的图片上传功能时,遇到一个 FileNotFoundException:
java.io.FileNotFoundException:
/private/var/folders/2s/jm22wykx6mg6lnbbvhnt_78m0000gn/T/tomcat.8080.7036948421786898818/
work/Tomcat/localhost/ROOT/./uploads/2026/10/03/xxx.jpg
(No such file or directory)
奇怪的是:我的代码里写的路径是 ./uploads,为什么报错路径跑到了 Tomcat 的临时目录里去了?
现象
上传接口代码:
@Value("${app.upload.dir:./uploads}")
private String uploadDir;
@PostMapping("/image")
public Result<String> uploadImage(@RequestParam("file") MultipartFile file) {
File dir = new File(uploadDir, datePath);
if (!dir.exists() && !dir.mkdirs()) {
throw new BizException(500, "无法创建上传目录");
}
File dest = new File(dir, filename);
file.transferTo(dest); // ← 这行报错
...
}
配置:
app:
upload:
dir: ./uploads
按理说文件应该存到项目根目录下的 uploads/,但报错路径却是:
/private/var/folders/.../Tomcat/localhost/ROOT/./uploads/...
跑到了 Tomcat 的临时目录里去了。
原因
关键在 MultipartFile.transferTo(File dest) 这个方法的行为。
看 Spring 官方文档:
If the destination file is a relative path, it is resolved relative to the temporary directory of the Servlet container.
翻译一下:如果 dest 是相对路径,它会被解析到 Servlet 容器的临时目录里。
而我传的是 new File("./uploads", datePath),是相对路径。
dir.mkdirs()会成功(因为相对路径在本进程的user.dir下能建成功);- 但
file.transferTo(dest)里的 dest 会被 Tomcat 解析到临时目录; - 结果:目录建在了 A 处,文件写到了 B 处——B 处目录不存在,报 FileNotFoundException。
解决
把相对路径转成绝对路径再传给 transferTo:
import java.nio.file.Path;
import java.nio.file.Paths;
// 转成绝对路径
Path rootDir = Paths.get(uploadDir).toAbsolutePath().normalize();
Path targetDir = rootDir.resolve(datePath);
Path targetFile = targetDir.resolve(filename);
Files.createDirectories(targetDir);
file.transferTo(targetFile.toFile()); // 现在 targetFile 是绝对路径,安全
同理,静态资源映射也要用绝对路径:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String absolute = Paths.get(uploadDir).toAbsolutePath().normalize().toString();
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:" + absolute + "/");
}
两处都改成绝对路径后,上传成功、静态资源也能访问。
举一反三
所有"往磁盘写文件"的地方,都要用绝对路径:
| 场景 | 建议 |
|---|---|
| 文件上传 | Paths.get(dir).toAbsolutePath() |
| 日志文件 | 配置里写绝对路径 |
| 导出 Excel | 临时文件用 Files.createTempFile() |
| 生成图片 | 目标路径必须是绝对路径 |
一个最简单的判断标准:
- 代码里如果出现
new File("./xxx")、new File("../xxx")这种相对路径,一律改成绝对路径; - 绝对路径 = 项目根目录 + 相对路径,或配置里直接写绝对路径。
总结
MultipartFile.transferTo对相对路径的处理是"相对 Servlet 容器临时目录"——和大多数人的直觉相反;- 所有往磁盘写文件的代码,一律用绝对路径;
- 文件上传、静态资源映射、导出——三处都容易踩这个坑;
- 排查方法:看报错路径,如果路径里出现了
/tmp/、/private/var/folders/、Tomcat/localhost——基本就是相对路径被解析到临时目录了。
参考
本文是"踩坑系列"第 3 篇: 第 1 篇:Element Plus el-dialog 在 sticky header 里弹不出的坑 第 2 篇:MySQL TINYINT(1) 被 MyBatis-Plus 映射成 Boolean 的坑