385 lines
13 KiB
Java
385 lines
13 KiB
Java
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;
|
||
}
|
||
}
|