前言
昨晚8點左右,正準備下班走人,突然,產品小姐姐的在QQ上猛地抖動了我一下。產品小姐姐果然是無事不登三寶殿。線上出了問題!!!!!!!好幾個版本沒有變動多的文件導入突然不行了。客戶催運營,運營催產品,產品催我這個小開發。哎,苦逼的程序員。
說明
本項目用的是 SpringBoot 2.x
問題重現
org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.46678487236333023.8030/work/Tomcat/localhost/extend] is not valid
我的腦門多了許多黑人問號??/tmp/tomcat.46678487236333023.8030/work/Tomcat/localhost/extend
這是個啥文件夾?上傳文件爲啥會操作這個文件夾?這個文件夾是啥時候創建的呢?
帶着這一連串的問題,我開始了面向google的開發。 根據The temporary upload location is not valid
關鍵字,搜索到如下結果:
說的是,這個文件夾沒有,需要手動在tmp下創建該文件夾。然後,我就在線上用我的common用戶,創建了這個文件夾,文件夾創建好之後,我接着嘗試去上傳Excel。緊接着又報了一個:
Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. /tmp/tomcat.46678487236333023.8030/work/Tomcat/localhost/extend/upload_2062a9c2_2ecf_4176_9622_6ae54d0fe80b_00000003.tmp (權限不夠)
權限不夠,然後一看我這個文件夾的默認權限drwxrwxr-x
,而我們項目部署的時候用的是Tomcat用戶,這個用戶是沒有權限在我新創建的文件夾下寫臨時文件。
而我小小的common用戶又沒有權限進行chmod。無奈只能請求我們的運維幫忙,用Tomcat用戶在tmp下創建這個文件夾。創建好之後,我再一試就可以。看似這個問題已經完美的解決了。但是,我的疑問還是沒有被解答。
解決疑問
- 上傳文件爲啥會操作這個文件呢?
/tmp/tomcat.46678487236333023.8030/work/Tomcat/localhost/extend
是啥時候創建的呢?
帶着這幾個疑問,我繼續google。
第一個問題 上傳文件爲啥會操作這個文件呢?
SpringBoot的文件上傳處理是基於Servlet實現的,Content-type是multipart/form-data, boundary=“boundaryStr”,在Servlet2.5及早期版本之前,文件上傳需要藉助commons-fileupload組件來實現。從Servlet 3.0規範之後,提供了對文件上傳的原生支持,進一步簡化了應用程序的實現。
以Tomcat爲例,在文件上傳之後會通過將數據寫入臨時文件,最終將文件實體傳參到應用層,如下:
Tomcat實現了Servlet3.0規範,通過ApplicationPart對文件上傳流實現封裝,其中,DiskFileItem描述了上傳文件實體,在請求解析時生成該對象,需要關注的是,DiskFileItem聲明瞭一個臨時文件,用於臨時存儲上傳文件的內容,SpringMVC對上層的請求實體再次封裝,最終構造爲MultipartFile傳遞給應用程序。
臨時文件;
臨時文件的路徑定義:
{temp_dir}/upload_xx_xxx.tmp
temp_dir是臨時目錄,通過系統屬性java.io.tmpdir
指定,默認值爲;
操作系統 | 路徑 |
---|---|
windows | C:\Users{username}\AppData\Local\Temp\ |
Linux | /tmp |
第一個問題解決了,接着就是第二個問題,既然,上傳需要用到這個文件夾,那麼這個文件夾是啥時候生成的呢?
第二個問題 {temp_dir}/upload_xx_xxx.tmp
是啥時候創建的呢?
很顯然,上傳的時候沒有生成文件夾,不然不會報is not valid
。那就只有可能是項目啓動的時候生成的。
爲了驗證我的想法:我在開發服務器進行了下模擬,首先,把tmp下所有tomcat爲前綴的文件夾都刪除了。然後重啓應用。重啓後查看。
重啓之後我發現/tmp
下新生成了tomcat.5195341930943680007.8030
這個文件夾。跟原來的tomcat.46678487236333023.8030
不一致。這麼說每次項目啓動之後就會生成一個新的tomcat.xxxx地址。爲了驗證我的猜想,我又把項目重啓了一遍。再觀察,果然如下,結果如下圖所示:
解決問題
最終解決這個問題呢?一個保險的方法就是指定上傳文件的臨時文件夾。在SpringBoot下只需要如下配置:
spring:
servlet:
multipart:
#開啓swagger
enabled: true
#最大上傳文件大小
max-file-size: 5MB
#臨時文件夾
location: /srv/www/extend
或者使用配置類,如下:
@Configuration
public static class FileConfig {
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.parse("5MB"));
factory.setMaxRequestSize(DataSize.parse("5MB"));
factory.setLocation("/srv/www/extend");
return factory.createMultipartConfig();
}
}
linux 命令查缺補漏
命令 | 作用 | 舉例 |
---|---|---|
rm -rf | 默默的刪除文件,慎用 | rm -rf tomcat.* 刪除所有前綴爲tomcat. 的文件夾 |
chmod | 給文件賦予權限 | |
ll | 查看該目錄下所有文件夾以及文件的屬性 | |
rz -be | 用於上傳大文件 | |
sz | 用於下載文件 | sz log.text 下載log.text文件 |
adduser | 用於創建新用戶 | adduser test |
su | 切換用戶 | su root 切換到root用戶 |
chmod
我們先用 ll
查看文件,結果如下:
總用量 4
drwxr-xr-x 3 test test 4096 6月 11 15:06 work
PS: 用 ls -ld 文件夾名
例如:ls -ld work/
可以指定查看work文件夾的屬性。結果跟用ll一樣的。
drwxr-xr-x | 3 | test | test | 4096 | 6月 11 15:06 | work |
---|---|---|---|---|---|---|
用戶權限 | 連接數 | 所有者 | 用戶組 | 文件大小 | 修改日期 | 文件夾名 |
文件類型與權限
- r:表示用戶可以查看該目錄下的內容,即可以使用"ls"命令
- w:表示用戶可以修改該目錄下的內容,包括增加、刪除、重命名等
- x: 表示用戶可以進入該目錄,既可以使用"cd"命令
我們不僅可以用rwx表示文件的權限,還可以同數字表示,具體如下: - r:4
- w:2
- x:1
每種身份各自的三個權限(r、w、x)分數是需要累加的,例如當權限爲[-rwxrwxr-x]
分數則是:
owner=rwx=4+2+1=7
group=rwx=4+2+1=7
others=r-x=4+1=5
所以,該文件的權限數字就是 775。
總結
本文由一個線上問題,將自己的種種不足都暴露出來了。挺好的。