该仓库为博客记录示例,如果需要可以前往博客查看内容 Java 使用 FTP/SFTP
项目中需要使用 FTP,所以做了简单的 FTP/SFTP
封装,此处仅做一下记录。
注:这里并未实现连接池管理,生产环境强烈建议手动实现连接池以提高性能!
注:此处参考自 IDEA UML 图中的颜色
- 蓝色:类/步骤
- 黄色:字段
- 红色:函数
- 紫色:配置
- 长方形:类/配置文件/依赖项
- 圆角长方形:字段/函数/对象/变量
- 箭头:拥有/向下依赖的意思
封装简单的通用操作
- 上传单个文件
- 上传使用
InputStream
(内存操作) - 下载单个文件
- 下载得到
InputStream
(内存操作) - 创建目录
- 递归创建目录
- 删除单个文件/空目录
- 获取指定目录下的文件信息列表
- 获取文件/目录信息
- 递归获取文件/目录信息
- 递归删除目录
- 监听目录变化(内部使用)
- 异步上传后等待结果
- 定义顶层接口
FtpOperator
,具体实现由子类(BasicFtpOperatorImpl
,SftpOperatorImpl
)完成 - 定义顶层配置文件基类
FtpClientConfig
,包含着 ftp 连接必须的一些东西,具体细节在子类配置中BasicFtpClientConfig
,SftpClientConfig
- 添加工厂类
FtpOperatorFactory
,根据不同子类的配置对象创建不同的 ftp 操作对象,并且一经创建就可以永久性使用 - 添加
FtpWatchConfig
,FtpWatch
,FtpWatchFactory
FTP 监听器 - 添加集成 SpringBoot 中,读取
application.yml
中的配置,并创建不同的FtpOperator
暴露给外部使用,动态初始化 FTP 监视器
注:这里使用 FTP 监视器的原因是为了避免每次上传数据后都要单独监听 FTP 目录的变化,造成 FTP 多线程连接数量过多 注:这里的并未实现 FTPClient 及 Jsch 的对象池管理,所以仅可参考实现,生产环境中仍需进行修改!
图解如下
具体的代码吾辈就不贴到这里了,全部的代码已经放到 GitHub 的公共仓库 上了。
@RunWith(SpringRunner.class)
@SpringBootTest
public class FtpSpringConfigTest {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private FtpOperator ftp;
@Test
public void put() throws UnsupportedEncodingException {
// 上传数据
final ByteArrayInputStream is = new ByteArrayInputStream("测试数据".getBytes("UTF-8"));
final boolean result = ftp.put(is, "/test.txt");
assertThat(result)
.isTrue();
}
@Test
public void exist() {
// 判断数据是否存在于 ftp 服务器
final boolean exist = ftp.exist("/test.txt");
assertThat(exist)
.isTrue();
}
@Test
public void get() {
// 从 ftp 服务器上下载数据
ftp.get("/test.txt", is -> {
try {
final List<String> list = IOUtils.readLines(is);
log.info("list: {}", list);
assertThat(list)
.isNotEmpty();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
@Test
public void mkdir() {
// 创建文件夹
assertThat(ftp.mkdir("/test"))
.isTrue();
}
@Test
public void mkdirR() {
// 递归创建文件夹
assertThat(ftp.mkdirR("/test/test2/test3"))
.isTrue();
}
@Test
public void ls() {
// 获取目录下的文件信息列表
final List<Stat> list = ftp.ls("/");
log.info("list: {}", list.stream()
.map(Stat::getPath)
.collect(Collectors.joining("\n")));
assertThat(list)
.isNotEmpty();
}
@Test
public void lsr() {
// 获取目录下的文件信息列表
final List<Stat> list = ftp.lsR("/");
log.info("list: {}", list.stream()
.map(Stat::getPath)
.collect(Collectors.joining("\n")));
assertThat(list)
.isNotEmpty();
}
@Test
public void rm() {
// 删除单个文件
assertThat(ftp.rm("/test.txt"))
.isTrue();
}
@Test
public void rmdir() {
// 删除指定空目录
assertThat(ftp.rmdir("/test/test2/test3"))
.isTrue();
}
@Test
public void rmdirR() {
// 递归删除指定目录
assertThat(ftp.rmdirR("/test"))
.isTrue();
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class FtpSpringConfigTest extends BaseTest {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private FtpOperator ftp;
@Test
public void watch() throws InterruptedException, UnsupportedEncodingException {
// 监听新文件 /test.md 的出现
final String path = "/test.md";
ftp.watch((Predicate<String>) str -> str.equals(path))
.thenAcceptAsync(stat -> {
log.warn("stat: {}", stat);
assertThat(ftp.exist(stat.getPath()))
.isNotNull();
});
// 创建测试文件
final ByteArrayInputStream is = new ByteArrayInputStream("测试数据".getBytes("UTF-8"));
log.warn("test file upload completed!");
assertThat(ftp.put(is, path))
.isTrue();
// 注意,这里有一个问题就是如果程序结束的太快,那么更新将变得不可能的!
Thread.sleep(2000);
// 删除测试文件
ftp.rm(path);
}
}
那么,关于 Java 中使用 FTP/SFTP
便到此为止啦