FTP 出站閘道器

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

  • ls (列出檔案)

  • nlst (列出檔名)

  • get (檢索檔案)

  • mget (檢索檔案(s))

  • rm (刪除檔案(s))

  • 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 命令,根據 FTP 協議,null 被視為客戶端工作目錄。所有其他命令必須提供 expression 以根據請求訊息評估遠端路徑。當您擴充套件 DefaultFtpSessionFactory 並實現 postProcessClientAfterConnect() 回撥時,可以使用 FTPClient.changeWorkingDirectory() 函式設定工作目錄。

使用 nlst 命令

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

nlst 列出遠端檔名,僅支援一個選項:

  • -f:不排序列表。

nlst 操作產生的訊息負載是檔名稱列表。

nlst 命令操作的遠端目錄在 file_remoteDirectory 訊息頭中提供。

與使用 LIST 命令的 ls 命令-1 選項不同,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();
}

檔案分發器流轉換器等框架元件在資料傳輸後會自動關閉 session。

以下示例展示瞭如何將檔案作為流消費:

<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 物件列表,每個物件表示一個檢索到的檔案)。

從版本 5.0 開始,如果 FileExistsModeIGNORE,輸出訊息的負載將不再包含由於檔案已存在而未獲取的檔案。此前,列表包含所有檔案,包括已存在的檔案。

用於確定遠端路徑的表示式應產生一個以 * 結尾的結果,例如 somedir/* 將獲取 somedir 下的完整樹。

從版本 5.0 開始,遞迴 mget 與新的 FileExistsMode.REPLACE_IF_MODIFIED 模式相結合,可用於定期在本地同步整個遠端目錄樹。無論 -P(保留時間戳)選項如何,此模式都會用遠端時間戳替換本地檔案的最後修改時間戳。

使用遞迴 (-R)

模式被忽略,並假定為 *。預設情況下,檢索整個遠端樹。但是,可以透過提供 FileListFilter 來過濾樹中的檔案。也可以以這種方式過濾樹中的目錄。FileListFilter 可以透過引用、filename-patternfilename-regex 屬性提供。例如,filename-regex="(subDir|.*1.txt)" 會檢索遠端目錄及其子目錄 subDir 中所有以 1.txt 結尾的檔案。然而,下一個示例展示了版本 5.0 提供的一種替代方法。

如果過濾了子目錄,則不會對該子目錄進行額外遍歷。

不允許使用 -dirs 選項(遞迴 mget 使用遞迴 ls 獲取目錄樹,因此列表本身不能包含目錄)。

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

持久化檔案列表過濾器現在有一個布林屬性 forRecursion。將此屬性設定為 true 也會設定 alwaysAcceptDirectories,這意味著對出站閘道器(lsmget)的遞迴操作現在每次都會遍歷完整的目錄樹。這是為了解決未檢測到目錄樹深層更改的問題。此外,forRecursion=true 會導致使用檔案的完整路徑作為元資料儲存鍵;這解決了如果同一名稱的檔案出現在不同目錄中多次時過濾器無法正常工作的問題。重要提示:這意味著對於頂層目錄以下的檔案,將不會在持久化元資料儲存中找到現有鍵。因此,此屬性預設為 false;這可能會在將來的版本中更改。

從版本 5.0 開始,透過將 alwaysAcceptDirectories 屬性設定為 true,可以配置 FtpSimplePatternFileListFilterFtpRegexPatternFileListFilter 以始終允許目錄透過。這樣做允許對簡單模式進行遞迴,如下例所示:

<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(或 expression)用於命名遠端檔案。其他可用屬性包括 remote-directorytemporary-remote-directory 及其 *-expression 等效屬性:use-temporary-file-nameauto-create-directory。有關更多資訊,請參見schema 文件。

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> 物件(即一個遠端檔案路徑列表,是傳輸結果)。

版本 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 表示式,用於在傳輸期間生成本地檔案的名稱。評估上下文的根物件是請求訊息。對於 mget 特別有用的 remoteFileName 變數也可用,例如 local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.something"

getmget 命令支援 local-directory-expression 屬性。它定義了一個 SpEL 表示式,用於在傳輸期間生成本地目錄的名稱。評估上下文的根物件是請求訊息,但對於 mget 特別有用的 remoteDirectory 變數也可用,例如:local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.something"。此屬性與 local-directory 屬性互斥。

對於所有命令,閘道器的 'expression' 屬性提供了命令作用的路徑。對於 mget 命令,expression 可能計算為 '*',表示檢索所有檔案,或 '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 上提供了 setWorkingDirExpression()(XML 中為 working-dir-expression)選項(XML 中為 <int-ftp:outbound-gateway>)。它允許您在執行時更改客戶端工作目錄。表示式根據請求訊息進行評估。在每次閘道器操作後,會恢復之前的工作目錄。

使用 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.txtpartialResultsfile1.txt。其 cause 是另一個 PartialSuccessException,其 derivedInputfile2.txtfile3.txtpartialResultsfile2.txt