SpringBoot使用hutool操作FTP
项目场景:
SpringBoot使用hutool操作FTP,可以实现从FTP服务器下载文件到本地,以及将本地文件上传到FTP服务器的功能。
实现步骤:
1、引入依赖
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.15</version>
</dependency>
2、yml配置
ftp:
# 服务器地址
host: 127.0.0.1
# 端口号
port: 21
# 用户名
userName: test
# 密码
password: test
3、Config配置类
我这里用的是static修饰的变量,方便工具类调用。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* ftp配置
*/
@Configuration
public class FtpConfig {
/**
* 服务器地址
*/
private static String host;
/**
* 端口
*/
private static Integer port;
/**
* 用户名
*/
private static String userName;
/**
* 密码
*/
private static String password;
@Value("${ftp.host}")
public void setHost(String host) {
FtpConfig.host = host;
}
public static String getHost() {
return host;
}
@Value("${ftp.port}")
public void setPort(Integer port) {
FtpConfig.port = port;
}
public static Integer getPort() {
return port;
}
@Value("${ftp.userName}")
public void setUserName(String userName) {
FtpConfig.userName = userName;
}
public static String getUserName() {
return userName;
}
@Value("${ftp.password}")
public void setPassword(String password) {
FtpConfig.password = password;
}
public static String getPassword() {
return password;
}
}
4、 FtpUtil工具类
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.extra.ftp.FtpMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* FTP服务工具类
*/
@Slf4j
public class FtpUtil {
/**
* 获取 FTPClient对象
*/
private static Ftp getFTPClient() {
try {
if(StringUtils.isBlank(FtpConfig.getHost()) || FtpConfig.getPort() == null
|| StringUtils.isBlank(FtpConfig.getUserName()) || StringUtils.isBlank(FtpConfig.getPassword())) {
throw new RuntimeException("ftp配置信息不能为空");
}
Ftp ftp = new Ftp(FtpConfig.getHost(),FtpConfig.getPort(),FtpConfig.getUserName(),FtpConfig.getPassword());
//设置为被动模式,防止防火墙拦截
ftp.setMode(FtpMode.Passive);
return ftp;
} catch (Exception e) {
e.printStackTrace();
log.error("获取ftp客户端异常",e);
throw new RuntimeException("获取ftp客户端异常:"+e.getMessage());
}
}
/**
* 下载ftp服务器上的文件到本地
* @param remoteFile ftp上的文件路径
* @param localFile 输出的目录,使用服务端的文件名
*/
public static void download(String remoteFile, String localPath) {
if(StringUtils.isBlank(remoteFile) || StringUtils.isBlank(localPath)) {
return;
}
Ftp ftp = getFTPClient();
try {
if(!FileUtil.exist(localPath)){
FileUtil.mkdir(localPath);
}
File lFile = FileUtil.file(localPath);
ftp.download(remoteFile, lFile);
} catch (Exception e) {
e.printStackTrace();
log.error("FTP文件下载异常",e);
} finally {
//关闭连接
try {
if(ftp != null) ftp.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 本地文件上传到ftp服务器上
* @param remoteDir 上传的ftp目录
* @param remoteFileName 保存到ftp服务器上的名称
* @param localFile 本地文件全名称
*/
public static boolean upload(String remoteDir, String remoteFileName, String localFile) {
if(StringUtils.isBlank(remoteDir) || StringUtils.isBlank(remoteFileName) || StringUtils.isBlank(localFile)) {
return false;
}
Ftp ftp = getFTPClient();
try {
File lFile = FileUtil.file(localFile);
if(!lFile.exists()) {
log.error("本地文件不存在");
return false;
}
if(StringUtils.isBlank(remoteFileName)) {
return ftp.upload(remoteDir, lFile);
} else {
return ftp.upload(remoteDir, remoteFileName, lFile);
}
} catch (Exception e) {
e.printStackTrace();
log.error("文件上传FTP异常",e);
return false;
} finally {
//关闭连接
try {
if(ftp != null) ftp.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 删除FTP服务器中的文件
* @param remoteFile ftp上的文件路径
*/
public static boolean delFile(String remoteFile) {
if(StringUtils.isBlank(remoteFile)) {
return false;
}
Ftp ftp = getFTPClient();
try {
return ftp.delFile(remoteFile);
} catch (Exception e) {
e.printStackTrace();
log.error("删除FTP服务器中的文件异常",e);
return false;
} finally {
//关闭连接
try {
if(ftp != null) ftp.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 遍历某个目录下所有文件,不会递归遍历
* @param path 目录
*/
public static List<String> listFile(String path) {
List<String> listFile = new ArrayList<>();
Ftp ftp = getFTPClient();
try {
FTPFile[] ftpFiles = ftp.lsFiles(path);
for (int i = 0; i < ftpFiles.length; i++) {
FTPFile ftpFile = ftpFiles[i];
if(ftpFile.isFile()){
listFile.add(ftpFile.getName());
}
}
return listFile;
} catch (Exception e) {
e.printStackTrace();
log.error("遍历某个目录下所有文件异常",e);
return null;
} finally {
//关闭连接
try {
if(ftp != null) ftp.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5、测试
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "ftpTest", method = RequestMethod.GET)
public void ftpTest() {
//上传文件到ftp
FtpUtil.upload("opt/upload","APP_RELATION.sql", "F:/APP_RELATION.sql");
//下载远程文件
FtpUtil.download("opt/upload/APP_RELATION.sql", "D:/");
//删除远程文件
FtpUtil.delFile("opt/upload/APP_RELATION.sql");
}
}
总结:
上传的时候碰到一个问题,就是本地开启防火墙时,上传的文件大小是0kb,必须要关了防火墙才正常,后来FTP模式设置为“被动模式”解决。
//设置为被动模式,防止防火墙拦截
ftp.setMode(FtpMode.Passive);
主动模式(Active Mode):
- 工作原理:客户端在本地打开一个非特权端口(通常大于1023),并通过这个端口发送PORT命令给服务器,告诉服务器客户端用于数据传输的端口号。然后,服务器使用其20端口(数据端口)主动连接到客户端指定的端口进行数据传输。
- 安全性:由于服务器需要主动连接到客户端的端口,这可能引发一些安全问题,特别是当客户端位于防火墙或NAT设备后面时。
- 适用场景:适用于客户端位于可以接受入站连接的网络环境,且没有防火墙或NAT设备限制的场景。
被动模式(Passive Mode):
- 工作原理:客户端发送PASV命令给服务器,服务器在本地打开一个端口(通常是高位的非特权端口),并通过PASV命令的响应告诉客户端这个端口号。然后,客户端主动连接到服务器指定的这个端口进行数据传输。
- 安全性:由于客户端主动连接到服务器,这种模式更适合于客户端位于防火墙或NAT设备后面的场景,因为这些设备通常允许出站连接但限制入站连接。
- 适用场景: 特别适用于网络环境不稳定、存在防火墙或NAT设备的场景。