为了实现真正的池化操作,我们还需要以下代码:
1.我们需要在SftpClient对象中创建一个
GenericObjectPool
对象池,这个才是真正的池子,它负责创建和存储所有的对象。2.我们还需要提供资源销毁的功能,也就是实现
AutoCloseable
,在服务停止时,需要把相关的资源销毁。
public class SftpClient implements AutoCloseable { private SftpFactory sftpFactory; GenericObjectPool<ChannelSftp> objectPool; // 构造方法1 public SftpClient(SftpProperties properties, GenericObjectPoolConfig<ChannelSftp> poolConfig) throws AwesomeException { this.sftpFactory = new SftpFactory(properties); objectPool = new GenericObjectPool<>(this.sftpFactory, poolConfig); } // 构造方法2 public SftpClient(SftpProperties properties) throws AwesomeException { this.sftpFactory = new SftpFactory(properties); SftpProperties.PoolConfig config = properties.getPool(); // 默认池化配置 if (Objects.isNull(config)) { objectPool = new GenericObjectPool<>(this.sftpFactory); } else { // 自定义池化配置 GenericObjectPoolConfig<ChannelSftp> poolConfig = new GenericObjectPoolConfig<>(); poolConfig.setMaxIdle(config.getMaxIdle()); poolConfig.setMaxTotal(config.getMaxTotal()); poolConfig.setMinIdle(config.getMinIdle()); poolConfig.setTestOnBorrow(config.isTestOnBorrow()); poolConfig.setTestOnCreate(config.isTestOnCreate()); poolConfig.setTestOnReturn(config.isTestOnReturn()); poolConfig.setTestWhileIdle(config.isTestWhileIdle()); poolConfig.setBlockWhenExhausted(config.isBlockWhenExhausted()); poolConfig.setMaxWait(Duration.ofMillis(config.getMaxWaitMillis())); poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(config.getTimeBetweenEvictionRunsMillis())); objectPool = new GenericObjectPool<>(this.sftpFactory, poolConfig); } } // 销毁资源 @Override public void close() throws Exception { // 销毁链接池 if (Objects.nonNull(this.objectPool)) { if (!this.objectPool.isClosed()) { this.objectPool.close(); } } this.objectPool = null; // 销毁sftpFactory if (Objects.nonNull(this.sftpFactory)) { this.sftpFactory.close(); } } } 复制代码
SFTP链接池的使用
我们已经对链接池进行了初始化,下面我们就可以从链接池中获取我们需要的ChannelSftp
来实现文件的上传下载了。
下面实现了多种文件上传和下载的方式:
1.直接把本地文件上传到SFTP服务器的指定路径;
2.把InputStream输入流提交到SFTP服务器指定路径中;
3.可以针对以上两种上传方式进行进度的监测;
4.把SFTP服务器中的指定文件下载到本地机器上;
5.把SFTP服务器˙中的文件写入指定的输出流;
6.针对以上两种下载方式,监测下载进度;
/** * 上传文件 * * @param srcFilePath * @param targetDir * @param targetFileName * @return * @throws AwesomeException */ public boolean uploadFile(String srcFilePath, String targetDir, String targetFileName) throws AwesomeException { return uploadFile(srcFilePath, targetDir, targetFileName, null); } /** * 上传文件 * * @param srcFilePath * @param targetDir * @param targetFileName * @param monitor * @return * @throws AwesomeException */ public boolean uploadFile(String srcFilePath, String targetDir, String targetFileName, SftpProgressMonitor monitor) throws AwesomeException { ChannelSftp channelSftp = null; try { // 从链接池获取对象 channelSftp = this.objectPool.borrowObject(); // 如果不存在目标文件夹 if (!exist(channelSftp, targetDir)) { mkdirs(channelSftp, targetDir); } channelSftp.cd(targetDir); // 上传文件 if (Objects.nonNull(monitor)) { channelSftp.put(srcFilePath, targetFileName, monitor); } else { channelSftp.put(srcFilePath, targetFileName); } return true; } catch (Exception e) { throw new AwesomeException(500, "upload file fail"); } finally { if (Objects.nonNull(channelSftp)) { // 返还对象给链接池 this.objectPool.returnObject(channelSftp); } } } /** * 上传文件到目标文件夹 * * @param in * @param targetDir * @param targetFileName * @return * @throws AwesomeException */ public boolean uploadFile(InputStream in, String targetDir, String targetFileName) throws AwesomeException { return uploadFile(in, targetDir, targetFileName, null); } /** * 上传文件,添加进度监视器 * * @param in * @param targetDir * @param targetFileName * @param monitor * @return * @throws AwesomeException */ public boolean uploadFile(InputStream in, String targetDir, String targetFileName, SftpProgressMonitor monitor) throws AwesomeException { ChannelSftp channelSftp = null; try { channelSftp = this.objectPool.borrowObject(); // 如果不存在目标文件夹 if (!exist(channelSftp, targetDir)) { mkdirs(channelSftp, targetDir); } channelSftp.cd(targetDir); if (Objects.nonNull(monitor)) { channelSftp.put(in, targetFileName, monitor); } else { channelSftp.put(in, targetFileName); } return true; } catch (Exception e) { throw new AwesomeException(500, "upload file fail"); } finally { if (Objects.nonNull(channelSftp)) { this.objectPool.returnObject(channelSftp); } } } /** * 下载文件 * * @param remoteFile * @param targetFilePath * @return * @throws AwesomeException */ public boolean downloadFile(String remoteFile, String targetFilePath) throws AwesomeException { return downloadFile(remoteFile, targetFilePath, null); } /** * 下载目标文件到本地 * * @param remoteFile * @param targetFilePath * @return * @throws AwesomeException */ public boolean downloadFile(String remoteFile, String targetFilePath, SftpProgressMonitor monitor) throws AwesomeException { ChannelSftp channelSftp = null; try { channelSftp = this.objectPool.borrowObject(); // 如果不存在目标文件夹 if (!exist(channelSftp, remoteFile)) { // 不用下载了 return false; } File targetFile = new File(targetFilePath); try (FileOutputStream outputStream = new FileOutputStream(targetFile)) { if (Objects.nonNull(monitor)) { channelSftp.get(remoteFile, outputStream, monitor); } else { channelSftp.get(remoteFile, outputStream); } } return true; } catch (Exception e) { throw new AwesomeException(500, "upload file fail"); } finally { if (Objects.nonNull(channelSftp)) { this.objectPool.returnObject(channelSftp); } } } /** * 下载文件 * * @param remoteFile * @param outputStream * @return * @throws AwesomeException */ public boolean downloadFile(String remoteFile, OutputStream outputStream) throws AwesomeException { return downloadFile(remoteFile, outputStream, null); } /** * 下载文件 * * @param remoteFile * @param outputStream * @param monitor * @return * @throws AwesomeException */ public boolean downloadFile(String remoteFile, OutputStream outputStream, SftpProgressMonitor monitor) throws AwesomeException { ChannelSftp channelSftp = null; try { channelSftp = this.objectPool.borrowObject(); // 如果不存在目标文件夹 if (!exist(channelSftp, remoteFile)) { // 不用下载了 return false; } if (Objects.nonNull(monitor)) { channelSftp.get(remoteFile, outputStream, monitor); } else { channelSftp.get(remoteFile, outputStream); } return true; } catch (Exception e) { throw new AwesomeException(500, "upload file fail"); } finally { if (Objects.nonNull(channelSftp)) { this.objectPool.returnObject(channelSftp); } } } /** * 创建文件夹 * * @param channelSftp * @param dir * @return */ protected boolean mkdirs(ChannelSftp channelSftp, String dir) { try { String pwd = channelSftp.pwd(); if (StringUtils.contains(pwd, dir)) { return true; } String relativePath = StringUtils.substringAfter(dir, pwd); String[] dirs = StringUtils.splitByWholeSeparatorPreserveAllTokens(relativePath, "/"); for (String path : dirs) { if (StringUtils.isBlank(path)) { continue; } try { channelSftp.cd(path); } catch (SftpException e) { channelSftp.mkdir(path); channelSftp.cd(path); } } return true; } catch (Exception e) { return false; } } /** * 判断文件夹是否存在 * * @param channelSftp * @param dir * @return */ protected boolean exist(ChannelSftp channelSftp, String dir) { try { channelSftp.lstat(dir); return true; } catch (Exception e) { return false; } } 复制代码
集成到SpringBoot中
我们可以通过java config
的方式,把我们已经实现好的SftpClient
类实例化到Spring IOC
容器中来管理,以便让开发人员在整个项目中通过@Autowired
的方式就可以直接使用。
- 配置
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author zouwei * @className SftpProperties * @date: 2022/8/19 下午12:12 * @description: */ @Data @Configuration @ConfigurationProperties(prefix = "sftp.config") public class SftpProperties { // 用户名 private String username; // 密码 private String password; // 主机名 private String host; // 端口 private int port; // 密钥 private String privateKeyFile; // 口令 private String passphrase; // 通道链接超时时间 private int timeout; // 链接池配置 private PoolConfig pool; @Data public static class PoolConfig { //最大空闲实例数,空闲超过此值将会被销毁淘汰 private int maxIdle; // 最小空闲实例数,对象池将至少保留2个空闲对象 private int minIdle; //最大对象数量,包含借出去的和空闲的 private int maxTotal; //对象池满了,是否阻塞获取(false则借不到直接抛异常) private boolean blockWhenExhausted; // BlockWhenExhausted为true时生效,对象池满了阻塞获取超时,不设置则阻塞获取不超时,也可在borrowObject方法传递第二个参数指定本次的超时时间 private long maxWaitMillis; // 创建对象后是否验证对象,调用objectFactory#validateObject private boolean testOnCreate; // 借用对象后是否验证对象 validateObject private boolean testOnBorrow; // 归还对象后是否验证对象 validateObject private boolean testOnReturn; // 定时检查期间是否验证对象 validateObject private boolean testWhileIdle; //定时检查淘汰多余的对象, 启用单独的线程处理 private long timeBetweenEvictionRunsMillis; //jmx监控,和springboot自带的jmx冲突,可以选择关闭此配置或关闭springboot的jmx配置 private boolean jmxEnabled; } } 复制代码
- java Bean注入
import com.example.awesomespring.exception.AwesomeException; import com.example.awesomespring.sftp.SftpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author zouwei * @className SftpConfig * @date: 2022/8/19 下午12:12 * @description: */ @Configuration public class SftpConfig { @Autowired private SftpProperties properties; // 创建SftpClient对象 @Bean(destroyMethod = "close") @ConditionalOnProperty(prefix = "sftp.config") public SftpClient sftpClient() throws AwesomeException { return new SftpClient(properties); } } 复制代码
- 通过以上代码,我们就可以在项目的任何地方直接使用SFTP客户端来上传和下载文件了。