一、项目结构
二、maven
<!-- ftp远程工具 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.53</version>
</dependency>
<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
三、配置文件
Ip: ip
Port: 21
UserName: admin
Password: password
RemotePath: /test
LocalPath: /test/one
fileBackupsPath: /test/testBACKS
rootPath: /
ftpEncoding: utf-8
ftpMaxTotal: 100
ftpMinIdel: 2
ftpMaxIdle: 20
ftpMaxWaitMillis: 20000
connectTimeout: 20000
四、整合FTP连接池
4.1 FtpClientFactory
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author gyDeBug
* @description:
* @param:
* @return:
* @date:2021/8/24
*/
@Component
@Slf4j
public class FtpClientFactory implements PooledObjectFactory<FTPClient> {
@Autowired
private FtpPoolConfig config;
//创建连接到池中
@Override
public PooledObject<FTPClient> makeObject() {
FTPClient ftpClient = new FTPClient();//创建客户端实例
return new DefaultPooledObject<>(ftpClient);
}
//销毁连接,当连接池空闲数量达到上限时,调用此方法销毁连接
@Override
public void destroyObject(PooledObject<FTPClient> pooledObject) {
FTPClient ftpClient = pooledObject.getObject();
try {
ftpClient.logout();
if (ftpClient.isConnected()) {
ftpClient.disconnect();
}
} catch (IOException e) {
throw new RuntimeException("销毁连接:", e);
}
}
//链接状态检查
@Override
public boolean validateObject(PooledObject<FTPClient> pooledObject) {
FTPClient ftpClient = pooledObject.getObject();
try {
return ftpClient.sendNoOp();
} catch (IOException e) {
return false;
}
}
//初始化连接
@Override
public void activateObject(PooledObject<FTPClient> pooledObject) throws Exception {
FTPClient ftpClient = pooledObject.getObject();
String host = config.getHost();
Integer port = config.getPort();
String username = config.getUserName();
String password = config.getPassWord();
String encoding = config.getEncoding();
ftpClient.setConnectTimeout(config.getConnectTimeout());
try {
ftpClient.connect(host,port);
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
ftpClient.disconnect();
log.warn("FTPServer 连接失败,replyCode: " + replyCode);
}
boolean login = ftpClient.login(username, password);
if(!login){
log.warn("ftpClient 登录失败: " +host);
}
ftpClient.setControlEncoding(encoding);
}catch (Exception e){
log.info("FTP连接或登录异常"+e.getMessage());
}
// ftpClient.changeWorkingDirectory(config.getWorkDir());
// ftpClient.setFileType(FTP.BINARY_FILE_TYPE);//设置上传文件类型为二进制,否则将无法打开文件
}
//钝化连接,使链接变为可用状态
@Override
public void passivateObject(PooledObject<FTPClient> pooledObject) throws Exception {
FTPClient ftpClient = pooledObject.getObject();
try {
ftpClient.changeWorkingDirectory(config.getRoot());
ftpClient.logout();
if (ftpClient.isConnected()) {
ftpClient.disconnect();
}
} catch (IOException e) {
throw new RuntimeException("该FTP连接不可用", e);
}
}
//用于连接池中获取pool属性
public FtpPoolConfig getConfig() {
return config;
}
}
4.2 FtpPool
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* @author gyDeBug
* @description: FTP连接池
* * 1.可以获取池中空闲链接
* * 2.可以将链接归还到池中
* * 3.当池中空闲链接不足时,可以创建链接
* @param:
* @return:
* @date:2021/8/24
*/
@Slf4j
@Component
public class FtpPool {
FtpClientFactory factory;
private final GenericObjectPool<FTPClient> internalPool;
//初始化连接池
public FtpPool(@Autowired FtpClientFactory factory){
this.factory=factory;
FtpPoolConfig config = factory.getConfig();
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
//是否开启空闲资源监测
poolConfig.setTestWhileIdle(true);
//空闲资源的检测周期(单位为毫秒)
poolConfig.setTimeBetweenEvictionRunsMillis(60000);
//资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除
poolConfig.setTimeBetweenEvictionRunsMillis(30000);
//做空闲资源检测时,每次的采样数
poolConfig.setNumTestsPerEvictionRun(-1);
poolConfig.setMaxTotal(config.getMaxTotal());
poolConfig.setMinIdle(config.getMinIdel());
poolConfig.setMaxIdle(config.getMaxIdle());
poolConfig.setMaxWaitMillis(config.getMaxWaitMillis());
this.internalPool = new GenericObjectPool<FTPClient>(factory,poolConfig);
log.info("初始化连接池成功this.factory{}",this.factory);
}
//从连接池中取连接
public FTPClient getFTPClient() {
try {
log.info("从连接池获取FTP连接信息,this.internalPool{}");
Thread t = Thread.currentThread();
log.info("FTP连接池当前线程:"+t.getName());
return internalPool.borrowObject();
} catch (Exception e) {
log.info("从连接池获取FTP连接信息失败:"+e);
e.printStackTrace();
return null;
}
}
//将链接归还到连接池
public void returnFTPClient(FTPClient ftpClient) {
try {
internalPool.returnObject(ftpClient);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 销毁池子
*/
public void destroy() {
try {
internalPool.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.3 FtpPoolConfig
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @author gyDeBug
* @description:
* @param:
* @return:
* @date:2021/8/24
*/
@Component
@Configuration
@Data
public class FtpPoolConfig {
private String Host;
private int Port;
private String UserName;
private String PassWord;
private String workDir;
private String encoding;
private String root;
private int MaxTotal;
private int MinIdel;
private int MaxIdle;
private int MaxWaitMillis;
private int connectTimeout;
@Value("${config.ip}")
public void setHost(String host) {
Host = host;
}
@Value("${config.Port}")
public void setPort(int port) {
Port = port;
}
@Value("${config.UserName}")
public void setUserName(String userName) {
UserName = userName;
}
@Value("${config.Password}")
public void setPassWord(String passWord) {
PassWord = passWord;
}
@Value("${config.RemotePath}")
public void setWorkDir(String workDir) {
this.workDir = workDir;
}
@Value("${config.ftpEncoding}")
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Value("${config.rootPath}")
public void setRoot(String root) {
this.root = root;
}
@Value("${config.ftpMaxTotal}")
public void setMaxTotal(int maxTotal) {
MaxTotal = maxTotal;
}
@Value("${config.ftpMinIdel}")
public void setMinIdel(int minIdel) {
MinIdel = minIdel;
}
@Value("${config.ftpMaxIdle}")
public void setMaxIdle(int maxIdle) {
MaxIdle = maxIdle;
}
@Value("${config.ftpMaxWaitMillis}")
public void setMaxWaitMillis(int maxWaitMillis) {
MaxWaitMillis = maxWaitMillis;
}
@Value("${config.connectTimeout}")
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
}
五、 调用FTP连接池下载文件
@Service
public class FtpServiceImp implements FtpService {
@Resource
private FtpPool pool;/**
*@Description: 连接池获取FTP连接信息,下载文件
*@Param:
*@return:
*@Author: gyDeBug
*@date: 2021/9/1
**/
@Override
public void downFtp(){
String remotePath = environment.getProperty("config.RemotePath").trim();
String localPath = environment.getProperty("config.LocalPath").trim();
String fileBackupsPath = environment.getProperty("config.fileBackupsPath").trim();
long time1=System.currentTimeMillis();
try{
FTPClient ftpClient = pool.getFTPClient();
Thread t = Thread.currentThread();
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(remotePath);
logger.info("FTP当前线程:"+t.getName());
logger.info("登录成功,从连接池获取FTP连接信息",ftpClient);
logger.info("切换至工作目录" + remotePath);
FTPFile[] fileList = ftpClient.listFiles();
// logger.info("FTP文件列表:"+fileList.toString());
if (fileList.length != 0) {
for (FTPFile file : fileList) {
//如果是文件
if(file.isFile()){
logger.info("文件:" + file);
File localFile = new File(localPath + "/" + file.getName());
OutputStream is = new FileOutputStream(localFile);
ftpClient.retrieveFile(file.getName(), is);
logger.info("============文件名" + file.getName());
is.close();
//再下载一次进行备份
File fileBackups = new File(fileBackupsPath + "/" + file.getName());
OutputStream os = new FileOutputStream(fileBackups);
ftpClient.retrieveFile(file.getName(), os);
os.close();
ftpClient.deleteFile(file.getName());
}
}
}
//归还
pool.returnFTPClient(ftpClient);
long time2=System.currentTimeMillis();
logger.info("下载文件成功,FTP耗时:" + (time2 - time1) + "ms");
}catch (Exception e) {
logger.info("FTP下载异常:"+e);
}
}
六、附加:FTP工具类
import cn.hutool.extra.ftp.FtpConfig;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* @author
* @description:
* @param:
* @return:
* @date:2021/8/24
*/
public class FtpUtil {
@Autowired
FtpPoolConfig config;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
FtpPool pool;
/**
* Description: 向FTP服务器上传文件
*
* @Version2.0
* @param file
* 上传到FTP服务器上的文件
* @return
* 成功返回文件名,否则返回null
*/
public String upload(MultipartFile file) throws Exception {
FTPClient ftpClient = pool.getFTPClient();
//开始进行文件上传
String fileName=UUID.randomUUID()+file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
InputStream input=file.getInputStream();
try {
boolean result=ftpClient.storeFile(fileName,input);//执行文件传输
if(!result){//上传失败
throw new RuntimeException("上传失败");
}
}catch(Exception e){
e.printStackTrace();
return null;
}finally {//关闭资源
input.close();
System.out.println("开始归还连接");
pool.returnFTPClient(ftpClient);//归还资源
}
return fileName;
}
/**
* Description: 从FTP服务器下载文件
*
* @Version1.0
* @param fileName
* FTP服务器中的文件名
* @param resp
* 响应客户的响应体
*/
public void downLoad(String fileName, HttpServletResponse resp) throws IOException {
FTPClient ftpClient = pool.getFTPClient();
resp.setContentType("application/force-download");// 设置强制下载不打开 MIME
resp.addHeader("Content-Disposition", "attachment;fileName="+fileName);// 设置文件名
//将文件直接读取到响应体中
OutputStream out = resp.getOutputStream();
ftpClient.retrieveFile(config.getWorkDir()+"/"+fileName, out);
out.flush();
out.close();
pool.returnFTPClient(ftpClient);
}
/**
* Description: 从FTP服务器读取图片
*
* @Version1.0
* @param fileName
* 需要读取的文件名
* @return
* 返回文件对应的Entity
*/
public ResponseEntity show(String fileName){
String username=config.getUserName();
String password=config.getPassWord();
String host=config.getHost();
String work=config.getWorkDir();
//ftp://root:root@192.168.xx.xx/+fileName
return ResponseEntity.ok(resourceLoader.getResource("ftp://"+username+":"+password+"@"+host+work+"/"+fileName));
}
}