文章目录
1. JSCH和SFTP基础概念
1.1 什么是JSCH?
JSCH(Java Secure Channel)是一个纯Java实现的SSH2协议库,由JCraft开发。它提供了完整的SSH客户端功能,包括:
- SFTP(SSH File Transfer Protocol):安全文件传输协议
- SCP(Secure Copy Protocol):安全复制协议
- SSH执行远程命令:在远程服务器执行命令
- 端口转发:本地和远程端口转发
- 公钥认证:支持各种公钥认证方式
1.2 SFTP协议特点
SFTP(SSH File Transfer Protocol)是基于SSH协议的文件传输协议,具有以下特点:
- 安全性高:所有数据传输都经过加密
- 跨平台:支持不同操作系统之间的文件传输
- 功能丰富:支持文件上传、下载、删除、重命名等操作
- 可靠性强:支持断点续传和完整性校验
1.3 JSCH的优势
- 纯Java实现:无需额外的本地库依赖
- 轻量级:jar包大小约280KB
- 功能完整:支持SSH2协议的大部分功能
- 开源免费:基于BSD许可证
- 活跃维护:持续更新和维护
1.4 常用场景
// 常见的SFTP使用场景示例
public class SftpUseCases {
// 1. 文件备份
public void backupFiles() {
// 将本地文件备份到远程服务器
}
// 2. 日志收集
public void collectLogs() {
// 从远程服务器下载日志文件
}
// 3. 数据同步
public void syncData() {
// 在不同服务器间同步数据文件
}
// 4. 部署文件
public void deployApplication() {
// 上传应用程序文件到服务器
}
// 5. 监控文件变化
public void monitorFileChanges() {
// 定期检查远程目录的文件变化
}
}
2. 环境配置和依赖管理
2.1 Maven依赖配置
在pom.xml
文件中添加JSCH依赖:
<dependencies>
<!-- JSCH核心依赖 -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- 日志依赖(可选) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
<!-- Apache Commons IO(文件操作辅助) -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- JSON处理(配置文件处理) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
2.2 Gradle依赖配置
在build.gradle
文件中添加依赖:
dependencies {
// JSCH核心依赖
implementation 'com.jcraft:jsch:0.1.55'
// 日志依赖
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'ch.qos.logback:logback-classic:1.2.12'
// 工具类依赖
implementation 'commons-io:commons-io:2.11.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
}
2.3 基础配置类
创建SFTP连接配置类:
package com.example.sftp.config;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* SFTP连接配置类
*/
public class SftpConfig {
@JsonProperty("host")
private String host;
@JsonProperty("port")
private int port = 22;
@JsonProperty("username")
private String username;
@JsonProperty("password")
private String password;
@JsonProperty("privateKeyPath")
private String privateKeyPath;
@JsonProperty("passphrase")
private String passphrase;
@JsonProperty("timeout")
private int timeout = 30000; // 30秒
@JsonProperty("strictHostKeyChecking")
private boolean strictHostKeyChecking = false;
@JsonProperty("preferredAuthentications")
private String preferredAuthentications = "publickey,password";
// 构造函数
public SftpConfig() {}
public SftpConfig(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public SftpConfig(String host, String username, String privateKeyPath, String passphrase) {
this.host = host;
this.username = username;
this.privateKeyPath = privateKeyPath;
this.passphrase = passphrase;
}
// Getters and Setters
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPrivateKeyPath() { return privateKeyPath; }
public void setPrivateKeyPath(String privateKeyPath) { this.privateKeyPath = privateKeyPath; }
public String getPassphrase() { return passphrase; }
public void setPassphrase(String passphrase) { this.passphrase = passphrase; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public boolean isStrictHostKeyChecking() { return strictHostKeyChecking; }
public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
this.strictHostKeyChecking = strictHostKeyChecking;
}
public String getPreferredAuthentications() { return preferredAuthentications; }
public void setPreferredAuthentications(String preferredAuthentications) {
this.preferredAuthentications = preferredAuthentications;
}
@Override
public String toString() {
return String.format("SftpConfig{host='%s', port=%d, username='%s', timeout=%d}",
host, port, username, timeout);
}
}
2.4 配置文件示例
创建配置文件sftp-config.json
:
{
"host": "192.168.1.100",
"port": 22,
"username": "ftpuser",
"password": "password123",
"timeout": 30000,
"strictHostKeyChecking": false,
"preferredAuthentications": "password,publickey"
}
创建公钥认证配置文件sftp-config-key.json
:
{
"host": "192.168.1.100",
"port": 22,
"username": "ftpuser",
"privateKeyPath": "/path/to/private/key",
"passphrase": "keypassphrase",
"timeout": 30000,
"strictHostKeyChecking": false,
"preferredAuthentications": "publickey,password"
}
3. SFTP连接管理
3.1 基础连接类
创建SFTP连接管理类:
package com.example.sftp.core;
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
* SFTP连接管理器
*/
public class SftpConnectionManager {
private static final Logger logger = LoggerFactory.getLogger(SftpConnectionManager.class);
private JSch jsch;
private Session session;
private ChannelSftp channelSftp;
private SftpConfig config;
public SftpConnectionManager(SftpConfig config) {
this.config = config;
this.jsch = new JSch();
}
/**
* 建立SFTP连接
*/
public void connect() throws JSchException {
logger.info("正在连接SFTP服务器: {}", config.getHost());
try {
// 创建会话
session = jsch.getSession(config.getUsername(), config.getHost(), config.getPort());
// 设置密码认证
if (config.getPassword() != null && !config.getPassword().isEmpty()) {
session.setPassword(config.getPassword());
}
// 设置私钥认证
if (config.getPrivateKeyPath() != null && !config.getPrivateKeyPath().isEmpty()) {
if (config.getPassphrase() != null && !config.getPassphrase().isEmpty()) {
jsch.addIdentity(config.getPrivateKeyPath(), config.getPassphrase());
} else {
jsch.addIdentity(config.getPrivateKeyPath());
}
}
// 配置会话属性
Properties properties = new Properties();
properties.put("StrictHostKeyChecking",
config.isStrictHostKeyChecking() ? "yes" : "no");
properties.put("PreferredAuthentications", config.getPreferredAuthentications());
session.setConfig(properties);
// 设置超时时间
session.setTimeout(config.getTimeout());
// 建立连接
session.connect();
logger.info("成功连接到SFTP服务器");
// 打开SFTP通道
Channel channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
logger.info("SFTP通道已建立");
} catch (JSchException e) {
logger.error("连接SFTP服务器失败: {}", e.getMessage());
disconnect(); // 清理资源
throw e;
}
}
/**
* 断开SFTP连接
*/
public void disconnect() {
if (channelSftp != null && channelSftp.isConnected()) {
channelSftp.disconnect();
logger.info("SFTP通道已断开");
}
if (session != null && session.isConnected()) {
session.disconnect();
logger.info("SSH会话已断开");
}
}
/**
* 检查连接状态
*/
public boolean isConnected() {
return session != null && session.isConnected() &&
channelSftp != null && channelSftp.isConnected();
}
/**
* 重新连接
*/
public void reconnect() throws JSchException {
logger.info("正在重新连接SFTP服务器...");
disconnect();
connect();
}
/**
* 获取SFTP通道
*/
public ChannelSftp getChannelSftp() {
if (!isConnected()) {
throw new IllegalStateException("SFTP连接未建立");
}
return channelSftp;
}
/**
* 获取会话信息
*/
public String getSessionInfo() {
if (session != null) {
return String.format("连接到 %s@%s:%d, 会话状态: %s",
session.getUserName(),
session.getHost(),
session.getPort(),
session.isConnected() ? "已连接" : "未连接");
}
return "无会话信息";
}
}
3.2 连接池管理
创建SFTP连接池:
package com.example.sftp.pool;
import com.example.sftp.core.SftpConnectionManager;
import com.example.sftp.config.SftpConfig;
import com.jcraft.jsch.JSchException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* SFTP连接池
*/
public class SftpConnectionPool {
private static final Logger logger = LoggerFactory.getLogger(SftpConnectionPool.class);
private final BlockingQueue<SftpConnectionManager> connectionPool;
private final SftpConfig config;
private final int maxConnections;
private final AtomicInteger currentConnections = new AtomicInteger(0);
private volatile boolean isShutdown = false;
public SftpConnectionPool(SftpConfig config, int maxConnections) {
this.config = config;
this.maxConnections = maxConnections;
this.connectionPool = new ArrayBlockingQueue<>(maxConnections);
// 预创建一些连接
initializePool();
}
/**
* 初始化连接池
*/
private void initializePool() {
int initialConnections = Math.min(2, maxConnections);
for (int i = 0; i < initialConnections; i++) {
try {
SftpConnectionManager connection = createConnection();
connectionPool.offer(connection);
currentConnections.incrementAndGet();
logger.info("预创建SFTP连接 {}/{}", i + 1, initialConnections);
} catch (Exception e) {
logger.warn("预创建SFTP连接失败: {}", e.getMessage());
}
}
logger.info("SFTP连接池初始化完成,当前连接数: {}", currentConnections.get());
}
/**
* 创建新连接
*/
private SftpConnectionManager createConnection() throws JSchException {
SftpConnectionManager connection = new SftpConnectionManager(config);
connection.connect();
return connection;
}
/**
* 获取连接
*/
public SftpConnectionManager getConnection() throws JSchException, InterruptedException {
return getConnection(30, TimeUnit.SECONDS);
}
/**
* 获取连接(带超时)
*/
public SftpConnectionManager getConnection(long timeout, TimeUnit unit)
throws JSchException, InterruptedException {
if (isShutdown) {
throw new IllegalStateException("连接池已关闭");
}
// 先尝试从池中获取
SftpConnectionManager connection = connectionPool.poll();
if (connection != null) {
// 检查连接是否有效
if (connection.isConnected()) {
logger.debug("从连接池获取连接");
return connection;
} else {
// 连接失效,尝试重连
try {
connection.reconnect();
logger.debug("重新连接成功");
return connection;
} catch (JSchException e) {
logger.warn("重新连接失败,创建新连接");
currentConnections.decrementAndGet();
}
}
}
// 如果池中没有可用连接且未达到最大连接数,创建新连接
if (currentConnections.get() < maxConnections) {
try {
connection = createConnection();
currentConnections.incrementAndGet();
logger.debug("创建新的SFTP连接");
return connection;
} catch (JSchException e) {
logger.error("创建新连接失败: {}", e.getMessage());
throw e;
}
}
// 等待可用连接
connection = connectionPool.poll(timeout, unit);
if (connection == null) {
throw new RuntimeException("获取SFTP连接超时");
}
// 检查连接有效性
if (!connection.isConnected()) {
try {
connection.reconnect();
} catch (JSchException e) {
currentConnections.decrementAndGet();
throw e;
}
}
return connection;
}
/**
* 归还连接
*/
public void returnConnection(SftpConnectionManager connection) {
if (connection == null || isShutdown) {
return;
}
if (connection.isConnected()) {
boolean offered = connectionPool.offer(connection);
if (!offered) {
// 池已满,关闭连接
connection.disconnect();
currentConnections.decrementAndGet();
logger.debug("连接池已满,关闭多余连接");
} else {
logger.debug("连接已归还到连接池");
}
} else {
// 连接已断开
currentConnections.decrementAndGet();
logger.debug("归还无效连接,当前连接数: {}", currentConnections.get());
}
}
/**
* 关闭连接池
*/
public void shutdown() {
isShutdown = true;
logger.info("正在关闭SFTP连接池...");
// 关闭所有连接
SftpConnectionManager connection;
while ((connection = connectionPool.poll()) != null) {
connection.disconnect();
}
currentConnections.set(0);
logger.info("SFTP连接池已关闭");
}
/**
* 获取连接池状态
*/
public String getPoolStatus() {
return String.format("连接池状态 - 总连接数: %d, 可用连接数: %d, 最大连接数: %d",
currentConnections.get(),
connectionPool.size(),
maxConnections);
}
}
3.3 连接测试工具
创建连接测试类:
package com.example.sftp.test;
import com.example.sftp.config.SftpConfig;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SFTP连接测试工具
*/
public class SftpConnectionTester {
private static final Logger logger = LoggerFactory.getLogger(SftpConnectionTester.class);
/**
* 测试SFTP连接
*/
public static boolean testConnection(SftpConfig config) {
SftpConnectionManager connectionManager = null;
try {
logger.info("开始测试SFTP连接: {}", config.getHost());
connectionManager = new SftpConnectionManager(config);
connectionManager.connect();
// 测试基本操作
String currentDir = connectionManager.getChannelSftp().pwd();
logger.info("当前目录: {}", currentDir);
// 测试列目录
var files = connectionManager.getChannelSftp().ls(currentDir);
logger.info("目录下文件数量: {}", files.size());
logger.info("SFTP连接测试成功");
return true;
} catch (JSchException e) {
logger.error("SFTP连接失败: {}", e.getMessage());
return false;
} catch (SftpException e) {
logger.error("SFTP操作失败: {}", e.getMessage());
return false;
} finally {
if (connectionManager != null) {
connectionManager.disconnect();
}
}
}
/**
* 测试不同认证方式
*/
public static void testAuthenticationMethods() {
// 测试密码认证
SftpConfig passwordConfig = new SftpConfig("192.168.1.100", "user", "password");
logger.info("测试密码认证: {}", testConnection(passwordConfig));
// 测试公钥认证
SftpConfig keyConfig = new SftpConfig("192.168.1.100", "user",
"/path/to/private/key", "passphrase");
logger.info("测试公钥认证: {}", testConnection(keyConfig));
}
public static void main(String[] args) {
// 基础连接测试
SftpConfig config = new SftpConfig();
config.setHost("192.168.1.100");
config.setUsername("ftpuser");
config.setPassword("password123");
config.setTimeout(10000);
boolean success = testConnection(config);
System.out.println("连接测试结果: " + (success ? "成功" : "失败"));
}
}
4. 文件上传操作
4.1 基础文件上传
创建文件上传工具类:
package com.example.sftp.operations;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.io.FileUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* SFTP文件上传操作类
*/
public class SftpUploadOperations {
private static final Logger logger = LoggerFactory.getLogger(SftpUploadOperations.class);
private final SftpConnectionManager connectionManager;
public SftpUploadOperations(SftpConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
/**
* 上传单个文件
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return 是否上传成功
*/
public boolean uploadFile(String localFilePath, String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 检查本地文件是否存在
File localFile = new File(localFilePath);
if (!localFile.exists()) {
logger.error("本地文件不存在: {}", localFilePath);
return false;
}
// 确保远程目录存在
String remoteDir = getDirectoryFromPath(remoteFilePath);
createRemoteDirectories(remoteDir);
// 记录上传开始时间
long startTime = System.currentTimeMillis();
long fileSize = localFile.length();
logger.info("开始上传文件: {} -> {}, 文件大小: {} bytes",
localFilePath, remoteFilePath, fileSize);
// 执行上传
channelSftp.put(localFilePath, remoteFilePath, ChannelSftp.OVERWRITE);
long duration = System.currentTimeMillis() - startTime;
double speed = fileSize / (duration / 1000.0) / 1024; // KB/s
logger.info("文件上传成功,耗时: {}ms, 速度: {:.2f} KB/s", duration, speed);
return true;
} catch (SftpException e) {
logger.error("上传文件失败: {} -> {}, 错误: {}",
localFilePath, remoteFilePath, e.getMessage());
return false;
}
}
/**
* 上传文件流
* @param inputStream 输入流
* @param remoteFilePath 远程文件路径
* @return 是否上传成功
*/
public boolean uploadStream(InputStream inputStream, String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 确保远程目录存在
String remoteDir = getDirectoryFromPath(remoteFilePath);
createRemoteDirectories(remoteDir);
logger.info("开始上传流到文件: {}", remoteFilePath);
// 执行上传
channelSftp.put(inputStream, remoteFilePath, ChannelSftp.OVERWRITE);
logger.info("流上传成功: {}", remoteFilePath);
return true;
} catch (SftpException e) {
logger.error("上传流失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return false;
}
}
/**
* 带进度监控的文件上传
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @param progressMonitor 进度监控器
* @return 是否上传成功
*/
public boolean uploadFileWithProgress(String localFilePath, String remoteFilePath,
SftpProgressMonitor progressMonitor) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
File localFile = new File(localFilePath);
if (!localFile.exists()) {
logger.error("本地文件不存在: {}", localFilePath);
return false;
}
// 确保远程目录存在
String remoteDir = getDirectoryFromPath(remoteFilePath);
createRemoteDirectories(remoteDir);
logger.info("开始带进度监控的文件上传: {} -> {}", localFilePath, remoteFilePath);
// 创建进度监控器
if (progressMonitor == null) {
progressMonitor = new DefaultProgressMonitor();
}
progressMonitor.init(ChannelSftp.OVERWRITE, localFilePath, remoteFilePath, localFile.length());
// 执行上传
channelSftp.put(localFilePath, remoteFilePath, progressMonitor, ChannelSftp.OVERWRITE);
logger.info("带进度监控的文件上传完成: {}", remoteFilePath);
return true;
} catch (SftpException e) {
logger.error("带进度监控的文件上传失败: {} -> {}, 错误: {}",
localFilePath, remoteFilePath, e.getMessage());
return false;
}
}
/**
* 批量上传文件
* @param localDirectory 本地目录
* @param remoteDirectory 远程目录
* @param recursive 是否递归上传子目录
* @return 上传结果统计
*/
public UploadResult batchUpload(String localDirectory, String remoteDirectory, boolean recursive) {
UploadResult result = new UploadResult();
try {
File localDir = new File(localDirectory);
if (!localDir.exists() || !localDir.isDirectory()) {
logger.error("本地目录不存在或不是目录: {}", localDirectory);
result.addError("本地目录不存在: " + localDirectory);
return result;
}
// 确保远程目录存在
createRemoteDirectories(remoteDirectory);
logger.info("开始批量上传: {} -> {}, 递归: {}", localDirectory, remoteDirectory, recursive);
// 获取所有需要上传的文件
File[] files = localDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
// 上传文件
String remoteFilePath = remoteDirectory + "/" + file.getName();
if (uploadFile(file.getAbsolutePath(), remoteFilePath)) {
result.addSuccess(file.getAbsolutePath());
} else {
result.addError("上传失败: " + file.getAbsolutePath());
}
} else if (file.isDirectory() && recursive) {
// 递归上传子目录
String subRemoteDir = remoteDirectory + "/" + file.getName();
UploadResult subResult = batchUpload(file.getAbsolutePath(), subRemoteDir, recursive);
result.merge(subResult);
}
}
}
logger.info("批量上传完成,成功: {}, 失败: {}",
result.getSuccessCount(), result.getErrorCount());
} catch (Exception e) {
logger.error("批量上传异常: {}", e.getMessage());
result.addError("批量上传异常: " + e.getMessage());
}
return result;
}
/**
* 上传文件并验证
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return 是否上传并验证成功
*/
public boolean uploadAndVerify(String localFilePath, String remoteFilePath) {
try {
// 先上传文件
if (!uploadFile(localFilePath, remoteFilePath)) {
return false;
}
// 验证文件大小
File localFile = new File(localFilePath);
ChannelSftp channelSftp = connectionManager.getChannelSftp();
ChannelSftp.LsEntry remoteEntry = channelSftp.lstat(remoteFilePath);
long localSize = localFile.length();
long remoteSize = remoteEntry.getAttrs().getSize();
if (localSize == remoteSize) {
logger.info("文件上传验证成功: {}, 大小: {} bytes", remoteFilePath, remoteSize);
return true;
} else {
logger.error("文件大小验证失败: 本地 {} bytes, 远程 {} bytes", localSize, remoteSize);
return false;
}
} catch (SftpException e) {
logger.error("文件上传验证异常: {}", e.getMessage());
return false;
}
}
/**
* 从路径中提取目录部分
*/
private String getDirectoryFromPath(String filePath) {
int lastSlash = filePath.lastIndexOf('/');
if (lastSlash > 0) {
return filePath.substring(0, lastSlash);
}
return "/";
}
/**
* 创建远程目录(递归创建)
*/
private void createRemoteDirectories(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 分割路径
String[] dirs = remotePath.split("/");
String currentPath = "";
for (String dir : dirs) {
if (dir.isEmpty()) continue;
currentPath += "/" + dir;
try {
// 尝试进入目录
channelSftp.cd(currentPath);
} catch (SftpException e) {
// 目录不存在,创建它
try {
channelSftp.mkdir(currentPath);
logger.debug("创建远程目录: {}", currentPath);
} catch (SftpException createException) {
logger.warn("创建目录失败: {}, 错误: {}", currentPath, createException.getMessage());
}
}
}
} catch (Exception e) {
logger.warn("创建远程目录异常: {}", e.getMessage());
}
}
}
/**
* 上传结果统计类
*/
class UploadResult {
private int successCount = 0;
private int errorCount = 0;
private java.util.List<String> successFiles = new java.util.ArrayList<>();
private java.util.List<String> errorMessages = new java.util.ArrayList<>();
public void addSuccess(String filePath) {
successCount++;
successFiles.add(filePath);
}
public void addError(String message) {
errorCount++;
errorMessages.add(message);
}
public void merge(UploadResult other) {
successCount += other.successCount;
errorCount += other.errorCount;
successFiles.addAll(other.successFiles);
errorMessages.addAll(other.errorMessages);
}
// Getters
public int getSuccessCount() { return successCount; }
public int getErrorCount() { return errorCount; }
public java.util.List<String> getSuccessFiles() { return successFiles; }
public java.util.List<String> getErrorMessages() { return errorMessages; }
}
/**
* 默认进度监控器
*/
class DefaultProgressMonitor implements com.jcraft.jsch.SftpProgressMonitor {
private static final Logger logger = LoggerFactory.getLogger(DefaultProgressMonitor.class);
private long totalSize;
private long transferredSize;
private String operation;
private String src;
private String dest;
private long startTime;
@Override
public void init(int op, String src, String dest, long max) {
this.operation = op == ChannelSftp.OVERWRITE ? "上传" : "下载";
this.src = src;
this.dest = dest;
this.totalSize = max;
this.transferredSize = 0;
this.startTime = System.currentTimeMillis();
logger.info("开始{}: {} -> {}, 总大小: {} bytes", operation, src, dest, totalSize);
}
@Override
public boolean count(long count) {
transferredSize += count;
if (totalSize > 0) {
double progress = (double) transferredSize / totalSize * 100;
long elapsed = System.currentTimeMillis() - startTime;
double speed = transferredSize / (elapsed / 1000.0) / 1024; // KB/s
if (transferredSize % (1024 * 1024) < count) { // 每MB输出一次进度
logger.info("{}进度: {:.2f}%, 已传输: {} bytes, 速度: {:.2f} KB/s",
operation, progress, transferredSize, speed);
}
}
return true; // 返回false可以取消传输
}
@Override
public void end() {
long elapsed = System.currentTimeMillis() - startTime;
double speed = transferredSize / (elapsed / 1000.0) / 1024; // KB/s
logger.info("{}完成: {} -> {}, 传输大小: {} bytes, 耗时: {}ms, 平均速度: {:.2f} KB/s",
operation, src, dest, transferredSize, elapsed, speed);
}
}
4.2 高级上传功能
创建高级上传功能类:
package com.example.sftp.operations;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.concurrent.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* SFTP高级上传功能
*/
public class SftpAdvancedUpload {
private static final Logger logger = LoggerFactory.getLogger(SftpAdvancedUpload.class);
private final SftpConnectionManager connectionManager;
private final ExecutorService executorService;
public SftpAdvancedUpload(SftpConnectionManager connectionManager) {
this.connectionManager = connectionManager;
this.executorService = Executors.newFixedThreadPool(5);
}
/**
* 断点续传上传
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return 是否上传成功
*/
public boolean resumableUpload(String localFilePath, String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
File localFile = new File(localFilePath);
if (!localFile.exists()) {
logger.error("本地文件不存在: {}", localFilePath);
return false;
}
long localFileSize = localFile.length();
long remoteFileSize = 0;
// 检查远程文件是否已存在
try {
ChannelSftp.LsEntry remoteEntry = channelSftp.lstat(remoteFilePath);
remoteFileSize = remoteEntry.getAttrs().getSize();
logger.info("远程文件已存在,大小: {} bytes", remoteFileSize);
} catch (SftpException e) {
logger.info("远程文件不存在,开始全新上传");
}
// 如果远程文件已完整,无需上传
if (remoteFileSize == localFileSize) {
logger.info("文件已完整存在,无需上传");
return true;
}
// 如果远程文件更大,可能数据损坏,重新上传
if (remoteFileSize > localFileSize) {
logger.warn("远程文件大小异常,删除后重新上传");
channelSftp.rm(remoteFilePath);
remoteFileSize = 0;
}
// 断点续传
if (remoteFileSize > 0) {
logger.info("开始断点续传,从位置 {} 开始", remoteFileSize);
try (RandomAccessFile localRandomFile = new RandomAccessFile(localFile, "r")) {
localRandomFile.seek(remoteFileSize);
byte[] buffer = new byte[8192];
int bytesRead;
try (OutputStream remoteOutputStream = channelSftp.put(remoteFilePath,
ChannelSftp.APPEND)) {
while ((bytesRead = localRandomFile.read(buffer)) != -1) {
remoteOutputStream.write(buffer, 0, bytesRead);
}
}
}
} else {
// 全新上传
channelSftp.put(localFilePath, remoteFilePath);
}
logger.info("断点续传上传完成: {}", remoteFilePath);
return true;
} catch (Exception e) {
logger.error("断点续传上传失败: {}", e.getMessage());
return false;
}
}
/**
* 压缩上传
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径(自动添加.gz后缀)
* @return 是否上传成功
*/
public boolean compressAndUpload(String localFilePath, String remoteFilePath) {
try {
File localFile = new File(localFilePath);
if (!localFile.exists()) {
logger.error("本地文件不存在: {}", localFilePath);
return false;
}
String compressedRemotePath = remoteFilePath + ".gz";
logger.info("开始压缩上传: {} -> {}", localFilePath, compressedRemotePath);
ChannelSftp channelSftp = connectionManager.getChannelSftp();
try (FileInputStream fis = new FileInputStream(localFile);
OutputStream remoteOut = channelSftp.put(compressedRemotePath);
GZIPOutputStream gzipOut = new GZIPOutputStream(remoteOut)) {
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = fis.read(buffer)) != -1) {
gzipOut.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
}
logger.info("压缩上传完成,原始大小: {} bytes, 压缩后位置: {}",
totalBytes, compressedRemotePath);
return true;
}
} catch (Exception e) {
logger.error("压缩上传失败: {}", e.getMessage());
return false;
}
}
/**
* 异步上传
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return Future对象
*/
public Future<Boolean> asyncUpload(String localFilePath, String remoteFilePath) {
return executorService.submit(() -> {
try {
logger.info("开始异步上传: {} -> {}", localFilePath, remoteFilePath);
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.put(localFilePath, remoteFilePath);
logger.info("异步上传完成: {}", remoteFilePath);
return true;
} catch (SftpException e) {
logger.error("异步上传失败: {}", e.getMessage());
return false;
}
});
}
/**
* 批量异步上传
* @param uploadTasks 上传任务列表
* @return 上传结果Future列表
*/
public java.util.List<Future<Boolean>> batchAsyncUpload(
java.util.List<UploadTask> uploadTasks) {
java.util.List<Future<Boolean>> futures = new java.util.ArrayList<>();
for (UploadTask task : uploadTasks) {
Future<Boolean> future = asyncUpload(task.getLocalPath(), task.getRemotePath());
futures.add(future);
}
return futures;
}
/**
* 上传文件并计算MD5校验
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return 是否上传成功且校验通过
*/
public boolean uploadWithMD5Verification(String localFilePath, String remoteFilePath) {
try {
// 计算本地文件MD5
String localMD5 = calculateFileMD5(localFilePath);
logger.info("本地文件MD5: {}", localMD5);
// 上传文件
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.put(localFilePath, remoteFilePath);
// 上传MD5文件
String md5FilePath = remoteFilePath + ".md5";
try (InputStream md5Stream = new ByteArrayInputStream(localMD5.getBytes(StandardCharsets.UTF_8))) {
channelSftp.put(md5Stream, md5FilePath);
}
logger.info("文件和MD5校验文件上传完成: {}", remoteFilePath);
return true;
} catch (Exception e) {
logger.error("MD5验证上传失败: {}", e.getMessage());
return false;
}
}
/**
* 计算文件MD5
*/
private String calculateFileMD5(String filePath) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
md5.update(buffer, 0, bytesRead);
}
}
byte[] digest = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* 关闭资源
*/
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
}
}
/**
* 上传任务类
*/
class UploadTask {
private String localPath;
private String remotePath;
public UploadTask(String localPath, String remotePath) {
this.localPath = localPath;
this.remotePath = remotePath;
}
public String getLocalPath() { return localPath; }
public String getRemotePath() { return remotePath; }
}
5. 文件下载操作
5.1 基础文件下载
创建文件下载工具类:
package com.example.sftp.operations;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Vector;
/**
* SFTP文件下载操作类
*/
public class SftpDownloadOperations {
private static final Logger logger = LoggerFactory.getLogger(SftpDownloadOperations.class);
private final SftpConnectionManager connectionManager;
public SftpDownloadOperations(SftpConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
/**
* 下载单个文件
* @param remoteFilePath 远程文件路径
* @param localFilePath 本地文件路径
* @return 是否下载成功
*/
public boolean downloadFile(String remoteFilePath, String localFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 检查远程文件是否存在
try {
channelSftp.lstat(remoteFilePath);
} catch (SftpException e) {
logger.error("远程文件不存在: {}", remoteFilePath);
return false;
}
// 确保本地目录存在
File localFile = new File(localFilePath);
File parentDir = localFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
logger.debug("创建本地目录: {}", parentDir.getAbsolutePath());
}
// 记录下载开始时间
long startTime = System.currentTimeMillis();
logger.info("开始下载文件: {} -> {}", remoteFilePath, localFilePath);
// 执行下载
channelSftp.get(remoteFilePath, localFilePath);
long duration = System.currentTimeMillis() - startTime;
long fileSize = localFile.length();
double speed = fileSize / (duration / 1000.0) / 1024; // KB/s
logger.info("文件下载成功,大小: {} bytes, 耗时: {}ms, 速度: {:.2f} KB/s",
fileSize, duration, speed);
return true;
} catch (SftpException e) {
logger.error("下载文件失败: {} -> {}, 错误: {}",
remoteFilePath, localFilePath, e.getMessage());
return false;
}
}
/**
* 下载文件到流
* @param remoteFilePath 远程文件路径
* @param outputStream 输出流
* @return 是否下载成功
*/
public boolean downloadToStream(String remoteFilePath, OutputStream outputStream) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
logger.info("开始下载文件到流: {}", remoteFilePath);
// 执行下载
channelSftp.get(remoteFilePath, outputStream);
logger.info("文件下载到流成功: {}", remoteFilePath);
return true;
} catch (SftpException e) {
logger.error("下载文件到流失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return false;
}
}
/**
* 带进度监控的文件下载
* @param remoteFilePath 远程文件路径
* @param localFilePath 本地文件路径
* @param progressMonitor 进度监控器
* @return 是否下载成功
*/
public boolean downloadFileWithProgress(String remoteFilePath, String localFilePath,
com.jcraft.jsch.SftpProgressMonitor progressMonitor) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 获取远程文件信息
ChannelSftp.LsEntry remoteEntry = channelSftp.lstat(remoteFilePath);
long fileSize = remoteEntry.getAttrs().getSize();
// 确保本地目录存在
File localFile = new File(localFilePath);
File parentDir = localFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
logger.info("开始带进度监控的文件下载: {} -> {}, 文件大小: {} bytes",
remoteFilePath, localFilePath, fileSize);
// 创建进度监控器
if (progressMonitor == null) {
progressMonitor = new DefaultProgressMonitor();
}
progressMonitor.init(ChannelSftp.OVERWRITE, remoteFilePath, localFilePath, fileSize);
// 执行下载
channelSftp.get(remoteFilePath, localFilePath, progressMonitor);
logger.info("带进度监控的文件下载完成: {}", localFilePath);
return true;
} catch (SftpException e) {
logger.error("带进度监控的文件下载失败: {} -> {}, 错误: {}",
remoteFilePath, localFilePath, e.getMessage());
return false;
}
}
/**
* 批量下载文件
* @param remoteDirectory 远程目录
* @param localDirectory 本地目录
* @param recursive 是否递归下载子目录
* @return 下载结果统计
*/
@SuppressWarnings("unchecked")
public DownloadResult batchDownload(String remoteDirectory, String localDirectory, boolean recursive) {
DownloadResult result = new DownloadResult();
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 检查远程目录是否存在
try {
channelSftp.lstat(remoteDirectory);
} catch (SftpException e) {
logger.error("远程目录不存在: {}", remoteDirectory);
result.addError("远程目录不存在: " + remoteDirectory);
return result;
}
// 确保本地目录存在
File localDir = new File(localDirectory);
if (!localDir.exists()) {
localDir.mkdirs();
logger.debug("创建本地目录: {}", localDirectory);
}
logger.info("开始批量下载: {} -> {}, 递归: {}", remoteDirectory, localDirectory, recursive);
// 列出远程目录内容
Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remoteDirectory);
for (ChannelSftp.LsEntry entry : files) {
String fileName = entry.getFilename();
// 跳过 . 和 .. 目录
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
String remoteFilePath = remoteDirectory + "/" + fileName;
String localFilePath = localDirectory + File.separator + fileName;
if (entry.getAttrs().isDir()) {
if (recursive) {
// 递归下载子目录
DownloadResult subResult = batchDownload(remoteFilePath, localFilePath, recursive);
result.merge(subResult);
}
} else {
// 下载文件
if (downloadFile(remoteFilePath, localFilePath)) {
result.addSuccess(localFilePath);
} else {
result.addError("下载失败: " + remoteFilePath);
}
}
}
logger.info("批量下载完成,成功: {}, 失败: {}",
result.getSuccessCount(), result.getErrorCount());
} catch (SftpException e) {
logger.error("批量下载异常: {}", e.getMessage());
result.addError("批量下载异常: " + e.getMessage());
}
return result;
}
/**
* 下载文件并验证完整性
* @param remoteFilePath 远程文件路径
* @param localFilePath 本地文件路径
* @return 是否下载并验证成功
*/
public boolean downloadAndVerify(String remoteFilePath, String localFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 获取远程文件大小
ChannelSftp.LsEntry remoteEntry = channelSftp.lstat(remoteFilePath);
long remoteSize = remoteEntry.getAttrs().getSize();
// 下载文件
if (!downloadFile(remoteFilePath, localFilePath)) {
return false;
}
// 验证文件大小
File localFile = new File(localFilePath);
long localSize = localFile.length();
if (localSize == remoteSize) {
logger.info("文件下载验证成功: {}, 大小: {} bytes", localFilePath, localSize);
return true;
} else {
logger.error("文件大小验证失败: 远程 {} bytes, 本地 {} bytes", remoteSize, localSize);
return false;
}
} catch (SftpException e) {
logger.error("文件下载验证异常: {}", e.getMessage());
return false;
}
}
/**
* 条件下载:只下载满足条件的文件
* @param remoteDirectory 远程目录
* @param localDirectory 本地目录
* @param filter 文件过滤器
* @return 下载结果统计
*/
@SuppressWarnings("unchecked")
public DownloadResult conditionalDownload(String remoteDirectory, String localDirectory,
FileFilter filter) {
DownloadResult result = new DownloadResult();
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 确保本地目录存在
File localDir = new File(localDirectory);
if (!localDir.exists()) {
localDir.mkdirs();
}
logger.info("开始条件下载: {} -> {}", remoteDirectory, localDirectory);
// 列出远程目录内容
Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remoteDirectory);
for (ChannelSftp.LsEntry entry : files) {
String fileName = entry.getFilename();
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
if (!entry.getAttrs().isDir() && filter.accept(entry)) {
String remoteFilePath = remoteDirectory + "/" + fileName;
String localFilePath = localDirectory + File.separator + fileName;
if (downloadFile(remoteFilePath, localFilePath)) {
result.addSuccess(localFilePath);
} else {
result.addError("下载失败: " + remoteFilePath);
}
}
}
logger.info("条件下载完成,成功: {}, 失败: {}",
result.getSuccessCount(), result.getErrorCount());
} catch (SftpException e) {
logger.error("条件下载异常: {}", e.getMessage());
result.addError("条件下载异常: " + e.getMessage());
}
return result;
}
}
/**
* 下载结果统计类
*/
class DownloadResult {
private int successCount = 0;
private int errorCount = 0;
private java.util.List<String> successFiles = new java.util.ArrayList<>();
private java.util.List<String> errorMessages = new java.util.ArrayList<>();
public void addSuccess(String filePath) {
successCount++;
successFiles.add(filePath);
}
public void addError(String message) {
errorCount++;
errorMessages.add(message);
}
public void merge(DownloadResult other) {
successCount += other.successCount;
errorCount += other.errorCount;
successFiles.addAll(other.successFiles);
errorMessages.addAll(other.errorMessages);
}
// Getters
public int getSuccessCount() { return successCount; }
public int getErrorCount() { return errorCount; }
public java.util.List<String> getSuccessFiles() { return successFiles; }
public java.util.List<String> getErrorMessages() { return errorMessages; }
}
/**
* 文件过滤器接口
*/
interface FileFilter {
boolean accept(ChannelSftp.LsEntry entry);
}
6. 目录操作和文件管理
6.1 目录操作类
package com.example.sftp.operations;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Vector;
import java.util.List;
import java.util.ArrayList;
/**
* SFTP目录操作类
*/
public class SftpDirectoryOperations {
private static final Logger logger = LoggerFactory.getLogger(SftpDirectoryOperations.class);
private final SftpConnectionManager connectionManager;
public SftpDirectoryOperations(SftpConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
/**
* 列出目录内容
*/
@SuppressWarnings("unchecked")
public List<FileInfo> listDirectory(String remotePath) {
List<FileInfo> fileList = new ArrayList<>();
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remotePath);
for (ChannelSftp.LsEntry entry : files) {
if (!".".equals(entry.getFilename()) && !"..".equals(entry.getFilename())) {
FileInfo fileInfo = new FileInfo(entry);
fileList.add(fileInfo);
}
}
logger.info("列出目录 {} 中的 {} 个项目", remotePath, fileList.size());
} catch (SftpException e) {
logger.error("列出目录失败: {}, 错误: {}", remotePath, e.getMessage());
}
return fileList;
}
/**
* 创建目录
*/
public boolean createDirectory(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.mkdir(remotePath);
logger.info("创建目录成功: {}", remotePath);
return true;
} catch (SftpException e) {
logger.error("创建目录失败: {}, 错误: {}", remotePath, e.getMessage());
return false;
}
}
/**
* 递归创建目录
*/
public boolean createDirectoriesRecursively(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
String[] dirs = remotePath.split("/");
String currentPath = "";
for (String dir : dirs) {
if (dir.isEmpty()) continue;
currentPath += "/" + dir;
try {
channelSftp.cd(currentPath);
} catch (SftpException e) {
try {
channelSftp.mkdir(currentPath);
logger.debug("创建目录: {}", currentPath);
} catch (SftpException createException) {
logger.error("创建目录失败: {}", currentPath);
return false;
}
}
}
logger.info("递归创建目录成功: {}", remotePath);
return true;
} catch (Exception e) {
logger.error("递归创建目录失败: {}", e.getMessage());
return false;
}
}
/**
* 删除空目录
*/
public boolean removeDirectory(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.rmdir(remotePath);
logger.info("删除目录成功: {}", remotePath);
return true;
} catch (SftpException e) {
logger.error("删除目录失败: {}, 错误: {}", remotePath, e.getMessage());
return false;
}
}
/**
* 递归删除目录及其内容
*/
@SuppressWarnings("unchecked")
public boolean removeDirectoryRecursively(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
// 首先删除目录中的所有文件和子目录
Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remotePath);
for (ChannelSftp.LsEntry entry : files) {
String fileName = entry.getFilename();
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
String filePath = remotePath + "/" + fileName;
if (entry.getAttrs().isDir()) {
// 递归删除子目录
removeDirectoryRecursively(filePath);
} else {
// 删除文件
channelSftp.rm(filePath);
logger.debug("删除文件: {}", filePath);
}
}
// 删除空目录
channelSftp.rmdir(remotePath);
logger.info("递归删除目录成功: {}", remotePath);
return true;
} catch (SftpException e) {
logger.error("递归删除目录失败: {}, 错误: {}", remotePath, e.getMessage());
return false;
}
}
/**
* 检查路径是否存在
*/
public boolean exists(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.lstat(remotePath);
return true;
} catch (SftpException e) {
return false;
}
}
/**
* 检查是否为目录
*/
public boolean isDirectory(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
ChannelSftp.LsEntry entry = channelSftp.lstat(remotePath);
return entry.getAttrs().isDir();
} catch (SftpException e) {
return false;
}
}
/**
* 获取当前工作目录
*/
public String getCurrentDirectory() {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
return channelSftp.pwd();
} catch (SftpException e) {
logger.error("获取当前目录失败: {}", e.getMessage());
return null;
}
}
/**
* 改变当前工作目录
*/
public boolean changeDirectory(String remotePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.cd(remotePath);
logger.info("切换到目录: {}", remotePath);
return true;
} catch (SftpException e) {
logger.error("切换目录失败: {}, 错误: {}", remotePath, e.getMessage());
return false;
}
}
}
/**
* 文件信息类
*/
class FileInfo {
private String name;
private long size;
private boolean isDirectory;
private long lastModified;
private String permissions;
private String owner;
private String group;
public FileInfo(ChannelSftp.LsEntry entry) {
this.name = entry.getFilename();
this.size = entry.getAttrs().getSize();
this.isDirectory = entry.getAttrs().isDir();
this.lastModified = entry.getAttrs().getMTime() * 1000L; // 转换为毫秒
this.permissions = entry.getAttrs().getPermissionsString();
this.owner = Integer.toString(entry.getAttrs().getUId());
this.group = Integer.toString(entry.getAttrs().getGId());
}
// Getters
public String getName() { return name; }
public long getSize() { return size; }
public boolean isDirectory() { return isDirectory; }
public long getLastModified() { return lastModified; }
public String getPermissions() { return permissions; }
public String getOwner() { return owner; }
public String getGroup() { return group; }
@Override
public String toString() {
return String.format("%s %s %s %8d %s %s",
permissions, owner, group, size,
new java.util.Date(lastModified), name);
}
}
6.2 文件管理操作
package com.example.sftp.operations;
import com.example.sftp.core.SftpConnectionManager;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SFTP文件管理操作类
*/
public class SftpFileOperations {
private static final Logger logger = LoggerFactory.getLogger(SftpFileOperations.class);
private final SftpConnectionManager connectionManager;
public SftpFileOperations(SftpConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
/**
* 删除文件
*/
public boolean deleteFile(String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.rm(remoteFilePath);
logger.info("删除文件成功: {}", remoteFilePath);
return true;
} catch (SftpException e) {
logger.error("删除文件失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return false;
}
}
/**
* 重命名/移动文件
*/
public boolean renameFile(String oldPath, String newPath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.rename(oldPath, newPath);
logger.info("重命名文件成功: {} -> {}", oldPath, newPath);
return true;
} catch (SftpException e) {
logger.error("重命名文件失败: {} -> {}, 错误: {}", oldPath, newPath, e.getMessage());
return false;
}
}
/**
* 获取文件大小
*/
public long getFileSize(String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
ChannelSftp.LsEntry entry = channelSftp.lstat(remoteFilePath);
return entry.getAttrs().getSize();
} catch (SftpException e) {
logger.error("获取文件大小失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return -1;
}
}
/**
* 获取文件最后修改时间
*/
public long getLastModified(String remoteFilePath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
ChannelSftp.LsEntry entry = channelSftp.lstat(remoteFilePath);
return entry.getAttrs().getMTime() * 1000L; // 转换为毫秒
} catch (SftpException e) {
logger.error("获取文件修改时间失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return -1;
}
}
/**
* 设置文件权限
*/
public boolean setPermissions(String remoteFilePath, int permissions) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.chmod(permissions, remoteFilePath);
logger.info("设置文件权限成功: {}, 权限: {}", remoteFilePath, Integer.toOctalString(permissions));
return true;
} catch (SftpException e) {
logger.error("设置文件权限失败: {}, 错误: {}", remoteFilePath, e.getMessage());
return false;
}
}
/**
* 创建符号链接
*/
public boolean createSymbolicLink(String targetPath, String linkPath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
channelSftp.symlink(targetPath, linkPath);
logger.info("创建符号链接成功: {} -> {}", linkPath, targetPath);
return true;
} catch (SftpException e) {
logger.error("创建符号链接失败: {} -> {}, 错误: {}", linkPath, targetPath, e.getMessage());
return false;
}
}
/**
* 读取符号链接目标
*/
public String readSymbolicLink(String linkPath) {
try {
ChannelSftp channelSftp = connectionManager.getChannelSftp();
return channelSftp.readlink(linkPath);
} catch (SftpException e) {
logger.error("读取符号链接失败: {}, 错误: {}", linkPath, e.getMessage());
return null;
}
}
}
7. 异常处理和错误管理
7.1 SFTP异常处理类
package com.example.sftp.exception;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SFTP异常处理工具类
*/
public class SftpExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(SftpExceptionHandler.class);
/**
* 处理JSCH异常
*/
public static SftpOperationResult handleJSchException(JSchException e) {
String message = e.getMessage();
SftpErrorCode errorCode;
if (message.contains("Auth fail")) {
errorCode = SftpErrorCode.AUTHENTICATION_FAILED;
} else if (message.contains("Connection refused")) {
errorCode = SftpErrorCode.CONNECTION_REFUSED;
} else if (message.contains("timeout")) {
errorCode = SftpErrorCode.CONNECTION_TIMEOUT;
} else if (message.contains("UnknownHostException")) {
errorCode = SftpErrorCode.UNKNOWN_HOST;
} else {
errorCode = SftpErrorCode.CONNECTION_ERROR;
}
logger.error("JSCH异常: {}", message);
return new SftpOperationResult(false, errorCode, message);
}
/**
* 处理SFTP异常
*/
public static SftpOperationResult handleSftpException(SftpException e) {
int id = e.id;
String message = e.getMessage();
SftpErrorCode errorCode;
switch (id) {
case ChannelSftp.SSH_FX_NO_SUCH_FILE:
errorCode = SftpErrorCode.FILE_NOT_FOUND;
break;
case ChannelSftp.SSH_FX_PERMISSION_DENIED:
errorCode = SftpErrorCode.PERMISSION_DENIED;
break;
case ChannelSftp.SSH_FX_FAILURE:
errorCode = SftpErrorCode.OPERATION_FAILED;
break;
case ChannelSftp.SSH_FX_BAD_MESSAGE:
errorCode = SftpErrorCode.BAD_MESSAGE;
break;
case ChannelSftp.SSH_FX_NO_CONNECTION:
errorCode = SftpErrorCode.NO_CONNECTION;
break;
default:
errorCode = SftpErrorCode.UNKNOWN_ERROR;
}
logger.error("SFTP异常: ID={}, 消息={}", id, message);
return new SftpOperationResult(false, errorCode, message);
}
/**
* 处理一般异常
*/
public static SftpOperationResult handleGeneralException(Exception e) {
String message = e.getMessage();
logger.error("一般异常: {}", message, e);
return new SftpOperationResult(false, SftpErrorCode.GENERAL_ERROR, message);
}
}
/**
* SFTP错误代码枚举
*/
enum SftpErrorCode {
SUCCESS(0, "操作成功"),
AUTHENTICATION_FAILED(1001, "认证失败"),
CONNECTION_REFUSED(1002, "连接被拒绝"),
CONNECTION_TIMEOUT(1003, "连接超时"),
UNKNOWN_HOST(1004, "未知主机"),
CONNECTION_ERROR(1005, "连接错误"),
FILE_NOT_FOUND(2001, "文件不存在"),
PERMISSION_DENIED(2002, "权限被拒绝"),
OPERATION_FAILED(2003, "操作失败"),
BAD_MESSAGE(2004, "消息格式错误"),
NO_CONNECTION(2005, "无连接"),
GENERAL_ERROR(9999, "一般错误"),
UNKNOWN_ERROR(9998, "未知错误");
private final int code;
private final String description;
SftpErrorCode(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription() { return description; }
}
/**
* SFTP操作结果类
*/
class SftpOperationResult {
private boolean success;
private SftpErrorCode errorCode;
private String message;
private Object data;
public SftpOperationResult(boolean success, SftpErrorCode errorCode, String message) {
this.success = success;
this.errorCode = errorCode;
this.message = message;
}
public static SftpOperationResult success() {
return new SftpOperationResult(true, SftpErrorCode.SUCCESS, "操作成功");
}
public static SftpOperationResult success(Object data) {
SftpOperationResult result = success();
result.setData(data);
return result;
}
// Getters and Setters
public boolean isSuccess() { return success; }
public SftpErrorCode getErrorCode() { return errorCode; }
public String getMessage() { return message; }
public Object getData() { return data; }
public void setData(Object data) { this.data = data; }
}
8. 完整应用示例
8.1 SFTP客户端工具类
package com.example.sftp.client;
import com.example.sftp.config.SftpConfig;
import com.example.sftp.core.SftpConnectionManager;
import com.example.sftp.operations.*;
import com.example.sftp.exception.SftpExceptionHandler;
import com.example.sftp.exception.SftpOperationResult;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SFTP客户端工具类
* 提供所有SFTP操作的统一接口
*/
public class SftpClient implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(SftpClient.class);
private final SftpConnectionManager connectionManager;
private final SftpUploadOperations uploadOps;
private final SftpDownloadOperations downloadOps;
private final SftpDirectoryOperations directoryOps;
private final SftpFileOperations fileOps;
private final SftpAdvancedUpload advancedUpload;
public SftpClient(SftpConfig config) throws JSchException {
this.connectionManager = new SftpConnectionManager(config);
this.connectionManager.connect();
this.uploadOps = new SftpUploadOperations(connectionManager);
this.downloadOps = new SftpDownloadOperations(connectionManager);
this.directoryOps = new SftpDirectoryOperations(connectionManager);
this.fileOps = new SftpFileOperations(connectionManager);
this.advancedUpload = new SftpAdvancedUpload(connectionManager);
logger.info("SFTP客户端初始化完成");
}
// 文件上传操作
public SftpOperationResult uploadFile(String localPath, String remotePath) {
try {
boolean success = uploadOps.uploadFile(localPath, remotePath);
return success ? SftpOperationResult.success() :
new SftpOperationResult(false, SftpErrorCode.OPERATION_FAILED, "上传失败");
} catch (Exception e) {
return SftpExceptionHandler.handleGeneralException(e);
}
}
// 文件下载操作
public SftpOperationResult downloadFile(String remotePath, String localPath) {
try {
boolean success = downloadOps.downloadFile(remotePath, localPath);
return success ? SftpOperationResult.success() :
new SftpOperationResult(false, SftpErrorCode.OPERATION_FAILED, "下载失败");
} catch (Exception e) {
return SftpExceptionHandler.handleGeneralException(e);
}
}
// 列出目录
public SftpOperationResult listDirectory(String remotePath) {
try {
var files = directoryOps.listDirectory(remotePath);
return SftpOperationResult.success(files);
} catch (Exception e) {
return SftpExceptionHandler.handleGeneralException(e);
}
}
// 创建目录
public SftpOperationResult createDirectory(String remotePath) {
try {
boolean success = directoryOps.createDirectory(remotePath);
return success ? SftpOperationResult.success() :
new SftpOperationResult(false, SftpErrorCode.OPERATION_FAILED, "创建目录失败");
} catch (Exception e) {
return SftpExceptionHandler.handleGeneralException(e);
}
}
// 删除文件
public SftpOperationResult deleteFile(String remotePath) {
try {
boolean success = fileOps.deleteFile(remotePath);
return success ? SftpOperationResult.success() :
new SftpOperationResult(false, SftpErrorCode.OPERATION_FAILED, "删除文件失败");
} catch (Exception e) {
return SftpExceptionHandler.handleGeneralException(e);
}
}
// 检查连接状态
public boolean isConnected() {
return connectionManager.isConnected();
}
// 重新连接
public SftpOperationResult reconnect() {
try {
connectionManager.reconnect();
return SftpOperationResult.success();
} catch (JSchException e) {
return SftpExceptionHandler.handleJSchException(e);
}
}
@Override
public void close() {
if (advancedUpload != null) {
advancedUpload.shutdown();
}
if (connectionManager != null) {
connectionManager.disconnect();
}
logger.info("SFTP客户端已关闭");
}
}
8.2 使用示例
package com.example.sftp.demo;
import com.example.sftp.client.SftpClient;
import com.example.sftp.config.SftpConfig;
import com.example.sftp.exception.SftpOperationResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SFTP使用示例
*/
public class SftpDemo {
private static final Logger logger = LoggerFactory.getLogger(SftpDemo.class);
public static void main(String[] args) {
// 配置SFTP连接
SftpConfig config = new SftpConfig();
config.setHost("192.168.1.100");
config.setPort(22);
config.setUsername("ftpuser");
config.setPassword("password123");
config.setTimeout(30000);
// 使用try-with-resources确保资源正确关闭
try (SftpClient client = new SftpClient(config)) {
// 1. 测试连接
logger.info("SFTP连接状态: {}", client.isConnected());
// 2. 列出根目录
SftpOperationResult result = client.listDirectory("/");
if (result.isSuccess()) {
logger.info("根目录文件列表获取成功");
} else {
logger.error("列出目录失败: {}", result.getMessage());
}
// 3. 创建测试目录
result = client.createDirectory("/test");
if (result.isSuccess()) {
logger.info("创建目录成功");
}
// 4. 上传文件
result = client.uploadFile("local.txt", "/test/remote.txt");
if (result.isSuccess()) {
logger.info("文件上传成功");
} else {
logger.error("文件上传失败: {}", result.getMessage());
}
// 5. 下载文件
result = client.downloadFile("/test/remote.txt", "downloaded.txt");
if (result.isSuccess()) {
logger.info("文件下载成功");
} else {
logger.error("文件下载失败: {}", result.getMessage());
}
// 6. 删除文件
result = client.deleteFile("/test/remote.txt");
if (result.isSuccess()) {
logger.info("文件删除成功");
}
} catch (Exception e) {
logger.error("SFTP操作异常: {}", e.getMessage(), e);
}
}
}
9. 最佳实践和注意事项
9.1 性能优化
- 使用连接池:避免频繁创建和销毁连接
- 批量操作:尽量使用批量上传/下载减少网络开销
- 压缩传输:对大文件使用压缩可以减少传输时间
- 并发控制:合理控制并发连接数,避免服务器过载
9.2 安全考虑
- 使用公钥认证:比密码认证更安全
- 严格主机密钥检查:生产环境建议启用
- 权限最小化:只给SFTP用户必要的权限
- 定期更换密钥:定期更新SSH密钥
9.3 错误处理
- 完善的异常处理:捕获并处理所有可能的异常
- 重试机制:对网络相关错误实现重试
- 日志记录:详细记录操作日志便于排查问题
- 优雅关闭:确保资源正确释放
9.4 监控和维护
- 连接状态监控:定期检查连接状态
- 传输进度监控:大文件传输需要进度显示
- 性能监控:监控传输速度和成功率
- 定期测试:定期测试SFTP连接的可用性