增强 SSH/SFTP 稳定性并完善安全校验与前端交互
This commit is contained in:
@@ -1,63 +1,61 @@
|
||||
package com.sshmanager.controller;
|
||||
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import com.sshmanager.dto.SftpFileInfo;
|
||||
import com.sshmanager.entity.Connection;
|
||||
import com.sshmanager.entity.User;
|
||||
import com.sshmanager.repository.UserRepository;
|
||||
import com.sshmanager.service.ConnectionService;
|
||||
import com.sshmanager.service.SftpService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
package com.sshmanager.controller;
|
||||
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import com.sshmanager.dto.SftpFileInfo;
|
||||
import com.sshmanager.entity.Connection;
|
||||
import com.sshmanager.entity.User;
|
||||
import com.sshmanager.repository.UserRepository;
|
||||
import com.sshmanager.service.ConnectionService;
|
||||
import com.sshmanager.service.SftpService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sftp")
|
||||
public class SftpController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SftpController.class);
|
||||
|
||||
private final ConnectionService connectionService;
|
||||
private final UserRepository userRepository;
|
||||
private final SftpService sftpService;
|
||||
|
||||
private final Map<String, SftpService.SftpSession> sessions = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* JSch ChannelSftp is not thread-safe. If the frontend triggers concurrent requests (e.g. rapid ".." navigation),
|
||||
* sharing one ChannelSftp can crash with internal stream exceptions. We serialize all SFTP ops per (user, connection).
|
||||
*/
|
||||
private final Map<String, Object> sessionLocks = new ConcurrentHashMap<>();
|
||||
|
||||
public SftpController(ConnectionService connectionService,
|
||||
UserRepository userRepository,
|
||||
SftpService sftpService) {
|
||||
this.connectionService = connectionService;
|
||||
this.userRepository = userRepository;
|
||||
this.sftpService = sftpService;
|
||||
}
|
||||
|
||||
private Long getCurrentUserId(Authentication auth) {
|
||||
User user = userRepository.findByUsername(auth.getName()).orElseThrow(() -> new IllegalStateException("User not found"));
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
private String sessionKey(Long userId, Long connectionId) {
|
||||
return userId + ":" + connectionId;
|
||||
}
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sftp")
|
||||
public class SftpController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SftpController.class);
|
||||
|
||||
private final ConnectionService connectionService;
|
||||
private final UserRepository userRepository;
|
||||
private final SftpService sftpService;
|
||||
|
||||
private final Map<String, SftpService.SftpSession> sessions = new ConcurrentHashMap<>();
|
||||
private final Map<String, Object> sessionLocks = new ConcurrentHashMap<>();
|
||||
|
||||
public SftpController(ConnectionService connectionService,
|
||||
UserRepository userRepository,
|
||||
SftpService sftpService) {
|
||||
this.connectionService = connectionService;
|
||||
this.userRepository = userRepository;
|
||||
this.sftpService = sftpService;
|
||||
}
|
||||
|
||||
private Long getCurrentUserId(Authentication auth) {
|
||||
User user = userRepository.findByUsername(auth.getName()).orElseThrow(() -> new IllegalStateException("User not found"));
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
private String sessionKey(Long userId, Long connectionId) {
|
||||
return userId + ":" + connectionId;
|
||||
}
|
||||
|
||||
private <T> T withSessionLock(String key, Supplier<T> action) {
|
||||
Object lock = sessionLocks.computeIfAbsent(key, k -> new Object());
|
||||
synchronized (lock) {
|
||||
@@ -79,103 +77,102 @@ public class SftpController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SftpService.SftpSession getOrCreateSession(Long connectionId, Long userId) throws Exception {
|
||||
String key = sessionKey(userId, connectionId);
|
||||
SftpService.SftpSession session = sessions.get(key);
|
||||
if (session == null || !session.isConnected()) {
|
||||
Connection conn = connectionService.getConnectionForSsh(connectionId, userId);
|
||||
String password = connectionService.getDecryptedPassword(conn);
|
||||
String privateKey = connectionService.getDecryptedPrivateKey(conn);
|
||||
String passphrase = connectionService.getDecryptedPassphrase(conn);
|
||||
session = sftpService.connect(conn, password, privateKey, passphrase);
|
||||
sessions.put(key, session);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<?> list(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam(required = false, defaultValue = ".") String path,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
List<SftpService.FileInfo> files = sftpService.listFiles(session, path);
|
||||
List<SftpFileInfo> dtos = files.stream()
|
||||
.map(f -> new SftpFileInfo(f.name, f.directory, f.size, f.mtime))
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
} catch (Exception e) {
|
||||
// If the underlying SFTP channel got into a bad state, force reconnect on next request.
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
String errorMsg = toSftpErrorMessage(e, path, "list");
|
||||
log.warn("SFTP list failed: connectionId={}, path={}, error={}", connectionId, path, errorMsg, e);
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", errorMsg);
|
||||
return ResponseEntity.status(500).body(err);
|
||||
}
|
||||
}
|
||||
|
||||
private String toSftpErrorMessage(Exception e, String path, String operation) {
|
||||
if (e.getMessage() != null && !e.getMessage().trim().isEmpty()) {
|
||||
return e.getMessage();
|
||||
}
|
||||
// Unwrap nested RuntimeExceptions to find the underlying SftpException (if any).
|
||||
Throwable cur = e;
|
||||
for (int i = 0; i < 10 && cur != null; i++) {
|
||||
if (cur instanceof SftpException) {
|
||||
return SftpService.formatSftpExceptionMessage((SftpException) cur, path, operation);
|
||||
}
|
||||
if (cur.getMessage() != null && !cur.getMessage().trim().isEmpty()) {
|
||||
return cur.getMessage();
|
||||
}
|
||||
cur = cur.getCause();
|
||||
}
|
||||
return operation + " failed";
|
||||
}
|
||||
|
||||
@GetMapping("/pwd")
|
||||
public ResponseEntity<Map<String, String>> pwd(
|
||||
@RequestParam Long connectionId,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
String pwd = sftpService.pwd(session);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("path", pwd);
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.warn("SFTP pwd failed: connectionId={}", connectionId, e);
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", e.getMessage() != null ? e.getMessage() : "pwd failed");
|
||||
return ResponseEntity.status(500).body(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SftpService.SftpSession getOrCreateSession(Long connectionId, Long userId) throws Exception {
|
||||
String key = sessionKey(userId, connectionId);
|
||||
SftpService.SftpSession session = sessions.get(key);
|
||||
if (session == null || !session.isConnected()) {
|
||||
Connection conn = connectionService.getConnectionForSsh(connectionId, userId);
|
||||
String password = connectionService.getDecryptedPassword(conn);
|
||||
String privateKey = connectionService.getDecryptedPrivateKey(conn);
|
||||
String passphrase = connectionService.getDecryptedPassphrase(conn);
|
||||
session = sftpService.connect(conn, password, privateKey, passphrase);
|
||||
sessions.put(key, session);
|
||||
}
|
||||
cleanupTask.recordAccess(key);
|
||||
return session;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<?> list(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam(required = false, defaultValue = ".") String path,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
List<SftpService.FileInfo> files = sftpService.listFiles(session, path);
|
||||
List<SftpFileInfo> dtos = files.stream()
|
||||
.map(f -> new SftpFileInfo(f.name, f.directory, f.size, f.mtime))
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
String errorMsg = toSftpErrorMessage(e, path, "list");
|
||||
log.warn("SFTP list failed: connectionId={}, path={}, error={}", connectionId, path, errorMsg, e);
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", errorMsg);
|
||||
return ResponseEntity.status(500).body(err);
|
||||
}
|
||||
}
|
||||
|
||||
private String toSftpErrorMessage(Exception e, String path, String operation) {
|
||||
if (e.getMessage() != null && !e.getMessage().trim().isEmpty()) {
|
||||
return e.getMessage();
|
||||
}
|
||||
Throwable cur = e;
|
||||
for (int i = 0; i < 10 && cur != null; i++) {
|
||||
if (cur instanceof SftpException) {
|
||||
return SftpService.formatSftpExceptionMessage((SftpException) cur, path, operation);
|
||||
}
|
||||
if (cur.getMessage() != null && !cur.getMessage().trim().isEmpty()) {
|
||||
return cur.getMessage();
|
||||
}
|
||||
cur = cur.getCause();
|
||||
}
|
||||
return operation + " failed";
|
||||
}
|
||||
|
||||
@GetMapping("/pwd")
|
||||
public ResponseEntity<Map<String, String>> pwd(
|
||||
@RequestParam Long connectionId,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
String pwd = sftpService.pwd(session);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("path", pwd);
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.warn("SFTP pwd failed: connectionId={}", connectionId, e);
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", e.getMessage() != null ? e.getMessage() : "pwd failed");
|
||||
return ResponseEntity.status(500).body(err);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/download")
|
||||
public ResponseEntity<StreamingResponseBody> download(
|
||||
@RequestParam Long connectionId,
|
||||
@@ -208,19 +205,19 @@ public class SftpController {
|
||||
return ResponseEntity.status(500).build();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/upload")
|
||||
public ResponseEntity<Map<String, String>> upload(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
@RequestParam("file") MultipartFile file,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
|
||||
@PostMapping("/upload")
|
||||
public ResponseEntity<Map<String, String>> upload(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
@RequestParam("file") MultipartFile file,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
String remotePath = (path == null || path.isEmpty() || path.equals("/"))
|
||||
? "/" + file.getOriginalFilename()
|
||||
: (path.endsWith("/") ? path + file.getOriginalFilename() : path + "/" + file.getOriginalFilename());
|
||||
@@ -230,114 +227,114 @@ public class SftpController {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Uploaded");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
public ResponseEntity<Map<String, String>> delete(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
@RequestParam boolean directory,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.delete(session, path, directory);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Deleted");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/mkdir")
|
||||
public ResponseEntity<Map<String, String>> mkdir(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.mkdir(session, path);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Created");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/rename")
|
||||
public ResponseEntity<Map<String, String>> rename(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String oldPath,
|
||||
@RequestParam String newPath,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.rename(session, oldPath, newPath);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Renamed");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/transfer-remote")
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
public ResponseEntity<Map<String, String>> delete(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
@RequestParam boolean directory,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.delete(session, path, directory);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Deleted");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/mkdir")
|
||||
public ResponseEntity<Map<String, String>> mkdir(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String path,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.mkdir(session, path);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Created");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/rename")
|
||||
public ResponseEntity<Map<String, String>> rename(
|
||||
@RequestParam Long connectionId,
|
||||
@RequestParam String oldPath,
|
||||
@RequestParam String newPath,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
return withSessionLock(key, () -> {
|
||||
try {
|
||||
SftpService.SftpSession session = getOrCreateSession(connectionId, userId);
|
||||
sftpService.rename(session, oldPath, newPath);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Renamed");
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
SftpService.SftpSession existing = sessions.remove(key);
|
||||
if (existing != null) {
|
||||
existing.disconnect();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage());
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/transfer-remote")
|
||||
public ResponseEntity<Map<String, String>> transferRemote(
|
||||
@RequestParam Long sourceConnectionId,
|
||||
@RequestParam String sourcePath,
|
||||
@@ -346,11 +343,11 @@ public class SftpController {
|
||||
Authentication authentication) {
|
||||
try {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
if (sourcePath == null || sourcePath.trim().isEmpty()) {
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", "sourcePath is required");
|
||||
return ResponseEntity.badRequest().body(err);
|
||||
}
|
||||
if (sourcePath == null || sourcePath.trim().isEmpty()) {
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", "sourcePath is required");
|
||||
return ResponseEntity.badRequest().body(err);
|
||||
}
|
||||
if (targetPath == null || targetPath.trim().isEmpty()) {
|
||||
Map<String, String> err = new HashMap<>();
|
||||
err.put("error", "targetPath is required");
|
||||
@@ -387,24 +384,61 @@ public class SftpController {
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", e.getMessage() != null ? e.getMessage() : "Transfer failed");
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/disconnect")
|
||||
public ResponseEntity<Map<String, String>> disconnect(
|
||||
@RequestParam Long connectionId,
|
||||
Authentication authentication) {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
SftpService.SftpSession session = sessions.remove(key);
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
sessionLocks.remove(key);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Disconnected");
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
error.put("error", e.getMessage() != null ? e.getMessage() : "Transfer failed");
|
||||
return ResponseEntity.status(500).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/disconnect")
|
||||
public ResponseEntity<Map<String, String>> disconnect(
|
||||
@RequestParam Long connectionId,
|
||||
Authentication authentication) {
|
||||
Long userId = getCurrentUserId(authentication);
|
||||
String key = sessionKey(userId, connectionId);
|
||||
SftpService.SftpSession session = sessions.remove(key);
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
sessionLocks.remove(key);
|
||||
cleanupTask.removeSession(key);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", "Disconnected");
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
public void cleanupExpiredSessions(int timeoutMinutes) {
|
||||
List<String> expired = cleanupTask.getExpiredSessions(timeoutMinutes);
|
||||
for (String key : expired) {
|
||||
SftpService.SftpSession session = sessions.remove(key);
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
sessionLocks.remove(key);
|
||||
cleanupTask.removeSession(key);
|
||||
log.info("Cleaned up expired SFTP session: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
private final SftpSessionExpiryCleanup cleanupTask = new SftpSessionExpiryCleanup();
|
||||
|
||||
public static class SftpSessionExpiryCleanup {
|
||||
private final Map<String, Long> lastAccessTime = new ConcurrentHashMap<>();
|
||||
|
||||
public void recordAccess(String key) {
|
||||
lastAccessTime.put(key, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void removeSession(String key) {
|
||||
lastAccessTime.remove(key);
|
||||
}
|
||||
|
||||
public List<String> getExpiredSessions(long timeoutMinutes) {
|
||||
long now = System.currentTimeMillis();
|
||||
long timeoutMillis = timeoutMinutes * 60 * 1000;
|
||||
return lastAccessTime.entrySet().stream()
|
||||
.filter(entry -> now - entry.getValue() > timeoutMillis)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user