Files
sftp-manager/src/main/java/com/sftp/manager/service/SftpService.java
liu 14289beb66 Initial commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 10:10:11 +08:00

385 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<FileInfo> listFiles(String sessionId, String path) throws Exception {
return listFiles(sessionId, path, false);
}
public List<FileInfo> listFiles(String sessionId, String path, boolean showHidden) throws Exception {
ChannelSftp channel = sessionManager.getSession(sessionId);
if (channel == null) {
throw new Exception("会话不存在或已断开");
}
try {
List<FileInfo> 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<String> paths) {
BatchDeleteResult result = new BatchDeleteResult();
int successCount = 0;
int failCount = 0;
List<String> 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;
}
}