package com.sftp.manager.service; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; import com.sftp.manager.dto.BatchDeleteResult; import com.sftp.manager.model.FileInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Vector; @Service public class SftpService { @Autowired private SessionManager sessionManager; public List listFiles(String sessionId, String path) throws Exception { return listFiles(sessionId, path, false); } public List listFiles(String sessionId, String path, boolean showHidden) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { List files = new ArrayList<>(); Vector entries = channel.ls(path); for (Object obj : entries) { ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) obj; String fileName = entry.getFilename(); // 跳过.和.. if (".".equals(fileName) || "..".equals(fileName)) { continue; } // 不展示隐藏文件时跳过以.开头的文件/目录(Unix 惯例) if (!showHidden && fileName.startsWith(".")) { continue; } FileInfo fileInfo = new FileInfo(); fileInfo.setName(fileName); fileInfo.setPath(path.endsWith("/") ? path + fileName : path + "/" + fileName); fileInfo.setSize(entry.getAttrs().getSize()); fileInfo.setDirectory(entry.getAttrs().isDir()); // 获取修改时间 int mtime = entry.getAttrs().getMTime(); fileInfo.setModifiedTime(LocalDateTime.ofInstant( Instant.ofEpochSecond(mtime), ZoneId.systemDefault())); // 获取权限 fileInfo.setPermissions(entry.getAttrs().getPermissionsString()); files.add(fileInfo); } return files; } catch (SftpException e) { throw new Exception("列出文件失败: " + e.getMessage(), e); } } public String pwd(String sessionId) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { return channel.pwd(); } catch (SftpException e) { throw new Exception("获取当前路径失败: " + e.getMessage(), e); } } public void cd(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { channel.cd(path); } catch (SftpException e) { throw new Exception("切换目录失败: " + e.getMessage(), e); } } public FileInfo getFileInfo(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { SftpATTRS attrs = channel.stat(path); FileInfo fileInfo = new FileInfo(); // 从路径提取文件名 String fileName = path; int lastSlash = path.lastIndexOf('/'); if (lastSlash >= 0 && lastSlash < path.length() - 1) { fileName = path.substring(lastSlash + 1); } fileInfo.setName(fileName); fileInfo.setPath(path); fileInfo.setSize(attrs.getSize()); fileInfo.setDirectory(attrs.isDir()); int mtime = attrs.getMTime(); fileInfo.setModifiedTime(LocalDateTime.ofInstant( Instant.ofEpochSecond(mtime), ZoneId.systemDefault())); fileInfo.setPermissions(attrs.getPermissionsString()); return fileInfo; } catch (SftpException e) { throw new Exception("获取文件信息失败: " + e.getMessage(), e); } } // 上传文件到SFTP public void uploadFile(String sessionId, InputStream inputStream, String remotePath, long fileSize) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { channel.put(inputStream, remotePath); } catch (SftpException e) { throw new Exception("上传失败: " + e.getMessage(), e); } // 注意:不在此处关闭流,由调用者使用try-with-resources管理 } // 从SFTP下载文件 public void downloadFile(String sessionId, String remotePath, OutputStream outputStream) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { channel.get(remotePath, outputStream); } catch (SftpException e) { throw new Exception("下载失败: " + e.getMessage(), e); } // 注意:不在此处关闭流,由调用者使用try-with-resources管理 } // SFTP间传输 public void transferBetweenSftp(String sourceSessionId, String sourcePath, String targetSessionId, String targetPath) throws Exception { // 创建临时文件 String tempDir = System.getProperty("java.io.tmpdir"); String tempFile = tempDir + File.separator + UUID.randomUUID().toString(); try { // 从源SFTP下载到临时文件 ChannelSftp sourceChannel = sessionManager.getSession(sourceSessionId); if (sourceChannel == null) { throw new Exception("源会话不存在或已断开"); } sourceChannel.get(sourcePath, tempFile); // 上传临时文件到目标SFTP ChannelSftp targetChannel = sessionManager.getSession(targetSessionId); if (targetChannel == null) { throw new Exception("目标会话不存在或已断开"); } targetChannel.put(tempFile, targetPath); } finally { // 删除临时文件 File file = new File(tempFile); if (file.exists()) { file.delete(); } } } // 删除单个文件或目录 public boolean deleteFile(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { FileInfo fileInfo = getFileInfo(sessionId, path); if (fileInfo.isDirectory()) { deleteDirectoryRecursive(sessionId, path); } else { channel.rm(path); } return true; } catch (SftpException e) { throw new Exception("删除失败: " + e.getMessage(), e); } } // 递归删除SFTP目录 private void deleteDirectoryRecursive(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { Vector entries = channel.ls(path); for (Object obj : entries) { ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) obj; String fileName = entry.getFilename(); if (".".equals(fileName) || "..".equals(fileName)) { continue; } String fullPath = path.endsWith("/") ? path + fileName : path + "/" + fileName; if (entry.getAttrs().isDir()) { deleteDirectoryRecursive(sessionId, fullPath); } else { channel.rm(fullPath); } } channel.rmdir(path); } catch (SftpException e) { throw new Exception("删除目录失败: " + e.getMessage(), e); } } // 创建目录(单级) public boolean createDirectory(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { try { channel.stat(path); throw new Exception("目录已存在: " + path); } catch (SftpException e) { if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) { throw e; } } channel.mkdir(path); return true; } catch (SftpException e) { throw new Exception("创建目录失败: " + e.getMessage(), e); } } // 创建多级目录 public boolean createDirectories(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { try { channel.stat(path); throw new Exception("目录已存在: " + path); } catch (SftpException e) { if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) { throw e; } } createDirectoriesRecursive(channel, path); return true; } catch (SftpException e) { throw new Exception("创建目录失败: " + e.getMessage(), e); } } // 递归创建多级目录 private void createDirectoriesRecursive(ChannelSftp channel, String path) throws SftpException { if (path == null || path.equals("/") || path.isEmpty()) { return; } String parentPath = getSftpParentPath(path); if (parentPath != null && !parentPath.equals(path) && !parentPath.isEmpty()) { try { channel.stat(parentPath); } catch (SftpException e) { if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { createDirectoriesRecursive(channel, parentPath); } else { throw e; } } } channel.mkdir(path); } // 获取 SFTP 父路径 private String getSftpParentPath(String path) { if (path == null || path.isEmpty()) return "/"; String p = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; int index = p.lastIndexOf("/"); if (index <= 0) return "/"; return p.substring(0, index); } // 重命名文件 public boolean renameFile(String sessionId, String oldPath, String newPath) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { channel.stat(oldPath); try { channel.stat(newPath); throw new Exception("目标文件已存在: " + newPath); } catch (SftpException e) { // 目标不存在,可以重命名 } channel.rename(oldPath, newPath); return true; } catch (SftpException e) { throw new Exception("重命名失败: " + e.getMessage(), e); } } // 批量删除 public BatchDeleteResult batchDelete(String sessionId, List paths) { BatchDeleteResult result = new BatchDeleteResult(); int successCount = 0; int failCount = 0; List failedFiles = new ArrayList<>(); for (String path : paths) { try { deleteFile(sessionId, path); successCount++; } catch (Exception e) { failCount++; failedFiles.add(path + " - " + e.getMessage()); } } result.setSuccessCount(successCount); result.setFailCount(failCount); result.setFailedFiles(failedFiles); return result; } }