项目场景:
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() {//上传文件到ftpFtpUtil.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设备的场景。