如需使用最新稳定版本,请使用 Spring Integration 7.0.4spring-doc.cadn.net.cn

FTP 出站通道适配器

FTP 出站通道适配器依赖于一个 MessageHandler 实现,该实现连接到 FTP 服务器,并为每个接收到的消息负载中的文件发起 FTP 传输。 它还支持多种文件表示形式,因此您不仅限于使用 java.io.File 类型的负载。 FTP 出站通道适配器支持以下负载:spring-doc.cadn.net.cn

以下示例展示了如何配置一个 outbound-channel-adapterspring-doc.cadn.net.cn

<int-ftp:outbound-channel-adapter id="ftpOutbound"
    channel="ftpChannel"
    session-factory="ftpSessionFactory"
    charset="UTF-8"
    remote-file-separator="/"
    auto-create-directory="true"
    remote-directory-expression="headers['remote_dir']"
    temporary-remote-directory-expression="headers['temp_remote_dir']"
    filename-generator="fileNameGenerator"
    use-temporary-filename="true"
    chmod="600"
    mode="REPLACE"/>

上述配置展示了如何使用 outbound-channel-adapter 元素配置 FTP 出站通道适配器,同时为各种属性提供值,例如 filename-generatoro.s.i.file.FileNameGenerator 策略接口的一个实现)、对 session-factory 的引用以及其他属性。 您还可以看到一些 *expression 属性的示例,这些属性允许您使用 SpEL 来配置设置,如 remote-directory-expressiontemporary-remote-directory-expressionremote-filename-generator-expressionfilename-generator 的 SpEL 替代方案,如上例所示)。 与任何允许使用 SpEL 的组件一样,可以通过 'payload' 和 'headers' 变量访问负载和消息头。 有关可用属性的更多详细信息,请参阅 模式spring-doc.cadn.net.cn

默认情况下,如果未指定文件名生成器,Spring Integration 将使用 o.s.i.file.DefaultFileNameGeneratorDefaultFileNameGenerator 根据 MessageHeadersfile_name 头部的值(如果存在)来确定文件名;或者,如果消息的负载已经是 java.io.File,则使用该文件的原始名称。
定义某些值(例如 remote-directory)可能依赖于平台或 FTP 服务器。 例如,正如在 forum.spring.io/showthread.php?p=333478&posted=1#post333478 中报告的那样,在某些平台上,您必须在目录定义的末尾添加一个斜杠(例如,remote-directory="/thing1/thing2/" 而不是 remote-directory="/thing1/thing2")。

从版本 4.1 开始,您可以在传输文件时指定 mode。 默认情况下,现有文件将被覆盖。 这些模式由 FileExistsMode 枚举定义,包含以下值:spring-doc.cadn.net.cn

IGNOREFAIL 不会传输文件。 FAIL 会导致抛出异常,而 IGNORE 会静默忽略传输(尽管会产生一条 DEBUG 日志条目)。spring-doc.cadn.net.cn

版本 5.2 引入了 chmod 属性,您可以在上传后使用它来更改远程文件的权限。 您可以使用传统的 Unix 八进制格式(例如,600 仅允许文件所有者具有读写权限)。 在使用 Java 配置适配器时,可以使用 setChmodOctal("600")setChmod(0600)。 仅当您的 FTP 服务器支持 SITE CHMOD 子命令时才适用。spring-doc.cadn.net.cn

避免写入部分文件

在处理文件传输时,一个常见的问题是可能会处理到不完整的文件。 也就是说,文件可能在传输尚未真正完成时就出现在文件系统中。spring-doc.cadn.net.cn

为了解决这个问题,Spring Integration FTP 适配器使用了一种通用算法:文件在完全传输之前会先以临时名称进行传输,传输完成后再进行重命名。spring-doc.cadn.net.cn

默认情况下,每个正在传输的文件在文件系统中都会带有一个额外的后缀,默认值为 .writing。 您可以通过设置 temporary-file-suffix 属性来更改此后缀。spring-doc.cadn.net.cn

然而,可能存在您不想使用此技术的情况(例如,如果服务器不允许重命名文件)。 对于此类情况,您可以通过将 use-temporary-file-name 设置为 false 来禁用此功能(默认值为 true)。 当此属性为 false 时,文件将以最终名称写入,且消费应用程序需要其他机制来检测文件在访问前是否已完全上传。spring-doc.cadn.net.cn

使用 Java 配置进行配置

以下 Spring Boot 应用程序展示了如何使用 Java 配置来配置出站适配器:spring-doc.cadn.net.cn

@SpringBootApplication
@IntegrationComponentScan
public class FtpJavaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context =
                    new SpringApplicationBuilder(FtpJavaApplication.class)
                        .web(false)
                        .run(args);
        MyGateway gateway = context.getBean(MyGateway.class);
        gateway.sendToFtp(new File("/foo/bar.txt"));
    }

    @Bean
    public SessionFactory<FTPFile> ftpSessionFactory() {
        DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        sf.setTestSession(true);
        return new CachingSessionFactory<FTPFile>(sf);
    }

    @Bean
    @ServiceActivator(inputChannel = "ftpChannel")
    public MessageHandler handler() {
        FtpMessageHandler handler = new FtpMessageHandler(ftpSessionFactory());
        handler.setRemoteDirectoryExpressionString("headers['remote-target-dir']");
        handler.setFileNameGenerator(new FileNameGenerator() {

            @Override
            public String generateFileName(Message<?> message) {
                 return "handlerContent.test";
            }

        });
        return handler;
    }

    @MessagingGateway
    public interface MyGateway {

         @Gateway(requestChannel = "toFtpChannel")
         void sendToFtp(File file);

    }
}

使用 Java DSL 进行配置

以下 Spring Boot 应用程序展示了如何使用 Java DSL 配置出站适配器:spring-doc.cadn.net.cn

@SpringBootApplication
@IntegrationComponentScan
public class FtpJavaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            new SpringApplicationBuilder(FtpJavaApplication.class)
                .web(false)
                .run(args);
        MyGateway gateway = context.getBean(MyGateway.class);
        gateway.sendToFtp(new File("/foo/bar.txt"));
    }

    @Bean
    public SessionFactory<FTPFile> ftpSessionFactory() {
        DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        sf.setTestSession(true);
        return new CachingSessionFactory<FTPFile>(sf);
    }

    @Bean
    public IntegrationFlow ftpOutboundFlow() {
        return IntegrationFlow.from("toFtpChannel")
                .handle(Ftp.outboundAdapter(ftpSessionFactory(), FileExistsMode.FAIL)
                        .useTemporaryFileName(false)
                        .fileNameExpression("headers['" + FileHeaders.FILENAME + "']")
                        .remoteDirectory(this.ftpServer.getTargetFtpDirectory().getName())
                ).get();
    }

    @MessagingGateway
    public interface MyGateway {

         @Gateway(requestChannel = "toFtpChannel")
         void sendToFtp(File file);

    }

}