基于Spring Boot使用FtpOutboundGateway实现动态FTP文件下载求助
Got it! 我正好做过类似的需求,用Spring Integration的FtpOutboundGateway完全可以实现动态匹配文件名下载,而且不用把文件存到本地,直接把文件流返回给客户端就行。下面给你一步步拆解完整的Java配置实现:
整体实现思路
核心思路是:
- 用
FtpOutboundGateway执行get -stream命令,直接获取文件流而非落地本地 - 通过SpEL表达式从客户端请求参数中动态读取文件名匹配模式
- 用Spring Integration的消息通道传递请求和响应,最后把文件流封装成HTTP响应返回给客户端
1. 先添加必要依赖
在你的pom.xml(Maven)或build.gradle(Gradle)中加入Spring Integration FTP相关依赖:
<!-- Spring Boot Integration 基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <!-- FTP 集成依赖 --> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-ftp</artifactId> </dependency> <!-- 工具类:用于InputStream转ByteArray --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> </dependency>
2. 配置FTP连接与网关
创建一个配置类,定义FTP连接工厂、消息通道和FtpOutboundGateway Bean:
@Configuration @EnableIntegration public class FtpIntegrationConfig { // 从配置文件读取FTP参数,也可以硬编码(不推荐) @Value("${ftp.host}") private String ftpHost; @Value("${ftp.port:21}") private int ftpPort; @Value("${ftp.username}") private String ftpUsername; @Value("${ftp.password}") private String ftpPassword; @Value("${ftp.remote-dir:/data}") private String ftpRemoteDir; // 1. 创建FTP会话工厂(带连接缓存,提升性能) @Bean public CachingSessionFactory<FTPFile> cachingFtpSessionFactory() { DefaultFtpSessionFactory factory = new DefaultFtpSessionFactory(); factory.setHost(ftpHost); factory.setPort(ftpPort); factory.setUsername(ftpUsername); factory.setPassword(ftpPassword); // 开启被动模式,适配大部分FTP服务器配置 factory.setClientMode(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE); return new CachingSessionFactory<>(factory); } // 2. 定义FtpOutboundGateway @Bean @ServiceActivator(inputChannel = "ftpRequestChannel", outputChannel = "ftpReplyChannel") public FtpOutboundGateway ftpOutboundGateway() { // 命令格式:get <远程目录> -stream(-stream表示返回文件流,不存本地) FtpOutboundGateway gateway = new FtpOutboundGateway( cachingFtpSessionFactory(), "get", "'" + ftpRemoteDir + "'" // 固定远程目录,也可以改成动态读取 ); // 关键选项:-stream 不落地本地,直接返回InputStream gateway.setOptions("-stream"); // 动态匹配文件名:从请求消息的payload中获取客户端传入的匹配模式 gateway.setRemoteFileExpressionString("payload"); // 只获取第一个匹配的文件(满足你"单个匹配文件"的需求) gateway.setMaxFetchSize(1); return gateway; } // 3. 定义请求/响应消息通道 @Bean public MessageChannel ftpRequestChannel() { return new DirectChannel(); } @Bean public PollableChannel ftpReplyChannel() { return new QueueChannel(); } }
3. 编写业务服务处理请求
创建一个服务类,负责接收客户端的文件名模式参数,发送请求到FTP网关,并处理返回的文件流:
@Service public class FtpDownloadService { private final MessageChannel ftpRequestChannel; private final PollableChannel ftpReplyChannel; // 构造注入消息通道 public FtpDownloadService(MessageChannel ftpRequestChannel, @Qualifier("ftpReplyChannel") PollableChannel ftpReplyChannel) { this.ftpRequestChannel = ftpRequestChannel; this.ftpReplyChannel = ftpReplyChannel; } public ResponseEntity<Resource> downloadMatchingFile(String filenamePattern) throws InterruptedException { // 1. 构建请求消息:把客户端传入的文件名模式作为payload Message<String> requestMsg = MessageBuilder.withPayload(filenamePattern).build(); // 2. 发送请求到FTP网关 boolean sendSuccess = ftpRequestChannel.send(requestMsg, 3000); if (!sendSuccess) { throw new RuntimeException("FTP请求发送失败,请检查连接"); } // 3. 接收网关返回的响应(超时时间5秒) Message<?> replyMsg = ftpReplyChannel.receive(5000); if (replyMsg == null) { throw new RuntimeException("未找到匹配模式的文件,或FTP请求超时"); } // 4. 处理返回的文件流 InputStream fileStream = (InputStream) replyMsg.getPayload(); ByteArrayResource resource = new ByteArrayResource(IOUtils.toByteArray(fileStream)); // 从消息头获取实际的文件名(用于响应头的下载文件名) FTPFile remoteFile = replyMsg.getHeaders().get(FileHeaders.REMOTE_FILE, FTPFile.class); String actualFilename = remoteFile != null ? remoteFile.getName() : "downloaded_file"; // 5. 封装成HTTP响应返回给客户端 return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + actualFilename + "\"") .body(resource); } }
4. 编写控制器接收客户端请求
创建一个REST接口,让客户端可以通过请求参数传递文件名匹配模式:
@RestController @RequestMapping("/api/ftp") public class FtpController { private final FtpDownloadService ftpDownloadService; public FtpController(FtpDownloadService ftpDownloadService) { this.ftpDownloadService = ftpDownloadService; } @GetMapping("/download") public ResponseEntity<Resource> downloadFile(@RequestParam String filenamePattern) throws InterruptedException { // 比如客户端请求:/api/ftp/download?filenamePattern=report_2024*.csv return ftpDownloadService.downloadMatchingFile(filenamePattern); } }
关键注意点
-stream选项:这是实现“无需本地保存”的核心,它让FtpOutboundGateway直接返回文件的InputStream,而不是写入本地文件。- 动态文件名模式:通过
remoteFileExpressionString("payload")从请求消息的payload中读取客户端传入的匹配模式,支持*、?等通配符。 setMaxFetchSize(1):确保只返回第一个匹配的文件,满足你“单个匹配文件”的需求。- 异常处理:代码中加入了请求超时、发送失败、无匹配文件的异常判断,你可以根据业务需求进一步优化异常类型和提示信息。
内容的提问来源于stack exchange,提问作者Imvejuru
相关产品推荐
相关产品推荐

