Spring Integration sftp 技术之 SFTP Outbound Gateway
本篇博文介绍spring integration sftp技术中的sftp outbound gateway相关内容。Sftp outbound gateway 其实质就是提供一组命令(如图1)来实现对服务器上文件的交互操作,包括文件的获取(文件对象和文件名等)、上传(单文件和多文件)、下载(单文件和多文件),删除,移动。具体在开发的过程中可以使用多种配置方式如xml,springboot等。本文在介绍SFTP Outbound Gateway 的基础上,使用SpringBoot开发框架进行相应的开发实践。
1.命令组
1.1 ls
该命令的功能是获取远程文件,包括文件对象和文件路径名称等,具体返回值根据配置的选项:
- -1 :获取一组远程文件的文件名;默认是获取一组FileInfo对象;
- -a:获取所有的文件(包括开始的文件,递归时使用);
- - f:检索结果不用排序;
- -dirs: 包括文件夹,默认是包括的;
- -links:包括链接符号,默认是包括的;
- -R:递归方式获取远程文件夹下所有文件,默认不递归的。
除此之外,还可以配置文件名过滤器等;
命令返回值: 通过ls命令获取的message payload,是一组文件名或者FileInfo对象,对象中提供了有关文件的修改时间,权限以及其他的信息;
ls命令作用的远程文件夹,由header头的file_remoteDirectory属性提供;
建议提醒:如果使用-R递归选择项,文件名将含有子文件夹,表明递归文件的相对路径;如果使用-dirs选项,每一个递归的子文件夹,返回的元素中将含有子文件夹名;在这种情况下,建议不用使用-1罗列文件名,因为返回的元素中不能够区分是文件还是文件夹?建议返回FileInfo对象。
下面是开发示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Bean @ServiceActivator (inputChannel = "sftpChannel2" ) public MessageHandler handler2() { //指定session配置和命令 SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "ls" , "payload" ); sftpOutboundGateway.setOptions( "-dirs" ); //配置项 return sftpOutboundGateway; } //使用Gateway触发 @MessagingGateway public interface MessageGateway { @Gateway (requestChannel = "sftpChannel2" ) List<FileInfo> listFileName(String dir); //指定远程文件夹 } |
1.2 nlst
该命令提供检索远程文件名的功能,相当于ls -1的命令;支持如下配置:
- -f:文件名不排序;
nlst命令作用的远程文件夹,由header头的file_remoteDirectory提供。
返回值:通过nlst获取的文件message payload,就是一组文件名列表;
1.3 get
该命令由于获取一个远程的文件,支持如下的选项:
- -P:文件下载之后,保持文件在本地的时间戳同远程服务器一致;
- -stream:以流的方式获取远程文件;
- -D:文件成功转移之后,删除远程文件;如果FileExistsMode设置为IGNORE,远程文件不会删除。
file_remoteDirectory 头包含了文件的远程路径,file_remoteFile属性为文件名;
返回值:使用get方法获取的message的payload是一个File对象,如果使用-straem,则payload就是一个InputStream文件流。
对于文本文件,有个通用的案例,使用file splitter 或 stream transformer。当以文件流的形式获取远程文件,Session在结束之后要及时关闭. Session由closeableResource属性header头文件,IntegrationMessageHeaderAccessor提供了流资源的关闭操作。
1.4 mget
该命令用来基于特定的文件模式过滤器获取多个文件,支持如下的设置:
- -P:保留远程文件的时间戳;
- -R:递归下载所有符合的文件;
- -x:没有文件匹配文件筛选模式,抛出异常,并返回空集合;
- -D:文件成功转移之后。如何FileExistsMode=IGNORE,本地文件存在,文件不会删除;
message payload返回的是List< >对象,集合元素是File。
注意:
在5.0版本之后,若FileExistsMode=IGNORE,payload不再包含已经存在的文件对象。
remote path的表达式应该是以结尾,类似myfiles/,表示获取完整的文件夹树myfiles;
注意,在版本5.0之后,MGET命令可以设置FileExistsMode.REPLACE_IF_MODIFIED模式,去同步整个文件夹,被修改的文件的时间戳也会相应修改。不用关心-P模式;
-R模式,默认情况下是整个文件夹,同时也支持设置文件或文件夹过滤器FileListFilter; 该过滤器提供两种方式filename-pattern或者filename-regex属性;例如filename-regex="(subDir|.*1.txt)" 获取subDir下所有以1.txt结尾的文件;
通常,将在local-directory-expression中使用#remoteDirectory变量,以便远程目录结构在本地保留。
下面是开发示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Bean @ServiceActivator (inputChannel = "sftpChannel3" ) public MessageHandler handler3() { SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mget" , "payload" ); sftpOutboundGateway.setOptions( "-R" ); sftpOutboundGateway.setFileExistsMode(FileExistsMode.REPLACE_IF_MODIFIED); sftpOutboundGateway.setLocalDirectory( new File( "E:\\sftp_tmp_dir" )); sftpOutboundGateway.setAutoCreateLocalDirectory( true ); return sftpOutboundGateway; } @MessagingGateway public interface MessageGateway { @Gateway (requestChannel = "sftpChannel3" ) List<File> listFile(String dir); } |
1.5 put
该命令是发送单个文件到远程服务器;
message的payload可以是File对象,byte[]数组,或者字符串;
remote-filename-generator用来命名远程文件。其他的属性如remote-directory,temporary-remote-directory等等;
返回值:put命令的message的payload的返回值是string,包含文件传输后在服务器上的整个路径;
1.6 mput
该命令是发送多个文件到服务器,支持如下配置:
- -R: 递归发送文件和子文件夹下的所有文件;
message payload必须是文件或者文件路径字符串,代表了本地文件夹;自版本5.1之后,也支持文件或者路径字符串集合;
put的配置,同样适合mput,同时除此之外,还提供过滤文件的mput-pattern,mput-regex,mput-filter等;
版本4.3之后,支持设置文件的权限;
返回值:mput执行之后的返回值,是一个List,包含文件转移之后的路径集合。
下面是开发示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //!important,put命令需要借助与sftpRemoteFileTemplate。 //看源码,可以发现outbound gateway 有多种构造函数; @Bean @ServiceActivator (inputChannel = "sftpChannel4" ) public MessageHandler handler4(){ SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory()); sftpRemoteFileTemplate.setRemoteDirectoryExpression( new LiteralExpression( "/send" )); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpRemoteFileTemplate, "put" , "payload" ); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } @Bean @ServiceActivator (inputChannel = "sftpChannel5" ) public MessageHandler handler5(){ SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory()); sftpRemoteFileTemplate.setRemoteDirectoryExpression( new LiteralExpression( "/send" )); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpRemoteFileTemplate, "mput" , "payload" ); //配置过滤器 sftpOutboundGateway.setMputFilter( new FileListFilter<File>() { @Override public List<File> filterFiles(File[] files) { if (...){ ... } return null ; } }); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } |
1.7 rm
该命令是删除远程文件。
如果删除成功,message payload的返回值是Boolean.TRUE;否则是Boolean.FALSE。
file_remoteDirectory头包含远程文件属性;
下面是开发示例:
1 2 3 4 5 6 7 | @Bean @ServiceActivator (inputChannel = "sftpChannel6" ) public MessageHandler handler6(){ SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "rm" , "payload" ); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } |
1.8 mv
该命令是移动文件在远程服务器上的位置。
返回值:转移成功,返回true,否则是false;
下面是开发示例:
1 2 3 4 5 6 7 8 | @Bean @ServiceActivator (inputChannel = "sftpChannel7" ) public MessageHandler handler7(){ SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mv" , "'send/22.TXT'" ); sftpOutboundGateway.setRenameExpression( new LiteralExpression( "send1/22.TXT" )); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } |
以下是干货(测试用例):
首先是POM文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | <?xml version= "1.0" encoding= "UTF-8" ?> <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion>4.0.0</modelVersion> <groupId>com.flower.springintegration</groupId> <artifactId>spring-integration-samples</artifactId> <version>v0.0.1</version> <name>SpringIntegrationExamples</name> <description>Spring Integration Samples</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency>--> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-sftp</artifactId> </dependency> <!-- https: //mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>1.4.0.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.2.RELEASE</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
接下来是yml文件配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | spring: datasource: type: com.zaxxer.hikari.HikariDataSource url: jdbc:mysql: //localhost:3306/springbatchexample?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 username: root password: root sftp: host: 127.0.0.1 port: 23 user: 47gamer password: wdnmd filePath: send: /send achieve: /achieve localPath: /sftp_tmp_dir |
然后是Sftp网关配置类SftpConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | package com.flower.integration.sftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.annotation.*; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.core.MessageSource; import org.springframework.integration.file.filters.AcceptOnceFileListFilter; import org.springframework.integration.file.filters.FileListFilter; import org.springframework.integration.file.remote.FileInfo; import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; import org.springframework.integration.sftp.gateway.SftpOutboundGateway; import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource; import org.springframework.integration.sftp.outbound.SftpMessageHandler; import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import javax.annotation.Resource; import java.io.File; import java.util.List; import java.util.Properties; /** * Sftp configuration. * * @Autor 47Gamer * @Date 2019-01-18 */ @Configuration @DependsOn( "sftpProperty" ) public class SftpConfig { @Resource(name = "sftpProperty" ) private SftpProperty sftpProperty; private static Logger log = LoggerFactory.getLogger(SftpConfig. class ); @Value( "${sftp.host}" ) private String sftpHost; @Value( "${sftp.port:23}" ) private int sftpPort; @Value( "${sftp.user}" ) private String sftpUser; @Value( "${sftp.privateKey:#{null}}" ) private org.springframework.core.io.Resource sftpPrivateKey; @Value( "${sftp.privateKeyPassphrase:}" ) private String sftpPrivateKeyPassphrase; @Value( "${sftp.password}" ) private String sftpPassword; /* @Bean public SessionFactory<LsEntry> sftpSessionFactory() { System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(false); factory.setHost(sftpProperty.getHost()); factory.setPort(sftpProperty.getPort()); factory.setUser(sftpProperty.getUser()); Properties jschProps = new Properties(); //!important 必须配置PreferredAuthentications,否则程序控制台会询问user name 和 password。 jschProps.put("StrictHostKeyChecking", "no"); jschProps.put("PreferredAuthentications", "password,gssapi-with-mic,publickey,keyboard-interactive"); factory.setSessionConfig(jschProps); // if (sftpPassword != null) { factory.setPassword(sftpProperty.getPassword()); // } else { // factory.setPrivateKey(sftpPrivateKey); // factory.setPrivateKeyPassphrase(sftpPrivateKeyPassphrase); // } factory.setAllowUnknownKeys(true); // //设置缓存的属性,缓存的size(), waitTimeout(). CachingSessionFactory<LsEntry> cachingSessionFactory = new CachingSessionFactory<LsEntry>(factory); cachingSessionFactory.setPoolSize(10); // cachingSessionFactory.setSessionWaitTimeout(1000); return cachingSessionFactory; // return new CachingSessionFactory<LsEntry>(factory); }*/ /** * 创建 spring-integration-sftp session * 避免使用jsch原生的创建session的方式 * * @return SessionFactory<LsEntry> */ @Bean public SessionFactory<LsEntry> sftpSessionFactory(){ System. out .println( "######################################################" ); DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory( true ); factory.setUser(sftpProperty.getUser()); factory.setHost(sftpProperty.getHost()); factory.setPort(sftpProperty.getPort()); factory.setPassword(sftpProperty.getPassword()); Properties jschProps = new Properties(); //!important 必须配置PreferredAuthentications,否则程序控制台会询问user name 和 password。 jschProps.put( "StrictHostKeyChecking" , "no" ); jschProps.put( "PreferredAuthentications" , "password,gssapi-with-mic,publickey,keyboard-interactive" ); factory.setSessionConfig(jschProps); factory.setAllowUnknownKeys( true ); //设置缓存的属性,缓存的size(), waitTimeout(). CachingSessionFactory<LsEntry> cachingSessionFactory = new CachingSessionFactory<LsEntry>(factory); // cachingSessionFactory.setPoolSize(2000); return cachingSessionFactory; } /** * 配置Outbound Channel Adapter. * * 实质上就是一个MessageHandler,接收Message Channel发送的信息流. * @return MessageHandler */ @ServiceActivator(inputChannel = "fileInChannel" ) @Bean public SftpMessageHandler sftpMessageHandler(){ SftpMessageHandler sftpMsgHandler = new SftpMessageHandler(sftpSessionFactory()); sftpMsgHandler.setRemoteDirectoryExpression( new LiteralExpression(sftpProperty.getSftpAchievePath())); sftpMsgHandler.setAutoCreateDirectory( true ); sftpMsgHandler.setCharset( "UFT-8" ); return sftpMsgHandler; } /** * 配置 Inbound Channel Adapter * * 监控sftp服务器文件的状态。一旦由符合条件的文件生成,就将其同步到本地服务器。 * 需要条件:inboundFileChannel的bean;轮询的机制;文件同步bean,SftpInboundFileSynchronizer; */ @Bean @InboundChannelAdapter(value = "inboundFileChannel" , poller = @Poller(cron = "0 1/10 * * * *" , maxMessagesPerPoll = "1" )) public MessageSource<File> fileMessageSource() { System. out .println( "=========================================================" ); //创建sftpInboundFileSynchronizer,并绑定到message source. SftpInboundFileSynchronizingMessageSource source = new SftpInboundFileSynchronizingMessageSource(sftpInboundFileSynchronizer()); //自动创建本地文件夹 source.setAutoCreateLocalDirectory( true ); source.setLocalDirectory( new File(sftpProperty.getLocalTempDir())); //设置文件过滤器 source.setLocalFilter( new AcceptOnceFileListFilter<File>()); return source; } /** * 为Inbound-channel-adapter提供bean */ @Bean public DirectChannel inboundFileChannel() { return new DirectChannel(); } /** * SftpInboundFileSynchronizer, * * 同步sftp文件至本地服务器. * <1> 可以放在service中获取bean使用.toLocal方法; * <2> 也可以使用inbound-channel-adapter中,做监控文件服务器的动态。 * * @return SftpInboundFileSynchronizer */ @Bean(name = "synFileChannel" ) public SftpInboundFileSynchronizer sftpInboundFileSynchronizer (){ SftpInboundFileSynchronizer fileSynchronize = new SftpInboundFileSynchronizer(sftpSessionFactory()); fileSynchronize.setDeleteRemoteFiles( true ); fileSynchronize.setPreserveTimestamp( true ); //!important fileSynchronize.setRemoteDirectory(sftpProperty.getSftpSendPath()); fileSynchronize.setFilter( new SftpSimplePatternFileListFilter( "*.*" )); //fileSynchronize.setLocalFilenameGeneratorExpression( ); fileSynchronize.setPreserveTimestamp( true ); return fileSynchronize; } /////////////////////////////////////////////////////////////////////// /** * 配置 SFTP Outbound Gateway * * @return MessageHandler */ @Bean @ServiceActivator(inputChannel = "sftpChannel" ) public MessageHandler handler() { SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "ls" , "payload" ); // MessageChannel message = sftpOutboundGateway.getOutputChannel(); sftpOutboundGateway.setLocalDirectory( new File( "E:\\sftp_tmp_dir" )); sftpOutboundGateway.setAutoCreateLocalDirectory( true ); // TODO dynanic path return sftpOutboundGateway; } @Bean @ServiceActivator(inputChannel = "sftpChannel2" ) public MessageHandler handler2() { SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "ls" , "payload" ); sftpOutboundGateway.setOptions( "-dirs" ); sftpOutboundGateway.setLocalDirectory( new File( "E:\\sftp_tmp_dir" )); sftpOutboundGateway.setAutoCreateLocalDirectory( true ); // TODO dynanic path return sftpOutboundGateway; } @Bean @ServiceActivator(inputChannel = "sftpChannel3" ) public MessageHandler handler3() { System. out .println( "========================= 3 ================================" ); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mget" , "payload" ); sftpOutboundGateway.setOptions( "-R" ); sftpOutboundGateway.setFileExistsMode(FileExistsMode.REPLACE_IF_MODIFIED); sftpOutboundGateway.setLocalDirectory( new File( "E:\\sftp_tmp_dir" )); sftpOutboundGateway.setAutoCreateLocalDirectory( true ); // TODO dynanic path return sftpOutboundGateway; } @Autowired private BeanFactory beanFactory; //outbound gateway,put命令需要借助与sftpRemoteFileTemplate。 //看源码,可以发现outbound gateway 有多种构造函数; @Bean @ServiceActivator(inputChannel = "sftpChannel4" ) public MessageHandler handler4(){ SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory()); sftpRemoteFileTemplate.setRemoteDirectoryExpression( new LiteralExpression( "/send" )); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpRemoteFileTemplate, "put" , "payload" ); // sftpOutboundGateway.setLocalDirectoryExpressionString("/get/"); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } @Bean @ServiceActivator(inputChannel = "sftpChannel5" ) public MessageHandler handler5(){ SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory()); sftpRemoteFileTemplate.setRemoteDirectoryExpression( new LiteralExpression( "/send" )); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpRemoteFileTemplate, "mput" , "payload" ); // sftpOutboundGateway.setLocalDirectoryExpressionString("/get/"); // sftpOutboundGateway.setOptions("-R"); sftpOutboundGateway.setMputFilter( new FileListFilter<File>() { @Override public List<File> filterFiles(File[] files) { return null ; } }); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } @Bean @ServiceActivator(inputChannel = "sftpChannel6" ) public MessageHandler handler6(){ SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "rm" , "payload" ); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } @Bean @ServiceActivator(inputChannel = "sftpChannel7" ) public MessageHandler handler7(){ // SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sftpSessionFactory()); // sftpRemoteFileTemplate.setRemoteDirectoryExpression(new LiteralExpression("/send")); SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mv" , "'send/22.TXT'" ); // sftpOutboundGateway.setRenameExpression(new LiteralExpression("/send1")); // sftpOutboundGateway.setChmod(777); // sftpOutboundGateway.setRenameExpressionString("send1"); sftpOutboundGateway.setRenameExpression( new LiteralExpression( "send1/22.TXT" )); // sftpOutboundGateway.setAutoCreateLocalDirectory(true); sftpOutboundGateway.setBeanFactory(beanFactory); return sftpOutboundGateway; } @MessagingGateway public interface UploadGateway { @Gateway(requestChannel = "sftpChannel" ) List<FileInfo> listFileInfo(String dir); @Gateway(requestChannel = "sftpChannel2" ) List<FileInfo> listFileName(String dir); @Gateway(requestChannel = "sftpChannel3" ) List<File> listFile(String dir); @Gateway(requestChannel = "sftpChannel4" ) String putFile(File source); @Gateway(requestChannel = "sftpChannel5" ) List<String> mputFile(File directory); @Gateway(requestChannel = "sftpChannel6" ) boolean removeFile(String file); @Gateway(requestChannel = "sftpChannel7" ) boolean moveFile(String file); } } |
映射yml文件里的stfp配置实体SftpProperty .java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package com.flower.integration.sftp; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; @Component( "sftpProperty" ) @ConfigurationProperties(prefix = "sftp" ) public class SftpProperty { private String host; private Integer port; private String user; private String password; private Map<String,String> filePath; //////////////////////////////////////////////////// public String getSftpSendPath(){ return filePath. get ( "send" ); } public String getSftpAchievePath(){ return filePath. get ( "achieve" ); } public String getLocalTempDir(){ return filePath. get ( "localPath" ); } /////////////////////////////////////////////////// public String getHost() { return host; } public void setHost(String host) { this .host = host; } public Integer getPort() { return port; } public void setPort(Integer port) { this .port = port; } public String getUser() { return user; } public void setUser(String user) { this .user = user; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public Map<String, String> getFilePath() { return filePath; } public void setFilePath(Map<String, String> filePath) { this .filePath = filePath; } } |
Service层:SftpService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | package com.flower.integration.sftp; import com.jcraft.jsch.ChannelSftp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @Service( "sftpService" ) public class SftpService { private Logger log = LoggerFactory.getLogger( this .getClass()); @Resource(name = "fileInChannel" ) protected MessageChannel messageChannel; @Autowired private SftpProperty sftpProperty; @Autowired private SessionFactory<ChannelSftp.LsEntry> sftpSessionFactory; /** * 发送文件到SFTP, 借用MessageChannel * * @param localFilePath file local path. */ public void sendFileToSftp(String localFilePath) { Path filePath = Paths. get (localFilePath); if (filePath.toFile().exists()) { Message<File> fileMessage = MessageBuilder.withPayload(filePath.toFile()).build(); boolean result = messageChannel.send(fileMessage); String resultMsg = result ? "Success" : "Failure" ; log.info( "File send to sftp {}, File: {}." , resultMsg, filePath.getFileName()); } else { log.warn( "No found file. {}" , filePath.getFileName()); } } /** * 删除sftp文件 * * @param sessionFactory sftp server. * @param remoteDirectory file directory. * @param fileName file * @return return true is remove success,or false. */ public boolean removeSftpRemoteFile(SessionFactory<ChannelSftp.LsEntry> sessionFactory, String remoteDirectory, String fileName) { SftpRemoteFileTemplate sftpRemoteFileTemplate = new SftpRemoteFileTemplate(sessionFactory); boolean direCheck = remoteDirectory.endsWith(sftpRemoteFileTemplate.getRemoteFileSeparator()); if (!direCheck) { remoteDirectory += sftpRemoteFileTemplate.getRemoteFileSeparator(); } boolean fileExist = sftpRemoteFileTemplate.exists(remoteDirectory + fileName); if (fileExist) { return sftpRemoteFileTemplate.remove(remoteDirectory + fileName); } else { log.warn( "No found file in the directory, {}." , remoteDirectory); return false ; } } /** * sftp文件重命名 * * @param sessionFactory sftp server * @param remoteDirectory file directory path. * @param sourceFileName source file name * @param targetFileName rename target name */ public void renameSftpRemoteFile(SessionFactory<ChannelSftp.LsEntry> sessionFactory, String remoteDirectory, String sourceFileName, String targetFileName) { SftpRemoteFileTemplate fileTemplate = new SftpRemoteFileTemplate(sessionFactory); boolean direCheck = remoteDirectory.endsWith(fileTemplate.getRemoteFileSeparator()); if (!direCheck) { remoteDirectory += fileTemplate.getRemoteFileSeparator(); } boolean fileExist = fileTemplate.exists(remoteDirectory + sourceFileName); if (fileExist) { fileTemplate.rename(remoteDirectory + sourceFileName, remoteDirectory + targetFileName); } else { log.warn( "No found file in the directory, {}." , remoteDirectory); } } /** * sftp文件是否存在 * * @param sessionFactory sftp server * @param directory file directory * @param fileName file name * @return true if file exist, or false. */ public boolean fileExist(SessionFactory<ChannelSftp.LsEntry> sessionFactory, String directory, String fileName) { SftpRemoteFileTemplate fileTemplate = new SftpRemoteFileTemplate(sessionFactory); boolean fileNameCheck = directory.endsWith(fileTemplate.getRemoteFileSeparator()); if (!fileNameCheck) { directory += fileTemplate.getRemoteFileSeparator(); } return fileTemplate.exists(directory + fileName); } /** * sftp检索文件 * * @param sessionFactory sftp server * @param directory file directory * @param fileNameFilter file name filter * @return file name list match filter */ public List<String> lsFileOfDirectory(SessionFactory<ChannelSftp.LsEntry> sessionFactory, String directory, String fileNameFilter) { SftpRemoteFileTemplate fileTemplate = new SftpRemoteFileTemplate(sessionFactory); if (!directory.endsWith(fileTemplate.getRemoteFileSeparator())) { directory += fileTemplate.getRemoteFileSeparator(); } ChannelSftp.LsEntry[] files = fileTemplate.list(directory + fileNameFilter); List<String> fileNames = new ArrayList<>(); for (ChannelSftp.LsEntry lsEntry : files) { boolean isDir = lsEntry.getAttrs().isDir(); if (!isDir) { fileNames.add(lsEntry.getFilename()); } } return fileNames; } @Autowired private BeanFactory beanFactory; /** * 本地发送文件至sftp服务器 * * @param sessionFactory sftp server * @param filePath file local path * @param targetPath target directory * @param mode FileExistsModel * NULL:默认,替换文件; * APPEND:若文件存在,追加内容; * REPLACE:替换文件; * APPEND_NO_FLUSH: * FAIL: * IGNORE: */ public void sendSftpFile(SessionFactory<ChannelSftp.LsEntry> sessionFactory, String filePath, String targetPath, FileExistsMode mode){ SftpRemoteFileTemplate fileTemplate = new SftpRemoteFileTemplate(sessionFactory); try { //设置远程sftp服务器配置 fileTemplate.setRemoteDirectoryExpression( new LiteralExpression(targetPath)); fileTemplate.setAutoCreateDirectory( true ); fileTemplate.setCharset( "UTF-8" ); fileTemplate.setBeanFactory(beanFactory); fileTemplate.afterPropertiesSet(); } catch (Exception e){ log.warn(e.getMessage()); } Path file = Paths. get (filePath); if (file.toFile().exists()){ Message<File> message = MessageBuilder.withPayload(file.toFile()).build(); if ( null == mode){ fileTemplate.send(message); } else { //fileTemplate.setFileNameGenerator(new DefaultFileNameGenerator()); if (fileTemplate.isUseTemporaryFileName()){ fileTemplate.setUseTemporaryFileName( false ); } fileTemplate.send(message, mode); } } } @Resource(name = "synFileChannel" ) private SftpInboundFileSynchronizer sftpInboundFileSynchronizer; public void synchronizedFileToLocal(String localDir){ File dir = Paths. get (localDir).toFile(); sftpInboundFileSynchronizer.synchronizeToLocalDirectory(dir); } } |
Controller层:用于测试service层方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | package com.flower.integration.sftp; import com.jcraft.jsch.ChannelSftp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.file.remote.FileInfo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.util.List; @RestController public class TestController { @Autowired private SftpService sftpService; @Autowired private SftpConfig.UploadGateway uploadGateway; @GetMapping( "/sftp" ) public void testSftpSpringBatch() { List<FileInfo> fileList = uploadGateway.listFileInfo( "/send" ); for (FileInfo file : fileList) { String fileName = file.getFilename(); String filePath = file.getRemoteDirectory(); ChannelSftp.LsEntry fileInfo = (ChannelSftp.LsEntry) file.getFileInfo(); boolean isDir = file.isDirectory(); boolean isLink = file.isLink(); long modifyTime = file.getModified(); System. out .println( "============================= " + fileName); System. out .println( "================== " + filePath); System. out .println( "================== " + fileInfo.getFilename()); System. out .println( "================== " + isDir); System. out .println( "================== " + isLink); System. out .println( "================== " + modifyTime); } } @GetMapping( "/sftp2" ) public void testSftpSpringBatch2() { List<FileInfo> fileNameList = uploadGateway.listFileName( "/send" ); for (FileInfo fileName : fileNameList) { System. out .println( "============================= " + fileName); } } @GetMapping( "/sftp3" ) public void testSftpSpringBatch3() throws InterruptedException { List<File> fileNameList = uploadGateway.listFile( "/send" ); for (File fileName : fileNameList) { System. out .println( "============================= " + fileName); } } @GetMapping( "/sftp4" ) public void testSftpSpringBatch4() throws InterruptedException { String result = uploadGateway.putFile( new File( "G:\\Redis.pdf" )); System. out .println( "============================= " + result); } @GetMapping( "/sftp5" ) public void testSftpSpringBatch5() throws InterruptedException { List<String> result = uploadGateway.mputFile( new File( "G:\\js" )); for (String fileName : result) { System. out .println( "============================= " + fileName); } } @GetMapping( "/sftp6" ) public void testSftpSpringBatch6() throws InterruptedException { boolean result = uploadGateway.removeFile( "/send/2.txt" ); System. out .println( "============================= " + result); } @GetMapping( "/sftp7" ) public void testSftpSpringBatch7() throws InterruptedException { boolean result = uploadGateway.moveFile( "/22.TXT" ); System. out .println( "============================= " + result); } } |
SpringIntegrationApp.java启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.flower.integration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication //@EnableScheduling public class SpringIntegrationApp { public static void main(String[] args) { SpringApplication.run(SpringIntegrationApp. class , args); System. out .println( "Spring-Integration application start success." ); } } |
junit单元测试类:自行在test文件夹下建立并测试SpringIntegrationExamplesApplicationTests.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.flower.integration.sftp; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner. class ) @SpringBootTest public class SpringIntegrationExamplesApplicationTests { @Test public void contextLoads() { } } |
进行单元测试SftpServiceTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | package com.flower.integration.sftp; import com.flower.integration.SpringIntegrationApp; import com.jcraft.jsch.ChannelSftp; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.file.support.FileExistsMode; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner. class ) @SpringBootTest(classes = SpringIntegrationApp. class ) @EnableIntegration public class SftpServiceTest { @Autowired private SftpService sftpService; @Autowired private SftpProperty sftpProperty; @Autowired private SessionFactory<ChannelSftp.LsEntry> sftpSessionFactory; @Before public void before (){ System. out .println( "00000000000000000000000000000000000000000000000000000" ); } @After public void after(){ } @Test public void sendFileToSftp() { //sftpService.sendFileToSftp(); } @Test public void testRemoveSftpRemoteFile(){ boolean result = sftpService.removeSftpRemoteFile( sftpSessionFactory, sftpProperty.getSftpSendPath(), "user333.csv" ); System. out .println( "=======" + result); } @Test public void testRenameSftpRemoteFile(){ sftpService.renameSftpRemoteFile(sftpSessionFactory, sftpProperty.getSftpSendPath(), "user.csv" , "user111.csv" ); } @Test public void testfileExist(){ boolean result = sftpService.fileExist(sftpSessionFactory, sftpProperty.getSftpSendPath(), "user111.csv" ); System. out .println( "++++++++++++" + result); } @Test public void testlsFileOfDirectory(){ List<String> result = sftpService.lsFileOfDirectory(sftpSessionFactory, sftpProperty.getSftpSendPath(), "*TXT" ); System. out .println( "-------------------" + result.toString()); } @Test public void testSendSftpFile() throws Exception { sftpService.sendSftpFile(sftpSessionFactory, "G:\\jquery.txt" , sftpProperty.getSftpAchievePath(), FileExistsMode.REPLACE); } @Test public void testSynchronizedFileToLocal(){ sftpService.synchronizedFileToLocal(sftpProperty.getLocalTempDir()); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具