SMB 支持

Spring Integration 为 SMB 文件传输操作提供支持。 服务器消息块 (SMB) 是一种简单的网络协议,允许您将文件传输到共享文件服务器。 您需要将此依赖项添加到您的项目中:

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-smb</artifactId>
    <version>{project-version}</version>
</dependency>
compile "org.springframework.integration:spring-integration-smb:{project-version}"

概述

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 可以使用自定义的 jcifs.CIFSContext 进行初始化。

SMB 协议最小/最大版本的设置必须在您的 jcifs.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 会话缓存

SmbSessionFactory 在每次请求 Session 时都会启动一个新连接。 在大多数情况下,这并非必要,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 属性,以便只有比此值旧的文件才会被过滤器通过。 默认年龄为 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 配置(smb-streaming-java)展示了一种在处理后删除远程文件以避免重复的技术。

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

使用 max-fetch-size 属性来限制每次轮询时(当需要获取时)获取的文件数量。 将其设置为 1 并在集群环境中运行时使用持久过滤器。 有关更多信息,请参阅 smb-max-fetch

适配器将远程目录和文件名分别放入 FileHeaders.REMOTE_DIRECTORYFileHeaders.REMOTE_FILE 头中。 FileHeaders.REMOTE_FILE_INFO 头提供了额外的远程文件信息(默认以 JSON 表示)。 如果将 SmbStreamingMessageSource 上的 fileInfoJson 属性设置为 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;
    }

}

请注意,在此示例中,转换器下游的消息处理程序有一个 advice,它在处理后删除远程文件。

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

配置入站通道适配器时,应考虑两个属性。 max-messages-per-poll,与所有轮询器一样,可用于限制每次轮询发出的消息数量(如果准备好的消息多于配置值)。 max-fetch-size 可以限制一次从远程服务器检索的文件数量。

以下场景假设起始状态是空本地目录:

  • max-messages-per-poll=2max-fetch-size=1:适配器获取一个文件,发出它,获取下一个文件,发出它,然后休眠直到下次轮询。

  • max-messages-per-poll=2max-fetch-size=2:适配器获取两个文件,然后发出每个文件。

  • max-messages-per-poll=2max-fetch-size=4:适配器获取最多四个文件(如果可用)并发出前两个(如果至少有两个)。 接下来的两个文件在下次轮询时发出。

  • max-messages-per-poll=2max-fetch-size 未指定:适配器获取所有远程文件并发出前两个(如果至少有两个)。 随后的文件在随后的轮询中发出(每次两个)。 当所有文件都被消费后,会再次尝试远程获取,以获取任何新文件。

当您部署应用程序的多个实例时,我们建议使用较小的 max-fetch-size,以避免一个实例“抢占”所有文件并使其他实例饿死。

max-fetch-size 的另一个用途是,如果您想停止获取远程文件,但继续处理已获取的文件。 在 MessageSource 上设置 maxFetchSize 属性(通过编程、JMX 或 控制总线)可以有效地阻止适配器获取更多文件,但允许轮询器继续为以前获取的文件发出消息。 如果轮询器在属性更改时处于活动状态,则更改将在下一次轮询时生效。

同步器可以提供一个 Comparator<SmbFile>。 这在通过 maxFetchSize 限制获取文件数量时很有用。

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 (检索文件(s))

  • rm (删除文件(s))

  • 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。 为了方便起见,SessioncloseableResource 头中提供,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,每个对象代表一个检索到的文件)。

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

您使用的表达式来确定远程路径应该产生一个以 结尾的结果,例如 myfiles/ 获取 myfiles 下的完整树。

您可以将递归 MGETFileExistsMode.REPLACE_IF_MODIFIED 模式结合使用,以定期在本地同步整个远程目录树。 此模式将本地文件的最后修改时间戳设置为远程文件的时间戳,而不管 -P(保留时间戳)选项。

Example 1. 使用递归 (-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;这可能会在未来的版本中更改。

您可以将 SmbSimplePatternFileListFilterSmbRegexPatternFileListFilter 配置为始终通过目录,方法是将 alwaysAcceptDirectorties 设置为 true。 这样做允许简单模式的递归,如以下示例所示:

<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 属性来提供其中一个过滤器。

另请参阅 smb-partial

使用 put 命令

put 将文件发送到远程服务器。 消息的有效载荷可以是 java.io.Filebyte[]Stringremote-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` command,smb-put-command>> 相同的属性。 此外,您可以使用 mput-patternmput-regexmput-filtermput-filter-expression 中的一个来过滤本地目录中的文件。 过滤器与递归一起工作,只要子目录本身通过过滤器。 未通过过滤器的子目录不会被递归。

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

另请参阅 smb-partial

使用 rm 命令

rm 命令没有选项。

如果删除操作成功,则生成的消息载荷为 Boolean.TRUE。 否则,消息载荷为 Boolean.FALSEfile_remoteDirectory 头中保存远程目录,file_remoteFile 头中保存文件名。

使用 mv 命令

mv 命令没有选项。

expression 属性定义“from”路径,rename-expression 属性定义“to”路径。 默认情况下,rename-expressionheaders['file_renameTo']。 此表达式不得评估为 null 或空 String。 如有必要,将创建所需的任何远程目录。 结果消息的有效载荷是 Boolean.TRUEfile_remoteDirectory 头中保存原始远程目录,file_remoteFile 头中保存文件名。 file_renameTo 头中保存新路径。

remoteDirectoryExpression 可以在 mv 命令中方便地使用。 如果“from”文件不是完整文件路径,则 remoteDirectoryExpression 的结果将用作远程目录。 “to”文件也适用相同的情况,例如,如果任务只是重命名某个目录中的远程文件。

附加命令信息

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 上,则网关抛出的 PartialSuccessExceptionderivedInputfile1.txtsubdirzoo.txtpartialResultsfile1.txt。 其 cause 是另一个 PartialSuccessException,其 derivedInputfile2.txtfile3.txtpartialResultsfile2.txt

远程文件信息

SmbStreamingMessageSource (smb-streaming-inbound)、SmbInboundFileSynchronizingMessageSource (smb-inbound) 和 SmbOutboundGateway 的“读”命令 (smb-outbound-gateway) 在消息中提供额外的头信息,以生成有关远程文件的信息:

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

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

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

由于 SmbInboundFileSynchronizingMessageSource 不会根据远程文件生成消息,而是使用本地副本,因此 AbstractInboundFileSynchronizer 在同步操作期间将远程文件的信息存储在 MetadataStore 中(可以外部配置),以 URI 样式 (protocol://host:port/remoteDirectory#remoteFileName) 存储。 当轮询本地文件时,SmbInboundFileSynchronizingMessageSource 会检索此元数据。 当本地文件被删除时,建议删除其元数据条目。 AbstractInboundFileSynchronizer 为此提供了 removeRemoteFileMetadata() 回调。 此外,还有一个 setMetadataStorePrefix() 用于元数据键。 建议此前缀与 MetadataStore-based FileListFilter 实现中使用的前缀不同,当这些组件之间共享相同的 MetadataStore 实例时,以避免条目覆盖,因为过滤器和 AbstractInboundFileSynchronizer 都使用相同的本地文件名作为元数据条目键。