FTP 輸出通道配接器

FTP 輸出通道配接器依賴 MessageHandler 實作,該實作連接到 FTP 伺服器,並為其接收到傳入訊息的 Payload 中的每個檔案啟動 FTP 傳輸。它也支援檔案的多種表示形式,因此您不僅限於 java.io.File 類型的 Payload。FTP 輸出通道配接器支援以下 Payload

  • 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-generatoro.s.i.file.FileNameGenerator 策略介面的實作)、對 session-factory 的參考以及其他屬性。您也可以看到一些 *expression 屬性的範例,這些屬性可讓您使用 SpEL 來設定諸如 remote-directory-expressiontemporary-remote-directory-expressionremote-filename-generator-expressionfilename-generator 的 SpEL 替代方案,如先前範例所示)之類的設定。與任何允許使用 SpEL 的元件一樣,可以透過 'payload' 和 'headers' 變數存取 Payload 和訊息標頭。有關可用屬性的更多詳細資訊,請參閱schema

預設情況下,如果未指定檔案名稱產生器,Spring Integration 會使用 o.s.i.file.DefaultFileNameGeneratorDefaultFileNameGenerator 根據 MessageHeadersfile_name 標頭的值(如果存在)判斷檔案名稱,或者,如果訊息的 Payload 已經是 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

IGNOREFAIL 不會傳輸檔案。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);

    }

}