You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:30:11