Skip to content

支持FTP工具类 #879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
支持FTP工具类
  • Loading branch information
pcshao committed Apr 15, 2025
commit 911c4c092b1d850167671d93bbb2f1f8947d901e
7 changes: 7 additions & 0 deletions eladmin-common/pom.xml
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
<modelVersion>4.0.0</modelVersion>
<properties>
<hutool.version>5.8.35</hutool.version>
<commons-net.version>3.7</commons-net.version>
</properties>

<artifactId>eladmin-common</artifactId>
@@ -20,5 +21,11 @@
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 用于FTP -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>${commons-net.version}</version> <!-- 版本号可能会有更新 -->
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -17,9 +17,13 @@

import lombok.Data;
import me.zhengjie.utils.ElConstant;
import me.zhengjie.utils.EncryptUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.util.Objects;

/**
* @author Zheng Jie
*/
@@ -57,4 +61,76 @@ public static class ElPath{

private String avatar;
}

// region 支持FTP配置
private FtpConfig ftp;

@Data
public static class FtpConfig {
private String host;
private int port;
private String username;
private String password;
private String mainPath;
private int connectTimeout = 5000; // ms
private int dataTimeout = 30000; // ms
// 连接池相关配置
private int minIdle = 1;
private int maxIdle = 3;
private int maxTotal = 5;
private long maxWait = 5000; // ms

public String getPassword() {
String password = this.password;
try {
password = EncryptUtils.desDecrypt(this.password);
} catch (Exception e) {
e.printStackTrace();
}
return password;
}
}

/**
* 获取刨除服务器文件主目录外的相对路径(用于FTP的相对路径)
*
* @param file
* @return
*/
public String getRelativePath(File file) {
String mainPath = getPath().getPath();
String ftpPath = getFtp().getMainPath();
if (Objects.nonNull(file)) {
if (file.getPath().contains(mainPath)) {
int len = file.getPath().length() - file.getName().length();
String result = file.getPath().substring(mainPath.length(), len);
// 处理分割符
return dealPath(result);
} else if (file.getPath().contains(ftpPath)) {
int len = file.getPath().length() - file.getName().length();
String result = file.getPath().substring(ftpPath.length(), len);
// 处理分割符
return dealPath(result);
}
}
return file.getPath();
}

/**
* 处理文件路径分隔符
*
* @param path
* @return
*/
private String dealPath(String path) {
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith(ElConstant.WIN) && path.contains("\\")) {
return path.replace("\\", "/");
} else if (os.toLowerCase().startsWith(ElConstant.MAC)) {
return path;
}
return path;
}

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package me.zhengjie.utils.ftp;

import lombok.extern.slf4j.Slf4j;
import me.zhengjie.config.properties.FileProperties;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.function.Function;

/**
* FTP连接池管理器
*
* @author pcshao.cn
* @date 13/04/2024
*/
@Slf4j
@Service
public class FtpConnectionPoolManager {

private volatile static GenericObjectPool<FTPClient> pool = null;

@Resource
private FileProperties fileProperties;

/**
* 初始化连接池
*/
public void initPool() {
if (Objects.nonNull(pool))
return;
else {
// 懒加载
synchronized (this) {
if (Objects.nonNull(pool))
return;
}
}
FileProperties.FtpConfig ftp = fileProperties.getFtp();
GenericObjectPoolConfig<FTPClient> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(ftp.getMaxTotal()); // 设置连接池最大连接数
poolConfig.setMaxIdle(ftp.getMaxIdle()); // 设置连接池最大空闲连接数
poolConfig.setMinIdle(ftp.getMinIdle()); // 设置连接池最小空闲连接数
poolConfig.setMaxWaitMillis(ftp.getMaxWait()); // 设置连接池最大等待时间,单位为毫秒
poolConfig.setTestOnBorrow(true); // 设置获取连接前进行检查

pool = new GenericObjectPool<>(new BasePooledObjectFactory<FTPClient>() {
@Override
public FTPClient create() throws Exception {
String server = ftp.getHost();
int port = ftp.getPort();
String username = ftp.getUsername();
String password = ftp.getPassword();
FTPClient ftpClient = new FTPClient();
// 设置控制连接的字符编码为UTF-8
ftpClient.setControlEncoding("UTF-8");
ftpClient.connect(server, port);
ftpClient.login(username, password);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.setConnectTimeout(ftp.getConnectTimeout()); // 连接超时时间为5秒
ftpClient.setDataTimeout(ftp.getDataTimeout()); // 数据传输超时时间为30秒
return ftpClient;
}

@Override
public void destroyObject(PooledObject<FTPClient> p) throws Exception {
FTPClient ftpClient = p.getObject();
ftpClient.logout();
ftpClient.disconnect();
}

@Override
public boolean validateObject(PooledObject<FTPClient> p) {
FTPClient ftpClient = p.getObject();
try {
log.warn("FTP连接池管理 检测FTP连接状态 reply:{} status:{} obj:{}", ftpClient.getReplyString(), ftpClient.getStatus(), ftpClient);
return ftpClient.isConnected() && ftpClient.sendNoOp();
} catch (Exception e) {
log.warn("FTP连接池管理 检测FTP连接失败 {}", e.getMessage());
if (log.isDebugEnabled())
log.debug(e.getMessage(), e);
return false;
}
}

@Override
public PooledObject<FTPClient> wrap(FTPClient ftpClient) {
DefaultPooledObject defaultPooledObject = new DefaultPooledObject(ftpClient);
return defaultPooledObject;
}
}, poolConfig);
}

/**
* 执行方法,包含获取和归还连接
*
* @param function
* @param <R>
* @return
* @throws Exception
*/
public <R> R execute(Function<FTPClient, R> function) throws Exception {
FTPClient ftpClient = null;
try {
ftpClient = borrowFTPClient();
return function.apply(ftpClient);
} finally {
returnFTPClient(ftpClient);
}
}

/**
* 获取一个连接
*
* @return
* @throws Exception
*/
public FTPClient borrowFTPClient() throws Exception {
initPool();
return pool.borrowObject();
}

/**
* 归还连接
*
* @param ftpClient
*/
public void returnFTPClient(FTPClient ftpClient) {
initPool();
pool.returnObject(ftpClient);
}

/**
* 关闭连接池
*/
public void close() {
if (Objects.isNull(pool))
return;
pool.close();
}
}
299 changes: 299 additions & 0 deletions eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
package me.zhengjie.utils.ftp;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
* @author pcshao.cn
* @date 20/01/2024
*/
@Slf4j
public class FtpUtil {

/**
* FTP文件上传
*
* @param ftpClient
* @param localFilePath
* @param remoteDirectory
* @return
*/
public static boolean uploadFile(FTPClient ftpClient, String localFilePath, String remoteDirectory) {
boolean done = false;
try {
// 检查远程目录是否存在
checkRemoteDir(ftpClient, remoteDirectory);

// 设置远程目录的字符编码为UTF-8
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

File localFile = new File(localFilePath);

FileInputStream inputStream = new FileInputStream(localFile);

String remoteFilePath = remoteDirectory + localFile.getName();

done = ftpClient.storeFile(remoteFilePath, inputStream);

if (done) {
log.info("FtpUtil uploadFile successfully.");
} else {
log.error("FtpUtil uploadFile failed. reply: {} remotePath: {} obj: {}", ftpClient.getReplyString(), remoteFilePath, ftpClient);
}
inputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return done;
}

/**
* FTP文件上传
*
* @param server
* @param port
* @param username
* @param password
* @param localFilePath
* @param remoteDirectory
*/
public static boolean uploadFile(String server, int port, String username, String password, String localFilePath, String remoteDirectory) {
boolean done = false;
FTPClient ftpClient = new FTPClient();
try {
// 设置控制连接的字符编码为UTF-8
ftpClient.setControlEncoding("UTF-8");
ftpClient.connect(server, port);
ftpClient.login(username, password);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

// 检查远程目录是否存在
checkRemoteDir(ftpClient, remoteDirectory);

// 设置远程目录的字符编码为UTF-8
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

File localFile = new File(localFilePath);

FileInputStream inputStream = new FileInputStream(localFile);

String remoteFilePath = remoteDirectory + localFile.getName();

done = ftpClient.storeFile(remoteFilePath, inputStream);

if (done) {
log.info("FtpUtil uploadFile successfully.");
} else {
log.info("FtpUtil uploadFile failed. reply: {} remotePath: {}", ftpClient.getReplyString(), remoteFilePath);
}
inputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
if (ftpClient.isConnected()) {
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
return done;
}

/**
* 检查远程目录,如果不存在则创建
*
* @param ftpClient
* @param remoteDirectory
* @throws IOException
*/
private static void checkRemoteDir(FTPClient ftpClient, String remoteDirectory) throws IOException {
FTPFile[] ftpFiles = ftpClient.mlistDir(remoteDirectory);
log.info("FtpUtil mlistDir {} reply: {}", remoteDirectory, ftpClient.getReplyString());
if (ftpFiles.length <= 0) {
ftpClient.makeDirectory(remoteDirectory);
log.info("FtpUtil makeDirectory {} reply: {}", remoteDirectory, ftpClient.getReplyString());
}
}

/**
* FTP文件下载
*
* @param ftpClient
* @param remoteFilePath
* @param localDirectory
* @return
*/
public static File downloadFile(FTPClient ftpClient, String remoteFilePath, String localDirectory) {
File localFile = null;
try {
// 设置远程目录的字符编码为UTF-8
String remoteDirectory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1);
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

String remoteFileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);
localFile = new File(localDirectory + remoteFileName);

OutputStream outputStream = new FileOutputStream(localFile);

boolean success = ftpClient.retrieveFile(remoteFilePath, outputStream);

if (success) {
log.info("FtpUtil downloaded successfully.");
} else {
log.info("FtpUtil download failed. reply: {} remotePath: {}", ftpClient.getReplyString(), remoteFilePath);
return null;
}

outputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
return null;
}
return localFile;
}

/**
* FTP文件下载
*
* @param server
* @param port
* @param username
* @param password
* @param remoteFilePath
* @param localDirectory
* @return
*/
public static File downloadFile(String server, int port, String username, String password, String remoteFilePath, String localDirectory) {
File localFile = null;
FTPClient ftpClient = new FTPClient();
try {
// 设置控制连接的字符编码为UTF-8
ftpClient.setControlEncoding("UTF-8");
ftpClient.connect(server, port);
ftpClient.login(username, password);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

// 设置远程目录的字符编码为UTF-8
String remoteDirectory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1);
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

String remoteFileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);
localFile = new File(localDirectory + remoteFileName);

OutputStream outputStream = new FileOutputStream(localFile);

boolean success = ftpClient.retrieveFile(remoteFilePath, outputStream);

if (success) {
log.info("FtpUtil downloaded successfully.");
} else {
log.info("FtpUtil download failed. reply: {} remotePath: {}", ftpClient.getReplyString(), remoteFilePath);
return null;
}

outputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
return null;
} finally {
try {
if (ftpClient.isConnected()) {
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
return localFile;
}

/**
* FTP文件删除
*
* @param ftpClient
* @param remoteFilePath
* @return
*/
public static boolean deleteFile(FTPClient ftpClient, String remoteFilePath) {
boolean success = false;
try {
// 设置远程目录的字符编码为UTF-8
String remoteDirectory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1);
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

String remoteFileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);

success = ftpClient.deleteFile(remoteFileName);

if (success) {
log.info("FtpUtil delete successfully.");
} else {
log.info("FtpUtil delete failed. reply: {} remotePath: {}", ftpClient.getReplyString(), remoteFilePath);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return success;
}

/**
* FTP文件删除
*
* @param server
* @param port
* @param username
* @param password
* @param remoteFilePath
* @return
*/
public static boolean deleteFile(String server, int port, String username, String password, String remoteFilePath) {
boolean success = false;
FTPClient ftpClient = new FTPClient();
try {
// 设置控制连接的字符编码为UTF-8
ftpClient.setControlEncoding("UTF-8");
ftpClient.connect(server, port);
ftpClient.login(username, password);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

// 设置远程目录的字符编码为UTF-8
String remoteDirectory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1);
ftpClient.changeWorkingDirectory(new String(remoteDirectory.getBytes(StandardCharsets.UTF_8)));

String remoteFileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);

success = ftpClient.deleteFile(remoteFileName);

if (success) {
log.info("FtpUtil delete successfully.");
} else {
log.info("FtpUtil delete failed. reply: {} remotePath: {}", ftpClient.getReplyString(), remoteFilePath);
}

} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
if (ftpClient.isConnected()) {
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
return success;
}
}
23 changes: 23 additions & 0 deletions eladmin-common/src/test/java/me/zhengjie/utils/FtpUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.zhengjie.utils;

import me.zhengjie.utils.ftp.FtpUtil;
import org.junit.jupiter.api.Test;

/**
* @author pcshao.cn
* @date 2025/3/12
*/
public class FtpUtilTest {

@Test
public void testDowndload() throws Exception {
String password = EncryptUtils.desDecrypt("xxx");
FtpUtil.downloadFile("ftp.xxx.xx", 21, "ftptest", password,
"/test.xlsx", "tempFile" + System.currentTimeMillis() + ".xlsx");
}

public void testUpload() throws Exception {
String password = EncryptUtils.desDecrypt("xxx");
FtpUtil.uploadFile("ftp.xxx.xx", 21, "ftptest", password, "tempFile", "");
}
}
14 changes: 14 additions & 0 deletions eladmin-system/src/main/resources/config/application-dev.yml
Original file line number Diff line number Diff line change
@@ -116,3 +116,17 @@ file:
# 文件大小 /M
maxSize: 100
avatarMaxSize: 5
# FTP文件服务器配置
ftp:
mainPath: \home\eladmin\file\
username: ftptest
password: B6890183857ABC452C9E3170906AF9AB97872252D8A4769E
host: pcshao.cn
port: 21
connectTimeout: 5000 # 连接超时时间 ms
dataTimeout: 30000 # 数据传输超时时间 ms
# FTP线程池
minIdle: 1
maxIdle: 3
maxTotal: 5
maxWait: 5000 # ms
14 changes: 14 additions & 0 deletions eladmin-system/src/main/resources/config/application-prod.yml
Original file line number Diff line number Diff line change
@@ -127,3 +127,17 @@ file:
# 文件大小 /M
maxSize: 100
avatarMaxSize: 5
# FTP文件服务器配置
ftp:
mainPath: \home\eladmin\file\
username: ftptest
password: B6890183857ABC452C9E3170906AF9AB97872252D8A4769E
host: pcshao.cn
port: 21
connectTimeout: 5000 # 连接超时时间 ms
dataTimeout: 30000 # 数据传输超时时间 ms
# FTP线程池
minIdle: 1
maxIdle: 3
maxTotal: 5
maxWait: 5000 # ms