背景
在项目开发中,一般文件存储很少再使用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; } } } 复制代码