跳到主要内容

FTP 外发通道适配器

QWen Plus 中英对照 FTP Outbound Channel Adapter

FTP outbound 通道适配器依赖于 MessageHandler 实现,该实现连接到 FTP 服务器,并为其接收到的每个文件发起一个 FTP 传输,这些文件位于传入消息的有效负载中。它还支持几种文件表示形式,因此您不仅限于 java.io.File 类型的有效负载。FTP outbound 通道适配器支持以下有效负载:

  • 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"/>
xml

前面的配置显示了如何使用 outbound-channel-adapter 元素来配置 FTP 外发通道适配器,同时为各种属性提供值,例如 filename-generatoro.s.i.file.FileNameGenerator 策略接口的一个实现)、对 session-factory 的引用以及其他属性。您还可以看到一些 *expression 属性的例子,这些属性允许您使用 SpEL 来配置诸如 remote-directory-expressiontemporary-remote-directory-expressionremote-filename-generator-expression(如上例所示,这是 filename-generator 的 SpEL 替代方案)等设置。与任何允许使用 SpEL 的组件一样,可以通过 'payload' 和 'headers' 变量访问有效负载和消息头。有关可用属性的更多详细信息,请参阅schema

备注

默认情况下,如果没有指定文件名生成器,Spring Integration 使用 o.s.i.file.DefaultFileNameGeneratorDefaultFileNameGenerator 根据 MessageHeaders 中的 file_name 头的值(如果存在)来确定文件名,或者,如果消息的有效载荷已经是 java.io.File,它将使用该文件的原始名称。

important

定义某些值(例如 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

IGNOREFAIL 不会传输文件。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 配置来配置 outbound 适配器的示例:

@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

使用 Java DSL 进行配置

以下 Spring Boot 应用程序展示了如何使用 Java DSL 配置 outbound 适配器的示例:

@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);

}

}
java