FTP 輸出閘道

FTP 輸出閘道提供一組有限的指令,用於與遠端 FTP 或 FTPS 伺服器互動。支援的指令如下:

  • ls (列出檔案)

  • nlst (列出檔案名稱)

  • get (擷取檔案)

  • mget (擷取檔案)

  • rm (移除檔案)

  • mv (移動/重新命名檔案)

  • put (傳送檔案)

  • mput (傳送多個檔案)

使用 ls 指令

ls 列出遠端檔案並支援以下選項

  • -1:擷取檔案名稱清單。預設為擷取 FileInfo 物件清單。

  • -a:包含所有檔案 (包括以 '.' 開頭的檔案)

  • -f:不排序清單

  • -dirs:包含目錄 (預設排除)

  • -links:包含符號連結 (預設排除)

  • -R:遞迴列出遠端目錄

此外,也提供檔案名稱篩選,方式與 inbound-channel-adapter 相同。請參閱 FTP 輸入通道配接器

ls 作業產生的訊息酬載是檔案名稱清單或 FileInfo 物件清單。這些物件提供修改時間、權限和其他詳細資訊等資訊。

ls 指令作用的遠端目錄會在 file_remoteDirectory 標頭中提供。

使用遞迴選項 (-R) 時,fileName 會包含任何子目錄元素,代表檔案的相對路徑 (相對於遠端目錄)。如果包含 -dirs 選項,則每個遞迴目錄也會以清單中的元素傳回。在這種情況下,建議您不要使用 -1 選項,因為您將無法區分檔案和目錄,而使用 FileInfo 物件則可以區分。

從 4.3 版開始,FtpSession 支援 list()listNames() 方法的 null 值。因此,您可以省略 expression 屬性。為了方便起見,Java 設定有兩個不含 expression 引數的建構函式。或 LSNLSTPUTMPUT 指令,null 會根據 FTP 協定視為用戶端工作目錄。所有其他指令都必須提供 expression,以針對請求訊息評估遠端路徑。當您擴充 DefaultFtpSessionFactory 並實作 postProcessClientAfterConnect() 回呼時,可以使用 FTPClient.changeWorkingDirectory() 函數設定工作目錄。

使用 nlst 指令

版本 5 引入了對 nlst 指令的支援。

nlst 列出遠端檔案名稱,且僅支援一個選項

  • -f:不排序清單

nlst 作業產生的訊息酬載是檔案名稱清單。

nlst 指令作用的遠端目錄會在 file_remoteDirectory 標頭中提供。

ls 指令-1 選項 (使用 LIST 指令) 不同,nlst 指令會將 NLST 指令傳送至目標 FTP 伺服器。當伺服器不支援 LIST 時 (例如,由於安全性限制),此指令很有用。nlst 作業的結果是不包含其他詳細資訊的名稱。因此,框架無法判斷實體是否為目錄,以執行篩選或遞迴清單等作業。

使用 get 指令

get 擷取遠端檔案。它支援以下選項

  • -P:保留遠端檔案的時間戳記。

  • -stream:將遠端檔案擷取為串流。

  • -D:在成功傳輸後刪除遠端檔案。如果傳輸遭到忽略,則不會刪除遠端檔案,因為 FileExistsModeIGNORE 且本機檔案已存在。

file_remoteDirectory 標頭提供遠端目錄名稱,而 file_remoteFile 標頭提供檔案名稱。

get 作業產生的訊息酬載是代表擷取檔案的 File 物件,或是在您使用 -stream 選項時為 InputStream-stream 選項允許將檔案擷取為串流。對於文字檔,常見的用例是將此作業與檔案分割器串流轉換器結合使用。當以串流方式取用遠端檔案時,您有責任在串流取用後關閉 Session。為了方便起見,SessioncloseableResource 標頭中提供,您可以使用 IntegrationMessageHeaderAccessor 上的便利方法存取。以下範例示範如何使用便利方法

Closeable closeable = new IntegrationMessageHeaderAccessor(message).getCloseableResource();
if (closeable != null) {
    closeable.close();
}

框架元件 (例如檔案分割器串流轉換器) 會在資料傳輸後自動關閉會期。

以下範例示範如何以串流方式取用檔案

<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
                            request-channel="inboundGetStream"
                            command="get"
                            command-options="-stream"
                            expression="payload"
                            remote-directory="ftpTarget"
                            reply-channel="stream" />

<int-file:splitter input-channel="stream" output-channel="lines" />
如果您在自訂元件中取用輸入串流,則必須關閉 Session。您可以在自訂程式碼中執行此操作,或將訊息副本路由至 service-activator 並使用 SpEL 來執行,如下列範例所示
<int:service-activator input-channel="closeSession"
    expression="headers['closeableResource'].close()" />

使用 mget 指令

mget 根據模式擷取多個遠端檔案,並支援以下選項

  • -P:保留遠端檔案的時間戳記。

  • -R:以遞迴方式擷取整個目錄樹狀結構。

  • -x:如果沒有檔案符合模式,則擲回例外狀況 (否則會傳回空白清單)。

  • -D:在成功傳輸後刪除每個遠端檔案。如果傳輸遭到忽略,則不會刪除遠端檔案,因為 FileExistsModeIGNORE 且本機檔案已存在。

mget 作業產生的訊息酬載是 List<File> 物件 (亦即,File 物件的 List,每個物件代表一個擷取的檔案)。

從 5.0 版開始,如果 FileExistsModeIGNORE,則輸出訊息的酬載不再包含由於檔案已存在而未擷取的檔案。先前,清單包含所有檔案,包括已存在的檔案。

用於判斷遠端路徑的運算式應產生以 結尾的結果 - 例如,somedir/ 將擷取 somedir 下的完整樹狀結構。

從 5.0 版開始,遞迴 mget 與新的 FileExistsMode.REPLACE_IF_MODIFIED 模式結合使用,可用於定期在本機同步整個遠端目錄樹狀結構。無論 -P (保留時間戳記) 選項為何,此模式都會以遠端時間戳記取代本機檔案的上次修改時間戳記。

使用遞迴 (-R)

模式會遭到忽略,且假設為 *。預設情況下,會擷取整個遠端樹狀結構。但是,可以透過提供 FileListFilter 來篩選樹狀結構中的檔案。樹狀結構中的目錄也可以透過這種方式篩選。FileListFilter 可以透過參考、filename-patternfilename-regex 屬性提供。例如,filename-regex="(subDir|.*1.txt)" 會擷取遠端目錄中以 1.txt 結尾的所有檔案,以及 subDir 子目錄。但是,下一個範例顯示了替代方案,版本 5.0 提供了此替代方案。

如果篩選了子目錄,則不會執行該子目錄的其他遍歷。

不允許使用 -dirs 選項 (遞迴 mget 使用遞迴 ls 取得目錄樹狀結構,因此目錄本身無法包含在清單中)。

通常,您會在 local-directory-expression 中使用 #remoteDirectory 變數,以便在本機保留遠端目錄結構。

持久檔案清單篩選器現在具有布林值屬性 forRecursion。將此屬性設定為 true,也會設定 alwaysAcceptDirectories,這表示輸出閘道 (lsmget) 上的遞迴作業現在每次都會遍歷完整的目錄樹狀結構。這是為了解決目錄樹狀結構深處的變更未偵測到的問題。此外,forRecursion=true 會導致完整檔案路徑用作中繼資料儲存區金鑰;這解決了如果具有相同名稱的檔案在不同目錄中多次出現,篩選器無法正常運作的問題。重要事項:這表示在持久中繼資料儲存區中的現有金鑰將不會針對最上層目錄下方的檔案找到。基於這個原因,此屬性預設為 false;這可能會在未來的版本中變更。

從 5.0 版開始,可以設定 FtpSimplePatternFileListFilterFtpRegexPatternFileListFilter,方法是將 alwaysAcceptDirectories 屬性設定為 true,以始終傳遞目錄。這樣做可讓簡單模式進行遞迴,如下列範例所示

<bean id="starDotTxtFilter"
        class="org.springframework.integration.ftp.filters.FtpSimplePatternFileListFilter">
    <constructor-arg value="*.txt" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>

<bean id="dotStarDotTxtFilter"
            class="org.springframework.integration.ftp.filters.FtpRegexPatternFileListFilter">
    <constructor-arg value="^.*\.txt$" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>

定義如先前範例中的篩選器後,您可以透過在閘道上設定 filter 屬性來使用其中一個篩選器。

使用 put 指令

put 指令會將檔案傳送至遠端伺服器。訊息的酬載可以是 java.io.Filebyte[]Stringremote-filename-generator (或運算式) 用於命名遠端檔案。其他可用的屬性包括 remote-directorytemporary-remote-directory 及其 *-expression 對等項目:use-temporary-file-nameauto-create-directory。請參閱 結構描述 文件以取得更多資訊。

put 作業產生的訊息酬載是 String,代表傳輸後伺服器上檔案的完整路徑。

版本 5.2 引入了 chmod 屬性,可在上傳後變更遠端檔案權限。您可以使用傳統的 Unix 八進位格式 (例如,600 僅允許檔案擁有者讀寫)。使用 java 設定配接器時,可以使用 setChmod(0600)。僅在您的 FTP 伺服器支援 SITE CHMOD 子指令時適用。

使用 mput 指令

mput 會將多個檔案傳送至伺服器,且僅支援一個選項

  • -R:遞迴。傳送目錄及其子目錄中的所有檔案 (可能已篩選)。

訊息酬載必須是 java.io.File (或 String),代表本機目錄。從 5.1 版開始,也支援 FileString 的集合。

此指令支援與 put 指令 相同的屬性。此外,可以使用 mput-patternmput-regexmput-filtermput-filter-expression 之一篩選本機目錄中的檔案。只要子目錄本身通過篩選器,篩選器就會與遞迴一起運作。未通過篩選器的子目錄不會遞迴。

mput 作業產生的訊息酬載是 List<String> 物件 (亦即,傳輸產生的遠端檔案路徑 List)。

版本 5.2 引入了 chmod 屬性,可讓您在上傳後變更遠端檔案權限。您可以使用傳統的 Unix 八進位格式 (例如,600 僅允許檔案擁有者讀寫)。使用 Java 設定配接器時,可以使用 setChmodOctal("600")setChmod(0600)。僅在您的 FTP 伺服器支援 SITE CHMOD 子指令時適用。

使用 rm 指令

rm 指令會移除檔案。

rm 指令沒有選項。

如果移除成功,rm 作業產生的訊息酬載為 Boolean.TRUE,否則為 Boolean.FALSEfile_remoteDirectory 標頭提供遠端目錄,而 file_remoteFile 標頭提供檔案名稱。

使用 mv 指令

mv 指令會移動檔案。

mv 指令沒有選項。

expression 屬性定義「來源」路徑,而 rename-expression 屬性定義「目標」路徑。預設情況下,rename-expressionheaders['file_renameTo']。此運算式不得評估為 null 或空白 String。如有必要,將建立任何必要的遠端目錄。結果訊息的酬載為 Boolean.TRUEfile_remoteDirectory 標頭提供原始遠端目錄,而 file_remoteFile 標頭提供檔案名稱。新路徑位於 file_renameTo 標頭中。

從 5.5.6 版開始,remoteDirectoryExpression 可在 mv 指令中方便使用。如果「來源」檔案不是完整檔案路徑,則 remoteDirectoryExpression 的結果會用作遠端目錄。這同樣適用於「目標」檔案,例如,如果任務只是重新命名某個目錄中的遠端檔案。

關於 FTP 輸出閘道指令的其他資訊

getmget 指令支援 local-filename-generator-expression 屬性。它定義 SpEL 運算式,以在傳輸期間產生本機檔案的名稱。評估內容的根物件是請求訊息。remoteFileName 變數 (對於 mget 特別有用) 也可用 - 例如,local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.something"

getmget 指令支援 local-directory-expression 屬性。它定義 SpEL 運算式,以在傳輸期間產生本機目錄的名稱。評估內容的根物件是請求訊息,但 remoteDirectory 變數 (對於 mget 特別有用) 也可用 - 例如:local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.something"。此屬性與 local-directory 屬性互斥。

對於所有指令,閘道的 'expression' 屬性提供指令作用的路徑。對於 mget 指令,運算式可能會評估為 '',表示擷取所有檔案,或 'somedirectory/' 等等。

以下範例顯示為 ls 指令設定的閘道

<int-ftp:outbound-gateway id="gateway1"
    session-factory="ftpSessionFactory"
    request-channel="inbound1"
    command="ls"
    command-options="-1"
    expression="payload"
    reply-channel="toSplitter"/>

傳送至 toSplitter 通道的訊息酬載是 String 物件的清單,每個物件都包含檔案名稱。如果省略 command-options 屬性,則會保留 FileInfo 物件。它使用空格分隔的選項 - 例如,command-options="-1 -dirs -links"

從 4.2 版開始,GETMGETPUTMPUT 指令支援 FileExistsMode 屬性 (使用命名空間支援時為 mode)。這會影響本機檔案存在 (GETMGET) 或遠端檔案存在 (PUTMPUT) 時的行為。支援的模式為 REPLACEAPPENDFAILIGNORE。為了向後相容性,PUTMPUT 作業的預設模式為 REPLACE。對於 GETMGET 作業,預設為 FAIL

從 5.0 版本開始,`FtpOutboundGateway` (XML 中的 <int-ftp:outbound-gateway>) 上提供了 setWorkingDirExpression() (XML 中的 working-dir-expression) 選項。它讓您可以在執行時期變更用戶端工作目錄。此運算式會針對請求訊息進行評估。先前的工作目錄會在每次閘道器操作後還原。

使用 Java 配置進行設定

以下 Spring Boot 應用程式展示了如何使用 Java 配置來設定輸出閘道器的範例

@SpringBootApplication
public class FtpJavaApplication {

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

    @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() {
        FtpOutboundGateway ftpOutboundGateway =
                          new FtpOutboundGateway(ftpSessionFactory(), "ls", "'my_remote_dir/'");
        ftpOutboundGateway.setOutputChannelName("lsReplyChannel");
        return ftpOutboundGateway;
    }

}

使用 Java DSL 進行設定

以下 Spring Boot 應用程式展示了如何使用 Java DSL 來設定輸出閘道器的範例

@SpringBootApplication
public class FtpJavaApplication {

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

    @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 FtpOutboundGatewaySpec ftpOutboundGateway() {
        return Ftp.outboundGateway(ftpSessionFactory(),
            AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
            .options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
            .regexFileNameFilter("(subFtpSource|.*1.txt)")
            .localDirectoryExpression("'localDirectory/' + #remoteDirectory")
            .localFilenameExpression("#remoteFileName.replaceFirst('ftpSource', 'localTarget')");
    }

    @Bean
    public IntegrationFlow ftpMGetFlow(AbstractRemoteFileOutboundGateway<FTPFile> ftpOutboundGateway) {
        return f -> f
            .handle(ftpOutboundGateway)
            .channel(c -> c.queue("remoteFileOutputChannel"));
    }

}

輸出閘道器部分成功 (mgetmput)

當您對多個檔案執行操作 (透過使用 mgetmput) 時,在一個或多個檔案傳輸後的一段時間,可能會發生例外狀況。在這種情況下 (從 4.2 版本開始),會拋出 PartialSuccessException。除了常見的 MessagingException 屬性 (failedMessagecause) 之外,此例外狀況還有兩個額外屬性

  • partialResults: 成功傳輸的結果。

  • derivedInput: 從請求訊息產生的檔案列表 (例如,用於 mput 的本地檔案傳輸)。

這些屬性讓您可以判斷哪些檔案已成功傳輸,哪些檔案未成功傳輸。

在遞迴 mput 的情況下,PartialSuccessException 可能會有巢狀的 PartialSuccessException 發生。

考慮以下目錄結構

root/
|- file1.txt
|- subdir/
   | - file2.txt
   | - file3.txt
|- zoo.txt

如果例外狀況發生在 file3.txt 上,則閘道器拋出的 PartialSuccessExceptionderivedInputfile1.txtsubdirzoo.txt,而 partialResultsfile1.txt。其 cause 是另一個 PartialSuccessException,其 derivedInputfile2.txtfile3.txt,而 partialResultsfile2.txt