Windows搭建SFTP服務器
https://www.cnblogs.com/wangjunguang/p/9453611.html
注意點:
1.以管理員權限運行FreeSSHd
2.如果無法啟動,應該是后台服務已經啟動,需要在服務里面關掉
寫一個SFTPClient工具類
package com.koukay.Controller.Controllers.util;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class SFTPClient {
private final static Logger log = LoggerFactory.getLogger(SFTPClient.class);
@Value("${sftp.directory}")
private String ftpPath;
private volatile boolean isConfig = false;
private ChannelSftpPool channelSftpPool;
@Value("${sftp.host}")
private String host ;// 服務器ip地址
@Value("${sftp.username}")
private String username ; //賬號
@Value("${sftp.password}")
private String password ;//密碼
@Value("${sftp.port}")
private int port ;// 端口號
private final Cache<Object, Object> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(15, TimeUnit.MINUTES)
.build();
private void initSFTPConfig() {
log();
if (isConfig) {
return;
}
isConfig = true;
if (log.isDebugEnabled()) {
log.debug("加載SFTP配置:{}");
}
int ftpPort = this.port;
String ftpServer =this.host;
String ftpAccount = this.username;
String ftpPassword = this.password;
//如果沒有配置路徑則獲取登錄用戶根目錄
if (StringUtils.isEmpty(ftpPath)) {
ftpPath = ".";
}
ftpPath = ftpPath.replaceAll("\\\\", "/");
channelSftpPool = new ChannelSftpPool(new ChannelSftpPoolConfig(), new ChannelSftpPooledObjectFactory(false,
null, ftpServer, ftpAccount, ftpPassword, ftpPort));
}
private void log() {
if (channelSftpPool != null) {
log.info("getMaxTotal = {},getNumActive = {},getNumIdle = {},createCount = {},borrowedCount = {},destroyedCount = {}",
channelSftpPool.internalPool.getMaxTotal(),
channelSftpPool.internalPool.getNumActive(),
channelSftpPool.internalPool.getNumIdle(),
channelSftpPool.internalPool.getCreatedCount(),
channelSftpPool.internalPool.getBorrowedCount(),
channelSftpPool.internalPool.getDestroyedCount());
}
}
public void rebuild() {
isConfig = false;
if (channelSftpPool != null) {
channelSftpPool.close();
}
initSFTPConfig();
}
/**
* 創建SFTP連接
*
* @return ChannelSftp
* @throws Exception
*/
public ChannelSftp createSftp() {
initSFTPConfig();
return channelSftpPool.getResource();
}
/**
* 關閉連接
*
* @param sftp sftp
*/
public void disconnect(ChannelSftp sftp) {
if (sftp != null) {
channelSftpPool.returnResourceObject(sftp);
}
}
/**
* 上傳文件
*
* @param fileName
* @param inputStream
* @return
* @throws Exception
*/
public boolean uploadFile(String fileName, InputStream inputStream) throws Exception {
initSFTPConfig();
ChannelSftp sftp = this.channelSftpPool.getResource();
createFolder(sftp);
try {
sftp.put(inputStream, getFileName(fileName), ChannelSftp.OVERWRITE);
return true;
} catch (Exception e) {
log.error("Upload file failure. TargetPath: {}", fileName, e);
throw new Exception("Upload File failure");
} finally {
if (sftp != null) this.disconnect(sftp);
}
}
/**
* 刪除文件
*
* @param fileName
* @return
* @throws Exception
*/
public boolean deleteFile(String fileName) throws Exception {
initSFTPConfig();
ChannelSftp sftp = this.channelSftpPool.getResource();
try {
fileName = getFileName(fileName);
sftp.rm(fileName);
return true;
} catch (Exception e) {
log.error("Delete file failure. TargetPath: {}", fileName, e);
throw new Exception("Delete File failure");
} finally {
if (sftp != null) this.disconnect(sftp);
}
}
/**
* 下載文件
*
* @param fileName
* @return
* @throws Exception
*/
public void downloadFile(String fileName, HttpServletResponse response) throws Exception {
initSFTPConfig();
ChannelSftp sftp = this.channelSftpPool.getResource();
OutputStream outputStream = response.getOutputStream();
try {
fileName = getFileName(fileName);
log.info("sftp pwd = {},fileName = {}", sftp.pwd(), fileName);
byte[] bytes = (byte[]) cache.getIfPresent(fileName);
if (bytes == null) {
bytes = getFromSftp(fileName);
}
if (bytes != null) {
IOUtils.copy(new ByteArrayInputStream(bytes), outputStream);
} else {
log.info("sftp fileName = {} non-existent!!!", fileName);
}
} catch (Exception e) {
log.error("Download file failure. TargetPath: {}", fileName, e);
throw new Exception("Download File failure");
} finally {
if (sftp != null) this.disconnect(sftp);
}
}
public byte[] getFromSftp(String fileName) {
initSFTPConfig();
ChannelSftp sftp = this.channelSftpPool.getResource();
try (InputStream in = sftp.get(fileName)) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
byte[] bytes = out.toByteArray();
return bytes;
} catch (SftpException | IOException e) {
log.error(e.getMessage(), e);
} finally {
if (sftp != null) this.disconnect(sftp);
}
return null;
}
/**
* 下載文件到特定目錄
*
* @param fileName
* @return
* @throws Exception
*/
public void downloadFileToDir(String fileName, String dirPath) throws Exception {
initSFTPConfig();
ChannelSftp sftp = this.channelSftpPool.getResource();
File file = new File(dirPath+File.separator+fileName);
FileOutputStream fos = new FileOutputStream(file);
try {
fileName = getFileName(fileName);
log.info("sftp pwd = {},fileName = {}", sftp.pwd(), fileName);
sftp.get(fileName, fos);
fos.flush();
} catch (Exception e) {
log.error("Download file failure. TargetPath: {}", fileName, e);
throw new Exception("Download File failure");
} finally {
if (sftp != null) this.disconnect(sftp);
if (fos != null) {
fos.close();
}
}
}
public String getFileName(String fileName) {
if (ftpPath.endsWith("/")) {
fileName = ftpPath + fileName;
} else {
fileName = ftpPath + "/" + fileName;
}
return fileName;
}
/**
* 校驗是否可以連接成功
*
* @return 是否成功
*/
public Boolean checkSftp() {
isConfig = false;
if (channelSftpPool != null) {
channelSftpPool.close();
}
initSFTPConfig();
ChannelSftp sftp = null;
try {
sftp = this.channelSftpPool.getResource();
sftp.cd(".");
} catch (Exception e) {
log.debug(e.getMessage());
return false;
} finally {
if (sftp != null) this.disconnect(sftp);
}
return true;
}
/**
* 判斷文件是否存在,刪除前判斷
* @param fileName
* @return
*/
public boolean fileExist(String fileName) {
initSFTPConfig();
ChannelSftp sftp = null;
try {
sftp = this.channelSftpPool.getResource();
SftpATTRS attrs = sftp.lstat(getFileName(fileName));
return true;
}catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
log.info("file {} not exist.", fileName);
}
}finally {
if (sftp != null) this.disconnect(sftp);
}
return false;
}
/**
* 判斷文件夾是否存在,不存在則創建
* @param sftp
* @return
*/
public boolean createFolder(ChannelSftp sftp) {
boolean exist=true;
String[] splitDirs = ftpPath.split("/");
try {
//判斷文件夾是否存在
sftp.stat(ftpPath);
} catch (SftpException e) {
//不存在的話逐級判斷並創建
for (int i = 0; i < splitDirs.length; i++) {
SftpATTRS attr =null;
String splitDir = splitDirs[i];
if (i==0) {
try {
//進入根目錄
sftp.cd("/");
} catch (SftpException sftpException) {
sftpException.printStackTrace();
}
}
if (StringUtils.isNotEmpty(splitDir)) {
try {
//判斷每級文件夾是否存在
attr = sftp.stat(splitDir);
sftp.cd(splitDir);
} catch (SftpException sftpException) {
if (attr==null){
try {
log.info("文件夾不存在,正在創建:" + splitDir);
sftp.mkdir(splitDir);
log.info("完成創建子目錄:" + splitDir);
exist= true;
sftp.cd(splitDir);
} catch (SftpException sftpException1) {
exist=false;
log.info("文件夾創建失敗:" + sftpException1);
}
}
}
}
}
}
return exist;
}
}
package com.koukay.Controller.Controllers.util; import com.jcraft.jsch.ChannelSftp; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; public class ChannelSftpPool extends Pool<ChannelSftp> { public ChannelSftpPool(GenericObjectPoolConfig<ChannelSftp> poolConfig, PooledObjectFactory<ChannelSftp> factory) { super(poolConfig, factory); } }
package com.koukay.Controller.Controllers.util; import com.jcraft.jsch.ChannelSftp; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Slf4j public class ChannelSftpPoolConfig extends GenericObjectPoolConfig<ChannelSftp> { private final static Logger log = LoggerFactory.getLogger(ChannelSftpPoolConfig.class); public ChannelSftpPoolConfig() { int maxTotal = str2Int(SpringBeanUtils.getProperty("koukay.sftp.maxTotal"), 10); int maxWaitMillis = str2Int(SpringBeanUtils.getProperty("koukay.sftp.maxWaitMillis"), 10000); setTestWhileIdle(true); setMinEvictableIdleTimeMillis(60000); setTimeBetweenEvictionRunsMillis(30000); setNumTestsPerEvictionRun(-1); setMaxWaitMillis(maxWaitMillis); setMaxTotal(maxTotal); } private int str2Int(String property, int defaultValue) { if (StringUtils.isNotBlank(property)) { try { return Integer.parseInt(property); } catch (NumberFormatException e) { log.error("property sftp.maxTotal is error,import_limit = {}", property); } } return defaultValue; } }
package com.koukay.Controller.Controllers.util; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Slf4j public class ChannelSftpPooledObjectFactory implements PooledObjectFactory<ChannelSftp> { private final static Logger log = LoggerFactory.getLogger(ChannelSftpPooledObjectFactory.class); private boolean publicKeyAuthentication; private String idRsaPath; private String ftpServer; private String ftpAccount; private String ftpPassword; private int ftpPort; public ChannelSftpPooledObjectFactory(boolean publicKeyAuthentication, String idRsaPath, String ftpServer, String ftpAccount, String ftpPassword, int ftpPort) { this.publicKeyAuthentication = publicKeyAuthentication; this.idRsaPath = idRsaPath; this.ftpServer = ftpServer; this.ftpAccount = ftpAccount; this.ftpPassword = ftpPassword; this.ftpPort = ftpPort; } @Override public PooledObject<ChannelSftp> makeObject() throws Exception { JSch jsch = new JSch(); if (publicKeyAuthentication) { jsch.addIdentity(idRsaPath); } Session session = createSession(jsch, ftpServer, ftpAccount, ftpPort); session.setPassword(ftpPassword); session.setServerAliveInterval(60000); session.connect(); Channel channel = session.openChannel("sftp"); channel.connect(); return new DefaultPooledObject<ChannelSftp>((ChannelSftp) channel); } @Override public void destroyObject(PooledObject<ChannelSftp> p) throws Exception { log.info("destroyObject ChannelSftp!"); disconnect(p.getObject()); } @Override public boolean validateObject(PooledObject<ChannelSftp> p) { return true; } @Override public void activateObject(PooledObject<ChannelSftp> p) throws Exception { p.getObject().getSession().sendKeepAliveMsg(); } @Override public void passivateObject(PooledObject<ChannelSftp> p) throws Exception { } /** * 創建session * * @param jsch * @param host * @param username * @param port * @return * @throws Exception */ private Session createSession(JSch jsch, String host, String username, Integer port) throws Exception { Session session; if (port <= 0) { session = jsch.getSession(username, host); } else { session = jsch.getSession(username, host, port); } if (session == null) { throw new Exception(host + " session is null"); } java.util.Properties config = new java.util.Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); return session; } /** * 關閉連接 * * @param sftp */ public void disconnect(ChannelSftp sftp) { try { if (sftp != null) { if (sftp.isConnected()) { sftp.disconnect(); } else if (sftp.isClosed()) { log.info("sftp is closed already"); } if (sftp.getSession() != null) { sftp.getSession().disconnect(); } } } catch (Exception e) { log.error("", e); } } }
package com.koukay.Controller.Controllers.util; import lombok.extern.slf4j.Slf4j; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.util.NoSuchElementException; @Slf4j public abstract class Pool<T> implements Closeable { private final static Logger log = LoggerFactory.getLogger(Pool.class); protected GenericObjectPool<T> internalPool; /** * Using this constructor means you have to set and initialize the internalPool yourself. */ public Pool() { } public Pool(final GenericObjectPoolConfig<T> poolConfig, PooledObjectFactory<T> factory) { initPool(poolConfig, factory); } @Override public void close() { destroy(); } public boolean isClosed() { return this.internalPool.isClosed(); } public void initPool(final GenericObjectPoolConfig<T> poolConfig, PooledObjectFactory<T> factory) { if (this.internalPool != null) { try { closeInternalPool(); } catch (Exception e) { log.error(e.getMessage(), e); } } this.internalPool = new GenericObjectPool<T>(factory, poolConfig); } public T getResource() { try { return internalPool.borrowObject(); } catch (NoSuchElementException nse) { if (null == nse.getCause()) { // The exception was caused by an exhausted pool throw new IllegalStateException( "Could not get a resource since the pool is exhausted", nse); } // Otherwise, the exception was caused by the implemented activateObject() or ValidateObject() throw new IllegalStateException("Could not get a resource from the pool", nse); } catch (Exception e) { throw new IllegalStateException("Could not get a resource from the pool", e); } } protected void returnResourceObject(final T resource) { if (resource == null) { return; } try { internalPool.returnObject(resource); } catch (Exception e) { throw new IllegalStateException("Could not return the resource to the pool", e); } } protected void returnBrokenResource(final T resource) { if (resource != null) { returnBrokenResourceObject(resource); } } protected void returnResource(final T resource) { if (resource != null) { returnResourceObject(resource); } } public void destroy() { closeInternalPool(); } protected void returnBrokenResourceObject(final T resource) { try { internalPool.invalidateObject(resource); } catch (Exception e) { throw new IllegalStateException("Could not return the broken resource to the pool", e); } } protected void closeInternalPool() { try { internalPool.close(); } catch (Exception e) { throw new IllegalStateException("Could not destroy the pool", e); } } /** * Returns the number of instances currently borrowed from this pool. * * @return The number of instances currently borrowed from this pool, -1 if * the pool is inactive. */ public int getNumActive() { if (poolInactive()) { return -1; } return this.internalPool.getNumActive(); } /** * Returns the number of instances currently idle in this pool. * * @return The number of instances currently idle in this pool, -1 if the * pool is inactive. */ public int getNumIdle() { if (poolInactive()) { return -1; } return this.internalPool.getNumIdle(); } /** * Returns an estimate of the number of threads currently blocked waiting for * a resource from this pool. * * @return The number of threads waiting, -1 if the pool is inactive. */ public int getNumWaiters() { if (poolInactive()) { return -1; } return this.internalPool.getNumWaiters(); } /** * Returns the mean waiting time spent by threads to obtain a resource from * this pool. * * @return The mean waiting time, in milliseconds, -1 if the pool is * inactive. */ public long getMeanBorrowWaitTimeMillis() { if (poolInactive()) { return -1; } return this.internalPool.getMeanBorrowWaitTimeMillis(); } /** * Returns the maximum waiting time spent by threads to obtain a resource * from this pool. * * @return The maximum waiting time, in milliseconds, -1 if the pool is * inactive. */ public long getMaxBorrowWaitTimeMillis() { if (poolInactive()) { return -1; } return this.internalPool.getMaxBorrowWaitTimeMillis(); } private boolean poolInactive() { return this.internalPool == null || this.internalPool.isClosed(); } public void addObjects(int count) { try { for (int i = 0; i < count; i++) { this.internalPool.addObject(); } } catch (Exception e) { throw new IllegalStateException("Error trying to add idle objects", e); } } }
package com.koukay.Controller.Controllers.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringBeanUtils implements ApplicationContextAware { public static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public static String getProperty(String key) { if (applicationContext != null) { return applicationContext.getEnvironment().getProperty(key); } return null; } }
依賴如下
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.5</version>
</dependency>
上面的可以引入項目直接用,以下寫個測試代碼,
package util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Properties; import java.util.Vector; import com.jcraft.jsch.*; /** * * @author patronli * */ public class FtpService { ChannelSftp sftp = null; Session sshSession =null; String host = "192.168.8.159";// 服務器ip地址 String username = "root"; String password = "123456"; int port = 22;// 端口號 /** * 連接sftp服務器 * * @param host * 主機 * @param port * 端口 * @param username * 用戶名 * @param password * 密碼 * @return */ public ChannelSftp connect(String host, int port, String username, String password) { System.out.println("開始連接sftp"); try { JSch jsch = new JSch(); jsch.getSession(username, host, port); sshSession = jsch.getSession(username, host, port); System.out.println("Session created."); sshSession.setPassword(password); Properties sshConfig = new Properties(); sshConfig.put("StrictHostKeyChecking", "no"); sshSession.setConfig(sshConfig); sshSession.connect(); System.out.println("Session connected."); System.out.println("Opening Channel."); Channel channel = sshSession.openChannel("sftp"); channel.connect(); sftp = (ChannelSftp) channel; System.out.println("Connected to " + host + "."); System.out.println("連接sftp成功.."); } catch (Exception e) { System.out.println("連接失敗"); e.printStackTrace(); } return sftp; } /** * 上傳文件 * * @param directory * 上傳的目錄 * @param uploadFile * 要上傳的文件 * @param sftp */ public void upload(String directory, String uploadFile, ChannelSftp sftp) { System.out.println("進入方法,開始讀取文件"); try { sftp.cd(directory); File file = new File(uploadFile); System.out.println("開始上傳報表文件" + file.getName()); sftp.put(new FileInputStream(file), file.getName()); System.out.println("上傳報表文件" + file.getName() + "完畢"); System.out.println("上傳報表文件成功"); } catch (Exception e) { System.out.println("上傳報表文件失敗"); e.printStackTrace(); } } /** * 下載文件 * * @param directory * 下載目錄 * @param sftp * 下載的文件 * @param path * 存在本地的路徑 */ public void download(String directory, String downloadFile, ChannelSftp sftp, String path) {// sftp路徑,鏈接,保存的路徑+文件名 try { sftp.cd(directory); File file = new File(path); sftp.get(downloadFile, new FileOutputStream(file)); System.out.println("下載成功:" + path); } catch (Exception e) { System.out.println("下載文件出現異常:::"); e.printStackTrace(); } } /** * 刪除文件 * * @param directory * 要刪除文件所在目錄 * @param deleteFile * 要刪除的文件 * @param sftp */ public void delete(String directory, String deleteFile, ChannelSftp sftp) { try { sftp.cd(directory); sftp.rm(deleteFile); } catch (Exception e) { e.printStackTrace(); } } /** * 列出目錄下的文件 * * @param directory * 要列出的目錄 * @param sftp * @return * @throws SftpException */ public Vector listFiles(String directory, ChannelSftp sftp) throws SftpException { return sftp.ls(directory); } public void deal(int judge) { String directory = ""; //改成自己要上傳的文件名 String fileName = "msdia80.dll"; String localUrl = "D:\\";// 文件下載到本地的地址,虛擬機上的格式/home/fy_flg/reportFile/ String path = localUrl + fileName;// 路徑+文件名 ChannelSftp sftp = connect(host, port, username, password); try { if (judge == 1) {// 表示上傳 directory = "/upload/";// sftp操作文件的目錄 upload(directory, path, sftp);// 上傳--sftp目錄,要上傳文件的地址 }else if (judge == 2){ //下載 directory = "/upload/";// sftp操作文件的目錄 path="C:\\Users\\lx\\Desktop\\"+fileName; fileName=directory+fileName; download(directory,fileName, sftp,path);// 下載文件--sftp目錄,要下載的文件名字,下載到的地方, }else if (judge == 3){ directory = "/upload/";// sftp操作文件的目錄 delete(directory, fileName, sftp);// 刪除--sftp目錄,要上傳文件的地址 } else if (judge == 4){ directory = "/upload/"; try { Vector vector = listFiles(directory, sftp); for (Object o : vector) { System.out.println(o.toString()); } } catch (SftpException e) { e.printStackTrace(); } } }finally { disconnect(); } } /** * 關閉連接 */ public void disconnect() { if (this.sftp != null) { if (this.sftp.isConnected()) { this.sftp.disconnect(); this.sftp = null; System.out.println("sftp 連接已關閉!"); } } if (this.sshSession != null) { if (this.sshSession.isConnected()) { this.sshSession.disconnect(); this.sshSession = null; System.out.println("sshSession 連接已關閉!"); } } } public static void main(String[] args) { FtpService sf = new FtpService(); sf.deal(4); } }