SFTP 出站閘道器

SFTP 出站閘道器提供了一組有限的命令,允許您與遠端 SFTP 伺服器互動

  • ls (列出檔案)

  • nlst (列出檔名)

  • get (檢索檔案)

  • mget (檢索多個檔案)

  • rm (刪除檔案)

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

  • put (傳送檔案)

  • mput (傳送多個檔案)

使用 ls 命令

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

  • -1: 檢索檔名列表。預設是檢索 FileInfo 物件列表

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

  • -f: 不對列表進行排序

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

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

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

此外,還提供了與 inbound-channel-adapter 類似的檔名過濾功能。

ls 操作產生的訊息有效載荷是檔名列表或 FileInfo 物件列表(取決於您是否使用 -1 選項)。這些物件提供修改時間、許可權等資訊。

ls 命令操作的遠端目錄會放在 file_remoteDirectory 頭部。

使用遞迴選項 (-R) 時,fileName 會包含任何子目錄元素,並表示檔案的相對路徑(相對於遠端目錄)。如果您使用 -dirs 選項,每個遞迴目錄也會作為列表中的一個元素返回。在這種情況下,我們建議您不要使用 -1 選項,因為那樣您將無法區分檔案和目錄,而使用 FileInfo 物件時可以做到這一點。

如果列出的遠端路徑以 / 符號開頭,SFTP 會將其視為絕對路徑;否則,將其視為當前使用者主目錄中的相對路徑。

使用 nlst 命令

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

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

  • -f: 不對列表進行排序

nlst 操作產生的訊息有效載荷是檔名列表。

file_remoteDirectory 頭部儲存了 nlst 命令操作的遠端目錄。

SFTP 協議本身不提供列出名稱的功能。此命令等同於帶有 -1 選項的 ls 命令,此處新增是為了方便。

使用 get 命令

get 檢索遠端檔案並支援以下選項

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

  • -stream: 將遠端檔案檢索為流。

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

file_remoteDirectory 頭部儲存遠端目錄,file_remoteFile 頭部儲存檔名。

get 操作產生的訊息有效載荷是一個表示已檢索檔案的 File 物件。如果您使用 -stream 選項,有效載荷將是 InputStream 而不是 File。對於文字檔案,一個常見的使用場景是將此操作與檔案拆分器流轉換器結合使用。將遠端檔案作為流消費時,您有責任在流消費後關閉 Session。為了方便,Session 會提供在 closeableResource 頭部,並且 IntegrationMessageHeaderAccessor 提供了便捷方法

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

框架元件,例如檔案拆分器流轉換器,在資料傳輸後會自動關閉會話。

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

<int-sftp: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,輸出訊息的有效載荷將不再包含因檔案已存在而未獲取的檔案。之前,陣列包含所有檔案,包括已存在的檔案。

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

從版本 5.0 開始,您可以結合遞迴 MGETFileExistsMode.REPLACE_IF_MODIFIED 模式,定期將整個遠端目錄樹同步到本地。此模式會將本地檔案的最後修改時間戳設定為遠端檔案的時間戳,而不管 -P(保留時間戳)選項如何。

使用遞迴 (-R) 時的注意事項

模式將被忽略,並假定為 *。預設情況下,檢索整個遠端樹。但是,您可以透過提供 FileListFilter 來過濾樹中的檔案。您也可以透過這種方式過濾樹中的目錄。FileListFilter 可以透過引用或透過 filename-patternfilename-regex 屬性提供。例如,filename-regex="(subDir|.*1.txt)" 會檢索遠端目錄和子目錄 subDir 中所有以 1.txt 結尾的檔案。但是,在本註釋之後,我們將描述另一種可用的替代方法。

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

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

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

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

從版本 5.0 開始,您可以透過將 alwaysAcceptDirectorties 設定為 true 來配置 SftpSimplePatternFileListFilterSftpRegexPatternFileListFilter 始終透過目錄。這樣做允許簡單模式下的遞迴,如下例所示

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

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

您可以透過在閘道器上使用 filter 屬性來提供這些過濾器之一。

使用 put 命令

put 將檔案傳送到遠端伺服器。訊息的有效載荷可以是 java.io.Filebyte[]String。使用 remote-filename-generator(或表示式)來命名遠端檔案。其他可用屬性包括 remote-directorytemporary-remote-directory 及其 *-expression 等價物:use-temporary-file-nameauto-create-directory。有關詳細資訊,請參見schema 文件

put 操作產生的訊息有效載荷是一個 String,其中包含傳輸後文件在伺服器上的完整路徑。

版本 4.3 引入了 chmod 屬性,它在上傳後更改遠端檔案許可權。您可以使用傳統的 Unix 八進位制格式(例如,600 僅允許檔案所有者讀寫)。使用 Java 配置介面卡時,可以使用 setChmod(0600)

使用 mput 命令

mput 將多個檔案傳送到伺服器,並支援以下選項

  • -R: 遞迴 — 傳送目錄和子目錄中所有檔案(可能已過濾)

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

支援與put 命令相同的屬性。此外,您可以使用 mput-patternmput-regexmput-filtermput-filter-expression 之一過濾本地目錄中的檔案。只要子目錄本身透過過濾器,過濾器就可以與遞迴一起使用。未透過過濾器的子目錄不會被遞迴。

mput 操作產生的訊息有效載荷是一個 List<String> 物件(即傳輸後生成的遠端檔案路徑的 List)。

版本 4.3 引入了 chmod 屬性,它允許您在上傳後更改遠端檔案許可權。您可以使用傳統的 Unix 八進位制格式(例如,600 僅允許檔案所有者讀寫)。使用 Java 配置介面卡時,可以使用 setChmodOctal("600")setChmod(0600)

使用 rm 命令

rm 命令沒有選項。

如果刪除操作成功,結果訊息有效載荷為 Boolean.TRUE。否則,訊息有效載荷為 Boolean.FALSEfile_remoteDirectory 頭部儲存遠端目錄,file_remoteFile 頭部儲存檔名。

使用 mv 命令

mv 命令沒有選項。

expression 屬性定義“來源”路徑,rename-expression 屬性定義“目標”路徑。預設情況下,rename-expressionheaders['file_renameTo']。此表示式不得評估為 null 或空 String。如果需要,將建立所需的任何遠端目錄。結果訊息的有效載荷是 Boolean.TRUEfile_remoteDirectory 頭部儲存原始遠端目錄,file_remoteFile 頭部儲存檔名。file_renameTo 頭部儲存新路徑。

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

附加命令資訊

getmget 命令支援 local-filename-generator-expression 屬性。它定義了一個 SpEL 表示式,用於在傳輸過程中生成本地檔案的名稱。評估上下文的根物件是請求訊息。remoteFileName 變數也可用。它對 mget 特別有用(例如:local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo")。

getmget 命令支援 local-directory-expression 屬性。它定義了一個 SpEL 表示式,用於在傳輸過程中生成本地目錄的名稱。評估上下文的根物件是請求訊息。remoteDirectory 變數也可用。它對 mget 特別有用(例如:local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.myheader")。此屬性與 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="-1",有效載荷將是 FileInfo 物件列表。您可以將選項作為空格分隔的列表提供(例如,command-options="-1 -dirs -links")。

從版本 4.2 開始,GETMGETPUTMPUT 命令支援 FileExistsMode 屬性(使用名稱空間支援時為 mode)。這會影響本地檔案存在(GETMGET)或遠端檔案存在(PUTMPUT)時的行為。支援的模式有 REPLACE(替換)、APPEND(追加)、FAIL(失敗)和 IGNORE(忽略)。為了向後相容,PUTMPUT 操作的預設模式是 REPLACE。對於 GETMGET 操作,預設模式是 FAIL

使用 Java 配置進行配置

以下 Spring Boot 應用展示瞭如何使用 Java 配置出站閘道器的示例

@SpringBootApplication
public class SftpJavaApplication {

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

    @Bean
    @ServiceActivator(inputChannel = "sftpChannel")
    public MessageHandler handler() {
        return new SftpOutboundGateway(ftpSessionFactory(), "ls", "'my_remote_dir/'");
    }

}

使用 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 SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
        DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        factory.setTestSession(true);
        return new CachingSessionFactory<>(sf);
    }

    @Bean
    public QueueChannelSpec remoteFileOutputChannel() {
        return MessageChannels.queue();
    }

    @Bean
    public IntegrationFlow sftpMGetFlow() {
        return IntegrationFlow.from("sftpMgetInputChannel")
            .handle(Sftp.outboundGateway(sftpSessionFactory(),
                            AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
                    .options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
                    .regexFileNameFilter("(subSftpSource|.*1.txt)")
                    .localDirectoryExpression("'myDir/' + #remoteDirectory")
                    .localFilenameExpression("#remoteFileName.replaceFirst('sftpSource', 'localTarget')"))
            .channel("remoteFileOutputChannel")
            .get();
    }

}

出站閘道器部分成功(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 上發生異常,由閘道器丟擲的 PartialSuccessExceptionderivedInput 將包含 file1.txtsubdirzoo.txt,並且 partialResults 包含 file1.txt。它的 cause 是另一個 PartialSuccessException,該異常的 derivedInput 包含 file2.txtfile3.txt,並且 partialResults 包含 file2.txt