使用Spring SFTP outbound gateway时文件被发往错误SFTP位置问题求助
问题根因定位
你遇到的偶发文件发错目的地的核心原因是DelegatingSessionFactory没有正确配置会话工厂的选择逻辑,并发场景下会出现会话工厂选择错误的问题,测试环境因为并发量低无法复现,属于典型的多线程上下文串扰问题:
- DelegatingSessionFactory默认会优先使用当前线程上下文绑定的lookup key选择对应SFTP会话工厂,你当前代码没有传递lookup key的逻辑,所有请求要么用初始化时设置的第一个工厂作为默认,要么复用了之前线程遗留的未清理的lookup key,导致文件被发往错误的银行服务器。
- DefaultSftpSessionFactory默认开启共享会话配置,多线程复用同一个会话时也可能出现操作串扰。
修复方案
1. 配置会话工厂自动选择逻辑
修改SftpMessageHandler的初始化逻辑,新增从Header中获取银行名称作为lookup key,自动匹配对应SFTP会话工厂,无需手动管理线程上下文:
@ServiceActivator(inputChannel = "toSftp") @Bean public SftpMessageHandler handler() { SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory()); // 新增:从header中获取bankName作为会话工厂的lookup key template.setSessionFactoryLocatorExpression(EXPRESSION_PARSER.parseExpression("headers['bankName']")); SftpMessageHandler handler = new SftpMessageHandler(template); handler.setRemoteDirectoryExpression(EXPRESSION_PARSER.parseExpression("headers['path']")); return handler; }
修改SFTPOutboundGateway的push方法,新增银行名称Header参数:
@MessagingGateway public interface SFTPOutboundGateway { @Gateway(requestChannel = "toSftp") void push(File file, @Header("path") String path, @Header("bankName") String bankName); // 同理给executeCommand方法也加上bankName header,避免ls命令也连错服务器 @Gateway(requestChannel = "sftpChannel") List<String> executeCommand(String path, @Header("bankName") String bankName); }
对应修改调用逻辑,push和executeCommand调用时传入当前银行名称参数即可。
2. 关闭会话共享避免串扰
创建DefaultSftpSessionFactory时关闭共享会话配置,保证每次请求使用独立会话:
for (BankConfigEntity bank : bankList) { DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(); factory.setHost(bank.getSftpHost()); factory.setUser(bank.getSftpUser()); factory.setPort(bank.getSftpPort()); factory.setPassword(bank.getSftpPass()); factory.setAllowUnknownKeys(true); // 新增:关闭共享会话 factory.setSharedSession(false); factories.put(bank.getBankName(), factory); }
3. 修复ls计数匹配逻辑
你当前使用-1 -R参数执行ls命令,部分SFTP服务器会返回带目录前缀的文件名,直接匹配纯文件名会出现匹配失败误判推送失败的问题,建议修改匹配逻辑:
// 从ls返回结果中提取纯文件名再匹配 boolean fileExists = remoteFiles.stream() .map(f -> new File(f).getName()) .anyMatch(fileName::equals); if (!fileExists) { // 原有的缺失处理逻辑 }
4. 新增排障日志(可选)
如果需要进一步验证修复效果,可以在 DelegatingSessionFactory 中新增选择日志,打印每个请求实际使用的SFTP主机信息,方便后续排查问题。
内容的提问来源于stack exchange,提问作者mnhmilu
相关产品推荐
相关产品推荐

