384
src/main/java/com/sftp/manager/service/SftpService.java
Normal file
384
src/main/java/com/sftp/manager/service/SftpService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user