Fix: 修复文件上传临时文件丢失问题

问题:
- Docker 环境下上传文件时出现 FileNotFoundException
- Tomcat 在异步任务执行前清理了临时文件 /tmp/tomcat.xxx/work/...

解决方案:
1. 配置 multipart.location 为持久化目录 ./data/upload-temp
2. 设置 file-size-threshold: 0 强制立即写入磁盘
3. 修改 SftpController.upload() 方法:
   - 在异步任务执行前将 MultipartFile 保存到持久化位置
   - 异步任务从保存的文件读取而非 MultipartFile.getInputStream()
   - 上传完成或失败后自动清理临时文件

影响范围:
- backend/src/main/resources/application.yml
- backend/src/main/java/com/sshmanager/controller/SftpController.java
This commit is contained in:
liumangmang
2026-03-18 23:24:53 +08:00
parent f892810763
commit e2f600c264
2 changed files with 28 additions and 3 deletions

View File

@@ -290,11 +290,21 @@ public class SftpController {
@RequestParam String path, @RequestParam String path,
@RequestParam("file") MultipartFile file, @RequestParam("file") MultipartFile file,
Authentication authentication) { Authentication authentication) {
java.io.File tempFile = null;
try { try {
Long userId = getCurrentUserId(authentication); Long userId = getCurrentUserId(authentication);
String taskId = UUID.randomUUID().toString(); String taskId = UUID.randomUUID().toString();
String taskKey = uploadTaskKey(userId, taskId); String taskKey = uploadTaskKey(userId, taskId);
// Save file to persistent location before async processing
java.io.File uploadTempDir = new java.io.File("./data/upload-temp");
if (!uploadTempDir.exists()) {
uploadTempDir.mkdirs();
}
tempFile = new java.io.File(uploadTempDir, taskId + "_" + file.getOriginalFilename());
file.transferTo(tempFile);
final java.io.File savedFile = tempFile;
UploadTaskStatus status = new UploadTaskStatus(taskId, userId, connectionId, UploadTaskStatus status = new UploadTaskStatus(taskId, userId, connectionId,
path, file.getOriginalFilename(), file.getSize()); path, file.getOriginalFilename(), file.getSize());
status.setController(this); status.setController(this);
@@ -308,11 +318,11 @@ public class SftpController {
try { try {
SftpService.SftpSession session = getOrCreateSession(connectionId, userId); SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
String remotePath = (path == null || path.isEmpty() || path.equals("/")) String remotePath = (path == null || path.isEmpty() || path.equals("/"))
? "/" + file.getOriginalFilename() ? "/" + savedFile.getName().substring(savedFile.getName().indexOf("_") + 1)
: (path.endsWith("/") ? path + file.getOriginalFilename() : path + "/" + file.getOriginalFilename()); : (path.endsWith("/") ? path + savedFile.getName().substring(savedFile.getName().indexOf("_") + 1) : path + "/" + savedFile.getName().substring(savedFile.getName().indexOf("_") + 1));
AtomicLong transferred = new AtomicLong(0); AtomicLong transferred = new AtomicLong(0);
try (java.io.InputStream in = file.getInputStream()) { try (java.io.InputStream in = new java.io.FileInputStream(savedFile)) {
sftpService.upload(session, remotePath, in, new SftpService.TransferProgressListener() { sftpService.upload(session, remotePath, in, new SftpService.TransferProgressListener() {
@Override @Override
public void onStart(long totalBytes) { public void onStart(long totalBytes) {
@@ -334,10 +344,19 @@ public class SftpController {
existing.disconnect(); existing.disconnect();
} }
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
// Clean up temp file after upload completes
if (savedFile.exists()) {
savedFile.delete();
}
} }
}); });
} catch (Exception e) { } catch (Exception e) {
status.markError(e.getMessage() != null ? e.getMessage() : "Upload failed"); status.markError(e.getMessage() != null ? e.getMessage() : "Upload failed");
// Clean up temp file on error
if (savedFile.exists()) {
savedFile.delete();
}
} }
}); });
status.setFuture(future); status.setFuture(future);
@@ -347,6 +366,10 @@ public class SftpController {
result.put("message", "Upload started"); result.put("message", "Upload started");
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} catch (Exception e) { } catch (Exception e) {
// Clean up temp file if initial save failed
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
Map<String, Object> error = new HashMap<>(); Map<String, Object> error = new HashMap<>();
error.put("error", e.getMessage()); error.put("error", e.getMessage());
return ResponseEntity.status(500).body(error); return ResponseEntity.status(500).body(error);

View File

@@ -9,6 +9,8 @@ spring:
multipart: multipart:
max-file-size: 2048MB max-file-size: 2048MB
max-request-size: 2048MB max-request-size: 2048MB
location: ./data/upload-temp
file-size-threshold: 0
datasource: datasource:
url: jdbc:h2:file:./data/sshmanager;DB_CLOSE_DELAY=-1 url: jdbc:h2:file:./data/sshmanager;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver driver-class-name: org.h2.Driver