SFTP 外发网关
SFTP outbound 网关提供了一组有限的命令,可以让您与远程 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
: 在成功传输后删除远程文件。如果传输被忽略,则远程文件不会被删除,因为FileExistsMode
是IGNORE
并且本地文件已存在。
file_remoteDirectory
标头保存远程目录,file_remoteFile
标头保存文件名。
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
:在成功传输后删除每个远程文件。如果传输被忽略,则远程文件不会被删除,因为FileExistsMode
是IGNORE
,并且本地文件已存在。
mget
操作产生的消息有效负载是一个 List<File>
对象(即,一个 File
对象的 List
,每个 File
对象表示一个已检索的文件)。
从 5.0 版本开始,如果 FileExistsMode
是 IGNORE
,输出消息的有效负载将不再包含由于文件已存在而未获取的文件。以前,数组包含所有文件,包括那些已经存在的文件。
你使用的确定远程路径的表达式应该产生以 *
结尾的结果,例如 myfiles/*
会获取 myfiles
下的完整树。
从 5.0 版本开始,你可以使用递归的 MGET
,结合 FileExistsMode.REPLACE_IF_MODIFIED
模式,定期将整个远程目录树同步到本地。此模式会将本地文件的最后修改时间戳设置为远程文件的时间戳,无论是否使用 -P
(保留时间戳)选项。
使用递归 (-R
) 时的注意事项
模式会被忽略,并假设为 *
。默认情况下,整个远程树都会被检索。但是,你可以通过提供 FileListFilter
来过滤树中的文件。你也可以用这种方式过滤树中的目录。FileListFilter
可以通过引用或 filename-pattern
或 filename-regex
属性来提供。例如,filename-regex="(subDir|.*1.txt)"
检索远程目录和子目录 subDir
中所有以 1.txt
结尾的文件。然而,在此说明之后我们描述了一种替代方法。
如果你过滤了一个子目录,则不会对该子目录进行额外的遍历。
不允许使用 -dirs
选项(递归的 mget
使用递归的 ls
来获取目录树,而目录本身不能包含在列表中)。
通常,你会在 local-directory-expression
中使用 #remoteDirectory
变量,以便保留远程目录结构到本地。
持久化文件列表过滤器现在有一个布尔属性 forRecursion
。将此属性设置为 true
时,也会设置 alwaysAcceptDirectories
,这意味着在出站网关 (ls
和 mget
) 上的递归操作每次都会遍历整个目录树。这是为了解决目录树深层的变化未被检测到的问题。此外,forRecursion=true
会使用文件的完整路径作为元数据存储键;这解决了如果不同目录中出现同名文件时过滤器无法正常工作的问题。重要提示:这意味着持久化元数据存储中现有的键将无法匹配顶级目录下的文件。因此,默认情况下该属性为 false
;这可能会在未来版本中更改。
从 5.0 版开始,你可以通过将 alwaysAcceptDirectorties
设置为 true
来配置 SftpSimplePatternFileListFilter
和 SftpRegexPatternFileListFilter
以始终通过目录。这样做允许简单模式的递归,如下例所示:
<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.File
,一个 byte[]
,或一个 String
。使用 remote-filename-generator
(或表达式)来命名远程文件。其他可用属性包括 remote-directory
、temporary-remote-directory
以及它们的 *-expression
对应项:use-temporary-file-name
和 auto-create-directory
。更多信息,请参阅 schema 文档。
由 put
操作产生的消息有效负载是一个 String
,它包含文件在传输到服务器后的完整路径。
版本 4.3 引入了 chmod
属性,它在上传后更改远程文件权限。你可以使用常规的 Unix 八进制格式(例如,600
仅允许文件所有者读写)。在使用 java 配置适配器时,你可以使用 setChmod(0600)
。
使用 mput
命令
mput
将多个文件发送到服务器,并支持以下选项:
-R
: 递归 — 发送目录和子目录中的所有文件(可能已过滤)
消息的有效载荷必须是一个表示本地目录的 java.io.File
(或 String
)。自 5.1 版起,也支持 File
或 String
的集合。
支持与put 命令相同的属性。此外,您可以使用 mput-pattern
、mput-regex
、mput-filter
或 mput-filter-expression
之一来过滤本地目录中的文件。只要子目录本身通过过滤器,过滤器就会递归工作。未通过过滤器的子目录不会被递归。
mput
操作 resulting from an mput
operation 的消息有效负载是一个 List<String>
对象(即,传输所产生的远程文件路径的 List
)。
注意:原文中的“resulting from an mput
operation”被重复了,可能是表述上的冗余,这里进行了适当的调整以确保语句通顺。
版本 4.3 引入了 chmod
属性,它允许你在上传后更改远程文件权限。你可以使用常规的 Unix 八进制格式(例如,600
仅允许文件所有者读写)。在使用 Java 配置适配器时,你可以使用 setChmodOctal("600")
或 setChmod(0600)
。
使用 rm
命令
rm
命令没有选项。
如果移除操作成功, resulting message payload 是 Boolean.TRUE
。否则,message payload 是 Boolean.FALSE
。file_remoteDirectory
标头持有远程目录,file_remoteFile
标头持有文件名。
使用 mv
命令
mv
命令没有选项。
expression
属性定义了 “from” 路径,rename-expression
属性定义了 “to” 路径。默认情况下,rename-expression
是 headers['file_renameTo']
。此表达式不能计算为 null 或空的 String
。如有必要,会创建所需的任何远程目录。结果消息的有效负载是 Boolean.TRUE
。file_remoteDirectory
标头保存原始远程目录,file_remoteFile
标头保存文件名。file_renameTo
标头保存新的路径。
从版本 5.5.6 开始,可以在 mv
命令中使用 remoteDirectoryExpression
以方便操作。如果“from”文件不是完整文件路径,则 remoteDirectoryExpression
的结果将用作远程目录。同样的规则也适用于“to”文件,例如,如果任务只是在某个目录中重命名远程文件。
额外的命令信息
get
和 mget
命令支持 local-filename-generator-expression
属性。它定义了一个 SpEL 表达式,在传输期间生成本地文件的名称。评估上下文的根对象是请求消息。remoteFileName
变量也可用。对于 mget
特别有用(例如:local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"
)。
get
和 mget
命令支持 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 版本开始,GET
、MGET
、PUT
和 MPUT
命令支持一个 FileExistsMode
属性(在使用命名空间支持时为 mode
)。这会影响本地文件存在时(GET
和 MGET
)或远程文件存在时(PUT
和 MPUT
)的行为。支持的模式有 REPLACE
、APPEND
、FAIL
和 IGNORE
。为了向后兼容,默认模式对于 PUT
和 MPUT
操作是 REPLACE
。对于 GET
和 MGET
操作,默认模式是 FAIL
。
使用Java配置
下面的 Spring Boot 应用展示了如何使用 Java 配置 outbound 网关的示例:
@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 配置 outbound 网关的示例:
@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();
}
}
外发网关部分成功 (mget
和 mput
)
当对多个文件执行操作(使用 mget
和 mput
)时,可能会在传输一个或多个文件之后的某个时间发生异常。在这种情况下(从 4.2 版本开始),会抛出一个 PartialSuccessException
。除了常规的 MessagingException
属性(failedMessage
和 cause
),此异常还具有两个额外的属性:
-
partialResults
: 成功的传输结果。 -
derivedInput
: 从请求消息生成的文件列表(例如,mput
的本地文件)。
这些属性让你确定哪些文件成功传输,哪些文件没有成功传输。
在递归 mput
的情况下,PartialSuccessException
可能会有嵌套的 PartialSuccessException
实例。
考虑以下目录结构:
root/
|- file1.txt
|- subdir/
| - file2.txt
| - file3.txt
|- zoo.txt
如果异常发生在 file3.txt
上,网关抛出的 PartialSuccessException
具有 derivedInput
:file1.txt
、subdir
和 zoo.txt
以及 partialResults
:file1.txt
。它的 cause
是另一个 PartialSuccessException
,该异常具有 derivedInput
:file2.txt
和 file3.txt
以及 partialResults
:file2.txt
。