封装FTPSClient连接ftps服务器

主要是connect方法中一些set方法操作是必要的

支持try with resource

java 复制代码
@Slf4j
public class FtpsClient implements AutoCloseable {
    private final String host;
    private final int port;
    private final String username;
    private final String password;
    private final int connectTimeout;

    private FTPSClient ftpsClient;

    public FtpsClient(String host, int port, String username, String password) {
        this(host, port, username, password, 10000);
    }

    public FtpsClient(String host, int port, String username, String password, int connectTimeout) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.connectTimeout = connectTimeout;
    }

    /**
     * 连接到FTPS服务器
     */
    public boolean connect() {
        if (isConnected()) {
            return true;
        }
        try {
            log.debug("[FtpsClient] Initializing FTPS client");
            ftpsClient = new FTPSClient();
            ftpsClient.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
            ftpsClient.setEndpointCheckingEnabled(false);
            ftpsClient.setConnectTimeout(connectTimeout);
            ftpsClient.setDataTimeout(Duration.ofSeconds(30));

            FTPClientConfig config = new FTPClientConfig();
            ftpsClient.configure(config);
            ftpsClient.addProtocolCommandListener(new PrintCommandListener(
                    // 打印ftp服务器日志,重定向到Slf4j
                    new PrintWriter(new LogWriter(log))
            ));
            ftpsClient.setControlEncoding("UTF-8");

            log.debug("[FtpsClient] Connecting to {}:{}", host, port);
            ftpsClient.connect(host, port);

            int reply = ftpsClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                throw new IOException("FTPS server refused connection: " + reply);
            }

            if (!ftpsClient.login(username, password)) {
                throw new IOException("Login failed for user: " + username);
            }

            ftpsClient.enterLocalPassiveMode();
            ftpsClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
            // 数据通道加密
            ftpsClient.execPROT("P");
            ftpsClient.setFileType(FTP.BINARY_FILE_TYPE);
            // 1MB buffer
            ftpsClient.setBufferSize(1024 * 1024);

            log.info("[FtpsClient] Connected to FTPS server: {}:{}", host, port);
            return true;
        } catch (Exception e) {
            log.error("[FtpsClient] Connection failed to {}:{} - {}", host, port, e.getMessage());
            disconnectSilently();
            throw new RuntimeException("FTPS connection failed: " + e.getMessage(), e);
        }
    }

    /**
     * 返回底层FTPS客户端实例
     */
    public FTPSClient operation() {
        connectedCheck();
        return ftpsClient;
    }

    /**
     * 上传文件到FTPS服务器
     *
     * @param localFilePath 本地文件路径
     * @param remoteDirectory 远程目录路径
     * @return 是否上传成功
     */
    public boolean uploadFile(String localFilePath, String remoteDirectory) {
        connectedCheck();
        File localFile = new File(localFilePath);
        try (InputStream is = Files.newInputStream(Paths.get(localFilePath))) {
            createRemoteDirectory(remoteDirectory);
            String remotePath = remoteDirectory + "/" + localFile.getName();

            log.debug("[FtpsClient] Uploading file: {}", localFilePath);
            if (!ftpsClient.storeFile(remotePath, is)) {
                throw new IOException("Upload failed. Server reply: " + ftpsClient.getReplyString());
            }

            verifyUploadSuccess(localFile, remotePath);
            log.info("[FtpsClient] File uploaded successfully: {}", remotePath);
            return true;
        } catch (Exception e) {
            log.error("[FtpsClient] Upload failed: {}", e.getMessage());
            throw new RuntimeException("FTPS upload failed: " + e.getMessage(), e);
        }
    }

    /**
     * 从FTPS服务器下载文件
     *
     * @param remoteFilePath 远程文件路径
     * @param localFilePath 本地存储文件路径
     * @return 是否下载成功
     */
    public boolean retrieveFile(String remoteFilePath, String localFilePath) {
        connectedCheck();
        try (OutputStream os = Files.newOutputStream(Paths.get(localFilePath))) {
            log.debug("[FtpsClient] Downloading file: {}", remoteFilePath);
            if (!ftpsClient.retrieveFile(remoteFilePath, os)) {
                throw new IOException("Download failed. Server reply: " + ftpsClient.getReplyString());
            }

            File localFile = new File(localFilePath);
            if (!localFile.exists() || localFile.length() == 0) {
                throw new IOException("Local file not created or empty");
            }

            log.info("[FtpsClient] File downloaded successfully: {}", localFilePath);
            return true;
        } catch (Exception e) {
            log.error("[FtpsClient] Download failed: {}", e.getMessage());
            throw new RuntimeException("FTPS download failed: " + e.getMessage(), e);
        }
    }

    /**
     * 递归创建远程目录
     */
    public boolean createRemoteDirectory(String path) throws IOException {
        connectedCheck();
        if (path == null || path.isEmpty()) {
            return true;
        }

        String[] dirs = path.split("/");
        StringBuilder currentPath = new StringBuilder();

        for (String dir : dirs) {
            if (dir.isEmpty()) {
                continue;
            }
            currentPath.append("/").append(dir);
            String dirPath = currentPath.toString();

            if (!ftpsClient.changeWorkingDirectory(dirPath)) {
                if (ftpsClient.makeDirectory(dirPath)) {
                    log.debug("[FtpsClient] Created directory: {}", dirPath);
                } else {
                    throw new IOException("Failed to create directory: " + dirPath);
                }
                ftpsClient.changeWorkingDirectory(dirPath);
            }
        }
        return true;
    }

    /**
     * 验证上传文件完整性
     *
     * @param localFile 本地文件
     * @param remotePath 远程路径
     * @throws IOException 抛出异常
     */
    private void verifyUploadSuccess(File localFile, String remotePath) throws IOException {
        long localSize = localFile.length();
        long remoteSize = ftpsClient.listFiles(remotePath)[0].getSize();

        if (localSize != remoteSize) {
            throw new IOException(String.format(
                    "Size mismatch! Local: %d bytes, Remote: %d bytes",
                    localSize, remoteSize
            ));
        }
        log.debug("[FtpsClient] File verification passed: {} bytes", remoteSize);
    }

    /**
     * 连接状态检查
     */
    private void connectedCheck() {
        if (!isConnected()) {
            throw new IllegalStateException("FTPS connection not established");
        }
    }

    /**
     * 判断连接状态
     * @return boolean
     */
    public boolean isConnected() {
        return ftpsClient != null && ftpsClient.isConnected();
    }

    /**
     * 静默断开连接
     */
    private void disconnectSilently() {
        try {
            close();
        } catch (Exception e) {
            log.debug("[FtpsClient] Error during disconnect: {}", e.getMessage());
        }
    }

    @Override
    public void close() {
        if (ftpsClient != null) {
            try {
                if (ftpsClient.isConnected()) {
                    ftpsClient.logout();
                }
            } catch (IOException e) {
                log.debug("[FtpsClient] Logout failed: {}", e.getMessage());
            } finally {
                try {
                    ftpsClient.disconnect();
                } catch (IOException e) {
                    log.debug("[FtpsClient] Disconnect failed: {}", e.getMessage());
                }
            }
        }
    }

    /**
     * 日志重定向辅助类
     * @author changxin.man
     * @date 2025/08/21
     */
    private static class LogWriter extends Writer {
        private final org.slf4j.Logger logger;

        LogWriter(org.slf4j.Logger logger) {
            this.logger = logger;
        }

        @Override
        public void write(char[] cbuf, int off, int len) {
            if (logger.isDebugEnabled()) {
                String message = new String(cbuf, off, len).trim();
                if (!message.isEmpty()) {
                    logger.debug("[FTPS] {}", message);
                }
            }
        }

        @Override
        public void flush() {}

        @Override
        public void close() {}
    }
}
相关推荐
happyprince37 分钟前
2026年04月07日热门github项目
github
奔跑草-39 分钟前
【AI日报】每日AI最新消息2026-04-07
人工智能·大模型·github·开源软件
CoovallyAIHub1 小时前
Sensors 2026 | 从无人机拍摄到跑道缺陷地图,机场巡检全流程自动化——Zadar机场全跑道验证
数据库·架构·github
内心的一片海1 小时前
服务器内存异常占用
运维·服务器
CoovallyAIHub1 小时前
15K Star中文首发!$5部署一个会自我进化的私人Agent——NousResearch开源Hermes Agent
git·架构·github
liulilittle2 小时前
C++ 无锁编程:单停多发送场景高性能方案
服务器·开发语言·c++·高性能·无锁·原子
m0_738120722 小时前
渗透基础知识ctfshow——Web应用安全与防护(第一章)
服务器·前端·javascript·安全·web安全·网络安全
亚空间仓鼠2 小时前
OpenEuler系统常用服务(四)
linux·运维·服务器·网络
无限进步_2 小时前
【C++】巧用静态变量与构造函数:一种非常规的求和实现
开发语言·c++·git·算法·leetcode·github·visual studio
降临-max2 小时前
Git 协同开发与冲突解决
笔记·git