解決SpringBoot上傳文件異常

SpringBoot項目上傳文件後報錯

異常信息:

 
1
2
3
4
 
exception is org.springframework.web.multipart. MultipartException: Failed to parse multipart servlet request; nested exception is org.apache.commons.fileupload. FileUploadBase$IOFileUploadException: Processing of multipart /form-data request failed. /tmp /tomcat.6749631969221816745.3131/work /Tomcat/localhost /dts/upload_3ba7ace3_a73f_463d_b13b_01fec9b0ae9a_00000522.tmp (No such file or directory)] with root cause
java.io. FileNotFoundException: /tmp/tomcat .6749631969221816745 .3131 /work/Tomcat /localhost/dts/upload_3ba7ace3_a73f_463d_b13b_01fec9b0ae9a_00000522.tmp (No such file or directory)
at java.io.FileOutputStream.open0(Native Method)
 

產生原因

找不到Tomcat的臨時文件

  • 爲什麼上傳的文件要緩存到本地?方便流讀取
  • 爲什麼臨時目錄會不存在?SpringBoot啓動時會生成一個臨時文件,但是系統會定期刪除臨時文件

org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest,拋出異常的類,代碼如下:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 
public List<FileItem> parseRequest(RequestContext ctx)
throws FileUploadException {
List<FileItem> items = new ArrayList<>();
boolean successful = false;
try {
FileItemIterator iter = getItemIterator(ctx);
// 文件工廠類,裏面保存了臨時目錄的地址
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException( "No FileItemFactory has been set.");
}
while (iter.hasNext()) {
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
// 創建了以上報錯信息中的臨時文件
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
// 流的拷貝,將輸入流數據寫入輸出流
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(String.format( "Processing of %s request failed. %s",
MULTIPART_FORM_DATA, e.getMessage()), e);
}
final FileItemHeaders fih = item.getHeaders();
fileItem.setHeaders(fih);
}
successful = true;
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
} finally {
if (!successful) {
for (FileItem fileItem : items) {
try {
fileItem.delete();
} catch ( Exception ignored) {
// ignored TODO perhaps add to tracker delete failure list somehow?
}
}
}
}
}
 

以下爲創建臨時文件

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
/**
* Creates and returns a { @link java.io.File File} representing a uniquely
* named temporary file in the configured repository path. The lifetime of
* the file is tied to the lifetime of the <code>FileItem</code> instance;
* the file will be deleted when the instance is garbage collected.
* 文件將被刪除在垃圾回收時
*
* @return The { @link java.io.File File} to be used for temporary storage.
*/
protected File getTempFile() {
if (tempFile == null) {
File tempDir = repository;
if (tempDir == null) {
//如果沒定義則用系統默認的
tempDir = new File(System.getProperty( "java.io.tmpdir"));
}
 
String tempFileName = format( "upload_%s_%s.tmp", UID, getUniqueId());
 
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}

其中用到的生成隨機文件名字的兩個方法

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
private static final String UID =
UUID.randomUUID().toString().replace( '-', '_');
 
 
private static String getUniqueId() {
final int limit = 100000000;
int current = COUNTER.getAndIncrement();
String id = Integer.toString(current);
 
// If you manage to get more than 100 million of ids, you'll
// start getting ids longer than 8 characters.
if (current < limit) {
id = ( "00000000" + id).substring( id.length());
}
return id;
}

首先看下FileItemFactory的實例化位置,在org.apache.catalina.connector.Request#parseParts中,代碼如下

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
// Create a new file upload handler
DiskFileItemFactory factory = new DiskFileItemFactory();
try {
factory.setRepository(location.getCanonicalFile());
} catch (IOException ioe) {
parameters.setParseFailedReason(FailReason.IO_ERROR);
partsParseException = ioe;
return;
}
 
 
//其中location代碼
// TEMPDIR = "javax.servlet.context.tempdir";
location = ((File) context.getServletContext().getAttribute(ServletContext.TEMPDIR));

解決問題

  • 方法1

應用重啓

  • 方法2

增加服務配置,自定義baseDir

 
1
 
server.tomcat.basedir= /tmp/tomcat
  • 方法3

注入bean,手動配置臨時目錄

 
1
2
3
4
5
6
7
 
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setLocation( "/tmp/tomcat");
return factory.createMultipartConfig();
}
 
  • 方法4

配置不刪除tmp目錄下的tomcat

 
1
2
3
4
 
vim /usr/lib/tmpfiles.d/tmp.conf
 
# 添加一行
x /tmp/ tomcat.*
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章