SpringBoot集成FTP上传文件功能实现
作者:齐 飞
文章介绍了FTP和SFTP的区别,强调SFTP基于SSH加密更安全,且使用单一端口,同时说明Spring Boot集成FTP上传需配置依赖、文件类、对象池及懒加载机制,以实现高效文件传输与资源管理,本文重点给大家介绍SpringBoot集成FTP上传文件功能实现,感兴趣的朋友一起看看吧

FTP是什么?
文件传输协议(英语:File Transfer Protocol,缩写:FTP)是一个用于在计算机网络上在客户端和服务器之间进行文件传输的应用层协议。
SFTP 和 FTP 的区别
SFTP 和 FTP 是两种不同的文件传输协议,它们在安全性、连接方式和功能上有显著差异。
FTP(File Transfer Protocol)
- 使用明文传输数据,包括用户名和密码。
- 需要两个端口:控制端口(21)和数据端口(20)。
- 不支持加密,容易被中间人攻击。
- 支持匿名登录和主动/被动模式。
SFTP(SSH File Transfer Protocol)
- 基于 SSH 协议,所有数据传输均加密。
- 仅使用单一端口(默认 22),简化防火墙配置。
- 支持文件操作(如重命名、删除)和目录列表。
- 无需额外配置,依赖 SSH 服务即可使用。
SpringBoot集成FTP上传文件
pom依赖
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>配置文件
ftp: host: host.test port: 21 username: ftptest01 password: xxxxxx path: /var/ftp/test/ outlink: https://host.test filePath: /test/ encoding: utf-8 maxActive: 100 minIdel: 2 maxIdel: 5 maxWaitMillis: 3000 passivemode: true
配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author qf
* @version 1.0
* @data 2025/7/20 18:28
*/
@Data
@Component
@ConfigurationProperties(prefix = "ftp")
public class FTPConfig {
private String host;
private Integer port;
private String username;
private String password;
private String encoding;
private Integer maxActive;
private Integer minIdel;
private Integer maxIdel;
private Integer maxWaitMillis;
private Boolean passivemode;
private String outlink;
private String filePath;
private String path;
}对象池的创建与管理
import lombok.extern.log4j.Log4j2;
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 qf
* @version 1.0
* @data 2025/7/20 18:34
*/
@Log4j2
@Component
public class FtpClientFactory implements PooledObjectFactory<FTPClient> {
@Autowired
private FTPConfig ftpConfig;
/**
* 创建连接放入池中
*
* @return
* @throws Exception
*/
@Override
public PooledObject<FTPClient> makeObject() throws Exception {
FTPClient ftpClient = new FTPClient();
//ftpClient.setControlEncoding(ftpConfig.getEncoding());
ftpClient.setConnectTimeout(ftpConfig.getMaxWaitMillis());
try {
ftpClient.connect(ftpConfig.getHost(), ftpConfig.getPort());
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
ftpClient.disconnect();
log.warn("FTP服务拒绝链接");
return null;
}
boolean login = ftpClient.login(ftpConfig.getUsername(), ftpConfig.getPassword());
if (!login) {
log.warn("ftpClient登入失败,username:{" + ftpConfig.getUsername() + "},password:{" + ftpConfig.getPassword() + "}");
throw new RuntimeException("ftpClient登入失败,username:{" + ftpConfig.getUsername() + "},password:{" + ftpConfig.getPassword() + "}");
}
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.setBufferSize(1024);
if (ftpConfig.getPassivemode()) {
ftpClient.enterLocalPassiveMode();
}
} catch (IOException e) {
e.printStackTrace();
}
log.info("添加一个FtpClient进入连接池");
return new DefaultPooledObject<>(ftpClient);
}
@Override
public void destroyObject(PooledObject<FTPClient> pooledObject) throws Exception {
FTPClient ftpClient = pooledObject.getObject();
try {
if (ftpClient != null && ftpClient.isConnected()) {
ftpClient.logout();
}
} catch (IOException e) {
throw new RuntimeException("没有获取到FtpClient或已断开连接:", e);
} finally {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 连接状态检查
*
* @param pooledObject
* @return
*/
@Override
public boolean validateObject(PooledObject<FTPClient> pooledObject) {
FTPClient ftpClient = pooledObject.getObject();
try {
return ftpClient.sendNoOp();
} catch (IOException e) {
log.info("无法验证FtpClient {}", e.getMessage());
return false;
}
}
@Override
public void activateObject(PooledObject<FTPClient> pooledObject) throws Exception {
}
@Override
public void passivateObject(PooledObject<FTPClient> pooledObject) throws Exception {
}
public FTPConfig getConfig() {
return this.ftpConfig;
}
}PooledObjectFactory 是 Apache Commons Pool 2 库中的一个核心接口,它定义了用于创建和管理池化对象(即那些可以被多个客户端重复使用的对象)生命周期的方法。通过实现这个接口,可以定制化对象的创建、激活、验证和销毁等过程,从而更好地控制对象池的行为。
其中:
- PooledObject makeObject() throws Exception
- 创建一个新的实例,并将其包装在一个 PooledObject 实例中返回。这是用来生成新的池对象的方法。
- void destroyObject(PooledObject p) throws Exception
- 销毁指定的池对象。当对象池决定不再需要某个对象时,会调用此方法来清理资源。
- boolean validateObject(PooledObject p)
- 验证池中的某个对象是否仍然有效。如果返回 true,则认为该对象可以继续使用;否则,可能需要重新创建或销毁该对象。
- void activateObject(PooledObject p) throws Exception
- 当从池中借用对象之前调用,用于“激活”对象,比如重置状态等操作。
- void passivateObject(PooledObject p) throws Exception
当对象归还到池后调用,用于将对象置于“钝化”状态,通常涉及清理工作以准备下次使用。
FTP连接池初始化
import lombok.extern.log4j.Log4j2;
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.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author qf
* @version 1.0
* @data 2025/7/20 18:47
*/
@Lazy
@Log4j2
@Component
public class FtpClientPool {
private FtpClientFactory factory;
private final GenericObjectPool<FTPClient> internalPool;
/**
* 初始化连接池
*/
public FtpClientPool(@Autowired FtpClientFactory factory) {
log.info("**********************初始化Ftp连接池*********************");
this.factory = factory;
FTPConfig config = factory.getConfig();
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(config.getMaxActive());
poolConfig.setMinIdle(config.getMinIdel());
poolConfig.setMaxIdle(config.getMaxIdel());
poolConfig.setMaxWaitMillis(config.getMaxWaitMillis());
this.internalPool = new GenericObjectPool<FTPClient>(factory, poolConfig);
this.internalPool.setTestOnBorrow(true);
log.info("连接池配置:{}", config.toString());
log.info("*********************初始化Ftp连接池完毕*******************");
}
/**
* 获取FTPClient
* @return
*/
public FTPClient getFTPClient(){
try {
log.info("从连接池中获取FtpClient");
return internalPool.borrowObject();
} catch (Exception e) {
log.error("从连接池获取FTPClient失败!");
return null;
}
}
/**
* 归还连接
* @param ftpClient
*/
public void returnFTPClient(FTPClient ftpClient) {
try {
ftpClient.getStatus();
log.info("归还FtpClient:" + ftpClient);
internalPool.returnObject(ftpClient);
} catch (IOException e) {
log.info("移除过期ftpClient {}", e.getMessage());
try {
internalPool.invalidateObject(ftpClient);
} catch (Exception ex) {
log.info("移除过期ftpClient失败 {}", e.getMessage());
}
}
}
/**
* 销毁连接池
*/
public void destory() {
log.info("*********************close FtpPool**********************");
internalPool.close();
}
}FTP工具类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
/**
* @author qf
* @version 1.0
* @data 2025/7/20 18:47
*/
@Lazy
@Slf4j
@Component
public class FTPClientUtil {
@Autowired
private FtpClientPool pool;
/**
* 上传文件
*
* @param file
* @param path
* @return
* @throws IOException
*/
public Boolean upload(MultipartFile file, String path) throws IOException {
FTPClient ftpClient = pool.getFTPClient();
InputStream inputStream = null;
Boolean result = false;
try {
inputStream = file.getInputStream();
createDir(ftpClient, path);
result = ftpClient.storeFile(path, inputStream);
log.info("文件{},服务器路径{},上传结果result:{}", file.getOriginalFilename(), path, result);
} catch (IOException e) {
e.printStackTrace();
} finally {
inputStream.close();
pool.returnFTPClient(ftpClient);
}
return result;
}
/**
* 创建文件夹
*
* @param ftpClient
* @param path
* @return
* @throws IOException
*/
private Boolean createDir(FTPClient ftpClient, String path) throws IOException {
Boolean flag = false;
String substring = path.substring(path.indexOf("/") + 1, path.lastIndexOf("/"));
ftpClient.changeWorkingDirectory("/");
//boolean b = ftpClient.changeWorkingDirectory(substring);
String[] split = substring.split("/");
for (String s : split) {
flag = ftpClient.makeDirectory(s);
boolean changeFlag=ftpClient.changeWorkingDirectory(s);
log.info("目录:{},创建目录结果:{},切换目录结果:{}",s,flag,changeFlag);
}
ftpClient.changeWorkingDirectory("/");
return flag;
}
}这里因为项目部署在多个服务器中,而有一些环境不使用ftp,因此使用懒加载的方式。
示例:
@Lazy
@Autowired
private FTPClientUtil ftpClientUtil;Controller
import com.qf.util.ftp.FTPClientUtil;
import com.qf.util.ftp.FTPConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
/**
* @author qf
* @version 1.0
* @data 2025/7/20 19:36
*/
@Slf4j
@RestController
@RequestMapping("api")
public class TestController {
@Autowired
private FTPConfig ftpConfig;
@Autowired
private FTPClientUtil ftpClientUtil;
@PostMapping(value = "/uploadExcelFile",headers = "content-type=multipart/form-data")
public Boolean uploadImgFile(@RequestParam("file") MultipartFile file) throws IOException {
String path = ftpConfig.getPath();
String originalFilename = file.getOriginalFilename();
String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileId = UUID.randomUUID().toString().replace("-", "");
String newFileName = fileId + fileSuffix;
String filePath = path + newFileName;
Boolean flag = ftpClientUtil.upload(file, filePath);
return flag;
}
}到此这篇关于SpringBoot集成FTP上传文件功能实现的文章就介绍到这了,更多相关springboot ftp上传文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
