这篇文章主要介绍“SpringBoot如何集成SFTP客户端实现文件上传下载”,在日常操作中,相信很多人在SpringBoot如何集成SFTP客户端实现文件上传下载问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringBoot如何集成SFTP客户端实现文件上传下载”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
背景
在项目开发中,一般文件存储很少再使用SFTP服务,但是也不排除合作伙伴使用SFTP来存储项目中的文件或者通过SFTP来实现文件数据的交互。
我遇到的项目中,就有银行和保险公司等合作伙伴通过SFTP服务来实现与我们项目的文件数据的交互。
为了能够顺利地完成与友商的SFTP服务的连通,我们需要在自己的项目中实现一套SFTP客户端工具。一般我们会采用Jsch来实现SFTP客户端。
依赖
<!--执行远程操作-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!--链接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
首先我们一定要引入
jsch
依赖,这个是我们实现SFTP客户端的基石;其次我们引入了链接池工具,为了避免每次执行SFTP命令都要重新创建链接,我们使用池化的方式优化了比较消耗资源的创建操作。创建工具类
为了更好的使用SFTP工具,我们把
jsch
中关于SFTP的相关功能提炼出来,做了一次简单的封装,做成了我们可以直接使用的工具类。里面只有两类方法:
1.创建Session与开启Session;
session创建好后,还不能创建channel,需要开启session后才能创建channel;
2.创建channel与开启channel;
channel也是一样,创建好的channel需要开启后才能真正地执行命令;
public class JschUtil {
/**
* 创建session
*
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param privateKeyFile 密钥文件
* @param passphrase 口令
* @return
* @throws AwesomeException
*/
public static Session createSession(String userName, String password, String host, int port, String privateKeyFile, String passphrase) throws AwesomeException {
return createSession(new JSch(), userName, password, host, port, privateKeyFile, passphrase);
}
/**
* 创建session
*
* @param jSch
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param privateKeyFile 密钥
* @param passphrase 口令
* @return
* @throws AwesomeException
*/
public static Session createSession(JSch jSch, String userName, String password, String host, int port, String privateKeyFile, String passphrase) throws AwesomeException {
try {
if (!StringUtils.isEmpty(privateKeyFile)) {
// 使用密钥验证方式,密钥可以是有口令的密钥,也可以是没有口令的密钥
if (!StringUtils.isEmpty(passphrase)) {
jSch.addIdentity(privateKeyFile, passphrase);
} else {
jSch.addIdentity(privateKeyFile);
}
}
// 获取session
Session session = jSch.getSession(userName, host, port);
if (!StringUtils.isEmpty(password)) {
session.setPassword(password);
}
// 不校验域名
session.setConfig("StrictHostKeyChecking", "no");
return session;
} catch (Exception e) {
throw new AwesomeException(500, "create session fail");
}
}
/**
* 创建session
*
* @param jSch
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @return
* @throws AwesomeException
*/
public static Session createSession(JSch jSch, String userName, String password, String host, int port) throws AwesomeException {
return createSession(jSch, userName, password, host, port, StringUtils.EMPTY, StringUtils.EMPTY);
}
/**
* 创建session
*
* @param jSch
* @param userName 用户名
* @param host 域名
* @param port 端口
* @return
* @throws AwesomeException
*/
private Session createSession(JSch jSch, String userName, String host, int port) throws AwesomeException {
return createSession(jSch, userName, StringUtils.EMPTY, host, port, StringUtils.EMPTY, StringUtils.EMPTY);
}
/**
* 开启session链接
*
* @param jSch
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param privateKeyFile 密钥
* @param passphrase 口令
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(JSch jSch, String userName, String password, String host, int port, String privateKeyFile, String passphrase, int timeout) throws AwesomeException {
Session session = createSession(jSch, userName, password, host, port, privateKeyFile, passphrase);
try {
if (timeout >= 0) {
session.connect(timeout);
} else {
session.connect();
}
return session;
} catch (Exception e) {
throw new AwesomeException(500, "session connect fail");
}
}
/**
* 开启session链接
*
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param privateKeyFile 密钥
* @param passphrase 口令
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(String userName, String password, String host, int port, String privateKeyFile, String passphrase, int timeout) throws AwesomeException {
Session session = createSession(userName, password, host, port, privateKeyFile, passphrase);
try {
if (timeout >= 0) {
session.connect(timeout);
} else {
session.connect();
}
return session;
} catch (Exception e) {
throw new AwesomeException(500, "session connect fail");
}
}
/**
* 开启session链接
*
* @param jSch
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(JSch jSch, String userName, String password, String host, int port, int timeout) throws AwesomeException {
return openSession(jSch, userName, password, host, port, StringUtils.EMPTY, StringUtils.EMPTY, timeout);
}
/**
* 开启session链接
*
* @param userName 用户名
* @param password 密码
* @param host 域名
* @param port 端口
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(String userName, String password, String host, int port, int timeout) throws AwesomeException {
return openSession(userName, password, host, port, StringUtils.EMPTY, StringUtils.EMPTY, timeout);
}
/**
* 开启session链接
*
* @param jSch
* @param userName 用户名
* @param host 域名
* @param port 端口
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(JSch jSch, String userName, String host, int port, int timeout) throws AwesomeException {
return openSession(jSch, userName, StringUtils.EMPTY, host, port, StringUtils.EMPTY, StringUtils.EMPTY, timeout);
}
/**
* 开启session链接
*
* @param userName 用户名
* @param host 域名
* @param port 端口
* @param timeout 链接超时时间
* @return
* @throws AwesomeException
*/
public static Session openSession(String userName, String host, int port, int timeout) throws AwesomeException {
return openSession(userName, StringUtils.EMPTY, host, port, StringUtils.EMPTY, StringUtils.EMPTY, timeout);
}
/**
* 创建指定通道
*
* @param session
* @param channelType
* @return
* @throws AwesomeException
*/
public static Channel createChannel(Session session, ChannelType channelType) throws AwesomeException {
try {
if (!session.isConnected()) {
session.connect();
}
return session.openChannel(channelType.getValue());
} catch (Exception e) {
throw new AwesomeException(500, "open channel fail");
}
}
/**
* 创建sftp通道
*
* @param session
* @return
* @throws AwesomeException
*/
public static ChannelSftp createSftp(Session session) throws AwesomeException {
return (ChannelSftp) createChannel(session, ChannelType.SFTP);
}
/**
* 创建shell通道
*
* @param session
* @return
* @throws AwesomeException
*/
public static ChannelShell createShell(Session session) throws AwesomeException {
return (ChannelShell) createChannel(session, ChannelType.SHELL);
}
/**
* 开启通道
*
* @param session
* @param channelType
* @param timeout
* @return
* @throws AwesomeException
*/
public static Channel openChannel(Session session, ChannelType channelType, int timeout) throws AwesomeException {
Channel channel = createChannel(session, channelType);
try {
if (timeout >= 0) {
channel.connect(timeout);
} else {
channel.connect();
}
return channel;
} catch (Exception e) {
throw new AwesomeException(500, "connect channel fail");
}
}
/**
* 开启sftp通道
*
* @param session
* @param timeout
* @return
* @throws AwesomeException
*/
public static ChannelSftp openSftpChannel(Session session, int timeout) throws AwesomeException {
return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout);
}
/**
* 开启shell通道
*
* @param session
* @param timeout
* @return
* @throws AwesomeException
*/
public static ChannelShell openShellChannel(Session session, int timeout) throws AwesomeException {
return (ChannelShell) openChannel(session, ChannelType.SHELL, timeout);
}
enum ChannelType {
SESSION("session"),
SHELL("shell"),
EXEC("exec"),
X11("x11"),
AGENT_FORWARDING("auth-agent@openssh.com"),
DIRECT_TCPIP("direct-tcpip"),
FORWARDED_TCPIP("forwarded-tcpip"),
SFTP("sftp"),
SUBSYSTEM("subsystem");
private final String value;
ChannelType(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
}
SFTP链接池化
我们通过实现
BasePooledObjectFactory
类来池化通道ChannelSftp
。这并不是真正池化的代码,下面的代码只是告知池化管理器如何创建对象和销毁对象。static class SftpFactory extends BasePooledObjectFactory<ChannelSftp> implements AutoCloseable {
private Session session;
private SftpProperties properties;
// 初始化SftpFactory
// 里面主要是创建目标session,后续可用通过这个session不断地创建ChannelSftp。
SftpFactory(SftpProperties properties) throws AwesomeException {
this.properties = properties;
String username = properties.getUsername();
String password = properties.getPassword();
String host = properties.getHost();
int port = properties.getPort();
String privateKeyFile = properties.getPrivateKeyFile();
String passphrase = properties.getPassphrase();
session = JschUtil.createSession(username, password, host, port, privateKeyFile, passphrase);
}
// 销毁对象,主要是销毁ChannelSftp
@Override
public void destroyObject(PooledObject<ChannelSftp> p) throws Exception {
p.getObject().disconnect();
}
// 创建对象ChannelSftp
@Override
public ChannelSftp create() throws Exception {
int timeout = properties.getTimeout();
return JschUtil.openSftpChannel(this.session, timeout);
}
// 包装创建出来的对象
@Override
public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {
return new DefaultPooledObject<>(channelSftp);
}
// 验证对象是否可用
@Override
public boolean validateObject(PooledObject<ChannelSftp> p) {
return p.getObject().isConnected();
}
// 销毁资源,关闭session
@Override
public void close() throws Exception {
if (Objects.nonNull(session)) {
if (session.isConnected()) {
session.disconnect();
}
session = null;
}
}
}
为了实现真正的池化操作,我们还需要以下代码:
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服务器