FTP 出站通道适配器

FTP 出站通道适配器依赖于一个 `MessageHandler` 实现,该实现连接到 FTP 服务器,并为接收到的每条消息的载荷中的文件启动 FTP 传输。它还支持文件的多种表示形式,因此您不仅限于 `java.io.File` 类型的载荷。FTP 出站通道适配器支持以下载荷类型:

  • `java.io.File`: 实际的文件对象

  • `byte[]`: 表示文件内容的字节数组

  • `java.lang.String`: 表示文件内容的文本

  • `java.io.InputStream`: 传输到远程文件的数据流

  • `org.springframework.core.io.Resource`: 用于传输到远程文件的数据资源

以下示例展示了如何配置 `outbound-channel-adapter`:

<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-generator` (实现了 `o.s.i.file.FileNameGenerator` 策略接口)、对 `session-factory` 的引用以及其他属性。您还可以看到一些 `*expression` 属性的示例,这些属性允许您使用 SpEL 配置设置,例如 `remote-directory-expression`、`temporary-remote-directory-expression` 和 `remote-filename-generator-expression` (SpEL 替代方案,对应前面的 `filename-generator`)。与任何允许使用 SpEL 的组件一样,可以通过 'payload' 和 'headers' 变量访问载荷和消息头。有关可用属性的更多详细信息,请参阅模式

默认情况下,如果没有指定文件命名生成器,Spring Integration 使用 `o.s.i.file.DefaultFileNameGenerator`。`DefaultFileNameGenerator` 根据 `MessageHeaders` 中 `file_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` 枚举定义,包括以下值:

  • `REPLACE` (默认) -> 替换 (默认)

  • REPLACE_IF_MODIFIED -> 如果修改则替换

  • APPEND -> 追加

  • APPEND_NO_FLUSH -> 追加不刷新

  • IGNORE -> 忽略

  • FAIL -> 失败

`IGNORE` 和 `FAIL` 不传输文件。`FAIL` 会抛出异常,而 `IGNORE` 会静默忽略传输(尽管会生成 `DEBUG` 日志条目)。

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

避免写入部分文件

处理文件传输时出现的一个常见问题是处理部分文件的可能性。也就是说,文件可能在传输实际完成之前就出现在文件系统中。

为了解决这个问题,Spring Integration FTP 适配器使用了一个通用算法:文件以临时名称传输,然后在完全传输后重命名。

默认情况下,每个正在传输的文件都会在文件系统中显示一个额外的后缀,默认情况下,该后缀为 `.writing`。您可以通过设置 `temporary-file-suffix` 属性来更改此后缀。

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

使用 Java 配置进行配置

以下 Spring Boot 应用程序展示了如何使用 Java 配置配置出站适配器:

@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 配置出站适配器:

@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);

    }

}