问题现象:文件神秘失踪
事情是这样的,我在写一个文件上传功能时,遇到了一个让人摸不着头脑的问题:
1
2
3
4
5
6
7
|
@PostMapping("/upload")
public String upload(MultipartFile file) {
// 明明指定了路径,为什么文件不在这里?
File floorPlatFile = new File("uploads/" + file.getOriginalFilename());
file.transferTo(floorPlatFile);
return "success";
}
|
代码看起来人畜无害,我满怀信心地启动项目,上传文件,然后去 uploads/ 目录下寻找… 结果空空如也!
更诡异的是,我翻遍了项目目录,甚至用 find 命令全局搜索,都找不到这个文件。它就像凭空消失了一样。
直到我无意间打开了 /tmp 目录,才发现文件居然安安静静地躺在这里!
破案过程:源码之下无秘密
我决定从源码入手,看看 transferTo() 到底做了什么。
找到罪魁祸首
MultipartFile 的默认实现是 StandardMultipartFile,它的 transferTo() 方法源码如下:
1
2
3
4
5
6
7
8
9
|
// org.springframework.web.multipart.support.StandardMultipartFile
public void transferTo(File dest) throws IOException, IllegalStateException {
// 关键判断:如果目标文件不是绝对路径
if (!dest.isAbsolute()) {
// 这里就是问题的根源!
dest = new File(this.location, dest.getPath());
}
this.part.write(dest.getPath());
}
|
看到这里,真相大白了!
原来 transferTo() 内部有一个“自作主张”的逻辑:当你传入相对路径时,它会自动拼接一个基础路径(this.location),而这个基础路径正是容器的临时目录!
location 从何而来?
继续追踪 this.location 的赋值过程:
1
2
3
4
5
|
// StandardMultipartFile 构造器
public StandardMultipartFile(Part part, String location) {
this.part = part;
this.location = location; // 这个 location 是哪里来的?
}
|
最终发现,这个 location 是在 StandardServletMultipartResolver 中设置的,它取自 MultipartConfigElement 配置的临时目录。如果没有特殊配置,默认就是 Servlet 容器的临时目录:
Tomcat:/tmp/tomcat-xxx/work/Tomcat/localhost/ROOT
Jetty:/tmp/jetty-xxx/work
Undertow:/tmp/undertow-xxx
所以,你传入的相对路径 uploads/file.txt,最终被拼接成了:
1
|
/tmp/tomcat-xxx/work/Tomcat/localhost/ROOT/uploads/file.txt
|
为什么这样设计?
这个设计看起来有点“反直觉”,但实际上是有充分考虑的:
安全性:Web 应用通常部署在只读的目录下(如 /usr/local/tomcat/webapps),不允许直接写入
隔离性:多个应用实例不会互相干扰
可靠性:临时目录是可写的,避免权限问题导致异常
解决方案:让文件去它该去的地方
最简单直接的方案,让 File 对象持有绝对路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Value("${file.upload.dir:/app/uploads}")
private String uploadDir;
public void uploadFile(MultipartFile file) throws IOException {
// 确保目录存在
File dir = new File(uploadDir);
if (!dir.exists()) {
dir.mkdirs();
}
// 使用绝对路径
File targetFile = new File(dir, file.getOriginalFilename()).getCanonicalFile();
file.transferTo(targetFile);
}
|