Initial commit

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
liu
2026-02-03 10:10:11 +08:00
commit 14289beb66
45 changed files with 15479 additions and 0 deletions

View File

@@ -0,0 +1,384 @@
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;
}
}