diff --git a/eladmin-common/pom.xml b/eladmin-common/pom.xml index e1510a7a3..1223b2385 100644 --- a/eladmin-common/pom.xml +++ b/eladmin-common/pom.xml @@ -8,6 +8,7 @@ 4.0.0 5.8.35 + 3.7 eladmin-common @@ -20,5 +21,11 @@ hutool-all ${hutool.version} + + + commons-net + commons-net + ${commons-net.version} + \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java index 6b7d2b6f9..2cc77d0dd 100644 --- a/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java +++ b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java @@ -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 } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpConnectionPoolManager.java b/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpConnectionPoolManager.java new file mode 100644 index 000000000..cfdff9454 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpConnectionPoolManager.java @@ -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 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 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() { + @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 p) throws Exception { + FTPClient ftpClient = p.getObject(); + ftpClient.logout(); + ftpClient.disconnect(); + } + + @Override + public boolean validateObject(PooledObject 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 wrap(FTPClient ftpClient) { + DefaultPooledObject defaultPooledObject = new DefaultPooledObject(ftpClient); + return defaultPooledObject; + } + }, poolConfig); + } + + /** + * 执行方法,包含获取和归还连接 + * + * @param function + * @param + * @return + * @throws Exception + */ + public R execute(Function 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(); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpUtil.java new file mode 100644 index 000000000..28398e5f6 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/ftp/FtpUtil.java @@ -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; + } +} diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/FtpUtilTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/FtpUtilTest.java new file mode 100644 index 000000000..f9b5c5d91 --- /dev/null +++ b/eladmin-common/src/test/java/me/zhengjie/utils/FtpUtilTest.java @@ -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", ""); + } +} diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index e227c7797..d8b7bf28b 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -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 diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index 77fef99bb..6b8b9e2e7 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -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