JSCH使用SFTP详细教程

发布于:2025-06-04 ⋅ 阅读:(21) ⋅ 点赞:(0)

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 性能优化

  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连接的可用性

网站公告

今日签到

点亮在社区的每一天
去签到