# sftp-spring-boot-starter **Repository Path**: javacoo/sftp-spring-boot-starter ## Basic Information - **Project Name**: sftp-spring-boot-starter - **Description**: 基于jsch简单封装 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2020-05-22 - **Last Updated**: 2024-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sftp-spring-boot-starter #### 背景问题 当时由于需要接入多家渠道SFTP服务器,推送文件,而历史代码中存在以下一些问题: 1. SFTP配置未统一集中管理,配置硬编码。 2. SFTP相关操作代码散落在业务代码中,且实现各异,问题排查,维护困难。 3. 在传送多个文件时效率较低(每个文件传送完成后都要关闭连接),且容易出错(频繁的关闭连接,打开连接,容易造成拿到未完成的连接,导致操作异常) #### 设计思路 针对上述问题,约定统一的渠道配置规则,即渠道->渠道配置的形式集中管理SFTP配置。 基于JCraft,结合渠道配置封装统一的sftp访问模板接口。 基于commons-pool2提供的池化功能,将sftp访问模板接口池化。 基于springboot starter机制,实现SFTP组件。 #### 知识准备 ##### 一:JCraft jcraft 是一个基于 Java 实现的 SSH 协议库,提供了一系列的类和方法来实现 SSH 客户端和服务器的功能。下面是 jcraft 的一些基本概念: ``` JSch:JSch 是 jcraft 中最重要的一个类,它是 SSH 协议的 Java 实现。使用 JSch 可以创建 SSH 客户端和服务器,进行连接和通信。 Session:Session 是一个远程 SSH 会话,它负责建立和维持与远程 SSH 服务器的连接。 Channel:Channel 是一个数据通道,用于在 SSH 会话中传输数据。可以通过创建不同类型的 Channel 来实现不同的功能,如执行远程命令、传输文件等。 UserInfo:UserInfo 是一个接口,用于处理用户身份验证。可以实现 UserInfo 接口来自定义身份验证逻辑。 ``` ##### 二:commons-pool2 **Apache Commons Pool2**‌是一个开源的Java库,用于实现和管理对象池。对象池是一种常见的设计模式,通过复用来分摊昂贵对象的创建和销毁代价,从而优化资源利用和提高应用程序性能。Commons Pool2提供了一套用于实现对象池化的API和灵活的配置选项,内置了多种各具特色的对象池实现,如GenericObjectPool,它实现了一个功能强大的对象池,可以方便地进行配置和扩展。通过使用Commons Pool2,开发者可以更加轻松地实现和管理对象池,提高应用程序的性能和可靠性。 使用Commons Pool2的基本步骤包括: 1. ‌**创建池工厂类**‌:这涉及到定义PooledObjectFactory接口的实现,用于创建、激活、验证和销毁池中的对象。 2. ‌**配置对象池**‌:通过设置最大池化对象数、最大空闲时间、最小空闲数等参数,根据不同的应用场景进行灵活配置。 3. ‌**对象的取用和回收**‌:使用BorrowObject方法从池中借出对象,使用完成后,使用ReturnObject方法将对象返还给池。 4. ‌**对象的激活和钝化**‌:当对象从池中借出时,它被激活;当对象被返还时,它被钝化。Commons Pool2提供了相应的接口和方法来处理这些状态转换。 此外,Commons Pool2还提供了invalidateObject方法来废弃一个对象,通常在对象发生了异常或其他问题时使用此方法废弃它。 在实际应用中,Commons Pool2被广泛应用于各种数据库连接池、线程池以及请求分发池中,通过复用对象来优化资源利用和提高性能。通过引入相关的依赖并按照上述步骤进行配置和使用,开发者可以轻松地实现和管理自己的对象池‌ ##### 三:springboot starter SpringBoot 中的 starter 是一种非常重要的机制,能够抛弃以前在 Spring 中的繁杂配置,将其统一集成进 starter,应用者只需要在 maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 让我们摆脱了各种依赖库的处理以及需要配置各种信息的困扰。SpringBoot 会自动通过 classpath 路径下的类发现需要的 Bean,并注册进 IOC 容器。SpringBoot 提供了针对日常企业应用研发各种场景的 spring-boot-starter 依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。 - 自定义 starter 的作用 在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,比如对 web 请求的日志打印。我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,这样会非常麻烦。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个 starter,复用的时候只需要将其在 maven pom 中引用依赖即可,让 SpringBoot 为我们完成自动装配,提高开发效率。 - 自定义 starter 的命名规则 SpringBoot提供的 starter 以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的 starter 使用 xxx-spring-boot-starter 命名规则。以区分 SpringBoot 生态提供的 starter。如:mybatis-spring-boot-starter。 - 如何自定义starter 步骤 1. 新建两个模块,命名规范: xxx-spring-boot-starter - xxx-spring-boot-autoconfigure:自动配置核心代码 - xxx-spring-boot-starter:管理依赖 - ps:如果不需要将自动配置代码和依赖项管理分离开来,则可以将它们组合到一个模块中。但SpringBoot 官方建议将两个模块分开。 2. 在 xxx-spring-boot-autoconfigure 项目中 - 引入 spring-boot-autoconfigure 的 maven 依赖 - 创建自定义的 XXXProperties 类: 这个类的属性根据需要是要出现在配置文件中的。 - 创建自定义的类,实现自定义的功能。 - 创建自定义的 XXXAutoConfiguration 类:这个类用于做自动配置时的一些逻辑,需将上方自定义类进行 Bean 对象创建,同时也要让 XXXProperties 类生效。 - 创建自定义的 spring.factories 文件:在 resources/META-INF 创建一个 spring.factories 文件和 spring-configuration-metadata.json,spring-configuration-metadata.json 文件是用于在填写配置文件时的智能提示,可要可不要,有的话提示起来更友好。spring.factories用于导入自动配置类,必须要有。 3. 在 xxx-spring-boot-starter 项目中引入 xxx-spring-boot-autoconfigure 依赖,其他项目使用该 starter 时只需要依赖 xxx-spring-boot-starter 即可。 #### 如何使用 ##### 第一步:pom依赖 ``` com.jccfc sftp-spring-boot-starter 1.0.1 ``` ##### 第二步:参数配置 ```properties #阿里 sftp.clients.alipay.host = 127.0.0.1 sftp.clients.alipay.port = 22 sftp.clients.alipay.username = alipay sftp.clients.alipay.password = YqK#m6a0 sftp.clients.alipay.path = /alipay/{yyyyMMdd} sftp.clients.alipay.type = 00 sftp.clients.alipay.desc = 阿里 #小杜 sftp.clients.xiaodu.host = 127.0.0.1 sftp.clients.xiaodu.port = 22 sftp.clients.xiaodu.username = xiaodu sftp.clients.xiaodu.password = W5eK2#%t sftp.clients.xiaodu.path = /xiaodu/{yyyyMMdd} sftp.clients.xiaodu.type = 00 sftp.clients.xiaodu.desc = 小杜 ``` **参数说明:** - sftp.clients.**XXXX**:XXXX 表示SFTP客户端配置key,一个渠道一个 - host:主机(必填) - port:端口(必填) - username:用户名(必填) - password:密码(必填) - path:路径(必填) - type:类型(扩展字段,可为空) - desc:描述(扩展字段,可为空) ##### 第三步:使用SftpHelper实现业务功能,如: ```java /** * 委外机构SFTP语音文件传输服务 *
  • * @author duanyong@jccfc.com * @date 2024/9/24 18:24 */ @Service public class OutSftpService { private static Logger logger = LoggerFactory.getLogger(OutSftpService.class); .... /** sftpHelper帮助类*/ @Autowired private SftpHelper sftpHelper; /** sftp客户端配置参数*/ @Autowired private SftpClientProperties sftpClientProperties; .... private void handle(String businessDate,String channel, SftpClientConfig config){ logger.info("开始执行委外机构SFTP文件传输任务,委外机构渠道ID:{},业务日期:{}",channel,businessDate); try{ Vector fileList; String dir = ""; if(StrUtil.isNotBlank(businessDate)){ dir = formatDir(config.getPath(),businessDate); //获取渠道下,指定目录文件集合 fileList = sftpHelper.list(channel,dir); }else{ //获取渠道下,当前日期目录文件集合 fileList = sftpHelper.list(channel); } List outCallFileList = new ArrayList<>(); for (ChannelSftp.LsEntry lsEntry : fileList) { if(lsEntry.getAttrs().isDir()){ continue; } String fileName = lsEntry.getFilename(); // 初始化文件对象 OutCallFile outCallFile = new OutCallFile(); //下载文件 downLoadFile(outCallFile,channel,dir,fileName); .... } }catch (Exception e){ ... } } } ... private void downLoadFile(OutCallFile outCallFile,String channel,String dir, String fileName) { logger.info("开始下载文件:目录:{},文件名:{}", dir,fileName); try { byte[] bytes; if(StrUtil.isNotBlank(dir)){ // 下载渠道下,指定目录文件 bytes = sftpHelper.downloadBytes(channel,dir, fileName); }else{ // 下载渠道下,当前日期目录文件 bytes = sftpHelper.downloadNow(channel, fileName); } ....... }catch (Exception e){ logger.error("文件下载失败,文件名:{}",fileName); } } ``` ##### 附:SFTP操作帮助类SftpHelper相关方法定义 ```java /** * 获取指定渠道下,默认当前日期目录下的文件名集合 *
  • 适用于路径配置如:/dingxiang/{yyyyMMdd}
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:47 * @param channel 渠道ID * @return: Vector 文件名集合 */ public Vector list(final String channel) /** * 获取渠道下,指定目录下的文件名集合 *
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:47 * @param channel 渠道ID * @param dir 全路径目录 * @return: Vector 文件名集合 */ public Vector list(final String channel,final String dir) /** * 下载默认当前日期目录下,指定文件名的文件为byte[] *
  • 适用于路径配置如:/dingxiang/{yyyyMMdd}
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:53 * @param channel 渠道ID * @param fileName 文件名 * @return: byte */ public byte[] downloadNow(final String channel,String fileName) /** * 下载指定目录下,指定文件名的文件为byte[] *
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:53 * @param channel 渠道ID * @param dir 全路径目录 * @param fileName 文件名 * @return: byte */ public byte[] downloadBytes(final String channel,final String dir,String fileName) /** * 文件下载 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:13 * @param channel 渠道ID * @param filePath 文件全路径 * @return: InputStream */ public InputStream download(final String channel,final String filePath) /** * 判断文件是否存在 *
  • 指定sftp文件路径和文件名,判断改文件是否存在当前指定的sftp目录下,若存在返回true,否则返回false
  • * @author duanyong@jccfc.com * @date 2020/5/15 13:51 * @param channel:渠道ID * @param sftpDir:sftp目录 * @param fileName:文件名 * @return: boolean */ public boolean exists(String channel, String sftpDir, String fileName) /** * 文件传输 *
  • 上传文件到渠道下昨天日期目录下
  • *
  • 适用于路径配置如:/dingxiang/{yyyyMMdd}
  • * @author duanyong@jccfc.com * @date 2020/5/15 11:14 * @param channel: 渠道ID * @param fileName: 文件名 * @param inputStream: 待上传的文件输入流 */ public void upload(final String channel,final String fileName, final InputStream inputStream) /** * 文件传输 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:11 * @param channel 渠道ID * @param dir 目录 * @param fileName 文件名 * @param inputStream * @return: void 输入流 */ public void upload(final String channel,final String dir,final String fileName, final InputStream inputStream) /** * 文件传输 *
  • 上传文件到渠道下当前日期目录
  • *
  • 适用于路径配置如:/dingxiang/{yyyyMMdd}
  • * @author duanyong@jccfc.com * @date 2020/5/15 11:14 * @param channel: 渠道ID * @param fileName: 文件名 * @param inputStream: 待上传的文件输入流 */ public void uploadNow(final String channel,final String fileName, final InputStream inputStream) /** * 递归创建目录 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:06 * @param channel 渠道ID * @param dir 全路径目录 * @return: void */ public void makeDirs(final String channel,final String dir) /** * 删除渠道下,指定目录文件 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:08 * @param channel 渠道ID * @param dir 全路径目录 * @param fileName 文件名 * @return: void */ public void delete(final String channel,final String dir,final String fileName) /** * 判断是否目录 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:15 * @param channel 渠道ID * @param path 路径 * @return: boolean */ public boolean isdir(String channel, String path) /** * 进入指定目录 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:16 * @param channel 渠道ID * @param path 路径 * @return: void */ public void cd(String channel, String path) /** * 删除目录 *
  • * @author duanyong@jccfc.com * @date 2024/9/29 10:59 * @param channel 渠道ID * @param path 路径 * @return: void */ public void rmdir(String channel, String path) ``` ------ #### 组件实现 ##### 一:工程结构 ``` sftp-spring-boot-starter └── src ├── main │ ├── java │ │ └── com.javacoo │ │ ├────── sftp │ │ │ ├──────client │ │ │ │ ├── api │ │ │ │ │ └── client │ │ │ │ │ └── SftpClient sftp操作接口 │ │ │ │ ├── config 参数配置 │ │ │ │ │ ├── SftpClientConfig sftpClient配置类 │ │ │ │ │ └── SftpPoolConfig sftp连接池配置 │ │ │ │ ├── exception 异常 │ │ │ │ │ ├── BaseException 自定义异常基类 │ │ │ │ │ └── SftpClientException 自定义Sftp异常 │ │ │ │ ├── template 模版 │ │ │ │ │ └── SftpTemplate Sftp操作Template │ │ │ │ ├── util 工具包 │ │ │ │ │ └── SftpHelper sftp操作帮助类 │ │ │ │ └── internal 接口内部实现 │ │ │ │ ├── DefaultSftpClient sftp客户端接口默认实现 │ │ │ │ ├── SftpTemplateFactory SftpTemplate工厂 │ │ │ │ └── SftpTemplatePool SftpTemplate池子 │ │ │ └──────starter │ │ │ ├── SftpClientProperties 配置类 │ │ │ └── SftpClientAutoConfiguration 自动配置类 │ └── resource │ └── META-INF | └── spring.factories └── test 测试 ``` ##### 二:主要依赖包及版本 | 名称 | 版本 | | ----------------------------------- |---------------| | spring-boot-starter | 2.1.8.RELEASE | | spring-boot-configuration-processor | 2.1.8.RELEASE | | spring-boot-autoconfigure | 2.1.8.RELEASE | | commons-pool2 | 2.7.0 | | jsch | 0.1.55 | | lombok | 1.18.6 | ##### 三:主要类结构关系图 ![](.\SftpClientAutoConfiguration.png) ##### 三:主要类及关键代码 组件对外提供SftpClient(Sftp客户端接口),和SftpHelper(sftp操作帮助类,提供Sftp客户端一些常用操作), ###### 1:相关配置类 - SftpClientConfig:Sftp服务器配置信息类,主要包含主机ip,端口号,用户名,密码,路径,描述等信息。 - SftpPoolConfig:sftp池配置类,该类继承GenericKeyedObjectPoolConfig,主要包含获取连接重试次数,是否检测对象有效,对象最大数量,最大空闲对象数量,最小空闲对象数量,在从对象池获取对象时是否检测对象有效,获取连接重试次数等信息。 ###### 2:基于jsch实现SFTP相关操作相关类 - SftpTemplate:Sftp操作模版类,此类包装了jsch 的ChannelSftp相关常用SFTP操作方法,结合SftpClientConfig,简化了部分操作,是需要被池化的对象。 ```java @Data @NoArgsConstructor @AllArgsConstructor public class SftpTemplate { /** sftp通道*/ private ChannelSftp channelSftp; /** sftp配置*/ private SftpClientConfig sftpClientConfig; ... /** * 下载渠道指定目录下远程文件 * * @param dir 文件目录 * @param name 文件名 * @return 文件字节数组 */ public byte[] download(String dir, String name) { if (!isExist(dir)) { throw new SftpClientException("目录{}不存在!", dir); } String absoluteFilePath = dir + "/" + name; if (!isExist(absoluteFilePath)) { throw new SftpClientException("文件{}不存在!", absoluteFilePath); } try { channelSftp.cd(dir); InputStream in = channelSftp.get(name); return ByteUtil.inputStreamToByteArray(in); } catch (SftpException e) { throw new SftpClientException(e.id,"sftp下载文件出错", e); } } ... /** * 下载渠道当前日期目录下文件 * * @param name 文件名 * @return 文件字节数组 */ public byte[] downloadNow(String name) { String path = StringUtil.formatDateNow(sftpClientConfig.getPath(),SftpClientConfig.REGEX); try { if(!isExist(path)){ mkdirs(path); } String absoluteFilePath = path + "/" + name; if (!isExist(absoluteFilePath)) { throw new SftpClientException("文件{}不存在!", absoluteFilePath); } channelSftp.cd(path); InputStream in = channelSftp.get(name); return ByteUtil.inputStreamToByteArray(in); } catch (SftpException e) { throw new SftpClientException(e.id,"sftp下载文件出错", e); } } ... } ``` ###### 3:基于commons-pool2实现池化相关类 - SftpTemplateFactory:SftpTemplate工厂,负责创建SftpTemplate并放入对象池中,该类继承BaseKeyedPooledObjectFactory(带KEY的池子),重写了create,wrap,destroyObject,validateObject方法。 ```java @Slf4j public class SftpTemplateFactory extends BaseKeyedPooledObjectFactory { private static final String CHANNEL_TYPE = "sftp"; private static Properties SHH_CONFIG = new Properties(); static { SHH_CONFIG.put("StrictHostKeyChecking", "no"); } /** * 创建一个SftpTemplate实例 * 这个方法必须支持并发多线程调用 * @author duanyong@jccfc.com * @date 2020/5/14 11:19 * @param config:Sftp配置信息 * @return: com.javacoo.sftp.client.template.SftpTemplate */ @Override public SftpTemplate create(SftpClientConfig config) { try { JSch jsch = new JSch(); Session sshSession = jsch.getSession(config.getUsername(), config.getHost(), config.getPort()); sshSession.setPassword(config.getPassword()); sshSession.setConfig(SHH_CONFIG); sshSession.setTimeout(config.getTimeout()); sshSession.connect(); ChannelSftp channel = (ChannelSftp) sshSession.openChannel(CHANNEL_TYPE); channel.connect(); return new SftpTemplate(channel,config); } catch (Exception e) { log.error("客户端:{},连接sftp失败:{}",config, e.getMessage()); throw new SftpClientException("客户端:{},连接sftp失败",e, config.toString()); } } /** * 用PooledObject的实例包装对象 * @author duanyong@jccfc.com * @date 2020/5/14 11:50 * @param sftpTemplate 被包装的对象 * @return: org.apache.commons.pool2.PooledObject 对象包装器 */ @Override public PooledObject wrap(SftpTemplate sftpTemplate) { return new DefaultPooledObject(sftpTemplate); } /** * 销毁对象 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 11:52 * @param config:Sftp配置信息 * @param p:包装对象 * @return: void */ @Override public void destroyObject(SftpClientConfig config,PooledObject p) throws Exception{ if (p==null) { return; } SftpTemplate sftpTemplate = p.getObject(); if (sftpTemplate == null) { return; } ChannelSftp channelSftp = sftpTemplate.getChannelSftp(); if (channelSftp != null) { channelSftp.disconnect(); } sftpTemplate.setChannelSftp(null); sftpTemplate.setSftpClientConfig(null); } /** * 检查连接是否可用 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 11:54 * @param config:Sftp配置信息 * @param p:包装对象 * @return: boolean */ @Override public boolean validateObject(SftpClientConfig config,PooledObject p){ if (p == null) { return false; } SftpTemplate sftpTemplate = p.getObject(); if (sftpTemplate == null) { return false; } try { sftpTemplate.getChannelSftp().cd("./"); return true; } catch (SftpException e) { return false; } } } ``` - SftpTemplatePool:存放SftpTemplate的池子,是组件的核心。该类构造时根据sftp连接池配置和SftpTemplate工厂完成对GenericKeyedObjectPool的构造,提供了根据客户端配置key获取和释放SftpTemplate对象的方法。 ```java @Slf4j @Data public class SftpTemplatePool{ /** SftpTemplate 对象池 */ private GenericKeyedObjectPool pool; /** sftp服务器配置 */ private Map clients; /** sftp连接池配置 */ private SftpPoolConfig sftpPoolConfig; public SftpTemplatePool(Map clients,SftpPoolConfig sftpPoolConfig) { this.clients = clients; this.sftpPoolConfig = sftpPoolConfig; this.pool = new GenericKeyedObjectPool<>(new SftpTemplateFactory()); setConfig(sftpPoolConfig); } /** * 设置config * @author duanyong@jccfc.com * @date 2020/9/27 16:32 * @param conf: * @return: void */ private void setConfig(SftpPoolConfig conf) { this.pool.setLifo(conf.isLifo()); .... } /** * 获取SftpTemplate *
  • * @author duanyong@jccfc.com * @date 2020/5/14 13:28 * @param key: 客户端配置KEY * @return: com.javacoo.sftp.client.template.SftpTemplate */ public SftpTemplate getClient(String key) throws Exception { if(!clients.containsKey(key)){ log.error("客户端:{},未配置sftp连接信息",key); throw new SftpClientException("客户端:{},未配置sftp连接信息",key); } log.debug("SftpTemplatePool->getClient->{}",key); return pool.borrowObject(clients.get(key)); } /** * 释放SftpTemplate *
  • * @author duanyong@jccfc.com * @date 2020/5/14 13:28 * @param key: 客户端配置KEY * @param sftpTemplate: * @return: void */ public void releaseClient(String key,SftpTemplate sftpTemplate) { try { pool.returnObject(clients.get(key),sftpTemplate); log.debug("SftpTemplatePool->releaseClient->{}",key); } catch (Exception e) { log.error("释放SftpTemplate异常:",e); } } } ``` ###### 4:组件对外提供接口及帮助类 - SftpClient:Sftp客户端接口,该接口定义了包括无返回值和有返回值,根据客户端配置KEY,运行处理器的方法,和Handler,ReturnHandler两个函数式接口。 ```java /** * 运行处理器 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:29 * @param key: 客户端配置KEY * @param handler: 处理器 * @return: void */ void run(String key,Handler handler); /** * 运行带返回值的处理器 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:37 * @param key 客户端配置KEY * @param handler 带返回值的处理器 * @return: T 返回值 */ T runAndReturn(String key,ReturnHandler handler); @FunctionalInterface interface Handler { /** *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:23 * @param template:操作模板类型 * @return: void */ void doHandle(SftpTemplate template) throws Exception; } @FunctionalInterface interface ReturnHandler { /** *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:23 * @param template:操作模板类型 * @return: void */ T doHandle(SftpTemplate template) throws Exception; } ``` - DefaultSftpClient:Sftp客户端接口默认实现类,该类集成了SftpTemplatePool,实现了根据客户端配置KEY,从SftpTemplatePool中获取SftpTemplate对象,并传入函数式接口中,完成具体操作。 ```java @Data @NoArgsConstructor @AllArgsConstructor @Slf4j @Builder public class DefaultSftpClient implements SftpClient { /** SftpTemplate连接池*/ private SftpTemplatePool sftpTemplatePool; /** * 从sftp连接池获取连接并执行操作 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:29 * @param key: 客户端配置KEY * @param handler: 处理器 * @return: void */ @Override public void run(String key,SftpClient.Handler handler) { SftpTemplate sftpTemplate = getClient(key).orElseThrow(()->new SftpClientException("客户端:{},获取sftp连接出错",key)); try { handler.doHandle(sftpTemplate); }catch (Exception e) { log.error("客户端:{},异常:{}",key, e.getMessage()); throw new SftpClientException("客户端:{},sftp发生异常", e,key); } finally { if (sftpTemplate != null) { sftpTemplatePool.releaseClient(key,sftpTemplate); } } } /** * 从sftp连接池获取连接并执行操作 *
  • * @author duanyong@jccfc.com * @date 2020/5/14 10:29 * @param key: 客户端配置KEY * @param handler: 处理器 * @return: T */ @Override public T runAndReturn(String key, ReturnHandler handler) { SftpTemplate sftpTemplate = getClient(key).orElseThrow(()->new SftpClientException("客户端:{},获取sftp连接出错",key)); try { return handler.doHandle(sftpTemplate); } catch (Exception e) { log.error("客户端:{},异常:{}",key, e.getMessage()); throw new SftpClientException("客户端:{},sftp发生异常", e,key); } finally { if (sftpTemplate != null) { sftpTemplatePool.releaseClient(key,sftpTemplate); } } } /** * 获取SftpTemplate *
  • * @author duanyong@jccfc.com * @date 2020/6/9 10:26 * @param key: 渠道key * @return: com.javacoo.sftp.client.template.SftpTemplate */ private Optional getClient(String key){ SftpTemplate client = null; //重试次数 AtomicInteger retryCount = new AtomicInteger(this.sftpTemplatePool.getSftpPoolConfig().getRetryCount()); while (client == null && retryCount.decrementAndGet() >= 0) { try { client = sftpTemplatePool.getClient(key); } catch (Exception e) { log.error("线程->{},从对象池中获client异常,还可以重试:{}次",Thread.currentThread().getName(),retryCount.get(),e); } } return Optional.ofNullable(client); } } ``` - SftpHelper:sftp操作帮助类,提供了Sftp客户端一些常用操作实现。 ```java @Data @Slf4j public class SftpHelper { /** SFTP 客户端*/ @Autowired private SftpClient sftpClient; ... /** * 下载默认当前日期目录下,指定文件名的文件为byte[] *
  • 适用于路径配置如:/dingxiang/{yyyyMMdd}
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:53 * @param channel 渠道ID * @param fileName 文件名 * @return: byte */ public byte[] downloadNow(final String channel,String fileName){ return sftpClient.runAndReturn(channel,sftpTemplate-> sftpTemplate.downloadNow(fileName)); } /** * 下载指定目录下,指定文件名的文件为byte[] *
  • * @author duanyong@jccfc.com * @date 2024/9/24 17:53 * @param channel 渠道ID * @param dir 全路径目录 * @param fileName 文件名 * @return: byte */ public byte[] downloadBytes(final String channel,final String dir,String fileName){ return sftpClient.runAndReturn(channel,sftpTemplate-> sftpTemplate.download(dir,fileName)); } ... } ``` ###### 5:基于springboot starter实现组件相关类 - SftpClientProperties:组件配置参数类,该类集成了commons-pool2池配置和sftp服务器配置。 ```java @Data @ConfigurationProperties(prefix = SftpClientProperties.PREFIX) public class SftpClientProperties { /** 前缀,默认值*/ public static final String PREFIX = "sftp"; /** 是否可用,默认值*/ public static final String ENABLED = "enabled"; /** * 池配置 */ @NestedConfigurationProperty private Pool pool = new Pool(); /** * sftp服务器配置 */ private Map clients = new LinkedHashMap<>(); /** * 池配置参数 */ @Data public static class Pool { ... } } ``` - SftpClientAutoConfiguration:组件自动配置类,该类集成了组件配置参数类,基于组件配置参数完成对SftpClient,SftpHelper对象的构建。 ```java @Configuration @EnableConfigurationProperties(value = SftpClientProperties.class) @ConditionalOnClass(SftpClient.class) @ConditionalOnProperty(prefix = SftpClientProperties.PREFIX, value = SftpClientProperties.ENABLED, matchIfMissing = true) public class SftpClientAutoConfiguration { /** sftp连接池配置参数*/ @Autowired private SftpClientProperties sftpClientProperties; @Bean @ConditionalOnMissingBean(SftpClient.class) public SftpClient createSftpClient() throws Exception { SftpPoolConfig sftpPoolConfig = createSftpPoolConfig(sftpClientProperties); SftpTemplatePool sftpTemplatePool = new SftpTemplatePool(sftpClientProperties.getClients(),sftpPoolConfig); return DefaultSftpClient.builder().sftpTemplatePool(sftpTemplatePool).build(); } @Bean @ConditionalOnClass(SftpClient.class) public SftpHelper createSftpHelper() { return new SftpHelper(); } /** * 创建SftpPoolConfig *
  • * @author duanyong@jccfc.com * @date 2020/5/14 13:49 * @param properties:sftp连接池配置参数 * @return: com.javacoo.sftp.client.config.SftpPoolConfig */ private SftpPoolConfig createSftpPoolConfig(SftpClientProperties properties) { SftpClientProperties.Pool pool = properties.getPool(); return SftpPoolConfig.builder() .maxTotal(pool.getMaxTotal()) .maxIdle(pool.getMaxIdle()) .minIdle(pool.getMinIdle()) .lifo(pool.isLifo()) .fairness(pool.isFairness()) .maxWaitMillis(pool.getMaxWaitMillis()) .minEvictableIdleTimeMillis(pool.getMinEvictableIdleTimeMillis()) .softMinEvictableIdleTimeMillis(pool.getSoftMinEvictableIdleTimeMillis()) .numTestsPerEvictionRun(pool.getNumTestsPerEvictionRun()) //.evictionPolicy(null) .evictionPolicyClassName(DefaultEvictionPolicy.class.getName()) .testOnCreate(pool.isTestOnCreate()) .testOnBorrow(pool.isTestOnBorrow()) .testOnReturn(pool.isTestOnReturn()) .testWhileIdle(pool.isTestWhileIdle()) .timeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRunsMillis()) .blockWhenExhausted(pool.isBlockWhenExhausted()) .jmxEnabled(pool.isJmxEnabled()) .jmxNamePrefix(pool.getJmxNamePrefix()) .jmxNameBase(pool.getJmxNameBase()) .retryCount(pool.getRetryCount()) .build(); } } ```