SFTP 出站通道适配器

SFTP 出站通道适配器是一种特殊的 MessageHandler,它连接到远程目录,并为接收到的每个文件(作为传入 Message 的有效载荷)启动文件传输。 它还支持文件的多种表示形式,因此您不限于 File 对象。 与 FTP 出站适配器类似,SFTP 出站通道适配器支持以下有效载荷:

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

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

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

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

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

以下示例展示了如何配置 SFTP 出站通道适配器:

<int-sftp:outbound-channel-adapter id="sftpOutboundAdapter"
    session-factory="sftpSessionFactory"
    channel="inputChannel"
    charset="UTF-8"
    remote-file-separator="/"
    remote-directory="foo/bar"
    remote-filename-generator-expression="payload.getName() + '-mysuffix'"
    filename-generator="fileNameGenerator"
    use-temporary-filename="true"
    chmod="600"
    mode="REPLACE"/>

有关这些属性的更多详细信息,请参阅 schema

SpEL 和 SFTP 出站适配器

与 Spring Integration 中的许多其他组件一样,您可以通过指定两个属性来配置 SFTP 出站通道适配器时使用 Spring Expression Language (SpEL):remote-directory-expressionremote-filename-generator-expression (前面已描述)。 表达式评估上下文将消息作为其根对象,这允许您使用可以根据消息中的数据(来自“有效载荷”或“头部”)动态计算文件名或现有目录路径的表达式。 在前面的示例中,我们使用一个表达式值定义了 remote-filename-generator-expression 属性,该表达式根据文件的原始名称计算文件名,同时还附加一个后缀:'-mysuffix'。

从 4.1 版本开始,您可以在传输文件时指定 mode。 默认情况下,现有文件会被覆盖。 模式由 FileExistsMode 枚举定义,其中包括以下值:

  • REPLACE (默认)

  • REPLACE_IF_MODIFIED

  • APPEND

  • APPEND_NO_FLUSH

  • IGNORE

  • FAIL

使用 IGNOREFAIL 时,文件不会被传输。 FAIL 会抛出异常,而 IGNORE 则悄悄地忽略传输(尽管会生成 DEBUG 日志条目)。

4.3 版本引入了 chmod 属性,您可以使用它在上传后更改远程文件权限。 您可以使用传统的 Unix 八进制格式(例如,600 只允许文件所有者读写)。 在使用 Java 配置适配器时,您可以使用 setChmodOctal("600")setChmod(0600)

避免部分写入的文件

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

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

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

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

使用 Java 配置进行配置

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

@SpringBootApplication
@IntegrationComponentScan
public class SftpJavaApplication {

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

    @Bean
    public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
        DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
        factory.setHost("localhost");
        factory.setPort(port);
        factory.setUser("foo");
        factory.setPassword("foo");
        factory.setAllowUnknownKeys(true);
        factory.setTestSession(true);
        return new CachingSessionFactory<SftpClient.DirEntry>(factory);
    }

    @Bean
    @ServiceActivator(inputChannel = "toSftpChannel")
    public MessageHandler handler() {
        SftpMessageHandler handler = new SftpMessageHandler(sftpSessionFactory());
        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 = "toSftpChannel")
         void sendToSftp(File file);

    }
}

使用 Java DSL 进行配置

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

@SpringBootApplication
public class SftpJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SftpJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public IntegrationFlow sftpOutboundFlow() {
        return IntegrationFlow.from("toSftpChannel")
            .handle(Sftp.outboundAdapter(this.sftpSessionFactory, FileExistsMode.FAIL)
                         .useTemporaryFileName(false)
                         .remoteDirectory("/foo")
            ).get();
    }

}