Spring Integration FTP/FTPS Adapters

Spring Integration FTP/FTPS Adapters

简介

Spring 官方为 FTP 和 FTPS 文件传输提供了插件。

使用方式

引入依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ftp</artifactId>
<version>6.0.0</version>
</dependency>

编写链接配置:

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
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
import org.springframework.integration.ftp.session.FtpSession;

@Configuration
public class FTPConfiguration {

@Bean
public SessionFactory<FTPFile> ftpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory() {
@Override
public synchronized FtpSession getSession() {
return super.getSession();
}
};
sf.setHost("xxx.xxx.xxx.xxx");
sf.setPort(21);
sf.setUsername("xxx");
sf.setPassword("xxx");
sf.setControlEncoding("UTF-8");
sf.setFileType(FTP.BINARY_FILE_TYPE);
sf.setClientMode(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE);
CachingSessionFactory<FTPFile> csf = new CachingSessionFactory<>(sf);
csf.setPoolSize(1);
return csf;
}

@Bean
public FtpRemoteFileTemplate ftpRemoteFileTemplate(@Qualifier("ftpSessionFactory") SessionFactory<FTPFile> sessionFactory) {
FtpRemoteFileTemplate ftpRemoteFileTemplate = new FtpRemoteFileTemplate(sessionFactory);
ExpressionParser parser = new SpelExpressionParser();
ftpRemoteFileTemplate.setRemoteDirectoryExpression(parser.parseExpression("''"));
ftpRemoteFileTemplate.setExistsMode(FtpRemoteFileTemplate.ExistsMode.STAT);
return ftpRemoteFileTemplate;
}

}

注:在使用匿名模式时将用户设为 “anonymous” 密码设为 “” 即可。

编写业务类:

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import jakarta.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.FileSystems;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

@Resource
FtpRemoteFileTemplate ftpRemoteFileTemplate;

@GetMapping
public ResponseEntity<List<FTPFile>> ls(@RequestParam String path) {
FTPFile[] result = ftpRemoteFileTemplate.list(path);
return ResponseEntity.ok(Arrays.stream(result).collect(Collectors.toList()));
}

@GetMapping
public ResponseEntity<Boolean> check(@RequestParam String path) {
return ResponseEntity.ok(ftpRemoteFileTemplate.exists(path));
}

@GetMapping("/read")
public ResponseEntity<String> read(@RequestParam String path) {
if (!ftpRemoteFileTemplate.exists(path)) {
throw new RuntimeException("file not exist");
}
final ByteArrayOutputStream cache = new ByteArrayOutputStream();
boolean success = ftpRemoteFileTemplate.get(path, stream -> FileCopyUtils.copy(stream, cache));
if (success) {
return ResponseEntity.ok(cache.toString());
} else {
throw new RuntimeException("file not exist");
}
}

@GetMapping("/download")
public void download(@RequestParam String path, HttpServletResponse httpServletResponse) {
if (!ftpRemoteFileTemplate.exists(path)) {
throw new RuntimeException("file not exist");
}
FTPFile file = ftpRemoteFileTemplate.list(path)[0];
if (file.isDirectory()) {
throw new RuntimeException("path is directory");
}
httpServletResponse.reset();
httpServletResponse.setContentType("application/octet-stream;charset=utf-8");
httpServletResponse.setHeader(
"Content-disposition",
"attachment; filename=" + file.getName());
OutputStream out;
try {
out = httpServletResponse.getOutputStream();
} catch (IOException e) {
throw new RuntimeException("outputStream error");
}
boolean success = ftpRemoteFileTemplate.get(path, stream -> FileCopyUtils.copy(stream, out));
if (!success) {
throw new RuntimeException("copy error");
}
}

@PostMapping
public Boolean write(@RequestParam MultipartFile file, @RequestParam String path) {
String dest = FileSystems.getDefault().getPath(path, file.getOriginalFilename()).toString();
Session<FTPFile> session = ftpRemoteFileTemplate.getSession();
session.write(file.getInputStream(), dest);
session.close();
return true;
}

@PutMapping
public Boolean mkdir(@RequestParam String path) {
try {
return ftpRemoteFileTemplate.getSession().mkdir(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@DeleteMapping
public Boolean delete(@RequestParam String path) throws IOException {
if (!ftpRemoteFileTemplate.exists(path)) {
throw new RuntimeException("file or directory not exist");
}
FTPFile[] files = ftpRemoteFileTemplate.list(path);
if (files.length == 0) {
return ftpRemoteFileTemplate.getSession().rmdir(path);
} else if (files.length == 1) {
FTPClient ftpClient = (FTPClient) ftpRemoteFileTemplate.getSession().getClientInstance();
if (ftpClient.deleteFile(path)) {
return true;
} else {
throw new RuntimeException("directory not empty");
}
} else {
throw new RuntimeException("directory not empty");
}
}
}

注:此处代码并没有详细进行完善,使用时请注意。

编写测试类:

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
### mkdir
PUT http://localhost:8080/test?path=demo

### upload file
POST http://localhost:8080/test
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="demo.txt"

< ./demo.txt
--WebAppBoundary--
Content-Disposition: form-data; name="path"

demo
--WebAppBoundary--

### ls
GET http://localhost:8080/test?path=/

### cat
GET http://localhost:8080/test/read?path=demo/demo.txt

### download
GET http://localhost:8080/test/download?path=demo/demo.txt

### rm
DELETE http://localhost:8080/test?path=demo

参考文档

官方手册


Spring Integration FTP/FTPS Adapters
https://wangqian0306.github.io/2022/spring-integration-ftp/
作者
WangQian
发布于
2022年11月24日
许可协议