封装FTPSClient连接ftps服务器
主要是connect方法中一些set方法操作是必要的
支持try with resource
@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服务器日志,重定向到Slf4jnew 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 bufferftpsClient.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());}}@Overridepublic 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;}@Overridepublic 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);}}}@Overridepublic void flush() {}@Overridepublic void close() {}}
}