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());
    }
}

 

posted @   47号Gamer丶  阅读(2048)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示