本文最后更新于:2020年12月30日 凌晨
场景
项目中需要使用 FTP,所以做了简单的 FTP/SFTP
封装,此处仅做一下记录。
注:这里并未实现连接池管理,生产环境强烈建议手动实现连接池以提高性能!
UML 图像说明
形状
注:此处参考自 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 的公共仓库 上了。
FTP 使用
FtpOperator API 图解
上传部分流程图解
使用 FtpOperator 进行基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| @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() { final boolean exist = ftp.exist("/test.txt"); assertThat(exist) .isTrue(); }
@Test public void get() { 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(); } }
|
使用 FtpOperator 上传文件并监听结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @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 { 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
便到此为止啦