Java 使用 FTP/SFTP

Java 使用 FTP/SFTP

场景

项目中需要使用 FTP,所以做了简单的 FTP/SFTP 封装,此处仅做一下记录。

注:这里并未实现连接池管理,生产环境强烈建议手动实现连接池以提高性能!

UML 图像说明

形状

注:此处参考自 IDEA UML 图中的颜色

  • 蓝色:类/步骤
  • 黄色:字段
  • 红色:函数
  • 紫色:配置

图形

  • 长方形:类/配置文件/依赖项
  • 圆角长方形:字段/函数/对象/变量
  • 箭头:拥有/向下依赖的意思

目标

封装简单的通用操作

  • 上传单个文件
  • 上传使用 InputStream(内存操作)
  • 下载单个文件
  • 下载得到 InputStream(内存操作)
  • 创建目录
  • 递归创建目录
  • 删除单个文件/空目录
  • 获取指定目录下的文件信息列表
  • 获取文件/目录信息
  • 递归获取文件/目录信息
  • 递归删除目录
  • 监听目录变化(内部使用)
  • 异步上传后等待结果

思路

  1. 定义顶层接口 FtpOperator,具体实现由子类(BasicFtpOperatorImpl, SftpOperatorImpl)完成
  2. 定义顶层配置文件基类 FtpClientConfig,包含着 ftp 连接必须的一些东西,具体细节在子类配置中 BasicFtpClientConfig, SftpClientConfig
  3. 添加工厂类 FtpOperatorFactory,根据不同子类的配置对象创建不同的 ftp 操作对象,并且一经创建就可以永久性使用
  4. 添加 FtpWatchConfig, FtpWatch, FtpWatchFactory FTP 监听器
  5. 添加集成 SpringBoot 中,读取 application.yml 中的配置,并创建不同的 FtpOperator 暴露给外部使用,动态初始化 FTP 监视器

注:这里使用 FTP 监视器的原因是为了避免每次上传数据后都要单独监听 FTP 目录的变化,造成 FTP 多线程连接数量过多
注:这里的并未实现 FTPClient 及 Jsch 的对象池管理,所以仅可参考实现,生产环境中仍需进行修改!

图解如下

图解

实现

具体的代码吾辈就不贴到这里了,全部的代码已经放到 GitHub 的公共仓库 上了。

FTP 使用

FtpOperator API 图解
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() {
// 判断数据是否存在于 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();
}
}

使用 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 {
// 监听新文件 /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 便到此为止啦