我正在一个项目中,在该项目中,我们从sftp服务器轮询文件,并将其流式传输到Rabbitmq队列中的对象中。现在,当rabbitmq关闭时,它仍会轮询并从服务器删除文件,并且当rabbitmq处于关闭状态时将其发送到队列时会丢失该文件。我正在使用 ExpressionEvaluatingRequestHandlerAdvice 在成功转换后删除文件。我的代码如下:
@Bean
public SessionFactory<ChannelSftp.LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost(sftpProperties.getSftpHost());
factory.setPort(sftpProperties.getSftpPort());
factory.setUser(sftpProperties.getSftpPathUser());
factory.setPassword(sftpProperties.getSftpPathPassword());
factory.setAllowUnknownKeys(true);
return new CachingSessionFactory<>(factory);
}
@Bean
public SftpRemoteFileTemplate sftpRemoteFileTemplate() {
return new SftpRemoteFileTemplate(sftpSessionFactory());
}
@Bean
@InboundChannelAdapter(channel = TransformerChannel.TRANSFORMER_OUTPUT, autoStartup = "false",
poller = @Poller(value = "customPoller"))
public MessageSource<InputStream> sftpMessageSource() {
SftpStreamingMessageSource messageSource = new SftpStreamingMessageSource(sftpRemoteFileTemplate,
null);
messageSource.setRemoteDirectory(sftpProperties.getSftpDirPath());
messageSource.setFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(),
"streaming"));
messageSource.setFilter(new SftpSimplePatternFileListFilter("*.txt"));
return messageSource;
}
@Bean
@Transformer(inputChannel = TransformerChannel.TRANSFORMER_OUTPUT,
outputChannel = SFTPOutputChannel.SFTP_OUTPUT,
adviceChain = "deleteAdvice")
public org.springframework.integration.transformer.Transformer transformer() {
return new SFTPTransformerService("UTF-8");
}
@Bean
public ExpressionEvaluatingRequestHandlerAdvice deleteAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setOnSuccessExpressionString(
"@sftpRemoteFileTemplate.remove(headers['file_remoteDirectory'] + headers['file_remoteFile'])");
advice.setPropagateEvaluationFailures(false);
return advice;
}
我不希望在Rabbitmq服务器关闭时从远程sftp服务器上删除/轮询文件。我怎样才能做到这一点?
更新
抱歉,我没有使用春季云流兔活页夹。这是变压器服务:
public class SFTPTransformerService extends StreamTransformer {
public SFTPTransformerService(String charset) {
super(charset);
}
@Override
protected Object doTransform(Message<?> message) throws Exception {
String fileName = message.getHeaders().get("file_remoteFile", String.class);
Object fileContents = super.doTransform(message);
return new customFileDTO(fileName, (String) fileContents);
}
}
UPDATE-2
根据建议,我在customPoller上添加了TransactionSynchronizationFactory
。现在,当兔子服务器关闭时,它不会轮询文件,但是当服务器启动时,它会不断地反复轮询同一文件!我不知道为什么?我想我无法在PollerSpec
上使用4.3.2版本的即时消息。
@Bean(name = "customPoller")
public PollerMetadata pollerMetadataDTX(StartStopTrigger startStopTrigger,
CustomTriggerAdvice customTriggerAdvice) {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setAdviceChain(Collections.singletonList(customTriggerAdvice));
pollerMetadata.setTrigger(startStopTrigger);
pollerMetadata.setMaxMessagesPerPoll(Long.valueOf(sftpProperties.getMaxMessagePoll()));
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor =
new ExpressionEvaluatingTransactionSynchronizationProcessor();
syncProcessor.setBeanFactory(applicationContext.getAutowireCapableBeanFactory());
syncProcessor.setBeforeCommitChannel(
applicationContext.getBean(TransformerChannel.TRANSFORMER_OUTPUT, MessageChannel.class));
syncProcessor
.setAfterCommitChannel(
applicationContext.getBean(SFTPOutputChannel.SFTP_OUTPUT, MessageChannel.class));
syncProcessor.setAfterCommitExpression(new SpelExpressionParser().parseExpression(
"@sftpRemoteFileTemplate.remove(headers['file_remoteDirectory'] + headers['file_remoteFile'])"));
DefaultTransactionSynchronizationFactory defaultTransactionSynchronizationFactory =
new DefaultTransactionSynchronizationFactory(syncProcessor);
pollerMetadata.setTransactionSynchronizationFactory(defaultTransactionSynchronizationFactory);
return pollerMetadata;
}
我不知道您是否需要此信息,但是我的CustomTriggerAdvice
和StartStopTrigger
看起来像这样:
@Component
public class CustomTriggerAdvice extends AbstractMessageSourceAdvice {
@Autowired private StartStopTrigger startStopTrigger;
@Override
public boolean beforeReceive(MessageSource<?> source) {
return true;
}
@Override
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
if (result == null) {
if (startStopTrigger.getStart()) {
startStopTrigger.stop();
}
} else {
if (!startStopTrigger.getStart()) {
startStopTrigger.stop();
}
}
return result;
}
}
public class StartStopTrigger implements Trigger {
private PeriodicTrigger startTrigger;
private boolean start;
public StartStopTrigger(PeriodicTrigger startTrigger, boolean start) {
this.startTrigger = startTrigger;
this.start = start;
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
if (!start) {
return null;
}
start = true;
return startTrigger.nextExecutionTime(triggerContext);
}
public void stop() {
start = false;
}
public void start() {
start = true;
}
public boolean getStart() {
return this.start;
}
}
答案 0 :(得分:0)
好吧,很高兴能看到您的SFTPTransformerService
并确定在出现经纪人异常的情况下如何执行onSuccessExpression
。
您还不仅应该引发异常而不执行删除,还应该考虑添加RequestHandlerRetryAdvice
以将文件重新发送到RabbitMQ:https://docs.spring.io/spring-integration/docs/5.0.6.RELEASE/reference/html/messaging-endpoints-chapter.html#retry-advice
更新
因此,好吧,由于Gary猜想您是在内部流程之后使用Spring Cloud Stream将消息发送到Rabbit Binder的(非常可悲的是,您最初并未共享该信息),因此您需要查看Binder错误处理:https://docs.spring.io/spring-cloud-stream/docs/Elmhurst.RELEASE/reference/htmlsingle/#_retry_with_the_rabbitmq_binder
这是正确的,ExpressionEvaluatingRequestHandlerAdvice
仅适用于SFTPTransformerService
,仅此而已。下游错误(在活页夹中)尚未包括在此过程中。
更新2
是的...我认为Gary是对的,除非我们在TransactionSynchronizationFactory
级别上配置customPoller
而不是ExpressionEvaluatingRequestHandlerAdvice
级别的Expression {EvaluatingRequestHandlerAdvice},否则我们别无选择。
可以使用DefaultTransactionSynchronizationFactory
配置ExpressionEvaluatingTransactionSynchronizationProcessor
,其目的与上述ExpressionEvaluatingRequestHandlerAdvice
相似,但是在事务级别上,它将包括从SFTP Channel Adapter和以发送到AMQP尝试结束于Rabbit Binder级别。
有关更多信息,请参见参考手册:https://docs.spring.io/spring-integration/reference/html/transactions.html#transaction-synchronization。
具有ExpressionEvaluatingRequestHandlerAdvice
(和任何AbstractRequestHandlerAdvice
)的点,它们仅在handleRequestMessage()
方法周围具有边界,因此仅在组成部分期间被声明。