From 004d4f407d70dbad43c762cf1abaa1a494c8d813 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 20 Dec 2023 14:22:09 +0800 Subject: [PATCH] fix code and add Ftp --- .../dromara/hutool/extra/ftp/AbstractFtp.java | 161 +--- .../dromara/hutool/extra/ftp/CommonsFtp.java | 750 +++++++++++++++++ .../org/dromara/hutool/extra/ftp/Ftp.java | 789 ++---------------- .../dromara/hutool/extra/ftp/FtpConfig.java | 159 ++-- .../org/dromara/hutool/extra/ftp/FtpMode.java | 9 +- .../dromara/hutool/extra/ssh/Connector.java | 113 +-- .../extra/ssh/engine/jsch/JschSftp.java | 56 +- .../extra/ssh/engine/sshj/SshjSftp.java | 45 +- .../org/dromara/hutool/extra/ftp/FtpTest.java | 36 +- .../dromara/hutool/extra/ssh/JschTest.java | 8 +- .../dromara/hutool/extra/ssh/SftpTest.java | 2 +- 11 files changed, 1071 insertions(+), 1057 deletions(-) create mode 100644 hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/CommonsFtp.java diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/AbstractFtp.java index a69b8d5ee..b142066e9 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/AbstractFtp.java @@ -13,15 +13,12 @@ package org.dromara.hutool.extra.ftp; import org.dromara.hutool.core.collection.CollUtil; -import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.file.FileNameUtil; -import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.text.CharUtil; -import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.core.text.StrUtil; -import java.io.Closeable; import java.io.File; -import java.nio.charset.Charset; import java.util.List; /** @@ -30,15 +27,15 @@ import java.util.List; * @author looly * @since 4.1.14 */ -public abstract class AbstractFtp implements Closeable { - - /** - * 默认编码 - */ - public static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; +public abstract class AbstractFtp implements Ftp { protected FtpConfig ftpConfig; + @Override + public FtpConfig getConfig() { + return this.ftpConfig; + } + /** * 构造 * @@ -49,74 +46,7 @@ public abstract class AbstractFtp implements Closeable { this.ftpConfig = config; } - /** - * 如果连接超时的话,重新进行连接 - * - * @return this - * @since 4.5.2 - */ - public abstract AbstractFtp reconnectIfTimeout(); - - /** - * 打开指定目录,具体逻辑取决于实现,例如在FTP中,进入失败返回{@code false}, SFTP中则抛出异常 - * - * @param directory directory - * @return 是否打开目录 - */ - public abstract boolean cd(String directory); - - /** - * 打开上级目录 - * - * @return 是否打开目录 - * @since 4.0.5 - */ - public boolean toParent() { - return cd(".."); - } - - /** - * 远程当前目录(工作目录) - * - * @return 远程当前目录 - */ - public abstract String pwd(); - - /** - * 判断给定路径是否为目录 - * - * @param dir 被判断的路径 - * @return 是否为目录 - * @since 5.7.5 - */ - public boolean isDir(final String dir) { - final String workDir = pwd(); - try { - return cd(dir); - } finally { - cd(workDir); - } - } - - /** - * 在当前远程目录(工作目录)下创建新的目录 - * - * @param dir 目录名 - * @return 是否创建成功 - */ - public abstract boolean mkdir(String dir); - - /** - * 文件或目录是否存在
- * - * - * @param path 目录 - * @return 是否存在 - */ + @Override public boolean exist(final String path) { if (StrUtil.isBlank(path)) { return false; @@ -130,14 +60,14 @@ public abstract class AbstractFtp implements Closeable { } final String fileName = FileNameUtil.getName(path); - if (".".equals(fileName) || "..".equals(fileName)) { + if (StrUtil.DOT.equals(fileName) || StrUtil.DOUBLE_DOT.equals(fileName)) { return false; } // 文件验证 - final String dir = StrUtil.defaultIfEmpty(StrUtil.removeSuffix(path, fileName), "."); + final String dir = StrUtil.defaultIfEmpty(StrUtil.removeSuffix(path, fileName), StrUtil.DOT); // issue#I7CSQ9 检查父目录为目录且是否存在 - if(!isDir(dir)){ + if (!isDir(dir)) { return false; } final List names; @@ -149,35 +79,7 @@ public abstract class AbstractFtp implements Closeable { return containsIgnoreCase(names, fileName); } - /** - * 遍历某个目录下所有文件和目录,不会递归遍历 - * - * @param path 需要遍历的目录 - * @return 文件和目录列表 - */ - public abstract List ls(String path); - - /** - * 删除指定目录下的指定文件 - * - * @param path 目录路径 - * @return 是否存在 - */ - public abstract boolean delFile(String path); - - /** - * 删除文件夹及其文件夹下的所有文件 - * - * @param dirPath 文件夹路径 - * @return boolean 是否删除成功 - */ - public abstract boolean delDir(String dirPath); - - /** - * 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录 - * - * @param dir 文件夹路径,绝对路径 - */ + @Override public void mkDirs(final String dir) { final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+"); @@ -207,24 +109,6 @@ public abstract class AbstractFtp implements Closeable { cd(now); } - /** - * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与file文件名相同。 - * 覆盖模式 - * - * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 需要上传的文件 - * @return 是否成功 - */ - public abstract boolean uploadFile(String destPath, File file); - - /** - * 下载文件 - * - * @param path 文件路径 - * @param outFile 输出文件或目录 - */ - public abstract void download(String path, File outFile); - /** * 下载文件-避免未完成的文件
* 来自:https://gitee.com/dromara/hutool/pulls/407
@@ -260,15 +144,6 @@ public abstract class AbstractFtp implements Closeable { } } - /** - * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 - * - * @param sourcePath ftp服务器目录 - * @param destDir 本地目录 - * @since 5.3.5 - */ - public abstract void recursiveDownloadFolder(String sourcePath, File destDir); - // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start /** @@ -279,18 +154,10 @@ public abstract class AbstractFtp implements Closeable { * @return 是否包含 */ private static boolean containsIgnoreCase(final List names, final String nameToFind) { - if (CollUtil.isEmpty(names)) { - return false; - } if (StrUtil.isEmpty(nameToFind)) { return false; } - for (final String name : names) { - if (nameToFind.equalsIgnoreCase(name)) { - return true; - } - } - return false; + return CollUtil.contains(names, nameToFind::equalsIgnoreCase); } // ---------------------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/CommonsFtp.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/CommonsFtp.java new file mode 100644 index 000000000..48ddc20ea --- /dev/null +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/CommonsFtp.java @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.extra.ftp; + +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.collection.CollUtil; +import org.dromara.hutool.core.collection.ListUtil; +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.io.file.FileNameUtil; +import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.extra.ssh.Connector; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Apache Commons FTP客户端封装
+ * 此客户端基于Apache-Commons-Net + *

+ * 常见搭建ftp的工具有: + *

    + *
  • filezila server ;根目录一般都是空
  • + *
  • linux vsftpd ; 使用的 系统用户的目录,这里往往都是不是根目录,如:/home/hutool/ftp
  • + *
+ * + * @author Looly + */ +public class CommonsFtp extends AbstractFtp { + + /** + * 默认端口 + */ + public static final int DEFAULT_PORT = 21; + + // region ----- of + /** + * 构造CommonsFtp,匿名登录 + * + * @param host 域名或IP + * @return CommonsFtp + */ + public static CommonsFtp of(final String host) { + return of(host, DEFAULT_PORT); + } + + /** + * 构造,匿名登录 + * + * @param host 域名或IP + * @param port 端口 + * @return CommonsFtp + */ + public static CommonsFtp of(final String host, final int port) { + return of(host, port, "anonymous", StrUtil.EMPTY); + } + + /** + * 构造 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 + * @param password 密码 + * @return CommonsFtp + */ + public static CommonsFtp of(final String host, final int port, final String user, final String password) { + return of(Connector.of(host, port, user, password), DEFAULT_CHARSET); + } + + /** + * 构造 + * + * @param connector 连接信息,包括host、port、user、password等信息 + * @param charset 编码 + * @return CommonsFtp + */ + public static CommonsFtp of(final Connector connector, final Charset charset) { + return of(connector, charset, null, null); + } + + /** + * 构造 + * + * @param connector 连接信息,包括host、port、user、password等信息 + * @param charset 编码 + * @param serverLanguageCode 服务器语言 例如:zh + * @param systemKey 服务器标识 例如:org.apache.commons.net.ftp.FTPClientConfig.SYST_NT + * @return CommonsFtp + */ + public static CommonsFtp of(final Connector connector, final Charset charset, final String serverLanguageCode, final String systemKey) { + return of(connector, charset, serverLanguageCode, systemKey, null); + } + + /** + * 构造 + * + * @param connector 连接信息,包括host、port、user、password等信息 + * @param charset 编码 + * @param serverLanguageCode 服务器语言 + * @param systemKey 系统关键字 + * @param mode 模式 + * @return CommonsFtp + */ + public static CommonsFtp of(final Connector connector, final Charset charset, final String serverLanguageCode, final String systemKey, final FtpMode mode) { + return new CommonsFtp(new FtpConfig(connector, charset, serverLanguageCode, systemKey), mode); + } + //endregion + + private FTPClient client; + private FtpMode mode; + /** + * 执行完操作是否返回当前目录 + */ + private boolean backToPwd; + + // region ----- 构造 + + /** + * 构造 + * + * @param config FTP配置 + * @param mode 模式 + */ + public CommonsFtp(final FtpConfig config, final FtpMode mode) { + super(config); + this.mode = mode; + this.init(); + } + + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public CommonsFtp(final FTPClient client) { + super(FtpConfig.of()); + this.client = client; + } + // endregion + + /** + * 初始化连接 + * + * @return this + */ + public CommonsFtp init() { + return this.init(this.ftpConfig, this.mode); + } + + /** + * 初始化连接 + * + * @param config FTP配置 + * @param mode 模式 + * @return this + */ + public CommonsFtp init(final FtpConfig config, final FtpMode mode) { + final FTPClient client = new FTPClient(); + // issue#I3O81Y@Gitee + client.setRemoteVerificationEnabled(false); + + final Charset charset = config.getCharset(); + if (null != charset) { + client.setControlEncoding(charset.toString()); + } + client.setConnectTimeout((int) config.getConnector().getTimeout()); + final String systemKey = config.getSystemKey(); + if (StrUtil.isNotBlank(systemKey)) { + final FTPClientConfig conf = new FTPClientConfig(systemKey); + final String serverLanguageCode = config.getServerLanguageCode(); + if (StrUtil.isNotBlank(serverLanguageCode)) { + conf.setServerLanguageCode(config.getServerLanguageCode()); + } + client.configure(conf); + } + + // connect + final Connector connector = config.getConnector(); + try { + // 连接ftp服务器 + client.connect(connector.getHost(), connector.getPort()); + client.setSoTimeout((int) config.getSoTimeout()); + // 登录ftp服务器 + client.login(connector.getUser(), connector.getPassword()); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + final int replyCode = client.getReplyCode(); // 是否成功登录服务器 + if (!FTPReply.isPositiveCompletion(replyCode)) { + try { + client.disconnect(); + } catch (final IOException e) { + // ignore + } + throw new FtpException("Login failed for user [{}], reply code is: [{}]", connector.getUser(), replyCode); + } + this.client = client; + if (mode != null) { + //noinspection resource + setMode(mode); + } + return this; + } + + /** + * 设置FTP连接模式,可选主动和被动模式 + * + * @param mode 模式枚举 + * @return this + * @since 4.1.19 + */ + public CommonsFtp setMode(final FtpMode mode) { + this.mode = mode; + switch (mode) { + case Active: + this.client.enterLocalActiveMode(); + break; + case Passive: + this.client.enterLocalPassiveMode(); + break; + } + return this; + } + + /** + * 设置执行完操作是否返回当前目录 + * + * @param backToPwd 执行完操作是否返回当前目录 + * @return this + * @since 4.6.0 + */ + public CommonsFtp setBackToPwd(final boolean backToPwd) { + this.backToPwd = backToPwd; + return this; + } + + /** + * 是否执行完操作返回当前目录 + * + * @return 执行完操作是否返回当前目录 + * @since 5.7.17 + */ + public boolean isBackToPwd() { + return this.backToPwd; + } + + /** + * 如果连接超时的话,重新进行连接 经测试,当连接超时时,client.isConnected()仍然返回ture,无法判断是否连接超时 因此,通过发送pwd命令的方式,检查连接是否超时 + * + * @return this + */ + @Override + public CommonsFtp reconnectIfTimeout() { + String pwd = null; + try { + pwd = pwd(); + } catch (final IORuntimeException fex) { + // ignore + } + + if (pwd == null) { + return this.init(); + } + return this; + } + + /** + * 改变目录 + * + * @param directory 目录 + * @return 是否成功 + */ + @Override + synchronized public boolean cd(final String directory) { + if (StrUtil.isBlank(directory)) { + // 当前目录 + return true; + } + + try { + return client.changeWorkingDirectory(directory); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 远程当前目录 + * + * @return 远程当前目录 + * @since 4.1.14 + */ + @Override + public String pwd() { + try { + return client.printWorkingDirectory(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + @Override + public List ls(final String path) { + return ArrayUtil.map(lsFiles(path), FTPFile::getName); + } + + /** + * 遍历某个目录下所有文件和目录,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 + * + * @param path 目录 + * @param predicate 过滤器,null表示不过滤,默认去掉"."和".."两种目录 + * @return 文件名或目录名列表 + */ + public List ls(final String path, final Predicate predicate) { + return CollUtil.map(lsFiles(path, predicate), FTPFile::getName); + } + + /** + * 遍历某个目录下所有文件和目录,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 + * + * @param path 目录 + * @param predicate 过滤器,null表示不过滤,默认去掉"."和".."两种目录 + * @return 文件或目录列表 + * @since 5.3.5 + */ + public List lsFiles(final String path, final Predicate predicate) { + final FTPFile[] ftpFiles = lsFiles(path); + if (ArrayUtil.isEmpty(ftpFiles)) { + return ListUtil.empty(); + } + + final List result = new ArrayList<>(ftpFiles.length - 2 <= 0 ? ftpFiles.length : ftpFiles.length - 2); + String fileName; + for (final FTPFile ftpFile : ftpFiles) { + fileName = ftpFile.getName(); + if (!StrUtil.equals(".", fileName) && !StrUtil.equals("..", fileName)) { + if (null == predicate || predicate.test(ftpFile)) { + result.add(ftpFile); + } + } + } + return result; + } + + /** + * 遍历某个目录下所有文件和目录,不会递归遍历 + * + * @param path 目录,如果目录不存在,抛出异常 + * @return 文件或目录列表 + * @throws FtpException 路径不存在 + * @throws IORuntimeException IO异常 + */ + public FTPFile[] lsFiles(final String path) throws FtpException, IORuntimeException { + String pwd = null; + if (StrUtil.isNotBlank(path)) { + pwd = pwd(); + if (!cd(path)) { + throw new FtpException("Change dir to [{}] error, maybe path not exist!", path); + } + } + + FTPFile[] ftpFiles; + try { + ftpFiles = this.client.listFiles(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + // 回到原目录 + cd(pwd); + } + + return ftpFiles; + } + + @Override + public boolean mkdir(final String dir) throws IORuntimeException { + try { + return this.client.makeDirectory(dir); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获取服务端目录状态。 + * + * @param path 路径 + * @return 状态int,服务端不同,返回不同 + * @throws IORuntimeException IO异常 + * @since 5.4.3 + */ + public int stat(final String path) throws IORuntimeException { + try { + return this.client.stat(path); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 判断ftp服务器目录内是否还有子元素(目录或文件) + * + * @param path 文件路径 + * @return 是否存在 + * @throws IORuntimeException IO异常 + */ + public boolean existFile(final String path) throws IORuntimeException { + final FTPFile[] ftpFileArr; + try { + ftpFileArr = client.listFiles(path); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + return ArrayUtil.isNotEmpty(ftpFileArr); + } + + @Override + public boolean delFile(final String path) throws IORuntimeException { + final String pwd = pwd(); + final String fileName = FileNameUtil.getName(path); + final String dir = StrUtil.removeSuffix(path, fileName); + if (!cd(dir)) { + throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path); + } + + boolean isSuccess; + try { + isSuccess = client.deleteFile(fileName); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + // 回到原目录 + cd(pwd); + } + return isSuccess; + } + + @Override + public boolean delDir(final String dirPath) throws IORuntimeException { + final FTPFile[] dirs; + try { + dirs = client.listFiles(dirPath); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + String name; + String childPath; + for (final FTPFile ftpFile : dirs) { + name = ftpFile.getName(); + childPath = StrUtil.format("{}/{}", dirPath, name); + if (ftpFile.isDirectory()) { + // 上级和本级目录除外 + if (!".".equals(name) && !"..".equals(name)) { + delDir(childPath); + } + } else { + delFile(childPath); + } + } + + // 删除空目录 + try { + return this.client.removeDirectory(dirPath); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 上传文件到指定目录,可选: + * + *
+	 * 1. remotePath为null或""上传到当前路径
+	 * 2. remotePath为相对路径则相对于当前路径的子路径
+	 * 3. remotePath为绝对路径则上传到此路径
+	 * 
+ * + * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param file 文件 + * @return 是否上传成功 + */ + @Override + public boolean uploadFile(final String remotePath, final File file) { + Assert.notNull(file, "file to upload is null !"); + if (!FileUtil.isFile(file)) { + throw new FtpException("[{}] is not a file!", file); + } + return uploadFile(remotePath, file.getName(), file); + } + + /** + * 上传文件到指定目录,可选: + * + *
+	 * 1. remotePath为null或""上传到当前路径
+	 * 2. remotePath为相对路径则相对于当前路径的子路径
+	 * 3. remotePath为绝对路径则上传到此路径
+	 * 
+ * + * @param file 文件 + * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param fileName 自定义在服务端保存的文件名 + * @return 是否上传成功 + * @throws IORuntimeException IO异常 + */ + public boolean uploadFile(final String remotePath, final String fileName, final File file) throws IORuntimeException { + try (final InputStream in = FileUtil.getInputStream(file)) { + return uploadFile(remotePath, fileName, in); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 上传文件到指定目录,可选: + * + *
+	 * 1. remotePath为null或""上传到当前路径
+	 * 2. remotePath为相对路径则相对于当前路径的子路径
+	 * 3. remotePath为绝对路径则上传到此路径
+	 * 
+ * + * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param fileName 文件名 + * @param fileStream 文件流 + * @return 是否上传成功 + * @throws IORuntimeException IO异常 + */ + public boolean uploadFile(final String remotePath, final String fileName, final InputStream fileStream) throws IORuntimeException { + try { + client.setFileType(FTPClient.BINARY_FILE_TYPE); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + String pwd = null; + if (this.backToPwd) { + pwd = pwd(); + } + + if (StrUtil.isNotBlank(remotePath)) { + mkDirs(remotePath); + if (!cd(remotePath)) { + throw new FtpException("Change dir to [{}] error, maybe dir not exist!", remotePath); + } + } + + try { + return client.storeFile(fileName, fileStream); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + if (this.backToPwd) { + cd(pwd); + } + } + } + + /** + * 递归上传文件(支持目录)
+ * 上传时,如果uploadFile为目录,只复制目录下所有目录和文件到目标路径下,并不会复制目录本身
+ * 上传时,自动创建父级目录 + * + * @param remotePath 目录路径 + * @param uploadFile 上传文件或目录 + */ + public void upload(final String remotePath, final File uploadFile) { + if (!FileUtil.isDirectory(uploadFile)) { + this.uploadFile(remotePath, uploadFile); + return; + } + + final File[] files = uploadFile.listFiles(); + if (ArrayUtil.isEmpty(files)) { + return; + } + + final List dirs = new ArrayList<>(files.length); + //第一次只处理文件,防止目录在前面导致先处理子目录,而引发文件所在目录不正确 + for (final File f : files) { + if (f.isDirectory()) { + dirs.add(f); + } else { + this.uploadFile(remotePath, f); + } + } + //第二次只处理目录 + for (final File f : dirs) { + final String dir = FileUtil.normalize(remotePath + "/" + f.getName()); + upload(dir, f); + } + } + + /** + * 下载文件 + * + * @param path 文件路径,包含文件名 + * @param outFile 输出文件或目录,当为目录时,使用服务端的文件名 + */ + @Override + public void download(final String path, final File outFile) { + final String fileName = FileNameUtil.getName(path); + final String dir = StrUtil.removeSuffix(path, fileName); + download(dir, fileName, outFile); + } + + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * + * @param sourcePath ftp服务器目录 + * @param destDir 本地目录 + */ + @Override + public void recursiveDownloadFolder(final String sourcePath, final File destDir) { + String fileName; + String srcFile; + File destFile; + for (final FTPFile ftpFile : lsFiles(sourcePath, null)) { + fileName = ftpFile.getName(); + srcFile = StrUtil.format("{}/{}", sourcePath, fileName); + destFile = FileUtil.file(destDir, fileName); + + if (!ftpFile.isDirectory()) { + // 本地不存在文件或者ftp上文件有修改则下载 + if (!FileUtil.exists(destFile) + || (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) { + download(srcFile, destFile); + } + } else { + // 服务端依旧是目录,继续递归 + FileUtil.mkdir(destFile); + recursiveDownloadFolder(srcFile, destFile); + } + } + } + + /** + * 下载文件 + * + * @param path 文件所在路径(远程目录),不包含文件名 + * @param fileName 文件名 + * @param outFile 输出文件或目录,当为目录时使用服务端文件名 + * @throws IORuntimeException IO异常 + */ + public void download(final String path, final String fileName, File outFile) throws IORuntimeException { + if (outFile.isDirectory()) { + outFile = new File(outFile, fileName); + } + if (!outFile.exists()) { + FileUtil.touch(outFile); + } + try (final OutputStream out = FileUtil.getOutputStream(outFile)) { + download(path, fileName, out); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 下载文件到输出流 + * + * @param path 文件路径 + * @param fileName 文件名 + * @param out 输出位置 + */ + public void download(final String path, final String fileName, final OutputStream out) { + download(path, fileName, out, null); + } + + /** + * 下载文件到输出流 + * + * @param path 服务端的文件路径 + * @param fileName 服务端的文件名 + * @param out 输出流,下载的文件写出到这个流中 + * @param fileNameCharset 文件名编码,通过此编码转换文件名编码为ISO8859-1 + * @throws IORuntimeException IO异常 + * @since 5.5.7 + */ + public void download(final String path, String fileName, final OutputStream out, final Charset fileNameCharset) throws IORuntimeException { + String pwd = null; + if (this.backToPwd) { + pwd = pwd(); + } + + if (!cd(path)) { + throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path); + } + + if (null != fileNameCharset) { + fileName = new String(fileName.getBytes(fileNameCharset), StandardCharsets.ISO_8859_1); + } + try { + client.setFileType(FTPClient.BINARY_FILE_TYPE); + client.retrieveFile(fileName, out); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + if (backToPwd) { + cd(pwd); + } + } + } + + /** + * 获取FTPClient客户端对象 + * + * @return {@link FTPClient} + */ + public FTPClient getClient() { + return this.client; + } + + @Override + public void close() throws IOException { + if (null != this.client) { + this.client.logout(); + if (this.client.isConnected()) { + this.client.disconnect(); + } + this.client = null; + } + } +} diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/Ftp.java index 3bc8a72e5..514c4820d 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/Ftp.java @@ -1,773 +1,156 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - package org.dromara.hutool.extra.ftp; -import org.dromara.hutool.core.collection.CollUtil; -import org.dromara.hutool.core.collection.ListUtil; -import org.dromara.hutool.core.io.file.FileUtil; -import org.dromara.hutool.core.io.IORuntimeException; -import org.dromara.hutool.core.io.file.FileNameUtil; -import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.util.CharsetUtil; -import org.apache.commons.net.ftp.FTPClient; -import org.apache.commons.net.ftp.FTPClientConfig; -import org.apache.commons.net.ftp.FTPFile; -import org.apache.commons.net.ftp.FTPReply; +import org.rythmengine.utils.F; +import java.io.Closeable; import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; -import java.util.function.Predicate; /** - * FTP客户端封装
- * 此客户端基于Apache-Commons-Net - *

- * 常见搭建ftp的工具有 - * 1、filezila server ;根目录一般都是空 - * 2、linux vsftpd ; 使用的 系统用户的目录,这里往往都是不是根目录,如:/home/ftpuser/ftp + * FTP的统一规范接口 * - * @author looly - * @since 4.1.8 + * @author Looly */ -public class Ftp extends AbstractFtp { +public interface Ftp extends Closeable { /** - * 默认端口 + * 默认编码 */ - public static final int DEFAULT_PORT = 21; - - private FTPClient client; - private FtpMode mode; - /** - * 执行完操作是否返回当前目录 - */ - private boolean backToPwd; + Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; /** - * 构造,匿名登录 + * 获取FTP配置 * - * @param host 域名或IP + * @return FTP配置 */ - public Ftp(final String host) { - this(host, DEFAULT_PORT); - } + FtpConfig getConfig(); /** - * 构造,匿名登录 - * - * @param host 域名或IP - * @param port 端口 - */ - public Ftp(final String host, final int port) { - this(host, port, "anonymous", ""); - } - - /** - * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - */ - public Ftp(final String host, final int port, final String user, final String password) { - this(host, port, user, password, CharsetUtil.UTF_8); - } - - /** - * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param charset 编码 - */ - public Ftp(final String host, final int port, final String user, final String password, final Charset charset) { - this(host, port, user, password, charset, null, null); - } - - /** - * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param charset 编码 - * @param serverLanguageCode 服务器语言 例如:zh - * @param systemKey 服务器标识 例如:org.apache.commons.net.ftp.FTPClientConfig.SYST_NT - */ - public Ftp(final String host, final int port, final String user, final String password, final Charset charset, final String serverLanguageCode, final String systemKey) { - this(host, port, user, password, charset, serverLanguageCode, systemKey, null); - } - - /** - * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param charset 编码 - * @param serverLanguageCode 服务器语言 - * @param systemKey 系统关键字 - * @param mode 模式 - */ - public Ftp(final String host, final int port, final String user, final String password, final Charset charset, final String serverLanguageCode, final String systemKey, final FtpMode mode) { - this(new FtpConfig(host, port, user, password, charset, serverLanguageCode, systemKey), mode); - } - - /** - * 构造 - * - * @param config FTP配置 - * @param mode 模式 - */ - public Ftp(final FtpConfig config, final FtpMode mode) { - super(config); - this.mode = mode; - this.init(); - } - - /** - * 构造 - * - * @param client 自定义实例化好的{@link FTPClient} - * @since 5.7.22 - */ - public Ftp(final FTPClient client) { - super(FtpConfig.of()); - this.client = client; - } - - /** - * 初始化连接 + * 如果连接超时的话,重新进行连接 * * @return this */ - public Ftp init() { - return this.init(this.ftpConfig, this.mode); - } + Ftp reconnectIfTimeout(); /** - * 初始化连接 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @return this - */ - public Ftp init(final String host, final int port, final String user, final String password) { - return this.init(host, port, user, password, null); - } - - /** - * 初始化连接 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param mode 模式 - * @return this - */ - public Ftp init(final String host, final int port, final String user, final String password, final FtpMode mode) { - return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset(), null, null), mode); - } - - /** - * 初始化连接 - * - * @param config FTP配置 - * @param mode 模式 - * @return this - */ - public Ftp init(final FtpConfig config, final FtpMode mode) { - final FTPClient client = new FTPClient(); - // issue#I3O81Y@Gitee - client.setRemoteVerificationEnabled(false); - - final Charset charset = config.getCharset(); - if (null != charset) { - client.setControlEncoding(charset.toString()); - } - client.setConnectTimeout((int) config.getConnectionTimeout()); - final String systemKey = config.getSystemKey(); - if (StrUtil.isNotBlank(systemKey)) { - final FTPClientConfig conf = new FTPClientConfig(systemKey); - final String serverLanguageCode = config.getServerLanguageCode(); - if (StrUtil.isNotBlank(serverLanguageCode)) { - conf.setServerLanguageCode(config.getServerLanguageCode()); - } - client.configure(conf); - } - - // connect - try { - // 连接ftp服务器 - client.connect(config.getHost(), config.getPort()); - client.setSoTimeout((int) config.getSoTimeout()); - // 登录ftp服务器 - client.login(config.getUser(), config.getPassword()); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - final int replyCode = client.getReplyCode(); // 是否成功登录服务器 - if (!FTPReply.isPositiveCompletion(replyCode)) { - try { - client.disconnect(); - } catch (final IOException e) { - // ignore - } - throw new FtpException("Login failed for user [{}], reply code is: [{}]", config.getUser(), replyCode); - } - this.client = client; - if (mode != null) { - //noinspection resource - setMode(mode); - } - return this; - } - - /** - * 设置FTP连接模式,可选主动和被动模式 - * - * @param mode 模式枚举 - * @return this - * @since 4.1.19 - */ - public Ftp setMode(final FtpMode mode) { - this.mode = mode; - switch (mode) { - case Active: - this.client.enterLocalActiveMode(); - break; - case Passive: - this.client.enterLocalPassiveMode(); - break; - } - return this; - } - - /** - * 设置执行完操作是否返回当前目录 - * - * @param backToPwd 执行完操作是否返回当前目录 - * @return this - * @since 4.6.0 - */ - public Ftp setBackToPwd(final boolean backToPwd) { - this.backToPwd = backToPwd; - return this; - } - - /** - * 是否执行完操作返回当前目录 - * - * @return 执行完操作是否返回当前目录 - * @since 5.7.17 - */ - public boolean isBackToPwd() { - return this.backToPwd; - } - - /** - * 如果连接超时的话,重新进行连接 经测试,当连接超时时,client.isConnected()仍然返回ture,无法判断是否连接超时 因此,通过发送pwd命令的方式,检查连接是否超时 - * - * @return this - */ - @Override - public Ftp reconnectIfTimeout() { - String pwd = null; - try { - pwd = pwd(); - } catch (final IORuntimeException fex) { - // ignore - } - - if (pwd == null) { - return this.init(); - } - return this; - } - - /** - * 改变目录 - * - * @param directory 目录 - * @return 是否成功 - */ - @Override - synchronized public boolean cd(final String directory) { - if (StrUtil.isBlank(directory)) { - // 当前目录 - return true; - } - - try { - return client.changeWorkingDirectory(directory); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 远程当前目录 + * 远程当前目录(工作目录) * * @return 远程当前目录 - * @since 4.1.14 */ - @Override - public String pwd() { + String pwd(); + + /** + * 打开指定目录,具体逻辑取决于实现,例如在FTP中,进入失败返回{@code false}, SFTP中则抛出异常 + * + * @param directory directory + * @return 是否打开目录 + */ + boolean cd(String directory); + + /** + * 打开上级目录 + * + * @return 是否打开目录 + */ + default boolean toParent() { + return cd(StrUtil.DOUBLE_DOT); + } + + /** + * 文件或目录是否存在
+ *

    + *
  • 提供路径为空则返回{@code false}
  • + *
  • 提供路径非目录但是以'/'或'\'结尾返回{@code false}
  • + *
  • 文件名是'.'或者'..'返回{@code false}
  • + *
+ * + * @param path 目录 + * @return 是否存在 + */ + boolean exist(final String path); + + /** + * 判断给定路径是否为目录 + * + * @param dir 被判断的路径 + * @return 是否为目录 + * @since 5.7.5 + */ + default boolean isDir(final String dir) { + final String workDir = pwd(); try { - return client.printWorkingDirectory(); - } catch (final IOException e) { - throw new IORuntimeException(e); + return cd(dir); + } finally { + cd(workDir); } } - @Override - public List ls(final String path) { - return ArrayUtil.map(lsFiles(path), FTPFile::getName); - } - /** - * 遍历某个目录下所有文件和目录,不会递归遍历
- * 此方法自动过滤"."和".."两种目录 + * 在当前远程目录(工作目录)下创建新的目录 * - * @param path 目录 - * @param predicate 过滤器,null表示不过滤,默认去掉"."和".."两种目录 - * @return 文件名或目录名列表 + * @param dir 目录名 + * @return 是否创建成功 */ - public List ls(final String path, final Predicate predicate) { - return CollUtil.map(lsFiles(path, predicate), FTPFile::getName); - } + boolean mkdir(String dir); /** - * 遍历某个目录下所有文件和目录,不会递归遍历
- * 此方法自动过滤"."和".."两种目录 + * 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录 * - * @param path 目录 - * @param predicate 过滤器,null表示不过滤,默认去掉"."和".."两种目录 - * @return 文件或目录列表 - * @since 5.3.5 + * @param dir 文件夹路径,绝对路径 */ - public List lsFiles(final String path, final Predicate predicate) { - final FTPFile[] ftpFiles = lsFiles(path); - if (ArrayUtil.isEmpty(ftpFiles)) { - return ListUtil.empty(); - } - - final List result = new ArrayList<>(ftpFiles.length - 2 <= 0 ? ftpFiles.length : ftpFiles.length - 2); - String fileName; - for (final FTPFile ftpFile : ftpFiles) { - fileName = ftpFile.getName(); - if (!StrUtil.equals(".", fileName) && !StrUtil.equals("..", fileName)) { - if (null == predicate || predicate.test(ftpFile)) { - result.add(ftpFile); - } - } - } - return result; - } + void mkDirs(final String dir); /** * 遍历某个目录下所有文件和目录,不会递归遍历 * - * @param path 目录,如果目录不存在,抛出异常 - * @return 文件或目录列表 - * @throws FtpException 路径不存在 - * @throws IORuntimeException IO异常 + * @param path 需要遍历的目录 + * @return 文件和目录列表 */ - public FTPFile[] lsFiles(final String path) throws FtpException, IORuntimeException { - String pwd = null; - if (StrUtil.isNotBlank(path)) { - pwd = pwd(); - if (!cd(path)) { - throw new FtpException("Change dir to [{}] error, maybe path not exist!", path); - } - } - - FTPFile[] ftpFiles; - try { - ftpFiles = this.client.listFiles(); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - // 回到原目录 - cd(pwd); - } - - return ftpFiles; - } - - @Override - public boolean mkdir(final String dir) throws IORuntimeException { - try { - return this.client.makeDirectory(dir); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } + List ls(String path); /** - * 获取服务端目录状态。 + * 删除指定目录下的指定文件 * - * @param path 路径 - * @return 状态int,服务端不同,返回不同 - * @throws IORuntimeException IO异常 - * @since 5.4.3 - */ - public int stat(final String path) throws IORuntimeException { - try { - return this.client.stat(path); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 判断ftp服务器目录内是否还有子元素(目录或文件) - * - * @param path 文件路径 + * @param path 目录路径 * @return 是否存在 - * @throws IORuntimeException IO异常 */ - public boolean existFile(final String path) throws IORuntimeException { - final FTPFile[] ftpFileArr; - try { - ftpFileArr = client.listFiles(path); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - return ArrayUtil.isNotEmpty(ftpFileArr); - } - - @Override - public boolean delFile(final String path) throws IORuntimeException { - final String pwd = pwd(); - final String fileName = FileNameUtil.getName(path); - final String dir = StrUtil.removeSuffix(path, fileName); - if (!cd(dir)) { - throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path); - } - - boolean isSuccess; - try { - isSuccess = client.deleteFile(fileName); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - // 回到原目录 - cd(pwd); - } - return isSuccess; - } - - @Override - public boolean delDir(final String dirPath) throws IORuntimeException { - final FTPFile[] dirs; - try { - dirs = client.listFiles(dirPath); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - String name; - String childPath; - for (final FTPFile ftpFile : dirs) { - name = ftpFile.getName(); - childPath = StrUtil.format("{}/{}", dirPath, name); - if (ftpFile.isDirectory()) { - // 上级和本级目录除外 - if (!".".equals(name) && !"..".equals(name)) { - delDir(childPath); - } - } else { - delFile(childPath); - } - } - - // 删除空目录 - try { - return this.client.removeDirectory(dirPath); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } + boolean delFile(String path); /** - * 上传文件到指定目录,可选: + * 删除文件夹及其文件夹下的所有文件 * - *
-	 * 1. remotePath为null或""上传到当前路径
-	 * 2. remotePath为相对路径则相对于当前路径的子路径
-	 * 3. remotePath为绝对路径则上传到此路径
-	 * 
- * - * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 文件 - * @return 是否上传成功 + * @param dirPath 文件夹路径 + * @return boolean 是否删除成功 */ - @Override - public boolean uploadFile(final String remotePath, final File file) { - Assert.notNull(file, "file to upload is null !"); - if (!FileUtil.isFile(file)) { - throw new FtpException("[{}] is not a file!", file); - } - return uploadFile(remotePath, file.getName(), file); - } + boolean delDir(String dirPath); /** - * 上传文件到指定目录,可选: + * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与file文件名相同。 + * 覆盖模式 * - *
-	 * 1. remotePath为null或""上传到当前路径
-	 * 2. remotePath为相对路径则相对于当前路径的子路径
-	 * 3. remotePath为绝对路径则上传到此路径
-	 * 
- * - * @param file 文件 - * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param fileName 自定义在服务端保存的文件名 - * @return 是否上传成功 - * @throws IORuntimeException IO异常 + * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param file 需要上传的文件 + * @return 是否成功 */ - public boolean uploadFile(final String remotePath, final String fileName, final File file) throws IORuntimeException { - try (final InputStream in = FileUtil.getInputStream(file)) { - return uploadFile(remotePath, fileName, in); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 上传文件到指定目录,可选: - * - *
-	 * 1. remotePath为null或""上传到当前路径
-	 * 2. remotePath为相对路径则相对于当前路径的子路径
-	 * 3. remotePath为绝对路径则上传到此路径
-	 * 
- * - * @param remotePath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param fileName 文件名 - * @param fileStream 文件流 - * @return 是否上传成功 - * @throws IORuntimeException IO异常 - */ - public boolean uploadFile(final String remotePath, final String fileName, final InputStream fileStream) throws IORuntimeException { - try { - client.setFileType(FTPClient.BINARY_FILE_TYPE); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - - String pwd = null; - if (this.backToPwd) { - pwd = pwd(); - } - - if (StrUtil.isNotBlank(remotePath)) { - mkDirs(remotePath); - if (!cd(remotePath)) { - throw new FtpException("Change dir to [{}] error, maybe dir not exist!", remotePath); - } - } - - try { - return client.storeFile(fileName, fileStream); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (this.backToPwd) { - cd(pwd); - } - } - } - - /** - * 递归上传文件(支持目录)
- * 上传时,如果uploadFile为目录,只复制目录下所有目录和文件到目标路径下,并不会复制目录本身
- * 上传时,自动创建父级目录 - * - * @param remotePath 目录路径 - * @param uploadFile 上传文件或目录 - */ - public void upload(final String remotePath, final File uploadFile) { - if (!FileUtil.isDirectory(uploadFile)) { - this.uploadFile(remotePath, uploadFile); - return; - } - - final File[] files = uploadFile.listFiles(); - if (ArrayUtil.isEmpty(files)) { - return; - } - - final List dirs = new ArrayList<>(files.length); - //第一次只处理文件,防止目录在前面导致先处理子目录,而引发文件所在目录不正确 - for (final File f : files) { - if (f.isDirectory()) { - dirs.add(f); - } else { - this.uploadFile(remotePath, f); - } - } - //第二次只处理目录 - for (final File f : dirs) { - final String dir = FileUtil.normalize(remotePath + "/" + f.getName()); - upload(dir, f); - } - } + boolean uploadFile(String destPath, File file); /** * 下载文件 * - * @param path 文件路径,包含文件名 - * @param outFile 输出文件或目录,当为目录时,使用服务端的文件名 + * @param path 文件路径 + * @param outFile 输出文件或目录 */ - @Override - public void download(final String path, final File outFile) { - final String fileName = FileNameUtil.getName(path); - final String dir = StrUtil.removeSuffix(path, fileName); - download(dir, fileName, outFile); - } + void download(String path, File outFile); /** - * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 * * @param sourcePath ftp服务器目录 * @param destDir 本地目录 + * @since 5.3.5 */ - @Override - public void recursiveDownloadFolder(final String sourcePath, final File destDir) { - String fileName; - String srcFile; - File destFile; - for (final FTPFile ftpFile : lsFiles(sourcePath, null)) { - fileName = ftpFile.getName(); - srcFile = StrUtil.format("{}/{}", sourcePath, fileName); - destFile = FileUtil.file(destDir, fileName); - - if (!ftpFile.isDirectory()) { - // 本地不存在文件或者ftp上文件有修改则下载 - if (!FileUtil.exists(destFile) - || (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) { - download(srcFile, destFile); - } - } else { - // 服务端依旧是目录,继续递归 - FileUtil.mkdir(destFile); - recursiveDownloadFolder(srcFile, destFile); - } - } - } - - /** - * 下载文件 - * - * @param path 文件所在路径(远程目录),不包含文件名 - * @param fileName 文件名 - * @param outFile 输出文件或目录,当为目录时使用服务端文件名 - * @throws IORuntimeException IO异常 - */ - public void download(final String path, final String fileName, File outFile) throws IORuntimeException { - if (outFile.isDirectory()) { - outFile = new File(outFile, fileName); - } - if (!outFile.exists()) { - FileUtil.touch(outFile); - } - try (final OutputStream out = FileUtil.getOutputStream(outFile)) { - download(path, fileName, out); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 下载文件到输出流 - * - * @param path 文件路径 - * @param fileName 文件名 - * @param out 输出位置 - */ - public void download(final String path, final String fileName, final OutputStream out) { - download(path, fileName, out, null); - } - - /** - * 下载文件到输出流 - * - * @param path 服务端的文件路径 - * @param fileName 服务端的文件名 - * @param out 输出流,下载的文件写出到这个流中 - * @param fileNameCharset 文件名编码,通过此编码转换文件名编码为ISO8859-1 - * @throws IORuntimeException IO异常 - * @since 5.5.7 - */ - public void download(final String path, String fileName, final OutputStream out, final Charset fileNameCharset) throws IORuntimeException { - String pwd = null; - if (this.backToPwd) { - pwd = pwd(); - } - - if (!cd(path)) { - throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path); - } - - if (null != fileNameCharset) { - fileName = new String(fileName.getBytes(fileNameCharset), StandardCharsets.ISO_8859_1); - } - try { - client.setFileType(FTPClient.BINARY_FILE_TYPE); - client.retrieveFile(fileName, out); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (backToPwd) { - cd(pwd); - } - } - } - - /** - * 获取FTPClient客户端对象 - * - * @return {@link FTPClient} - */ - public FTPClient getClient() { - return this.client; - } - - @Override - public void close() throws IOException { - if (null != this.client) { - this.client.logout(); - if (this.client.isConnected()) { - this.client.disconnect(); - } - this.client = null; - } - } + void recursiveDownloadFolder(String sourcePath, File destDir); } diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpConfig.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpConfig.java index 036fe5cb2..8ddaaee15 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpConfig.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpConfig.java @@ -12,6 +12,8 @@ package org.dromara.hutool.extra.ftp; +import org.dromara.hutool.extra.ssh.Connector; + import java.io.Serializable; import java.nio.charset.Charset; @@ -32,44 +34,24 @@ public class FtpConfig implements Serializable { return new FtpConfig(); } - /** - * 主机 - */ - private String host; - /** - * 端口 - */ - private int port; - /** - * 用户名 - */ - private String user; - /** - * 密码 - */ - private String password; + private Connector connector; /** * 编码 */ private Charset charset; - /** - * 连接超时时长,单位毫秒 - */ - private long connectionTimeout; - /** * Socket连接超时时长,单位毫秒 */ private long soTimeout; /** - * 设置服务器语言 + * 服务器语言 */ private String serverLanguageCode; /** - * 设置服务器系统关键词 + * 服务器系统关键词 */ private String systemKey; @@ -82,114 +64,137 @@ public class FtpConfig implements Serializable { /** * 构造 * - * @param host 主机 - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param charset 编码 + * @param connector 连接信息,包括host、port、user、password等信息 + * @param charset 编码 */ - public FtpConfig(final String host, final int port, final String user, final String password, final Charset charset) { - this(host, port, user, password, charset, null, null); + public FtpConfig(final Connector connector, final Charset charset) { + this(connector, charset, null, null); } /** * 构造 * - * @param host 主机 - * @param port 端口 - * @param user 用户名 - * @param password 密码 + * @param connector 连接信息,包括host、port、user、password等信息 * @param charset 编码 * @param serverLanguageCode 服务器语言 * @param systemKey 系统关键字 - * @since 5.5.7 */ - public FtpConfig(final String host, final int port, final String user, final String password, final Charset charset, final String serverLanguageCode, final String systemKey) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; + public FtpConfig(final Connector connector, final Charset charset, final String serverLanguageCode, final String systemKey) { + this.connector = connector; this.charset = charset; this.serverLanguageCode = serverLanguageCode; this.systemKey = systemKey; } - public String getHost() { - return host; + /** + * 获取连接信息 + * + * @return 连接信息 + */ + public Connector getConnector() { + return connector; } - public FtpConfig setHost(final String host) { - this.host = host; + /** + * 设置连接信息 + * + * @param connector 连接信息 + * @return this + */ + public FtpConfig setConnector(final Connector connector) { + this.connector = connector; return this; } - public int getPort() { - return port; - } - - public FtpConfig setPort(final int port) { - this.port = port; - return this; - } - - public String getUser() { - return user; - } - - public FtpConfig setUser(final String user) { - this.user = user; - return this; - } - - public String getPassword() { - return password; - } - - public FtpConfig setPassword(final String password) { - this.password = password; + /** + * 设置超时,注意此方法会调用{@link Connector#setTimeout(long)}
+ * 此方法需在{@link #setConnector(Connector)}后调用,否则会创建空的Connector + * @param timeout 链接超时 + * @return this + */ + public FtpConfig setConnectionTimeout(final long timeout){ + if(null == connector){ + connector = Connector.of(); + } + connector.setTimeout(timeout); return this; } + /** + * 获取编码 + * + * @return 编码 + */ public Charset getCharset() { return charset; } + /** + * 设置编码 + * + * @param charset 编码 + * @return this + */ public FtpConfig setCharset(final Charset charset) { this.charset = charset; return this; } - public long getConnectionTimeout() { - return connectionTimeout; - } - - public FtpConfig setConnectionTimeout(final long connectionTimeout) { - this.connectionTimeout = connectionTimeout; - return this; - } - + /** + * 获取读取数据超时时间 + * + * @return 读取数据超时时间 + */ public long getSoTimeout() { return soTimeout; } + /** + * 设置读取数据超时时间 + * + * @param soTimeout 读取数据超时时间 + * @return this + */ public FtpConfig setSoTimeout(final long soTimeout) { this.soTimeout = soTimeout; return this; } + /** + * 获取服务器语言 + * + * @return 服务器语言 + */ public String getServerLanguageCode() { return serverLanguageCode; } + /** + * 设置服务器语言 + * + * @param serverLanguageCode 服务器语言 + * @return this + */ public FtpConfig setServerLanguageCode(final String serverLanguageCode) { this.serverLanguageCode = serverLanguageCode; return this; } + /** + * 获取服务器系统关键词 + * + * @return 服务器系统关键词 + */ public String getSystemKey() { return systemKey; } + /** + * 设置服务器系统关键词 + * + * @param systemKey 服务器系统关键词 + * @return this + */ public FtpConfig setSystemKey(final String systemKey) { this.systemKey = systemKey; return this; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpMode.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpMode.java index 7c76b4577..1ecc89fe7 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpMode.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ftp/FtpMode.java @@ -22,8 +22,13 @@ package org.dromara.hutool.extra.ftp; * @since 4.1.19 */ public enum FtpMode { - /** 主动模式 */ + + /** + * 主动模式 + */ Active, - /** 被动模式 */ + /** + * 被动模式 + */ Passive } diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/Connector.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/Connector.java index fddfcd22f..44e257fd7 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/Connector.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/Connector.java @@ -13,16 +13,59 @@ package org.dromara.hutool.extra.ssh; /** - * 连接者对象,提供一些连接的基本信息 + * 连接者对象,提供一些连接的基本信息,包括: + *
    + *
  • host:主机名
  • + *
  • port:端口
  • + *
  • user:用户名(默认root)
  • + *
  • password:密码
  • + *
  • timeout:连接超时毫秒数
  • + *
* * @author looly */ public class Connector { + + /** + * 创建Connector,所有参数为默认,用于构建模式 + * + * @return Connector + */ + public static Connector of() { + return new Connector(); + } + + /** + * 创建Connector + * + * @param host 主机名 + * @param port 端口 + * @param user 用户名 + * @param password 密码 + * @return Connector + */ + public static Connector of(final String host, final int port, final String user, final String password) { + return of(host, port, user, password, 0); + } + + /** + * 创建Connector + * + * @param host 主机名 + * @param port 端口 + * @param user 用户名 + * @param password 密码 + * @param timeout 连接超时时长,0表示默认 + * @return Connector + */ + public static Connector of(final String host, final int port, final String user, final String password, final long timeout) { + return new Connector(host, port, user, password, timeout); + } + private String host; private int port; private String user = "root"; private String password; - private String group; private long timeout; /** @@ -31,19 +74,6 @@ public class Connector { public Connector() { } - /** - * 构造 - * - * @param user 用户名 - * @param password 密码 - * @param group 组 - */ - public Connector(final String user, final String password, final String group) { - this.user = user; - this.password = password; - this.group = group; - } - /** * 构造 * @@ -51,19 +81,7 @@ public class Connector { * @param port 端口 * @param user 用户名 * @param password 密码 - */ - public Connector(final String host, final int port, final String user, final String password) { - this(host, port, user, password, 0); - } - - /** - * 构造 - * - * @param host 主机名 - * @param port 端口 - * @param user 用户名 - * @param password 密码 - * @param timeout 连接超时时长,0表示默认 + * @param timeout 连接超时时长,0表示默认 */ public Connector(final String host, final int port, final String user, final String password, final long timeout) { this.host = host; @@ -86,9 +104,11 @@ public class Connector { * 设定主机名 * * @param host 主机名 + * @return this */ - public void setHost(final String host) { + public Connector setHost(final String host) { this.host = host; + return this; } /** @@ -104,9 +124,11 @@ public class Connector { * 设定端口号 * * @param port 端口号 + * @return this */ - public void setPort(final int port) { + public Connector setPort(final int port) { this.port = port; + return this; } /** @@ -122,9 +144,11 @@ public class Connector { * 设定用户名 * * @param name 用户名 + * @return this */ - public void setUser(final String name) { + public Connector setUser(final String name) { this.user = name; + return this; } /** @@ -140,27 +164,11 @@ public class Connector { * 设定密码 * * @param password 密码 + * @return this */ - public void setPassword(final String password) { + public Connector setPassword(final String password) { this.password = password; - } - - /** - * 获得用户组名 - * - * @return 用户组 - */ - public String getGroup() { - return group; - } - - /** - * 设定用户组名 - * - * @param group 用户组 - */ - public void setGroup(final String group) { - this.group = group; + return this; } /** @@ -176,9 +184,11 @@ public class Connector { * 设置连接超时时间 * * @param timeout 连接超时时间 + * @return this */ - public void setTimeout(final long timeout) { + public Connector setTimeout(final long timeout) { this.timeout = timeout; + return this; } /** @@ -191,7 +201,6 @@ public class Connector { ", port=" + port + ", user='" + user + '\'' + ", password='" + password + '\'' + - ", group='" + group + '\'' + ", timeout=" + timeout + '}'; } diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSftp.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSftp.java index 1ffd3e869..8af81ab47 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSftp.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSftp.java @@ -52,8 +52,7 @@ public class JschSftp extends AbstractFtp { private Session session; private ChannelSftp channel; - // ---------------------------------------------------------------------------------------- Constructor start - + //region ----- of /** * 构造 * @@ -61,9 +60,10 @@ public class JschSftp extends AbstractFtp { * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 * @param sshPass 远程主机密码 + * @return JschSftp */ - public JschSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - this(sshHost, sshPort, sshUser, sshPass, DEFAULT_CHARSET); + public static JschSftp of(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { + return of(sshHost, sshPort, sshUser, sshPass, DEFAULT_CHARSET); } /** @@ -74,11 +74,12 @@ public class JschSftp extends AbstractFtp { * @param sshUser 远程主机用户名 * @param sshPass 远程主机密码 * @param charset 编码 - * @since 4.1.14 + * @return JschSftp */ - public JschSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final Charset charset) { - this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, charset)); + public static JschSftp of(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final Charset charset) { + return new JschSftp(new FtpConfig(Connector.of(sshHost, sshPort, sshUser, sshPass), charset)); } + // endregion /** * 构造 @@ -132,20 +133,22 @@ public class JschSftp extends AbstractFtp { init(); } // ---------------------------------------------------------------------------------------- Constructor end + /** * 初始化 */ @SuppressWarnings("resource") public void init() { - if(null == this.channel){ - if(null == this.session){ + if (null == this.channel) { + if (null == this.session) { final FtpConfig config = this.ftpConfig; - this.session = new JschSession(new Connector( - config.getHost(), - config.getPort(), - config.getUser(), - config.getPassword(), - config.getConnectionTimeout())) + final Connector connector = config.getConnector(); + this.session = new JschSession(Connector.of( + connector.getHost(), + connector.getPort(), + connector.getUser(), + connector.getPassword(), + connector.getTimeout())) .getRaw(); } @@ -158,8 +161,8 @@ public class JschSftp extends AbstractFtp { } try { - if(!channel.isConnected()){ - channel.connect((int) Math.max(this.ftpConfig.getConnectionTimeout(), 0)); + if (!channel.isConnected()) { + channel.connect((int) Math.max(this.ftpConfig.getConnector().getTimeout(), 0)); } channel.setFilenameEncoding(this.ftpConfig.getCharset().toString()); } catch (final JSchException | SftpException e) { @@ -169,7 +172,7 @@ public class JschSftp extends AbstractFtp { @Override public JschSftp reconnectIfTimeout() { - if (StrUtil.isBlank(this.ftpConfig.getHost())) { + if (StrUtil.isBlank(this.ftpConfig.getConnector().getHost())) { throw new FtpException("Host is blank!"); } try { @@ -188,7 +191,7 @@ public class JschSftp extends AbstractFtp { * @since 4.1.14 */ public ChannelSftp getClient() { - if(false == this.channel.isConnected()){ + if (false == this.channel.isConnected()) { init(); } return this.channel; @@ -462,7 +465,7 @@ public class JschSftp extends AbstractFtp { @SuppressWarnings("resource") @Override public boolean uploadFile(final String destPath, final File file) { - if(!FileUtil.isFile(file)){ + if (!FileUtil.isFile(file)) { throw new FtpException("[{}] is not a file!", file); } this.mkDirs(destPath); @@ -585,7 +588,7 @@ public class JschSftp extends AbstractFtp { if (!item.getAttrs().isDir()) { // 本地不存在文件或者ftp上文件有修改则下载 if (!FileUtil.exists(destFile) - || (item.getAttrs().getMTime() > (destFile.lastModified() / 1000))) { + || (item.getAttrs().getMTime() > (destFile.lastModified() / 1000))) { download(srcFile, destFile); } } else { @@ -638,11 +641,12 @@ public class JschSftp extends AbstractFtp { @Override public String toString() { - return "Sftp{" + - "host='" + this.ftpConfig.getHost() + '\'' + - ", port=" + this.ftpConfig.getPort() + - ", user='" + this.ftpConfig.getUser() + '\'' + - '}'; + final Connector connector = this.ftpConfig.getConnector(); + return "JschSftp{" + + "host='" + connector.getHost() + '\'' + + ", port=" + connector.getPort() + + ", user='" + connector.getUser() + '\'' + + '}'; } /** diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSftp.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSftp.java index e37d4e0fe..e08723422 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSftp.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSftp.java @@ -20,7 +20,6 @@ import net.schmizz.sshj.xfer.FileSystemFile; import org.dromara.hutool.core.collection.CollUtil; import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.extra.ftp.AbstractFtp; import org.dromara.hutool.extra.ftp.FtpConfig; import org.dromara.hutool.extra.ftp.FtpException; @@ -45,27 +44,17 @@ import java.util.List; */ public class SshjSftp extends AbstractFtp { - private SSHClient ssh; - private SFTPClient sftp; - - /** - * 构造,使用默认端口 - * - * @param sshHost 主机 - */ - public SshjSftp(final String sshHost) { - this(new FtpConfig(sshHost, 22, null, null, CharsetUtil.UTF_8)); - } - + // region ----- of /** * 构造 * * @param sshHost 主机 * @param sshUser 用户名 * @param sshPass 密码 + * @return SshjSftp */ - public SshjSftp(final String sshHost, final String sshUser, final String sshPass) { - this(new FtpConfig(sshHost, 22, sshUser, sshPass, CharsetUtil.UTF_8)); + public static SshjSftp of(final String sshHost, final String sshUser, final String sshPass) { + return of(sshHost, 22, sshUser, sshPass); } /** @@ -75,9 +64,10 @@ public class SshjSftp extends AbstractFtp { * @param sshPort 端口 * @param sshUser 用户名 * @param sshPass 密码 + * @return SshjSftp */ - public SshjSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, CharsetUtil.UTF_8)); + public static SshjSftp of(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { + return of(sshHost, sshPort, sshUser, sshPass, DEFAULT_CHARSET); } /** @@ -88,10 +78,15 @@ public class SshjSftp extends AbstractFtp { * @param sshUser 用户名 * @param sshPass 密码 * @param charset 编码 + * @return SshjSftp */ - public SshjSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final Charset charset) { - this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, charset)); + public static SshjSftp of(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final Charset charset) { + return new SshjSftp(new FtpConfig(Connector.of(sshHost, sshPort, sshUser, sshPass), charset)); } + //endregion + + private SSHClient ssh; + private SFTPClient sftp; /** * 构造 @@ -106,8 +101,9 @@ public class SshjSftp extends AbstractFtp { /** * 构造 + * * @param sshClient {@link SSHClient} - * @param charset 编码 + * @param charset 编码 */ public SshjSftp(final SSHClient sshClient, final Charset charset) { super(FtpConfig.of().setCharset(charset)); @@ -122,12 +118,7 @@ public class SshjSftp extends AbstractFtp { * @since 5.7.18 */ public void init() { - this.ssh = SshjUtil.openClient(new Connector( - ftpConfig.getHost(), - ftpConfig.getPort(), - ftpConfig.getUser(), - ftpConfig.getPassword(), - ftpConfig.getConnectionTimeout())); + this.ssh = SshjUtil.openClient(this.ftpConfig.getConnector()); try { ssh.setRemoteCharset(ftpConfig.getCharset()); @@ -139,7 +130,7 @@ public class SshjSftp extends AbstractFtp { @Override public AbstractFtp reconnectIfTimeout() { - if (StrUtil.isBlank(this.ftpConfig.getHost())) { + if (StrUtil.isBlank(this.ftpConfig.getConnector().getHost())) { throw new FtpException("Host is blank!"); } try { diff --git a/hutool-extra/src/test/java/org/dromara/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/org/dromara/hutool/extra/ftp/FtpTest.java index 027e7d885..c08a5a06b 100644 --- a/hutool-extra/src/test/java/org/dromara/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/org/dromara/hutool/extra/ftp/FtpTest.java @@ -28,7 +28,7 @@ public class FtpTest { @Disabled public void ftpsTest() { final FTPSClient ftpsClient = new FTPSClient(); - final Ftp ftp = new Ftp(ftpsClient); + final CommonsFtp ftp = new CommonsFtp(ftpsClient); ftp.cd("/file/aaa"); Console.log(ftp.pwd()); @@ -39,7 +39,7 @@ public class FtpTest { @Test @Disabled public void cdTest() { - final Ftp ftp = new Ftp("looly.centos"); + final CommonsFtp ftp = CommonsFtp.of("looly.centos"); ftp.cd("/file/aaa"); Console.log(ftp.pwd()); @@ -50,7 +50,7 @@ public class FtpTest { @Test @Disabled public void uploadTest() { - final Ftp ftp = new Ftp("localhost"); + final CommonsFtp ftp = CommonsFtp.of("localhost"); final boolean upload = ftp.uploadFile("/temp", FileUtil.file("d:/test/test.zip")); Console.log(upload); @@ -61,17 +61,17 @@ public class FtpTest { @Test @Disabled public void reconnectIfTimeoutTest() throws InterruptedException { - final Ftp ftp = new Ftp("looly.centos"); + final CommonsFtp ftp = CommonsFtp.of("looly.centos"); Console.log("打印pwd: " + ftp.pwd()); Console.log("休眠一段时间,然后再次发送pwd命令,抛出异常表明连接超时"); Thread.sleep(35 * 1000); - try{ + try { Console.log("打印pwd: " + ftp.pwd()); - }catch (final FtpException e) { - e.printStackTrace(); + } catch (final FtpException e) { + Console.error(e, e.getMessage()); } Console.log("判断是否超时并重连..."); @@ -85,8 +85,8 @@ public class FtpTest { @Test @Disabled public void recursiveDownloadFolder() { - final Ftp ftp = new Ftp("looly.centos"); - ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); + final CommonsFtp ftp = CommonsFtp.of("looly.centos"); + ftp.recursiveDownloadFolder("/", FileUtil.file("d:/test/download")); IoUtil.closeQuietly(ftp); } @@ -94,11 +94,11 @@ public class FtpTest { @Test @Disabled public void recursiveDownloadFolderSftp() { - final JschSftp ftp = new JschSftp("127.0.0.1", 22, "test", "test"); + final JschSftp ftp = JschSftp.of("127.0.0.1", 22, "test", "test"); ftp.cd("/file/aaa"); Console.log(ftp.pwd()); - ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); + ftp.recursiveDownloadFolder("/", FileUtil.file("d:/test/download")); IoUtil.closeQuietly(ftp); } @@ -106,13 +106,13 @@ public class FtpTest { @Test @Disabled public void downloadTest() { - final Ftp ftp = new Ftp("localhost"); + final CommonsFtp ftp = CommonsFtp.of("localhost"); final List fileNames = ftp.ls("temp/"); - for(final String name: fileNames) { + for (final String name : fileNames) { ftp.download("", - name, - FileUtil.file("d:/test/download/" + name)); + name, + FileUtil.file("d:/test/download/" + name)); } IoUtil.closeQuietly(ftp); @@ -121,7 +121,7 @@ public class FtpTest { @Test @Disabled public void isDirTest() throws Exception { - try (final Ftp ftp = new Ftp("127.0.0.1", 21)) { + try (final CommonsFtp ftp = CommonsFtp.of("127.0.0.1", 21)) { Console.log(ftp.pwd()); ftp.isDir("/test"); Console.log(ftp.pwd()); @@ -131,7 +131,7 @@ public class FtpTest { @Test @Disabled public void existSftpTest() { - try (final JschSftp ftp = new JschSftp("127.0.0.1", 22, "test", "test")) { + try (final JschSftp ftp = JschSftp.of("127.0.0.1", 22, "test", "test")) { Console.log(ftp.pwd()); Console.log(ftp.exist(null)); Console.log(ftp.exist("")); @@ -154,7 +154,7 @@ public class FtpTest { @Test @Disabled public void existFtpTest() throws Exception { - try (final Ftp ftp = new Ftp("127.0.0.1", 21)) { + try (final CommonsFtp ftp = CommonsFtp.of("127.0.0.1", 21)) { Console.log(ftp.pwd()); Console.log(ftp.exist(null)); Console.log(ftp.exist("")); diff --git a/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/JschTest.java b/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/JschTest.java index 27bec7d4e..afb397b8b 100644 --- a/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/JschTest.java +++ b/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/JschTest.java @@ -35,7 +35,7 @@ public class JschTest { @Disabled public void bindPortTest() { //新建会话,此会话用于ssh连接到跳板机(堡垒机),此处为10.1.1.1:22 - final JschSession session = new JschSession(new Connector("looly.centos", 22, "test", "123456")); + final JschSession session = new JschSession(Connector.of("looly.centos", 22, "test", "123456")); // 将堡垒机保护的内网8080端口映射到localhost,我们就可以通过访问http://localhost:8080/访问内网服务了 session.bindLocalPort(8080, new InetSocketAddress("172.20.12.123", 8080)); } @@ -45,7 +45,7 @@ public class JschTest { @Disabled public void bindRemotePort() { // 建立会话 - final JschSession session = new JschSession(new Connector("looly.centos", 22, "test", "123456")); + final JschSession session = new JschSession(Connector.of("looly.centos", 22, "test", "123456")); // 绑定ssh服务端8089端口到本机的8000端口上 session.bindRemotePort(new InetSocketAddress(8089), new InetSocketAddress("localhost", 8000)); // 保证一直运行 @@ -55,7 +55,7 @@ public class JschTest { @Test @Disabled public void sftpTest() { - final JschSession session = new JschSession(new Connector("looly.centos", 22, "root", "123456")); + final JschSession session = new JschSession(Connector.of("looly.centos", 22, "root", "123456")); final JschSftp jschSftp = session.openSftp(CharsetUtil.UTF_8); jschSftp.mkDirs("/opt/test/aaa/bbb"); Console.log("OK"); @@ -65,7 +65,7 @@ public class JschTest { @Test @Disabled public void reconnectIfTimeoutTest() throws InterruptedException { - final JschSession session = new JschSession(new Connector("sunnyserver", 22,"mysftp","liuyang1234")); + final JschSession session = new JschSession(Connector.of("sunnyserver", 22,"mysftp","liuyang1234")); final JschSftp jschSftp = session.openSftp(CharsetUtil.UTF_8); Console.log("打印pwd: " + jschSftp.pwd()); diff --git a/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/SftpTest.java b/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/SftpTest.java index 7e887c1d1..ec115f2a5 100644 --- a/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/SftpTest.java +++ b/hutool-extra/src/test/java/org/dromara/hutool/extra/ssh/SftpTest.java @@ -34,7 +34,7 @@ public class SftpTest { @BeforeEach @Disabled public void init() { - sshjSftp = new SshjSftp("ip", 22, "test", "test", CharsetUtil.UTF_8); + sshjSftp = SshjSftp.of("ip", 22, "test", "test"); } @Test