自定义Jsch连接池,实现Sftp文件上传和Shell远程解压
♉前言
在工作过程中遇到了文件上传和文件线上解压等场景,于是决定使用Jsch的SFTP,最开始使用的是一个单纯的SFTP工具类,但是在使用过程发现其是不支持多线程的,于是将其改造成了多线程版本,使用一段时间后,发现文件上传等接口的时间有点长,于是使用XRebel进行排查问题,发现在创建Sftp和shell连接耗费的时间有点长,于是决定将其适配连接池
♉代码
以下代码使用Springboot框架作为代码环境,代码中有一些自定义异常类我没放上来,如果有需要可以自定义,或者将使用了的地方替换成RunTimeException
🐕Maven配置
pom.xml
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jsch -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!--超级nb的一个工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.4</version>
</dependency>
<!--自定义连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version>
</dependency>
🐺SpringBoot配置文件配置
application.yml
配置文件中连接池相关配置
upload-sftp:
host: 192.168.11.112
port: 22
username: root
password: 123456
system: linux
path: /home/upload
sftp-pool:
max-total: 20
max-idle: 20
min-idle: 10
shell-pool:
max-total: 10
max-idle: 10
min-idle: 5
🦁配置相关类
FileChannelConfig
import com.pzx.demo.common.sftp.core.factory.SftpFactory;
import com.pzx.demo.common.sftp.core.factory.ShellFactory;
import com.pzx.demo.common.sftp.core.helper.SftpHelper;
import com.pzx.demo.common.sftp.core.helper.ShellHelper;
import com.pzx.demo.common.sftp.core.pool.SftpPool;
import com.pzx.demo.common.sftp.core.pool.ShellPool;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author JunPzx
* @version v1.0.0
* @date 2021/8/3 15:10
*/
@Configuration
@EnableConfigurationProperties(FileChannelProperties.class)
public class FileChannelConfig {
@Bean
public SftpFactory sftpFactory(FileChannelProperties properties) {
return new SftpFactory(properties);
}
@Bean
public SftpPool sftpPool(SftpFactory sftpFactory) {
final SftpPool sftpPool = new SftpPool(sftpFactory);
try {
// 初始化连接池
sftpPool.init();
} catch (Exception e) {
e.printStackTrace();
}
return sftpPool;
}
@Bean
public SftpHelper sftpHelper(SftpPool sftpPool) {
return new SftpHelper(sftpPool);
}
@Bean
public ShellFactory shellFactory(FileChannelProperties properties) {
return new ShellFactory(properties);
}
@Bean
public ShellPool shellPool(ShellFactory shellFactory) {
final ShellPool shellPool = new ShellPool(shellFactory);
try {
// 初始化连接池
shellPool.init();
} catch (Exception e) {
e.printStackTrace();
}
return shellPool;
}
@Bean
public ShellHelper shellHelper(ShellPool shellPool) {
return new ShellHelper(shellPool);
}
FileChannelProperties
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelShell;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author JunPzx
* @version v1.0.0
* @date 2021/8/4 14:11
*/
@Data
@ConfigurationProperties(prefix = "upload-sftp")
public class FileChannelProperties {
private static final String LINUX = "linux";
private static final String WINDOWS = "windows";
/**
* 文件保存根路径
*/
private static String path;
/**
* 使用的系统,默认使用linux
*/
private static String system = LINUX;
/**
* 主机
*/
private static String host;
/**
* 端口
*/
private static int port = 22;
/**
* 账号
*/
private static String username = "root";
/**
* 密码
*/
private static String password = "root";
private SftpPool sftpPool = new SftpPool();
private ShellPool shellPool = new ShellPool();
public static String getPath() {
if (StrUtil.isBlank(system) || LINUX.equalsIgnoreCase(system)) {
return StrUtil.isNotBlank(path) ? path : "/";
}
return StrUtil.isNotBlank(path) ? path : "C:/";
}
public void setPath(String path) {
FileChannelProperties.path = path;
}
public static String getHost() {
return host;
}
public void setHost(String host) {
FileChannelProperties.host = host;
}
public static int getPort() {
return port;
}
public void setPort(int port) {
FileChannelProperties.port = port;
}
public static String getUsername() {
return username;
}
public void setUsername(String username) {
FileChannelProperties.username = username;
}
public static String getPassword() {
return password;
}
public void setPassword(String password) {
FileChannelProperties.password = password;
}
public void setSystem(String system) {
FileChannelProperties.system = system;
}
public static class SftpPool extends GenericObjectPoolConfig<ChannelSftp> {
/**
* 最大连接数
*/
private int maxTotal = DEFAULT_MAX_TOTAL;
/**
* 最大空闲数
*/
private int maxIdle = DEFAULT_MAX_IDLE;
/**
* 最小空闲数
*/
private int minIdle = DEFAULT_MIN_IDLE;
public SftpPool() {
super();
}
@Override
public int getMaxTotal() {
return maxTotal;
}
@Override
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
@Override
public int getMaxIdle() {
return maxIdle;
}
@Override
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
@Override
public int getMinIdle() {
return minIdle;
}
@Override
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
}
public static class ShellPool extends GenericObjectPoolConfig<ChannelShell> {
/**
* 最大连接数
*/
private int maxTotal = DEFAULT_MAX_TOTAL;
/**
* 最大空闲数
*/
private int maxIdle = DEFAULT_MAX_IDLE;
/**
* 最小空闲数
*/
private int minIdle = DEFAULT_MIN_IDLE;
public ShellPool() {
super();
}
@Override
public int getMaxTotal() {
return maxTotal;
}
@Override
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
@Override
public int getMaxIdle() {
return maxIdle;
}
@Override
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
@Override
public int getMinIdle() {
return minIdle;
}
@Override
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
}
}
🐅工厂相关类
SftpFactory
import com.jcraft.jsch.*;
import com.pzx.demo.common.sftp.core.config.FileChannelProperties;
import com.pzx.demo.core.exception.ApplicationException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import java.util.Properties;
/**
* Sftp工厂
*
* @author JunPzx
* @version v1.0.0
* @date 2021/8/4 14:15
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SftpFactory extends BasePooledObjectFactory<ChannelSftp> {
private FileChannelProperties properties;
public SftpFactory(FileChannelProperties fileChannelProperties) {
this.properties = fileChannelProperties;
}
@Override
public ChannelSftp create() throws Exception {
try {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(FileChannelProperties.getUsername(), FileChannelProperties.getHost(), FileChannelProperties.getPort());
sshSession.setPassword(FileChannelProperties.getPassword());
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
ChannelSftp channel = (ChannelSftp) sshSession.openChannel("sftp");
channel.connect();
return channel;
} catch (JSchException e) {
throw new ApplicationException("连接SFTP失败");
}
}
@Override
public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {
return new DefaultPooledObject<>(channelSftp);
}
@Override
public void destroyObject(PooledObject<ChannelSftp> p) {
ChannelSftp channelSftp = p.getObject();
channelSftp.disconnect();
}
@Override
public boolean validateObject(final PooledObject<ChannelSftp> p) {
final ChannelSftp channelSftp = p.getObject();
try {
if (channelSftp.isClosed()) {
return false;
}
channelSftp.cd("/");
} catch (SftpException e) {
return false;
}
return true;
}
}
ShellFactory
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.pzx.demo.common.sftp.core.config.FileChannelProperties;
import com.pzx.demo.core.exception.ApplicationException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import java.io.*;
import java.util.Properties;
/**
* 命令执行器工厂
*
* @author JunPzx
* @version v1.0.0
* @date 2021/8/4 14:15
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ShellFactory extends BasePooledObjectFactory<ChannelShell> {
private FileChannelProperties properties;
public ShellFactory(FileChannelProperties fileChannelProperties) {
this.properties = fileChannelProperties;
}
@Override
public ChannelShell create() throws Exception {
try {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(FileChannelProperties.getUsername(), FileChannelProperties.getHost(), FileChannelProperties.getPort());
sshSession.setPassword(FileChannelProperties.getPassword());
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
ChannelShell shell = (ChannelShell) sshSession.openChannel("shell");
shell.connect();
return shell;
} catch (JSchException e) {
throw new ApplicationException("连接Shell失败");
}
}
@Override
public PooledObject<ChannelShell> wrap(ChannelShell channelShell) {
return new DefaultPooledObject<>(channelShell);
}
@Override
public void destroyObject(PooledObject<ChannelShell> p) {
ChannelShell channelShell = p.getObject();
channelShell.disconnect();
}
@Override
public boolean validateObject(final PooledObject<ChannelShell> p) {
final ChannelShell channelShell = p.getObject();
try {
if (channelShell.isClosed()) {
return false;
}
final OutputStream outputStream = channelShell.getOutputStream();
final InputStream inputStream = channelShell.getInputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
String commandFlag = ("echo $?");
printWriter.println("cd /");
printWriter.println(commandFlag);
printWriter.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String msg;
boolean flag = false;
int count = 0;
while ((msg = in.readLine()) != null) {
if (!flag) {
flag = msg.contains(commandFlag);
} else {
return "0".equals(msg) || "1".equals(msg);
}
// 这个地方代码的必要性不是很强,只要配置了空闲连接检测,这段代码可以删掉,但是尽量还是留着,防止意外
if (!flag) {
count++;
if (count > 20) {
return false;
}
}
}
return true;
} catch (IOException e) {
return false;
}
}
}
🐆相关使用工具类
SftpHelper
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ArrayUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import com.pzx.demo.common.sftp.core.config.FileChannelProperties;
import com.pzx.demo.common.sftp.core.pool.SftpPool;
import com.pzx.demo.core.exception.ApplicationSftpException;
import lombok.extern.log4j.Log4j2;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.stream.Collectors;
/**
* Sftp工具类
*
* @author 2021/8/3
* @author Junpzx
*/
@Log4j2
public class SftpHelper {
/**
* 每个目录下最大子文件(夹)数量
*/
private static final int MAX_CHILD_FILE_NUMBER = 1000;
private static final String NODE_SEPARATOR = "/";
private final static String PATH = FileChannelProperties.getPath();
private SftpPool pool;
public SftpHelper(SftpPool pool) {
this.pool = pool;
}
/**
* 附件上传
*
* @param fileName 文件名
* @param inputStream 文件流
* @return 上传后的文件完整路径
*/
public String upload(String fileName, InputStream inputStream) {
return upload(null, fileName, inputStream);
}
/**
* 文件上传
*
* @param relativePath 文件保存的相对路径(最后一级目录)
* @param fileName 文件名
* @param inputStream 文件流
* @return 上传后的文件完整路径
*/
public String upload(String relativePath, String fileName, InputStream inputStream) {
ChannelSftp sftp = pool.borrowObject();
try {
String filePath = PATH;
if (relativePath != null && !relativePath.trim().isEmpty()) {
filePath = PATH + relativePath;
}
if (!dirIsExist(filePath)) {
filePath = generateValidPath(filePath, sftp);
}
filePath = filePath.concat(fileName);
sftp.put(inputStream, filePath);
sftp.chmod(Integer.parseInt("777", 8), filePath);
return filePath;
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP上传文件出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 文件下载
*
* @param fileUrl 文件路径
* @return 文件字节数组
*/
public byte[] download(String fileUrl) {
ChannelSftp sftp = pool.borrowObject();
try {
InputStream inputStream = sftp.get(fileUrl);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int n;
byte[] data = new byte[1024];
while ((n = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, n);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | SftpException e) {
throw new ApplicationSftpException("SFTP下载文件出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 创建目录(只能创建一级目录,如果需要创建多级目录,需要调用mkdirs方法)
*
* @param path 目录路径
*/
private void createFolder(String path) {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.mkdir(path);
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP创建文件夹出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 文件读取
*
* @param fileUrl 文件路径
* @return 文件字节数组
*/
public String read(String fileUrl) {
ChannelSftp sftp = pool.borrowObject();
try {
InputStream inputStream = sftp.get(fileUrl);
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String str, resultStr = "";
while ((str = in.readLine()) != null) {
resultStr = resultStr.concat(str);
}
return resultStr;
} catch (SftpException | IOException e) {
throw new ApplicationSftpException("SFTP读取文件出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 判断目录是否存在
*
* @param url 文件夹目录
* @return ture:存在;false:不存在
*/
public boolean dirIsExist(String url) {
ChannelSftp sftp = pool.borrowObject();
try {
if (isDirectory(url)) {
sftp.cd(url);
String pwd = sftp.pwd();
return pwd.equals(url) || pwd.concat("/").equals(url);
}
return false;
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP读取文件夹出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 删除文件 或 删除文件夹
* 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
*
* @param remoteDirOrRemoteFile 要删除的文件 或 文件夹
*/
public void delete(String remoteDirOrRemoteFile) {
ChannelSftp sftp = pool.borrowObject();
try {
List<String> targetFileOrDirContainer = new ArrayList<>(8);
targetFileOrDirContainer.add(remoteDirOrRemoteFile);
List<String> toBeDeletedEmptyDirContainer = new ArrayList<>(8);
if (isDirectory(remoteDirOrRemoteFile)) {
toBeDeletedEmptyDirContainer.add(remoteDirOrRemoteFile);
}
collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, targetFileOrDirContainer);
if (!toBeDeletedEmptyDirContainer.isEmpty()) {
String targetDir;
for (int i = toBeDeletedEmptyDirContainer.size() - 1; i >= 0; i--) {
targetDir = toBeDeletedEmptyDirContainer.get(i);
sftp.rmdir(targetDir);
}
}
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP删除文件或者文件夹出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 删除相关文件 并 采集所有 需要被删除的 文件夹
* <p>
* 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
*
* @param toBeDeletedEmptyDirContainer 所有待删除的空文件夹集合
* @param targetFileOrDirContainer 本次, 要删除的文件的集合 或 本次, 要删除的文件所在文件夹的集合
*/
private void collectToBeDeletedEmptyDir(List<String> toBeDeletedEmptyDirContainer,
List<String> targetFileOrDirContainer) {
List<String> todoCallDirContainer = new ArrayList<>(8);
List<String> subfolderList;
for (String remoteDirOrRemoteFile : targetFileOrDirContainer) {
subfolderList = fileDeleteExecutor(remoteDirOrRemoteFile);
toBeDeletedEmptyDirContainer.addAll(subfolderList);
todoCallDirContainer.addAll(subfolderList);
}
if (!todoCallDirContainer.isEmpty()) {
collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, todoCallDirContainer);
}
}
/**
* 删除remoteDirOrRemoteFile指向的文件 或 删除remoteDirOrRemoteFile指向的文件夹下的所有子级文件
* 注: 如果是文件夹, 只会删除该文件夹下的子级文件;不会删除该文件夹下的孙子级文件(如果有孙子级文件的话)
*
* @param remoteDirOrRemoteFile 要删除的文件 或 要 文件夹 【绝对路径】
* @return remoteDirOrRemoteFile指向的文件夹 下的 文件夹集合
* 注: 如果remoteDirOrRemoteFile指向的是文件的话,返回空的集合
* 注: 只会包含子级文件夹,不包含孙子级文件夹(如果有孙子级文件夹的话)
*/
private List<String> fileDeleteExecutor(String remoteDirOrRemoteFile) {
ChannelSftp sftp = pool.borrowObject();
try {
List<String> subfolderList = new ArrayList<>(8);
// 如果是文件,直接删除
if (!isDirectory(remoteDirOrRemoteFile)) {
sftp.rm(remoteDirOrRemoteFile);
return subfolderList;
}
// 保证 remoteDirOrRemoteFile 以 “/” 开头,以 “/” 结尾
remoteDirOrRemoteFile = handlePath(remoteDirOrRemoteFile, true, true);
Vector<?> vector = sftp.ls(remoteDirOrRemoteFile);
String fileName;
String sftpAbsoluteFilename;
// 列出文件名
for (Object item : vector) {
ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) item;
fileName = entry.getFilename();
if (invalidFileName(fileName)) {
continue;
}
sftpAbsoluteFilename = remoteDirOrRemoteFile + fileName;
// 如果是文件,直接删除
if (!isDirectory(sftpAbsoluteFilename)) {
sftp.rm(sftpAbsoluteFilename);
continue;
}
subfolderList.add(sftpAbsoluteFilename);
}
return subfolderList;
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP删除文件或者文件夹出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 从给定路径中截取文件名
*
* @param path 路径, 如: /files/abc/info.yml
* @return 文件名, 如: info.yml
*/
private String getFilenameFromPath(String path) {
return path.substring(path.lastIndexOf(NODE_SEPARATOR) + 1);
}
/**
* 路径处理器
* <p>
* 根据参数控制处理类型,如:
* 当: originPath 为【var/apps】时,
* 当: handleHead 为 true, 处理结果为【/var/apps】
* 当: handleTail 为 true, 处理结果为【var/apps/】
* 当: handleHead 和 handleTail 均为 true, 处理结果为【/var/apps/】
*
* @param originPath 要处理的路径
* @param handleHead 处理 起始处
* @param handleTail 处理 结尾处
* @return 处理后的路径
*/
private String handlePath(String originPath, boolean handleHead, boolean handleTail) {
if (originPath == null || "".equals(originPath.trim())) {
return NODE_SEPARATOR;
}
if (handleHead && !originPath.startsWith(NODE_SEPARATOR)) {
originPath = NODE_SEPARATOR.concat(originPath);
}
if (handleTail && !originPath.endsWith(NODE_SEPARATOR)) {
originPath = originPath.concat(NODE_SEPARATOR);
}
return originPath;
}
/**
* 判断是否为无效的文件名
* 注:文件名(夹)名为【.】或【..】时,是无效的
*
* @param fileName 文件名
* @return 是有无效
*/
private boolean invalidFileName(String fileName) {
return ".".equals(fileName) || "..".equals(fileName);
}
/**
* 判断SFTP上的path是否为文件夹
* 注:如果该路径不存在,那么会返回false
*
* @param path SFTP上的路径
* @return 判断结果
*/
private boolean isDirectory(String path) {
ChannelSftp sftp = pool.borrowObject();
// 合法的错误id
// int legalErrorId = 4;
try {
sftp.cd(path);
return true;
} catch (SftpException e) {
// 如果 path不存在,那么报错信息为【No such file】,错误id为【2】
// 如果 path存在,但是不能cd进去,那么报错信息形如【Can't change directory: /files/sqljdbc4-3.0.jar】,错误id为【4】
return false;
} finally {
pool.returnObject(sftp);
}
}
/**
* 创建多级文件目录
*
* @param dirs 每个目录的名称数组
* @param tempPath 临时路径,传入""空字符串,主要为了递归调用方便
* @param length 数组长度
* @param index 当前索引,为了递归调用
*/
private void mkdirs(ChannelSftp sftp, String[] dirs, String tempPath, int length, int index) {
// 以"/a/b/c/d"为例按"/"分隔后,第0位是"";顾下标从1开始
index++;
if (index < length) {
// 目录不存在,则创建文件夹
tempPath += "/" + dirs[index];
}
try {
sftp.cd(tempPath);
if (index < length) {
mkdirs(sftp, dirs, tempPath, length, index);
}
} catch (SftpException ex) {
try {
sftp.mkdir(tempPath);
sftp.chmod(Integer.parseInt("777", 8), tempPath);
sftp.cd(tempPath);
} catch (SftpException e) {
return;
}
mkdirs(sftp, dirs, tempPath, length, index);
}
}
/**
* 统计目录下文件(夹)数量
*
* @param path 目录路径
* @return 文件数量
*/
private int countFiles(String path) throws SftpException {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.cd(path);
return sftp.ls(path).size();
} finally {
pool.returnObject(sftp);
}
}
/**
* 获取某个文件夹下的所有文件名称
*
* @param path 文件夹路径
* @param fileTypes 文件类型,如果为null或者长度为0,则获取所有文件名称,如果已指定,则获取指定类型的文件类型
* @return 文件名称
*/
public List<String> queryFileName(String path, String... fileTypes) {
ChannelSftp sftp = pool.borrowObject();
try {
Vector<ChannelSftp.LsEntry> ls = sftp.ls(path);
return ls.stream().map(ChannelSftp.LsEntry::getFilename).filter(
name -> {
if (ArrayUtil.isNotEmpty(fileTypes)) {
return FileNameUtil.isType(name, fileTypes);
}
return true;
}).collect(Collectors.toList());
} catch (SftpException e) {
throw new ApplicationSftpException("SFTP获取某个文件夹下的所有文件名称出错", e);
} finally {
pool.returnObject(sftp);
}
}
/**
* 校验路径是否可用
*
* @param path 路径
* @return 是否可用
*/
private boolean validatePathValid(String path, ChannelSftp sftp) {
int countFiles = 0;
try {
countFiles = countFiles(path);
} catch (SftpException e) {
mkdirs(sftp, path.split("/"), "", path.split("/").length, 0);
}
return countFiles <= MAX_CHILD_FILE_NUMBER;
}
/**
* 生成有效路径
*
* @param path 参数路径
* @return 解析后的有效路径
*/
private String generateValidPath(String path, ChannelSftp sftp) {
if (validatePathValid(path, sftp)) {
return path;
} else {
String newPath = path + String.valueOf(System.currentTimeMillis()).substring(9);
mkdirs(sftp, newPath.split("/"), "", newPath.split("/").length, 0);
return newPath;
}
}
}
ShellHelper
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.ChannelShell;
import com.pzx.demo.common.sftp.core.pool.ShellPool;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author JunPzx
* @version v1.0.0
* @date 2021/8/5 14:13
*/
@Log4j2
public class ShellHelper {
private ShellPool pool;
public ShellHelper(ShellPool shellPool) {
this.pool = shellPool;
}
/**
* 远程解压缩指定目录下的指定名字的文件
*
* @param path:指定解压文件的目录
* @param fileName:需要解压的文件名字
* @param targetPath :解压完成后的存放路径
*/
public boolean remoteZipToFile(String path, String fileName, String targetPath) {
ChannelShell channelShell = pool.borrowObject();
try {
final OutputStream outputStream = channelShell.getOutputStream();
final InputStream inputStream = channelShell.getInputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
String fileSuffix = FileNameUtil.getSuffix(fileName);
String command;
switch (fileSuffix) {
case "gz":
command = "tar -zxvf " + path + fileName;
break;
case "zip":
command = "unzip -O cp936 " + path + fileName;
break;
default:
return false;
}
if (StrUtil.isNotBlank(targetPath)) {
command += ("gz".equals(fileSuffix) ? " -C" : " -d") + " /" + targetPath + "/";
}
printWriter.println(command);
String commandFlag = "echo $?";
printWriter.println(commandFlag);
printWriter.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String msg;
boolean flag = false;
while ((msg = in.readLine()) != null && !flag) {
System.out.println(msg);
flag = msg.contains(commandFlag);
}
return true;
} catch (IOException e) {
log.error("解压文件出错", e);
return false;
} finally {
pool.returnObject(channelShell);
}
}
/**
* 解压文件,获取文件中的每个文件和文件夹名称路径存到Map中
*
* @param zipFile 需要解压的Zip文件
* @return {@link HashMap} Key: 路径 Value:类型(Directory或者File)
*/
public Map<String, String> unZip(MultipartFile zipFile) {
Map<String, String> returnMap = new HashMap<>(8);
try (ZipArchiveInputStream inputStream = getZipFile(zipFile)) {
ZipArchiveEntry entry;
while ((entry = inputStream.getNextZipEntry()) != null) {
if (entry.isDirectory()) {
returnMap.put(entry.getName(), "Directory");
} else {
returnMap.put(entry.getName(), "File");
}
}
} catch (Exception ignored) {
}
return returnMap;
}
private ZipArchiveInputStream getZipFile(File zipFile) throws Exception {
return new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(zipFile)), "GBK", true);
}
private ZipArchiveInputStream getZipFile(MultipartFile zipFile) throws Exception {
return new ZipArchiveInputStream(new BufferedInputStream(zipFile.getInputStream()), "GBK", true);
}
}
🦌连接池相关类
SftpPool
import com.jcraft.jsch.ChannelSftp;
import com.pzx.demo.common.sftp.core.factory.SftpFactory;
import com.pzx.demo.core.exception.ApplicationSftpException;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPool;
/**
* @author JunPzx
* @version v1.0.0
* @date 2021/8/4 14:20
*/
@Data
public class SftpPool {
private GenericObjectPool<ChannelSftp> pool;
public SftpPool(SftpFactory factory) {
this.pool = new GenericObjectPool<>(factory, factory.getProperties().getSftpPool());
pool.setTestOnBorrow(true);
pool.setTimeBetweenEvictionRunsMillis(1000 * 60 * 30);
pool.setTestWhileIdle(true);
}
/**
* 获取一个sftp连接对象
*
* @return sftp连接对象
*/
public ChannelSftp borrowObject() {
try {
return pool.borrowObject();
} catch (Exception e) {
throw new ApplicationSftpException("获取ftp连接失败", e);
}
}
/**
* 归还一个sftp连接对象
*
* @param channelSftp sftp连接对象
*/
public void returnObject(ChannelSftp channelSftp) {
if (channelSftp != null) {
pool.returnObject(channelSftp);
}
}
/**
* 初始化连接池
*
* @throws Exception e
*/
public void init() throws Exception {
for (int i = 0; i < pool.getMaxIdle(); i++) {
pool.addObject();
}
}
}
ShellPool
import com.jcraft.jsch.ChannelShell;
import com.pzx.demo.common.sftp.core.factory.ShellFactory;
import com.pzx.demo.core.exception.ApplicationSftpException;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPool;
/**
* @author JunPzx
* @version v1.0.0
* @date 2021/8/4 14:20
*/
@Data
public class ShellPool {
private GenericObjectPool<ChannelShell> pool;
public ShellPool(ShellFactory factory) {
this.pool = new GenericObjectPool<>(factory, factory.getProperties().getShellPool());
pool.setTestOnBorrow(true);
pool.setTimeBetweenEvictionRunsMillis(1000 * 60 * 30);
pool.setTestWhileIdle(true);
}
/**
* 获取一个执行器连接对象
*
* @return 执行器连接对象
*/
public ChannelShell borrowObject() {
try {
return pool.borrowObject();
} catch (Exception e) {
throw new ApplicationSftpException("获取shell连接失败", e);
}
}
/**
* 归还一个执行器连接对象
*
* @param channelShell 执行器连接对象
*/
public void returnObject(ChannelShell channelShell) {
if (channelShell != null) {
pool.returnObject(channelShell);
}
}
/**
* 初始化连接池
*
* @throws Exception e
*/
public void init() throws Exception {
for (int i = 0; i < pool.getMaxIdle(); i++) {
pool.addObject();
}
}
}
♉写在最后
以上就是相关代码,如果对连接池有疑问可以查看利用commons-pool2自定义对象池,Respect
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 星辰大海-Secret丶君
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果