跳到主要内容
版本:7.0.2

SMB 支持

DeepSeek V3 中英对照 SMB Support

Spring Integration 为 SMB 文件传输操作提供了支持。

服务器消息块 (SMB) 是一种简单的网络协议,允许您将文件传输到共享文件服务器。

此依赖项为项目所需:

<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-smb</artifactId>
<version>7.0.2</version>
</dependency>

概述

Java CIFS 客户端库已被选作 CIFS/SMB 网络协议的 Java 实现。其 SmbFile 抽象被简单地封装到 Spring Integration 的“远程文件”基础架构中,例如 SmbSessionSmbRemoteFileTemplate 等。

SMB通道适配器及其支持类的实现与现有的(S)FTP或AWS S3协议组件完全相似。因此,如果您熟悉这些组件,使用起来会相当直观。

Spring Integration 支持通过提供三个客户端端点来通过 SMB 发送和接收文件:入站通道适配器、出站通道适配器和出站网关。它还提供了方便的基于命名空间的配置选项来定义这些客户端组件。

要使用 SMB 命名空间,请在 XML 文件的头部添加以下内容:

xmlns:int-smb="http://www.springframework.org/schema/integration/smb"
xsi:schemaLocation="http://www.springframework.org/schema/integration/smb
https://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd"

SMB 会话工厂

在配置 SMB 适配器之前,必须先配置 SMB 会话工厂。您可以使用常规的 bean 定义来配置 SMB 会话工厂,如下例所示:

SmbSessionFactory 提供了设置 SMB 协议最小/最大版本的选项。例如,支持最低版本 SMB 2.1 和最高版本 SMB 3.1.1:

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}

SmbSessionFactory 可以使用自定义的 org.codelibs.jcifs.smb.CIFSContext 进行初始化。

备注

SMB 协议的最小/最大版本设置必须在您对 org.codelibs.jcifs.smb.CIFSContext 的实现中完成。

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory(new MyCIFSContext());
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
return smbSession;
}

SMB 会话缓存

每次请求 Session 时,SmbSessionFactory 都会启动一个新连接。在大多数情况下,这是不必要的,Session 可以被缓存。为此,如前所述,应将 SmbSessionFactory 包装到 CachingSessionFactory 的实例中:

@Bean
public CachingSessionFactory cachingSessionFactory(SmbSessionFactory smbSessionFactory) {
cachingSessionFactory cachingSessionFactory = new CachingSessionFactory(smbSessionFactory, 10);
cachingSessionFactory.setSessionWaitTimeout(1000);
return cachingSessionFactory;
}

然后,它的 bean 可以被注入到下面描述的通道适配器中。

SMB 入站通道适配器

要将SMB文件下载到本地,提供了 SmbInboundFileSynchronizingMessageSource。它是 AbstractInboundFileSynchronizingMessageSource 的简单扩展,需要注入 SmbInboundFileSynchronizer。为了过滤远程文件,您仍然可以使用任何现有的 FileListFilter 实现,但特别提供了 SmbRegexPatternFileListFilterSmbSimplePatternFileListFilter

@Bean
public SmbInboundFileSynchronizer smbInboundFileSynchronizer() {
SmbInboundFileSynchronizer fileSynchronizer =
new SmbInboundFileSynchronizer(smbSessionFactory());
fileSynchronizer.setFilter(compositeFileListFilter());
fileSynchronizer.setRemoteDirectory("mySharedDirectoryPath");
fileSynchronizer.setDeleteRemoteFiles(true);
return fileSynchronizer;
}

@Bean
public CompositeFileListFilter<SmbFile> compositeFileListFilter() {
CompositeFileListFilter<SmbFile> filters = new CompositeFileListFilter<>();
filters.addFilter(new SmbRegexPatternFileListFilter("^(?i).+((\\.txt))$"));
return filters;
}

@Bean
public MessageChannel smbFileInputChannel() {
return new DirectChannel();
}

@Bean
@InboundChannelAdapter(value = "smbFileInputChannel",
poller = @Poller(fixedDelay = "2000"))
public MessageSource<File> smbMessageSource() {
SmbInboundFileSynchronizingMessageSource messageSource =
new SmbInboundFileSynchronizingMessageSource(smbInboundFileSynchronizer());
messageSource.setLocalDirectory(new File("myLocalDirectoryPath"));
messageSource.setAutoCreateLocalDirectory(true);
return messageSource;
}

对于XML配置,提供了 <int-smb:inbound-channel-adapter> 组件。

从 6.2 版本开始,你可以使用 SmbLastModifiedFileListFilter 基于最后修改时间策略来过滤 SMB 文件。该过滤器可以配置一个 age 属性,以便只有修改时间早于此值的文件才能通过过滤器。age 的默认值为 60 秒,但你应该选择一个足够大的值,以避免过早地选取文件(例如,由于网络故障等原因)。更多信息请查阅其 Javadoc。

相反,从版本6.5开始,引入了SmbRecentFileListFilter,它只接受那些不超过指定age(年龄)的文件。

使用 Java DSL 进行配置

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

@SpringBootApplication
public class SmbJavaApplication {

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

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}

@Bean
public IntegrationFlow smbInboundFlow() {
return IntegrationFlow
.from(Smb.inboundAdapter(smbSessionFactory())
.preserveTimestamp(true)
.remoteDirectory("smbSource")
.regexFilter(".*\\.txt$")
.localFilename(f -> f.toUpperCase() + ".a")
.localDirectory(new File("d:\\smb_files")),
e -> e.id("smbInboundAdapter")
.autoStartup(true)
.poller(Pollers.fixedDelay(5000)))
.handle(m -> System.out.println(m.getPayload()))
.get();
}
}

SMB 流式入站通道适配器

此适配器生成负载类型为 InputStream 的消息,使得文件无需写入本地文件系统即可被获取。由于会话保持开启状态,消费应用程序需在文件消费完毕后负责关闭会话。会话通过 closeableResource 头部(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)提供。标准框架组件(例如 FileSplitterStreamTransformer)会自动关闭会话。有关这些组件的更多信息,请参阅文件分割器流转换器。以下示例展示了如何配置 inbound-streaming-channel-adapter

<int-smb:inbound-streaming-channel-adapter id="smbInbound"
channel="smbChannel"
session-factory="sessionFactory"
filename-pattern="*.txt"
filename-regex=".*\.txt"
filter="filter"
filter-expression="@myFilterBean.check(#root)"
remote-file-separator="/"
comparator="comparator"
max-fetch-size="1"
remote-directory-expression="'foo/bar'">
<int:poller fixed-rate="1000" />
</int-smb:inbound-streaming-channel-adapter>

filename-patternfilename-regexfilterfilter-expression 中只能使用一个。

SmbStreamingMessageSource 适配器通过基于内存 SimpleMetadataStoreSmbPersistentAcceptOnceFileListFilter 来防止远程文件重复处理。默认情况下,此过滤器还会结合文件名模式(或正则表达式)一起应用。如果需要允许重复文件,可以使用 AcceptAllFileListFilter。其他任何用例都可以通过 CompositeFileListFilter(或 ChainFileListFilter)来处理。Java 配置(文档后续部分)展示了一种在处理后删除远程文件以避免重复的技术。

有关 SmbPersistentAcceptOnceFileListFilter 的更多信息及其使用方法,请参阅远程持久化文件列表过滤器

使用 max-fetch-size 属性来限制每次轮询时获取的文件数量。在集群环境中运行时,请将其设置为 1 并使用持久化过滤器。更多信息请参阅入站通道适配器:控制远程文件获取

适配器将远程目录和文件名分别存储在 FileHeaders.REMOTE_DIRECTORYFileHeaders.REMOTE_FILE 头部信息中。FileHeaders.REMOTE_FILE_INFO 头部则提供额外的远程文件信息(默认以 JSON 格式表示)。若将 SmbStreamingMessageSourcefileInfoJson 属性设置为 false,该头部将包含一个 SmbFileInfo 对象。

使用 Java 配置进行配置

以下Spring Boot应用程序展示了如何通过Java配置来配置入站适配器的示例:

@SpringBootApplication
public class SmbJavaApplication {

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

@Bean
@InboundChannelAdapter(channel = "stream")
public MessageSource<InputStream> smbMessageSource() {
SmbStreamingMessageSource messageSource = new SmbStreamingMessageSource(template());
messageSource.setRemoteDirectory("smbSource/");
messageSource.setFilter(new AcceptAllFileListFilter<>());
messageSource.setMaxFetchSize(1);
return messageSource;
}

@Bean
@Transformer(inputChannel = "stream", outputChannel = "data")
public org.springframework.integration.transformer.Transformer transformer() {
return new StreamTransformer("UTF-8");
}

@Bean
public SmbRemoteFileTemplate template() {
return new SmbRemoteFileTemplate(smbSessionFactory());
}

@ServiceActivator(inputChannel = "data", adviceChain = "after")
@Bean
public MessageHandler handle() {
return System.out::println;
}

@Bean
public ExpressionEvaluatingRequestHandlerAdvice after() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setOnSuccessExpression(
"@template.remove(headers['file_remoteDirectory'] + headers['file_remoteFile'])");
advice.setPropagateEvaluationFailures(true);
return advice;
}

}

请注意,在此示例中,transformer 下游的消息处理器包含一个 advice,用于在处理后删除远程文件。

入站通道适配器:控制远程文件获取

请参阅 SFTP:控制远程文件获取 以了解完全相同的通用功能。

SMB 出站通道适配器

对于向SMB共享写入文件,以及XML <int-smb:outbound-channel-adapter> 组件,我们使用 SmbMessageHandler。在Java配置中,SmbMessageHandler 应配置 SmbSessionFactory(或 SmbRemoteFileTemplate)。

@Bean
@ServiceActivator(inputChannel = "storeToSmbShare")
public MessageHandler smbMessageHandler(SmbSessionFactory smbSessionFactory) {
SmbMessageHandler handler = new SmbMessageHandler(smbSessionFactory);
handler.setRemoteDirectoryExpression(
new LiteralExpression("remote-target-dir"));
handler.setFileNameGenerator(m ->
m.getHeaders().get(FileHeaders.FILENAME, String.class) + ".test");
handler.setAutoCreateDirectory(true);
return handler;
}

使用 Java DSL 进行配置

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

@SpringBootApplication
@IntegrationComponentScan
public class SmbJavaApplication {

public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(SmbJavaApplication.class)
.web(false)
.run(args);
MyGateway gateway = context.getBean(MyGateway.class);
gateway.sendToSmb(new File("/foo/bar.txt"));
}

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}

@Bean
public IntegrationFlow smbOutboundFlow() {
return IntegrationFlow.from("toSmbChannel")
.handle(Smb.outboundAdapter(smbSessionFactory(), FileExistsMode.REPLACE)
.useTemporaryFileName(false)
.fileNameExpression("headers['" + FileHeaders.FILENAME + "']")
.remoteDirectory("smbTarget")
).get();
}

@MessagingGateway
public interface MyGateway {

@Gateway(requestChannel = "toSmbChannel")
void sendToSmb(File file);
}

}

SMB 出站网关

SMB出站网关提供了一组有限的命令来与远程SMB服务器交互。支持的命令包括:

  • 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 对象时则可以做到这一点。

使用 nlst 命令

nlst 用于列出远程文件名,仅支持一个选项:

  • -f: 不排序列表

nlst 操作返回的消息载荷是一个文件名列表。

file_remoteDirectory 标头保存了 nlst 命令所操作的远程目录。

使用 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-smb:outbound-gateway session-factory="smbSessionFactory"
request-channel="inboundGetStream"
command="get"
command-options="-stream"
expression="payload"
remote-directory="smbTarget"
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,每个 File 对象代表一个已检索到的文件)。

important

如果 FileExistsModeIGNORE,则输出消息的有效负载不再包含由于文件已存在而未获取的文件。之前,该数组包含所有文件,包括那些已经存在的文件。

您所使用的表达式决定了远程路径,其结果应以 * 结尾,例如 myfiles/* 会获取 myfiles 下的完整目录树。

你可以使用递归的 MGET 命令,结合 FileExistsMode.REPLACE_IF_MODIFIED 模式,来定期将整个远程目录树同步到本地。无论是否使用 -P(保留时间戳)选项,此模式都会将本地文件的最后修改时间戳设置为远程文件的时间戳。

important

使用递归 (-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;这可能会在未来的版本中发生改变。

你可以通过将 alwaysAcceptDirectorties 设置为 true 来配置 SmbSimplePatternFileListFilterSmbRegexPatternFileListFilter 始终接受目录。这样做允许对简单模式进行递归,如下例所示:

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

<bean id="dotStarDotTxtFilter"
class="org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter">
<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 documentation

put 操作产生的消息负载是一个 String,其中包含传输后文件在服务器上的完整路径。

使用 mput 命令

mput 向服务器发送多个文件,并支持以下选项:

  • -R: 递归 — 发送目录及子目录中的所有文件(可能经过筛选)

消息负载必须是一个表示本地目录的 java.io.File(或 String)。也支持 FileString 的集合。

支持与 put 命令 相同的属性。此外,您可以使用 mput-patternmput-regexmput-filtermput-filter-expression 中的任意一个来筛选本地目录中的文件。只要子目录本身通过筛选器,筛选器就会递归应用于子目录。未通过筛选器的子目录不会被递归处理。

mput 操作产生的消息载荷是一个 List<String> 对象(即传输产生的远程文件路径的 List)。

使用 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 标头保存新路径。

为方便起见,mv 命令中可使用 remoteDirectoryExpression。如果“源”文件不是完整文件路径,则使用 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-smb:outbound-gateway id="gateway1"
session-factory="smbSessionFactory"
request-channel="inbound1"
command="ls"
command-options="-1"
expression="payload"
reply-channel="toSplitter"/>

发送到 toSplitter 通道的消息负载是一个 String 对象列表,每个对象包含一个文件的名称。如果省略 command-options="-1",负载将是一个 FileInfo 对象列表。你可以提供一个以空格分隔的选项列表,例如 command-options="-1 -dirs -links"

GETMGETPUTMPUT 命令支持 FileExistsMode 属性(使用命名空间支持时为 mode)。这会影响本地文件已存在(GETMGET)或远程文件已存在(PUTMPUT)时的行为。支持的模式包括 REPLACEAPPENDFAILIGNORE。为了向后兼容,PUTMPUT 操作的默认模式是 REPLACE。对于 GETMGET 操作,默认模式是 FAIL

使用 Java 配置进行配置

以下Spring Boot应用程序展示了如何使用Java配置来配置出站网关的示例:

@SpringBootApplication
public class SmbJavaApplication {

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

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}

@Bean
@ServiceActivator(inputChannel = "smbChannel")
public MessageHandler handler() {
SmbOutboundGateway smbOutboundGateway =
new SmbOutboundGateway(smbSessionFactory(), "'my_remote_dir/'");
smbOutboundGateway.setOutputChannelName("replyChannel");
return smbOutboundGateway;
}

}

使用 Java DSL 进行配置

以下Spring Boot应用程序展示了如何使用Java DSL配置出站网关的示例:

@SpringBootApplication
public class SmbJavaApplication {

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

@Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("myHost");
smbSession.setPort(445);
smbSession.setDomain("myDomain");
smbSession.setUsername("myUser");
smbSession.setPassword("myPassword");
smbSession.setShareAndDir("myShareAndDir");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}

@Bean
public SmbOutboundGatewaySpec smbOutboundGateway() {
return Smb.outboundGateway(smbSessionFactory(),
AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.regexFileNameFilter("(subSmbSource|.*.txt)")
.localDirectoryExpression("'localDirectory/' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')");
}

@Bean
public IntegrationFlow smbFlow(AbstractRemoteFileOutboundGateway<SmbFile> smbOutboundGateway) {
return f -> f
.handle(smbOutboundGateway)
.channel(c -> c.queue("remoteFileOutputChannel"));
}

}

出站网关部分成功(mgetmput

在对多个文件执行操作时(通过使用 mgetmput),可能会在一个或多个文件传输完成后的某个时刻发生异常。此时会抛出 PartialSuccessException。除了常规的 MessagingException 属性(failedMessagecause)外,此异常还有两个额外属性:

  • partialResults: 成功的传输结果。

  • derivedInput: 由请求消息生成的文件列表(例如,对于 mput 操作需要传输的本地文件)。

这些属性让你能够确定哪些文件传输成功,哪些没有成功。

在递归 mput 的情况下,PartialSuccessException 可能包含嵌套的 PartialSuccessException 实例。

考虑以下目录结构:

root/
|- file1.txt
|- subdir/
| - file2.txt
| - file3.txt
|- zoo.txt

若异常发生在 file3.txt 上,网关抛出的 PartialSuccessException 将包含 file1.txtsubdirzoo.txt 作为 derivedInput,以及 file1.txt 作为 partialResults。其 cause 是另一个 PartialSuccessException,该异常的 derivedInputfile2.txtfile3.txtpartialResultsfile2.txt

远程文件信息

SmbStreamingMessageSourceSMB 流式入站通道适配器)、SmbInboundFileSynchronizingMessageSourceSMB 入站通道适配器)以及 SmbOutboundGatewaySMB 出站网关)的“读取”命令会在生成的消息中提供额外的头部信息,这些信息包含有关远程文件的详细信息:

  • FileHeaders.REMOTE_HOST_PORT - 文件传输操作期间远程会话所连接的主机:端口对;

  • FileHeaders.REMOTE_DIRECTORY - 执行操作的远程目录;

  • FileHeaders.REMOTE_FILE - 远程文件名;仅适用于单文件操作。

由于 SmbInboundFileSynchronizingMessageSource 并非直接针对远程文件生成消息,而是使用本地副本,AbstractInboundFileSynchronizer 会在同步操作期间以 URI 格式(protocol://host:port/remoteDirectory#remoteFileName)将远程文件信息存储于 MetadataStore(可外部配置)中。当轮询本地文件时,SmbInboundFileSynchronizingMessageSource 会检索此元数据。建议在删除本地文件时移除其对应的元数据条目。为此,AbstractInboundFileSynchronizer 提供了 removeRemoteFileMetadata() 回调方法。此外,还可通过 setMetadataStorePrefix() 设置元数据键的前缀。当这些组件共享同一 MetadataStore 实例时,建议使此前缀与基于 MetadataStoreFileListFilter 实现中所用的前缀不同,以避免因过滤器与 AbstractInboundFileSynchronizer 均使用相同本地文件名作为元数据条目键而导致条目被覆盖。