SFTP 出站通道介面卡

SFTP 出站通道介面卡是一個特殊的 MessageHandler,它連線到遠端目錄,併為接收到的每條以檔案作為負載(payload)的入站 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前面已描述)。表示式評估上下文將訊息作為其根物件,這使您能夠使用基於訊息中的資料(來自 'payload' 或 'headers')動態計算檔名或現有目錄路徑的表示式。在前面的示例中,我們使用一個表示式值定義了 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();
    }

}