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。為了方便起見,SessioncloseableResource 標頭中提供,而 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 開始,您可以設定 SftpSimplePatternFileListFilterSftpRegexPatternFileListFilter,方法是將 alwaysAcceptDirectorties 設定為 true,以始終傳遞目錄。這樣做允許簡單模式的遞迴,如下列範例所示

<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[]Stringremote-filename-generator(或運算式)用於命名遠端檔案。其他可用的屬性包括 remote-directorytemporary-remote-directory 及其 *-expression 等效項:use-temporary-file-nameauto-create-directory。如需更多資訊,請參閱結構描述文件

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) 時的行為。支援的模式為 REPLACEAPPENDFAILIGNORE。為了向後相容性,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 上發生,則閘道擲回的 PartialSuccessException 具有 file1.txtsubdirzoo.txtderivedInput,以及 file1.txtpartialResults。其 cause 是另一個 PartialSuccessException,其 derivedInputfile2.txtfile3.txt,而 partialResultsfile2.txt