当前位置: 首页 > news >正文

JSCH使用SFTP详细教程

文章目录

    • 1. JSCH和SFTP基础概念
      • 1.1 什么是JSCH?
      • 1.2 SFTP协议特点
      • 1.3 JSCH的优势
      • 1.4 常用场景
    • 2. 环境配置和依赖管理
      • 2.1 Maven依赖配置
      • 2.2 Gradle依赖配置
      • 2.3 基础配置类
      • 2.4 配置文件示例
    • 3. SFTP连接管理
      • 3.1 基础连接类
      • 3.2 连接池管理
      • 3.3 连接测试工具
    • 4. 文件上传操作
      • 4.1 基础文件上传
      • 4.2 高级上传功能
    • 5. 文件下载操作
      • 5.1 基础文件下载
    • 6. 目录操作和文件管理
      • 6.1 目录操作类
      • 6.2 文件管理操作
    • 7. 异常处理和错误管理
      • 7.1 SFTP异常处理类
    • 8. 完整应用示例
      • 8.1 SFTP客户端工具类
      • 8.2 使用示例
    • 9. 最佳实践和注意事项
      • 9.1 性能优化
      • 9.2 安全考虑
      • 9.3 错误处理
      • 9.4 监控和维护

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 Setterspublic 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; }@Overridepublic 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/slogger.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);}// Getterspublic 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;@Overridepublic 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);}@Overridepublic 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/sif (transferredSize % (1024 * 1024) < count) { // 每MB输出一次进度logger.info("{}进度: {:.2f}%, 已传输: {} bytes, 速度: {:.2f} KB/s", operation, progress, transferredSize, speed);}}return true; // 返回false可以取消传输}@Overridepublic void end() {long elapsed = System.currentTimeMillis() - startTime;double speed = transferredSize / (elapsed / 1000.0) / 1024; // KB/slogger.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 {// 计算本地文件MD5String 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/slogger.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);}// Getterspublic 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());}// Getterspublic 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; }@Overridepublic 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 Setterspublic 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);}}@Overridepublic 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 性能优化

  1. 使用连接池:避免频繁创建和销毁连接
  2. 批量操作:尽量使用批量上传/下载减少网络开销
  3. 压缩传输:对大文件使用压缩可以减少传输时间
  4. 并发控制:合理控制并发连接数,避免服务器过载

9.2 安全考虑

  1. 使用公钥认证:比密码认证更安全
  2. 严格主机密钥检查:生产环境建议启用
  3. 权限最小化:只给SFTP用户必要的权限
  4. 定期更换密钥:定期更新SSH密钥

9.3 错误处理

  1. 完善的异常处理:捕获并处理所有可能的异常
  2. 重试机制:对网络相关错误实现重试
  3. 日志记录:详细记录操作日志便于排查问题
  4. 优雅关闭:确保资源正确释放

9.4 监控和维护

  1. 连接状态监控:定期检查连接状态
  2. 传输进度监控:大文件传输需要进度显示
  3. 性能监控:监控传输速度和成功率
  4. 定期测试:定期测试SFTP连接的可用性

相关文章:

  • 【小红书】API接口,获取笔记列表
  • H.264编码
  • 深拷贝与浅拷贝的区别?如何手写实现一个深拷贝?
  • 基于51单片机和8X8点阵屏、独立按键的填充消除类小游戏
  • Linux操作系统-命令基础
  • 【leetcode-两数之和】
  • el-select 实现分页加载,切换也数滚回到顶部,自定义高度
  • MAU算法流程理解
  • 剑指offer14_二进制中1的个数
  • Nginx 的配置文件
  • VBA模拟进度条
  • 谈C语言变量的作用域,加深对全局变量和局部变量的理解
  • 【判断酒酒花数】2022-3-31
  • 对数正态分布LogNormal
  • DDD 到底是什么? 它试图解决什么核心问题?
  • Day-15【选择与循环】选择结构-if语句
  • 海盗64位GameServer的使用体验
  • 访谈 | 吴恩达全景解读 AI Agents 发展现状:多智能体、工具生态、评估体系、语音栈、Vibe Coding 及创业建议一文尽览
  • Python基于PCA、PCA-kernel、LDA的鸢尾花数据降维项目实战
  • 【深度学习-Day 22】框架入门:告别数据瓶颈 - 掌握PyTorch Dataset、DataLoader与TensorFlow tf.data实战
  • 盐城网站建设制作工作室/信阳搜索引擎优化
  • 网站快照倒退可怕吗/最新一周新闻
  • 招商加盟网/重庆放心seo整站优化
  • cdr可以做网站页面吗/如何搭建网站
  • 虚拟服务器建网站/公司网站的作用
  • 网页qq空间登录/seol英文啥意思