发布网友 发布时间:2024-09-29 08:43
共1个回答
热心网友 时间:2024-10-30 06:17
背景在项目开发中,一般文件存储很少再使用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需要开启后才能真正地执行命令;
publicclassJschUtil{/***创建session**@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramprivateKeyFile密钥文件*@parampassphrase口令*@return*@throwsAwesomeException*/publicstaticSessioncreateSession(StringuserName,Stringpassword,Stringhost,intport,StringprivateKeyFile,Stringpassphrase)throwsAwesomeException{returncreateSession(newJSch(),userName,password,host,port,privateKeyFile,passphrase);}/***创建session**@paramjSch*@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramprivateKeyFile密钥*@parampassphrase口令*@return*@throwsAwesomeException*/publicstaticSessioncreateSession(JSchjSch,StringuserName,Stringpassword,Stringhost,intport,StringprivateKeyFile,Stringpassphrase)throwsAwesomeException{try{if(!StringUtils.isEmpty(privateKeyFile)){//使用密钥验证方式,密钥可以是有口令的密钥,也可以是没有口令的密钥if(!StringUtils.isEmpty(passphrase)){jSch.addIdentity(privateKeyFile,passphrase);}else{jSch.addIdentity(privateKeyFile);}}//获取sessionSessionsession=jSch.getSession(userName,host,port);if(!StringUtils.isEmpty(password)){session.setPassword(password);}//不校验域名session.setConfig("StrictHostKeyChecking","no");returnsession;}catch(Exceptione){thrownewAwesomeException(500,"createsessionfail");}}/***创建session**@paramjSch*@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@return*@throwsAwesomeException*/publicstaticSessioncreateSession(JSchjSch,StringuserName,Stringpassword,Stringhost,intport)throwsAwesomeException{returncreateSession(jSch,userName,password,host,port,StringUtils.EMPTY,StringUtils.EMPTY);}/***创建session**@paramjSch*@paramuserName用户名*@paramhost域名*@paramport端口*@return*@throwsAwesomeException*/privateSessioncreateSession(JSchjSch,StringuserName,Stringhost,intport)throwsAwesomeException{returncreateSession(jSch,userName,StringUtils.EMPTY,host,port,StringUtils.EMPTY,StringUtils.EMPTY);}/***开启session链接**@paramjSch*@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramprivateKeyFile密钥*@parampassphrase口令*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(JSchjSch,StringuserName,Stringpassword,Stringhost,intport,StringprivateKeyFile,Stringpassphrase,inttimeout)throwsAwesomeException{Sessionsession=createSession(jSch,userName,password,host,port,privateKeyFile,passphrase);try{if(timeout>=0){session.connect(timeout);}else{session.connect();}returnsession;}catch(Exceptione){thrownewAwesomeException(500,"sessionconnectfail");}}/***开启session链接**@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramprivateKeyFile密钥*@parampassphrase口令*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(StringuserName,Stringpassword,Stringhost,intport,StringprivateKeyFile,Stringpassphrase,inttimeout)throwsAwesomeException{Sessionsession=createSession(userName,password,host,port,privateKeyFile,passphrase);try{if(timeout>=0){session.connect(timeout);}else{session.connect();}returnsession;}catch(Exceptione){thrownewAwesomeException(500,"sessionconnectfail");}}/***开启session链接**@paramjSch*@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(JSchjSch,StringuserName,Stringpassword,Stringhost,intport,inttimeout)throwsAwesomeException{returnopenSession(jSch,userName,password,host,port,StringUtils.EMPTY,StringUtils.EMPTY,timeout);}/***开启session链接**@paramuserName用户名*@parampassword密码*@paramhost域名*@paramport端口*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(StringuserName,Stringpassword,Stringhost,intport,inttimeout)throwsAwesomeException{returnopenSession(userName,password,host,port,StringUtils.EMPTY,StringUtils.EMPTY,timeout);}/***开启session链接**@paramjSch*@paramuserName用户名*@paramhost域名*@paramport端口*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(JSchjSch,StringuserName,Stringhost,intport,inttimeout)throwsAwesomeException{returnopenSession(jSch,userName,StringUtils.EMPTY,host,port,StringUtils.EMPTY,StringUtils.EMPTY,timeout);}/***开启session链接**@paramuserName用户名*@paramhost域名*@paramport端口*@paramtimeout链接超时时间*@return*@throwsAwesomeException*/publicstaticSessionopenSession(StringuserName,Stringhost,intport,inttimeout)throwsAwesomeException{returnopenSession(userName,StringUtils.EMPTY,host,port,StringUtils.EMPTY,StringUtils.EMPTY,timeout);}/***创建指定通道**@paramsession*@paramchannelType*@return*@throwsAwesomeException*/publicstaticChannelcreateChannel(Sessionsession,ChannelTypechannelType)throwsAwesomeException{try{if(!session.isConnected()){session.connect();}returnsession.openChannel(channelType.getValue());}catch(Exceptione){thrownewAwesomeException(500,"openchannelfail");}}/***创建sftp通道**@paramsession*@return*@throwsAwesomeException*/publicstaticChannelSftpcreateSftp(Sessionsession)throwsAwesomeException{return(ChannelSftp)createChannel(session,ChannelType.SFTP);}/***创建shell通道**@paramsession*@return*@throwsAwesomeException*/publicstaticChannelShellcreateShell(Sessionsession)throwsAwesomeException{return(ChannelShell)createChannel(session,ChannelType.SHELL);}/***开启通道**@paramsession*@paramchannelType*@paramtimeout*@return*@throwsAwesomeException*/publicstaticChannelopenChannel(Sessionsession,ChannelTypechannelType,inttimeout)throwsAwesomeException{Channelchannel=createChannel(session,channelType);try{if(timeout>=0){channel.connect(timeout);}else{channel.connect();}returnchannel;}catch(Exceptione){thrownewAwesomeException(500,"connectchannelfail");}}/***开启sftp通道**@paramsession*@paramtimeout*@return*@throwsAwesomeException*/publicstaticChannelSftpopenSftpChannel(Sessionsession,inttimeout)throwsAwesomeException{return(ChannelSftp)openChannel(session,ChannelType.SFTP,timeout);}/***开启shell通道**@paramsession*@paramtimeout*@return*@throwsAwesomeException*/publicstaticChannelShellopenShellChannel(Sessionsession,inttimeout)throwsAwesomeException{return(ChannelShell)openChannel(session,ChannelType.SHELL,timeout);}enumChannelType{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");privatefinalStringvalue;ChannelType(Stringvalue){this.value=value;}publicStringgetValue(){returnthis.value;}}}SFTP链接池化我们通过实现BasePooledObjectFactory类来池化通道ChannelSftp。这并不是真正池化的代码,下面的代码只是告知池化管理器如何创建对象和销毁对象。
staticclassSftpFactoryextendsBasePooledObjectFactory<ChannelSftp>implementsAutoCloseable{privateSessionsession;privateSftpPropertiesproperties;//初始化SftpFactory//里面主要是创建目标session,后续可用通过这个session不断地创建ChannelSftp。SftpFactory(SftpPropertiesproperties)throwsAwesomeException{this.properties=properties;Stringusername=properties.getUsername();Stringpassword=properties.getPassword();Stringhost=properties.getHost();intport=properties.getPort();StringprivateKeyFile=properties.getPrivateKeyFile();Stringpassphrase=properties.getPassphrase();session=JschUtil.createSession(username,password,host,port,privateKeyFile,passphrase);}//销毁对象,主要是销毁ChannelSftp@OverridepublicvoiddestroyObject(PooledObject<ChannelSftp>p)throwsException{p.getObject().disconnect();}//创建对象ChannelSftp@OverridepublicChannelSftpcreate()throwsException{inttimeout=properties.getTimeout();returnJschUtil.openSftpChannel(this.session,timeout);}//包装创建出来的对象@OverridepublicPooledObject<ChannelSftp>wrap(ChannelSftpchannelSftp){returnnewDefaultPooledObject<>(channelSftp);}//验证对象是否可用@OverridepublicbooleanvalidateObject(PooledObject<ChannelSftp>p){returnp.getObject().isConnected();}//销毁资源,关闭session@Overridepublicvoidclose()throwsException{if(Objects.nonNull(session)){if(session.isConnected()){session.disconnect();}session=null;}}}为了实现真正的池化操作,我们还需要以下代码:
1.我们需要在SftpClient对象中创建一个GenericObjectPool对象池,这个才是真正的池子,它负责创建和存储所有的对象。
2.我们还需要提供资源销毁的功能,也就是实现AutoCloseable,在服务停止时,需要把相关的资源销毁。
publicclassSftpClientimplementsAutoCloseable{privateSftpFactorysftpFactory;GenericObjectPool<ChannelSftp>objectPool;//构造方法1publicSftpClient(SftpPropertiesproperties,GenericObjectPoolConfig<ChannelSftp>poolConfig)throwsAwesomeException{this.sftpFactory=newSftpFactory(properties);objectPool=newGenericObjectPool<>(this.sftpFactory,poolConfig);}//构造方法2publicSftpClient(SftpPropertiesproperties)throwsAwesomeException{this.sftpFactory=newSftpFactory(properties);SftpProperties.PoolConfigconfig=properties.getPool();//默认池化配置if(Objects.isNull(config)){objectPool=newGenericObjectPool<>(this.sftpFactory);}else{//自定义池化配置GenericObjectPoolConfig<ChannelSftp>poolConfig=newGenericObjectPoolConfig<>();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());poolC