在處理銀行業務的時候,數據交互還使用老舊的sftp技術,最近在使用sftp的JSCH庫時發現,採用公私鑰方式連接server,總是無法斷開連接,研究發現這種模式下如果存在併發的鏈接時,disconnect方法實際不起左右,這樣會產生大量鏈接,最終造成無法再鏈接上;解決辦法採用Threadlocal鏡像複製方式,爲每個鏈接建立單獨的工具對象
public class SftpUtil {
private final static Logger log = LoggerFactory.getLogger(SftpUtil.class);
/** SFTP */
public static final String SFTP = "sftp";
/** 通道 */
private ChannelSftp channel;
/** session */
private Session session;
/** 規避多線程併發不斷開問題 */
private static ThreadLocal<SftpUtil> sftpLocal = new ThreadLocal<SftpUtil>();
/**
* 獲取sftpchannel
*
* @param connectConfig 連接配置
* @return
* @throws Exception
* @throws JSchException
*/
private void init(ConnectConfig connectConfig) throws Exception {
String host = connectConfig.getHost();
int port = connectConfig.getPort();
String userName = connectConfig.getUserName();
//創建JSch對象
JSch jsch = new JSch();
//添加私鑰(信任登錄方式)
if (StringUtils.isNotBlank(connectConfig.getPrivateKey())) {
jsch.addIdentity(connectConfig.getPrivateKey());
}
session = jsch.getSession(userName, host, port);
if (log.isInfoEnabled()) {
log.info(" JSCH Session created,sftpHost = {}, sftpUserName={}", host, userName);
}
//設置密碼
if (StringUtils.isNotBlank(connectConfig.getPassWord())) {
session.setPassword(connectConfig.getPassWord());
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
//設置超時
session.setTimeout(connectConfig.getTimeout());
//建立連接
session.connect();
if (log.isInfoEnabled()) {
log.info("JSCH Session connected.sftpHost = {}, sftpUserName={}", host, userName);
}
//打開SFTP通道
channel = (ChannelSftp) session.openChannel(SFTP);
//建立SFTP通道的連接
channel.connect();
if (log.isInfoEnabled()) {
log.info("Connected successfully to sftpHost = {}, sftpUserName={}", host, userName);
}
}
/**
* 是否已連接
*
* @return
*/
private boolean isConnected() {
return null != channel && channel.isConnected();
}
/**
* 獲取本地線程存儲的sftp客戶端
*
* @return
* @throws Exception
*/
public static SftpUtil getSftpUtil(ConnectConfig connectConfig) throws Exception {
SftpUtil sftpUtil = sftpLocal.get();
if (null == sftpUtil || !sftpUtil.isConnected()) {
sftpLocal.set(new SftpUtil(connectConfig));
}
return sftpLocal.get();
}
/**
* 釋放本地線程存儲的sftp客戶端
*/
public static void release() {
if (null != sftpLocal.get()) {
sftpLocal.get().closeChannel();
sftpLocal.set(null);
}
}
/**
* 構造函數
* <p>
* 非線程安全,故權限爲私有
* </p>
*
* @throws Exception
*/
private SftpUtil(ConnectConfig connectConfig) throws Exception {
super();
init(connectConfig);
}
/**
* 關閉通道
*
* @throws Exception
*/
public void closeChannel() {
if (null != channel) {
try {
channel.disconnect();
} catch (Exception e) {
log.error("關閉SFTP通道發生異常:", e);
}
}
if (null != session) {
try {
session.disconnect();
} catch (Exception e) {
log.error("SFTP關閉 session異常:", e);
}
}
}
/**
* 下載文件
*
* @param downDir 下載目錄
* @param src 源文件
* @param dst 保存後的文件名稱或目錄
* @throws Exception
*/
public void downFile(String downDir, String src, String dst) throws Exception {
channel.cd(downDir);
channel.get(src, dst);
}
/**
* 刪除文件
*
* @param filePath 文件全路徑
* @throws SftpException
*/
public void deleteFile(String filePath) throws SftpException {
channel.rm(filePath);
}
@SuppressWarnings("unchecked")
public List<String> listFiles(String dir) throws SftpException {
Vector<LsEntry> files = channel.ls(dir);
if (null != files) {
List<String> fileNames = new ArrayList<String>();
Iterator<LsEntry> iter = files.iterator();
while (iter.hasNext()) {
String fileName = iter.next().getFilename();
if (StringUtils.equals(".", fileName) || StringUtils.equals("..", fileName)) {
continue;
}
fileNames.add(fileName);
}
return fileNames;
}
return null;
}
}