FTP 出站通道介面卡
FTP 出站通道介面卡依賴於一個 `MessageHandler` 實現,該實現連線到 FTP 伺服器,併為接收到的每條訊息的載荷中的檔案啟動 FTP 傳輸。它還支援檔案的多種表示形式,因此您不僅限於 `java.io.File` 型別的載荷。FTP 出站通道介面卡支援以下載荷型別:
-
`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-generator` (實現了 `o.s.i.file.FileNameGenerator` 策略介面)、對 `session-factory` 的引用以及其他屬性。您還可以看到一些 `*expression` 屬性的示例,這些屬性允許您使用 SpEL 配置設定,例如 `remote-directory-expression`、`temporary-remote-directory-expression` 和 `remote-filename-generator-expression` (SpEL 替代方案,對應前面的 `filename-generator`)。與任何允許使用 SpEL 的元件一樣,可以透過 'payload' 和 'headers' 變數訪問載荷和訊息頭。有關可用屬性的更多詳細資訊,請參閱模式。
預設情況下,如果沒有指定檔案命名生成器,Spring Integration 使用 `o.s.i.file.DefaultFileNameGenerator`。`DefaultFileNameGenerator` 根據 `MessageHeaders` 中 `file_name` 頭的值(如果存在)確定檔名,或者,如果訊息的載荷已經是 `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 -> 失敗
`IGNORE` 和 `FAIL` 不傳輸檔案。`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);
}
}