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 運算式語言 (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();
    }

}