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.
* 文件將被刪除在垃圾回收時
*
*/
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.*
|