• Java连接FTP服务器,并使用ftp连接池进行文件操作


    使用Java连接FTP服务器进行文件相关操作,并且使用FTP连接池降低资源消耗,提高响应速率。

    1、导入Pom依赖

    1. commons-net
    2. commons-net
    3. 3.9.0

    2、创建FTP的配置

    1. ftp:
    2. # 服务器地址
    3. host: xx.xxx.xx.xxx
    4. # 端口号
    5. port: 21
    6. # 用户名
    7. userName: xxx
    8. # 密码
    9. password: xxxxxxx
    10. # 工作目录
    11. workingDirectory: /ftpTest
    12. # 编码
    13. encoding: utf-8
    14. #被动模式
    15. passiveMode: true
    16. #连接超时时间
    17. clientTimeout: 30000
    18. # 线程数
    19. threaNum: 1
    20. # 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE2=LOCAL_FILE_TYPE(二进制文件)
    21. transferFileType: 2
    22. # 是否重命名
    23. renameUploaded: true
    24. # 重新连接时间
    25. retryTimes: 1200
    26. # 缓存大小
    27. bufferSize: 8192
    28. # 最大数
    29. maxTotal: 50
    30. # 最小空闲
    31. minldle: 10
    32. # 最大空闲
    33. maxldle: 50
    34. # 最大等待时间
    35. maxWait: 30000
    36. # 池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
    37. blockWhenExhausted: true
    38. # 取对象时验证
    39. testOnBorrow: true
    40. # 回收验证
    41. testOnReturn: true
    42. # 创建时验证
    43. testOnCreate: true
    44. # 空闲验证
    45. testWhileldle: false
    46. # 后进先出
    47. lifo: false

    3、创建FTP配置类

    1. import lombok.Getter;
    2. import lombok.Setter;
    3. import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    4. import org.springframework.boot.context.properties.ConfigurationProperties;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.apache.commons.net.ftp.FTPClient;
    7. /**
    8. * Ftp配置类
    9. */
    10. @Configuration
    11. @ConfigurationProperties(prefix = "ftp")
    12. @Getter
    13. @Setter
    14. public class FtpConfig extends GenericObjectPoolConfig {
    15. /**
    16. * FTP服务器地址
    17. */
    18. private String host;
    19. /**
    20. * FTP服务器端口
    21. */
    22. private Integer port;
    23. /**
    24. * FTP用户名
    25. */
    26. private String userName;
    27. /**
    28. * FTP密码
    29. */
    30. private String password;
    31. /**
    32. * FTP服务器根目录
    33. */
    34. private String workingDirectory;
    35. /**
    36. * 传输编码
    37. */
    38. String encoding;
    39. /**
    40. * 被动模式:在这种模式下,数据连接是由客户程序发起的
    41. */
    42. boolean passiveMode;
    43. /**
    44. * 连接超时时间
    45. */
    46. int clientTimeout;
    47. /**
    48. * 线程数
    49. */
    50. int threaNum;
    51. /**
    52. * 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二进制文件)
    53. */
    54. int transferFileType;
    55. /**
    56. * 是否重命名
    57. */
    58. boolean renameUploaded;
    59. /**
    60. * 重新连接时间
    61. */
    62. int retryTimes;
    63. /**
    64. * 缓存大小
    65. */
    66. int bufferSize;
    67. /**
    68. * 最大数
    69. */
    70. int maxTotal;
    71. /**
    72. * 最小空闲
    73. */
    74. int minldle;
    75. /**
    76. * 最大空闲
    77. */
    78. int maxldle;
    79. /**
    80. * 最大等待时间
    81. */
    82. int maxWait;
    83. /**
    84. * 池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
    85. */
    86. boolean blockWhenExhausted;
    87. /**
    88. * 取对象时验证
    89. */
    90. boolean testOnBorrow;
    91. /**
    92. * 回收验证
    93. */
    94. boolean testOnReturn;
    95. /**
    96. * 创建时验证
    97. */
    98. boolean testOnCreate;
    99. /**
    100. * 空闲验证
    101. */
    102. boolean testWhileldle;
    103. /**
    104. * 后进先出
    105. */
    106. boolean lifo;
    107. }

    4、创建工厂连接对象并注入配置

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.apache.commons.lang3.StringUtils;
    3. import org.apache.commons.net.ftp.FTP;
    4. import org.apache.commons.net.ftp.FTPClient;
    5. import org.apache.commons.net.ftp.FTPReply;
    6. import org.apache.commons.pool2.PooledObject;
    7. import org.apache.commons.pool2.PooledObjectFactory;
    8. import org.apache.commons.pool2.impl.DefaultPooledObject;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.stereotype.Component;
    11. /**
    12. * FtpClient 工厂连接对象
    13. */
    14. @Component
    15. @Slf4j
    16. public class FTPClientFactory implements PooledObjectFactory {
    17. /**
    18. * 注入 ftp 连接配置
    19. */
    20. @Autowired
    21. FtpConfig config;
    22. /**
    23. * 创建连接到池中
    24. *
    25. * @return
    26. * @throws Exception
    27. */
    28. @Override
    29. public PooledObject makeObject() throws Exception {
    30. FTPClient ftpClient = new FTPClient();
    31. ftpClient.setConnectTimeout(config.getClientTimeout());
    32. ftpClient.connect(config.getHost(), config.getPort());
    33. int reply = ftpClient.getReplyCode();
    34. if (!FTPReply.isPositiveCompletion(reply)) {
    35. ftpClient.disconnect();
    36. return null;
    37. }
    38. boolean success;
    39. if (StringUtils.isBlank(config.getUserName())) {
    40. success = ftpClient.login("anonymous", "anonymous");
    41. } else {
    42. success = ftpClient.login(config.getUserName(), config.getPassword());
    43. }
    44. if (!success) {
    45. return null;
    46. }
    47. ftpClient.setFileType(config.getTransferFileType());
    48. ftpClient.setBufferSize(1024);
    49. ftpClient.setControlEncoding(config.getEncoding());
    50. if (config.isPassiveMode()) {
    51. ftpClient.enterLocalPassiveMode();
    52. }
    53. log.debug("创建ftp连接");
    54. return new DefaultPooledObject<>(ftpClient);
    55. }
    56. /**
    57. * 链接状态检查
    58. *
    59. * @param pool
    60. * @return
    61. */
    62. @Override
    63. public boolean validateObject(PooledObject pool) {
    64. FTPClient ftpClient = pool.getObject();
    65. try {
    66. return ftpClient != null && ftpClient.sendNoOp();
    67. } catch (Exception e) {
    68. return false;
    69. }
    70. }
    71. /**
    72. * 销毁连接,当连接池空闲数量达到上限时,调用此方法销毁连接
    73. *
    74. * @param pool
    75. * @throws Exception
    76. */
    77. @Override
    78. public void destroyObject(PooledObject pool) throws Exception {
    79. FTPClient ftpClient = pool.getObject();
    80. if (ftpClient != null) {
    81. try {
    82. ftpClient.disconnect();
    83. log.debug("销毁ftp连接");
    84. } catch (Exception e) {
    85. log.error("销毁ftpClient异常,error:", e.getMessage());
    86. }
    87. }
    88. }
    89. /**
    90. * 钝化连接,是连接变为可用状态
    91. *
    92. * @param p
    93. * @throws Exception
    94. */
    95. @Override
    96. public void passivateObject(PooledObject p) throws Exception{
    97. FTPClient ftpClient = p.getObject();
    98. try {
    99. ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
    100. ftpClient.logout();
    101. if (ftpClient.isConnected()) {
    102. ftpClient.disconnect();
    103. }
    104. } catch (Exception e) {
    105. throw new RuntimeException("Could not disconnect from server.", e);
    106. }
    107. }
    108. /**
    109. * 初始化连接
    110. *
    111. * @param pool
    112. * @throws Exception
    113. */
    114. @Override
    115. public void activateObject(PooledObject pool) throws Exception {
    116. FTPClient ftpClient = pool.getObject();
    117. ftpClient.connect(config.getHost(),config.getPort());
    118. ftpClient.login(config.getUserName(), config.getPassword());
    119. ftpClient.setControlEncoding(config.getEncoding());
    120. ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
    121. //设置上传文件类型为二进制,否则将无法打开文件
    122. ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
    123. }
    124. /**
    125. * 获取 FTP 连接配置
    126. * @return
    127. */
    128. public FtpConfig getConfig(){
    129. return config;
    130. }
    131. }

    5、创建客户端对象service接口

    1. import com.aicut.monitor.config.FtpConfig;
    2. import org.apache.commons.net.ftp.FTPClient;
    3. /**
    4. * 获取 ftp 客户端对象的接口
    5. */
    6. public interface FTPPoolService {
    7. /**
    8. * 获取ftpClient
    9. * @return
    10. */
    11. FTPClient borrowObject();
    12. /**
    13. * 归还ftpClient
    14. * @param ftpClient
    15. * @return
    16. */
    17. void returnObject(FTPClient ftpClient);
    18. /**
    19. * 获取 ftp 配置信息
    20. * @return
    21. */
    22. FtpConfig getFtpPoolConfig();
    23. }

    6、创建FTP接口实现类 

    1. import com.aicut.monitor.config.FTPClientFactory;
    2. import com.aicut.monitor.config.FtpConfig;
    3. import com.aicut.monitor.service.FTPPoolService;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.apache.commons.net.ftp.FTPClient;
    6. import org.apache.commons.pool2.impl.GenericObjectPool;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.stereotype.Component;
    9. import javax.annotation.PostConstruct;
    10. @Component
    11. @Slf4j
    12. public class FTPPoolServiceImpl implements FTPPoolService {
    13. /**
    14. * ftp 连接池生成
    15. */
    16. private GenericObjectPool pool;
    17. /**
    18. * ftp 客户端配置文件
    19. */
    20. @Autowired
    21. private FtpConfig config;
    22. /**
    23. * ftp 客户端工厂
    24. */
    25. @Autowired
    26. private FTPClientFactory factory;
    27. /**
    28. * 初始化pool
    29. */
    30. @PostConstruct
    31. private void initPool() {
    32. this.pool = new GenericObjectPool(this.factory, this.config);
    33. }
    34. /**
    35. * 获取ftpClient
    36. */
    37. @Override
    38. public FTPClient borrowObject() {
    39. if (this.pool != null) {
    40. try {
    41. return this.pool.borrowObject();
    42. } catch (Exception e) {
    43. log.error("获取 FTPClient 失败 ", e);
    44. }
    45. }
    46. return null;
    47. }
    48. /**
    49. * 归还 ftpClient
    50. */
    51. @Override
    52. public void returnObject(FTPClient ftpClient) {
    53. if (this.pool != null && ftpClient != null) {
    54. this.pool.returnObject(ftpClient);
    55. }
    56. }
    57. @Override
    58. public FtpConfig getFtpPoolConfig() {
    59. return config;
    60. }
    61. }

    7、FTP工具类

    1. import cn.hutool.core.util.CharsetUtil;
    2. import com.aicut.monitor.enums.DownloadStatus;
    3. import com.aicut.monitor.enums.UploadStatus;
    4. import com.aicut.monitor.enums.uploadImageType;
    5. import com.aicut.monitor.service.FTPPoolService;
    6. import lombok.extern.slf4j.Slf4j;
    7. import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
    8. import org.apache.commons.compress.archivers.zip.UnixStat;
    9. import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
    10. import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
    11. import org.apache.commons.compress.parallel.InputStreamSupplier;
    12. import org.apache.commons.io.IOUtils;
    13. import org.apache.commons.io.input.NullInputStream;
    14. import org.apache.commons.net.ftp.FTP;
    15. import org.apache.commons.net.ftp.FTPClient;
    16. import org.apache.commons.net.ftp.FTPFile;
    17. import org.springframework.beans.factory.annotation.Autowired;
    18. import org.springframework.stereotype.Component;
    19. import javax.servlet.http.HttpServletResponse;
    20. import java.io.*;
    21. import java.net.URLEncoder;
    22. import java.nio.file.Files;
    23. import java.text.ParseException;
    24. import java.text.SimpleDateFormat;
    25. import java.util.Arrays;
    26. import java.util.Date;
    27. import java.util.HashMap;
    28. import java.util.List;
    29. import java.util.Map;
    30. import java.util.concurrent.ExecutorService;
    31. import java.util.concurrent.LinkedBlockingQueue;
    32. import java.util.concurrent.ThreadPoolExecutor;
    33. import java.util.concurrent.TimeUnit;
    34. import java.util.stream.Collectors;
    35. import java.util.zip.ZipEntry;
    36. import java.util.zip.ZipOutputStream;
    37. /**
    38. * FTP工具类
    39. *
    40. * @author YJ2023085043
    41. */
    42. @Component
    43. @Slf4j
    44. public class FtpUtil {
    45. /**
    46. * ftp 连接池
    47. */
    48. @Autowired
    49. FTPPoolService ftpPoolService;
    50. public static final String DIR_SPLIT = "/";
    51. public static final String HTTP_protocol = "http://";
    52. /**
    53. * 上传单个文件
    54. *
    55. * @param uploadPath 上传路径
    56. * @param fileName 文件名
    57. * @param input 文件输入流
    58. * @return 上传结果
    59. */
    60. public UploadStatus upload(String uploadPath, String fileName, InputStream input) {
    61. FTPClient ftpClient = ftpPoolService.borrowObject();
    62. try {
    63. // 切换到工作目录
    64. if (!ftpClient.changeWorkingDirectory(uploadPath)) {
    65. ftpClient.makeDirectory(uploadPath);
    66. ftpClient.changeWorkingDirectory(uploadPath);
    67. }
    68. // 文件写入
    69. boolean storeFile = ftpClient.storeFile(fileName, input);
    70. if (storeFile) {
    71. log.info("文件:{}上传成功", fileName);
    72. return UploadStatus.UploadNewFileSuccess;
    73. } else {
    74. throw new RuntimeException("ftp文件写入异常");
    75. }
    76. } catch (IOException e) {
    77. log.error("文件:{}上传失败", fileName, e);
    78. return UploadStatus.UploadNewFileFailed;
    79. } finally {
    80. IOUtils.closeQuietly(input);
    81. ftpPoolService.returnObject(ftpClient);
    82. }
    83. }
    84. /**
    85. * 从FTP服务器上下载文件,支持断点续传,下载百分比汇报
    86. *
    87. * @param ftpPath 远程文件路径
    88. * @param fileName 远程文件名
    89. * @param local 本地文件完整绝对路径
    90. * @return 下载的状态
    91. * @throws IOException
    92. */
    93. public DownloadStatus downloadFile(String ftpPath, String fileName, String local) throws IOException {
    94. FTPClient ftpClient = ftpPoolService.borrowObject();
    95. // 设置被动模式,由于Linux安全性考虑,端口没有全部放开,所有被动模式不能用
    96. ftpClient.enterLocalPassiveMode();
    97. // 设置以二进制方式传输
    98. ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
    99. DownloadStatus result;
    100. try {
    101. // 检查远程文件是否存在
    102. FTPFile[] files = ftpClient.listFiles(ftpPath,file -> file.getName().equals(fileName));
    103. if (files.length != 1) {
    104. log.info("远程文件不存在");
    105. return DownloadStatus.RemoteFileNotExist;
    106. }
    107. long lRemoteSize = files[0].getSize();
    108. File f = new File(local+DIR_SPLIT+fileName);
    109. // 本地存在文件,进行断点下载
    110. if (f.exists()) {
    111. long localSize = f.length();
    112. // 判断本地文件大小是否大于远程文件大小
    113. if (localSize >= lRemoteSize) {
    114. log.info("本地文件大于远程文件,下载中止");
    115. return DownloadStatus.LocalFileBiggerThanRemoteFile;
    116. }
    117. // 进行断点续传,并记录状态
    118. FileOutputStream out = new FileOutputStream(f, true);
    119. ftpClient.setRestartOffset(localSize);
    120. InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName);
    121. byte[] bytes = new byte[1024];
    122. long step = lRemoteSize / 100;
    123. // 文件过小,step可能为0
    124. step = step == 0 ? 1 : step;
    125. long process = localSize / step;
    126. int c;
    127. while ((c = in.read(bytes)) != -1) {
    128. out.write(bytes, 0, c);
    129. localSize += c;
    130. long nowProcess = localSize / step;
    131. if (nowProcess > process) {
    132. process = nowProcess;
    133. if (process % 10 == 0) {
    134. log.info("下载进度:" + process);
    135. }
    136. }
    137. }
    138. in.close();
    139. out.close();
    140. boolean isDo = ftpClient.completePendingCommand();
    141. if (isDo) {
    142. result = DownloadStatus.DownloadFromBreakSuccess;
    143. } else {
    144. result = DownloadStatus.DownloadFromBreakFailed;
    145. }
    146. } else {
    147. OutputStream out = new FileOutputStream(f);
    148. InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName);
    149. byte[] bytes = new byte[1024];
    150. long step = lRemoteSize / 100;
    151. // 文件过小,step可能为0
    152. step = step == 0 ? 1 : step;
    153. long process = 0;
    154. long localSize = 0L;
    155. int c;
    156. while ((c = in.read(bytes)) != -1) {
    157. out.write(bytes, 0, c);
    158. localSize += c;
    159. long nowProcess = localSize / step;
    160. if (nowProcess > process) {
    161. process = nowProcess;
    162. if (process % 10 == 0) {
    163. log.info("下载进度:" + process);
    164. }
    165. }
    166. }
    167. in.close();
    168. out.close();
    169. boolean upNewStatus = ftpClient.completePendingCommand();
    170. if (upNewStatus) {
    171. result = DownloadStatus.DownloadNewSuccess;
    172. } else {
    173. result = DownloadStatus.DownloadNewFailed;
    174. }
    175. }
    176. } catch (Exception e) {
    177. log.error("download error", e);
    178. } finally {
    179. ftpPoolService.returnObject(ftpClient);
    180. }
    181. return DownloadStatus.DownloadNewFailed;
    182. }
    183. /**
    184. * 下载文件到本地 *
    185. *
    186. * @param ftpPath FTP服务器文件目录 *
    187. * @param ftpFileName 文件名称 *
    188. * @param localPath 下载后的文件路径 *
    189. * @return
    190. */
    191. public boolean download(String ftpPath, String ftpFileName, String localPath) {
    192. FTPClient ftpClient = ftpPoolService.borrowObject();
    193. OutputStream outputStream = null;
    194. try {
    195. FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName));
    196. if (ftpFiles != null && ftpFiles.length > 0) {
    197. FTPFile ftpFile = ftpFiles[0];
    198. File localFile = new File(localPath + DIR_SPLIT + ftpFile.getName());
    199. // 判断本地路径目录是否存在,不存在则创建
    200. if (!localFile.getParentFile().exists()) {
    201. localFile.getParentFile().mkdirs();
    202. }
    203. outputStream = Files.newOutputStream(localFile.toPath());
    204. ftpClient.retrieveFile(ftpFile.getName(), outputStream);
    205. log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize());
    206. log.info("下载文件成功...");
    207. return true;
    208. } else {
    209. log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName);
    210. }
    211. } catch (Exception e) {
    212. log.error("下载文件失败...",e);
    213. } finally {
    214. IOUtils.closeQuietly(outputStream);
    215. ftpPoolService.returnObject(ftpClient);
    216. }
    217. return false;
    218. }
    219. /**
    220. * 下载文件到浏览器 *
    221. *
    222. * @param ftpPath FTP服务器文件目录 *
    223. * @param ftpFileName 文件名称 *
    224. * @param response
    225. * @return
    226. */
    227. public void download(HttpServletResponse response, String ftpPath, String ftpFileName) {
    228. FTPClient ftpClient = ftpPoolService.borrowObject();
    229. OutputStream outputStream = null;
    230. try {
    231. FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName));
    232. response.setContentType("application/octet-stream");
    233. response.setCharacterEncoding("utf8");
    234. response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(ftpFileName,"UTF-8") );
    235. outputStream = response.getOutputStream();
    236. if (ftpFiles != null && ftpFiles.length > 0) {
    237. FTPFile ftpFile = ftpFiles[0];
    238. ftpClient.retrieveFile(ftpPath+DIR_SPLIT+ftpFile.getName(), outputStream);
    239. log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize());
    240. log.info("下载文件成功...");
    241. } else {
    242. log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName);
    243. }
    244. } catch (Exception e) {
    245. log.error("下载文件失败...",e);
    246. } finally {
    247. IOUtils.closeQuietly(outputStream);
    248. ftpPoolService.returnObject(ftpClient);
    249. }
    250. }
    251. public void ftpZipFileDownload (HttpServletResponse response,String ftpPath) {
    252. //从FTP上下载文件并打成ZIP包给用户下载
    253. ZipOutputStream zipOut = null;
    254. try {
    255. //文件名称
    256. String zipFileName = "导出数据.zip";
    257. response.reset();
    258. // 设置导出文件头
    259. response.setContentType("application/octet-stream");
    260. response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(zipFileName,"UTF-8") );
    261. // 定义Zip输出流
    262. zipOut = new ZipOutputStream(response.getOutputStream());
    263. zipFTPFile(ftpPath,zipOut,"");
    264. } catch (IOException e) {
    265. log.error("当前:"+ftpPath+"下载FTP文件--->下载文件失败:"+e.getMessage());
    266. } finally {
    267. // 关闭zip文件输出流
    268. if (null != zipOut) {
    269. try {
    270. zipOut.closeEntry();
    271. zipOut.close();
    272. } catch (IOException e) {
    273. log.error("当前:"+ftpPath+"下载FTP文件--->关闭zip文件输出流出错:"+e.getMessage());
    274. }
    275. }
    276. }
    277. }
    278. public void zipFTPFile(String ftpPath, ZipOutputStream zipOut,String foldPath){
    279. FTPClient ftpClient = ftpPoolService.borrowObject();
    280. try {
    281. // 切换到指定目录中,如果切换失败说明目录不存在
    282. if(!ftpClient.changeWorkingDirectory(ftpPath)){
    283. log.error("切换目录失败");
    284. throw new RuntimeException("切换目录失败");
    285. }
    286. // 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据
    287. ftpClient.enterLocalPassiveMode();
    288. // 遍历路径下的所有文件
    289. FTPFile[] fileList = ftpClient.listFiles();
    290. byte[] byteReader = new byte[1024];
    291. ByteArrayOutputStream os = null;
    292. for (FTPFile tempFile : fileList) {
    293. if (tempFile.isFile()) {
    294. os = new ByteArrayOutputStream();
    295. // 从FTP上下载downFileName该文件把该文件转化为字节数组的输出流
    296. ftpClient.retrieveFile(tempFile.getName(), os);
    297. byte[] bytes = os.toByteArray();
    298. InputStream ins = new ByteArrayInputStream(bytes);
    299. int len;
    300. zipOut.putNextEntry(new ZipEntry(foldPath + tempFile.getName()));
    301. // 读入需要下载的文件的内容,打包到zip文件
    302. while ((len = ins.read(byteReader)) > 0) {
    303. zipOut.write(byteReader, 0, len);
    304. }
    305. }else{
    306. //如果是文件夹,则递归调用该方法
    307. zipOut.putNextEntry(new ZipEntry(tempFile.getName() + DIR_SPLIT));
    308. zipFTPFile(ftpPath + DIR_SPLIT + tempFile.getName(), zipOut, tempFile.getName() + DIR_SPLIT);
    309. }
    310. }
    311. zipOut.flush();
    312. } catch (IOException e) {
    313. e.printStackTrace();
    314. } finally {
    315. ftpPoolService.returnObject(ftpClient);
    316. }
    317. }
    318. /**
    319. * 得到某个目录下的文件名列表
    320. *
    321. * @param ftpDirPath FTP上的目标文件路径
    322. * @return
    323. * @throws IOException
    324. */
    325. public List getFileList(String ftpDirPath) {
    326. FTPClient ftpClient = ftpPoolService.borrowObject();
    327. try {
    328. FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath);
    329. if (ftpFiles != null && ftpFiles.length > 0) {
    330. return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList());
    331. }
    332. log.error(String.format("路径有误,或目录【%s】为空", ftpDirPath));
    333. } catch (Exception e) {
    334. log.error("获取目录下文件列表失败", e);
    335. } finally {
    336. ftpPoolService.returnObject(ftpClient);
    337. }
    338. return null;
    339. }
    340. /**
    341. * 删除文件
    342. *
    343. * @param ftpPath 服务器文件存储路径
    344. * @param fileName 文件名
    345. * @return
    346. * @throws IOException
    347. */
    348. public boolean deleteFile(String ftpPath, String fileName) {
    349. FTPClient ftpClient = ftpPoolService.borrowObject();
    350. try {
    351. // 在 ftp 目录下获取文件名与 fileName 匹配的文件信息
    352. FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(fileName));
    353. // 删除文件
    354. if (ftpFiles != null && ftpFiles.length > 0) {
    355. boolean del;
    356. String deleteFilePath = ftpPath + DIR_SPLIT + fileName;
    357. FTPFile ftpFile = ftpFiles[0];
    358. if (ftpFile.isDirectory()) {
    359. //递归删除该目录下的所有文件后删除目录
    360. FTPFile[] files = ftpClient.listFiles(ftpPath + DIR_SPLIT + fileName);
    361. for (FTPFile file : files) {
    362. if(file.isDirectory()){
    363. deleteFile(ftpPath + DIR_SPLIT + fileName,file.getName());
    364. }else{
    365. del = ftpClient.deleteFile(deleteFilePath + DIR_SPLIT + file.getName());
    366. log.info(del ? "文件:{}删除成功" : "文件:{}删除失败", file.getName());
    367. }
    368. }
    369. del = ftpClient.removeDirectory(deleteFilePath);
    370. } else {
    371. del = ftpClient.deleteFile(deleteFilePath);
    372. }
    373. log.info(del ? "文件:{}删除成功" : "文件:{}删除失败", fileName);
    374. return del;
    375. } else {
    376. log.warn("文件:{}未找到", fileName);
    377. }
    378. } catch (IOException e) {
    379. e.printStackTrace();
    380. } finally {
    381. ftpPoolService.returnObject(ftpClient);
    382. }
    383. return false;
    384. }
    385. /**
    386. * 上传文件到FTP服务器,支持断点续传
    387. * @param uploadPath 远程文件存放路径
    388. * @param fileName 上传文件名
    389. * @param input 文件输入流
    390. * @return 上传结果
    391. * @throws IOException
    392. */
    393. public UploadStatus uploadFile(String uploadPath, String fileName, InputStream input) {
    394. FTPClient ftpClient = ftpPoolService.borrowObject();
    395. UploadStatus result = UploadStatus.UploadNewFileFailed;
    396. try {
    397. // 设置PassiveMode传输
    398. ftpClient.enterLocalPassiveMode();
    399. // 设置以二进制流的方式传输
    400. ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
    401. ftpClient.setControlEncoding(CharsetUtil.UTF_8);
    402. //切换到工作目录
    403. if(!ftpClient.changeWorkingDirectory(uploadPath)){
    404. ftpClient.makeDirectory(uploadPath);
    405. ftpClient.changeWorkingDirectory(uploadPath);
    406. }
    407. // 检查远程是否存在文件
    408. FTPFile[] files = ftpClient.listFiles(uploadPath,file -> file.getName().equals(fileName));
    409. if (files.length == 1) {
    410. long remoteSize = files[0].getSize();
    411. //根据文件输入流获取文件对象
    412. File f = getFileFromInputStream(input);
    413. long localSize = f.length();
    414. // 文件存在
    415. if (remoteSize == localSize) {
    416. return UploadStatus.FileExits;
    417. } else if (remoteSize > localSize) {
    418. return UploadStatus.RemoteFileBiggerThanLocalFile;
    419. }
    420. // 尝试移动文件内读取指针,实现断点续传
    421. result = uploadFile(fileName, f, ftpClient, remoteSize);
    422. // 如果断点续传没有成功,则删除服务器上文件,重新上传
    423. if (result == UploadStatus.UploadFromBreakFailed) {
    424. if (!ftpClient.deleteFile(fileName)) {
    425. return UploadStatus.DeleteRemoteFaild;
    426. }
    427. result = uploadFile(fileName, f, ftpClient, 0);
    428. }
    429. } else {
    430. result = uploadFile(fileName, getFileFromInputStream(input), ftpClient, 0);
    431. }
    432. } catch (Exception e) {
    433. log.error("上传文件失败", e);
    434. } finally {
    435. ftpPoolService.returnObject(ftpClient);
    436. }
    437. return result;
    438. }
    439. /**
    440. * 从输入流中获取文件对象
    441. * @param inputStream
    442. * @return
    443. */
    444. public static File getFileFromInputStream(InputStream inputStream) {
    445. File file = null;
    446. try {
    447. // 创建临时文件
    448. file = File.createTempFile("temp", null);
    449. // 将输入流写入临时文件
    450. byte[] buffer = new byte[1024];
    451. int bytesRead;
    452. try (FileOutputStream outputStream = new FileOutputStream(file)) {
    453. while ((bytesRead = inputStream.read(buffer)) != -1) {
    454. outputStream.write(buffer, 0, bytesRead);
    455. }
    456. }
    457. } catch (IOException e) {
    458. e.printStackTrace();
    459. }
    460. return file;
    461. }
    462. /**
    463. * 递归创建远程服务器目录
    464. *
    465. * @param remote 远程服务器文件绝对路径
    466. * @param ftpClient FTPClient对象
    467. * @return 目录创建是否成功
    468. * @throws IOException
    469. */
    470. public UploadStatus createDirectory(String remote, FTPClient ftpClient) throws IOException {
    471. UploadStatus status = UploadStatus.CreateDirectorySuccess;
    472. String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
    473. if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1))) {
    474. // 如果远程目录不存在,则递归创建远程服务器目录
    475. int start = 0;
    476. int end = 0;
    477. if (directory.startsWith("/")) {
    478. start = 1;
    479. } else {
    480. start = 0;
    481. }
    482. end = directory.indexOf("/", start);
    483. while (true) {
    484. String subDirectory = new String(remote.substring(start, end).getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1);
    485. if (!ftpClient.changeWorkingDirectory(subDirectory)) {
    486. if (ftpClient.makeDirectory(subDirectory)) {
    487. ftpClient.changeWorkingDirectory(subDirectory);
    488. } else {
    489. log.info("创建目录失败");
    490. return UploadStatus.CreateDirectoryFail;
    491. }
    492. }
    493. start = end + 1;
    494. end = directory.indexOf("/", start);
    495. // 检查所有目录是否创建完毕
    496. if (end <= start) {
    497. break;
    498. }
    499. }
    500. }
    501. return status;
    502. }
    503. /**
    504. * 上传文件到服务器,新上传和断点续传
    505. *
    506. * @param remoteFileName 远程文件名,在上传之前已经将服务器工作目录做了改变,一定要注意这里的 remoteFile 已经别被编码 ISO-8859-1
    507. * @param localFile 本地文件File句柄,绝对路径
    508. * @param ftpClient FTPClient引用
    509. * @return
    510. * @throws IOException
    511. */
    512. public UploadStatus uploadFile(String remoteFileName, File localFile, FTPClient ftpClient, long remoteSize) {
    513. if (null == ftpClient) {
    514. ftpClient = ftpPoolService.borrowObject();
    515. }
    516. if (null == ftpClient) {
    517. return null;
    518. }
    519. UploadStatus status = UploadStatus.UploadNewFileFailed;
    520. try (RandomAccessFile raf = new RandomAccessFile(localFile, "r");
    521. OutputStream out = ftpClient.appendFileStream(remoteFileName);) {
    522. // 显示进度的上传
    523. log.info("localFile.length():" + localFile.length());
    524. long step = localFile.length() / 100;
    525. // 文件过小,step可能为0
    526. step = step == 0 ? 1 : step;
    527. long process = 0;
    528. long localreadbytes = 0L;
    529. // 断点续传
    530. if (remoteSize > 0) {
    531. ftpClient.setRestartOffset(remoteSize);
    532. process = remoteSize / step;
    533. raf.seek(remoteSize);
    534. localreadbytes = remoteSize;
    535. }
    536. byte[] bytes = new byte[1024];
    537. int c;
    538. while ((c = raf.read(bytes)) != -1) {
    539. out.write(bytes, 0, c);
    540. localreadbytes += c;
    541. if (localreadbytes / step != process) {
    542. process = localreadbytes / step;
    543. if (process % 10 == 0) {
    544. log.info("上传进度:" + process);
    545. }
    546. }
    547. }
    548. out.flush();
    549. raf.close();
    550. out.close();
    551. // FTPUtil的upload方法在执行ftpClient.completePendingCommand()之前应该先关闭OutputStream,否则主线程会在这里卡死执行不下去。
    552. // 原因是completePendingCommand()会一直在等FTP Server返回226 Transfer complete,但是FTP Server只有在接受到OutputStream执行close方法时,才会返回。
    553. boolean result = ftpClient.completePendingCommand();
    554. if (remoteSize > 0) {
    555. status = result ? UploadStatus.UploadFromBreakSuccess : UploadStatus.UploadFromBreakFailed;
    556. } else {
    557. status = result ? UploadStatus.UploadNewFileSuccess : UploadStatus.UploadNewFileFailed;
    558. }
    559. } catch (Exception e) {
    560. log.error("uploadFile error ", e);
    561. }
    562. return status;
    563. }
    564. /**
    565. * 获取FTP某一特定目录下的文件数量
    566. *
    567. * @param ftpDirPath FTP上的目标文件路径
    568. */
    569. public Integer getFileNum(String ftpDirPath) {
    570. FTPClient ftpClient = ftpPoolService.borrowObject();
    571. try {
    572. FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath);
    573. if (ftpFiles != null && ftpFiles.length > 0) {
    574. return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList()).size();
    575. }
    576. log.error(String.format("路径有误,或目录【%s】为空", ftpDirPath));
    577. } catch (IOException e) {
    578. log.error("文件获取异常:", e);
    579. } finally {
    580. ftpPoolService.returnObject(ftpClient);
    581. }
    582. return null;
    583. }
    584. /**
    585. * 获取文件夹下文件数量
    586. * @param ftpPath
    587. * @return
    588. */
    589. public Map getDirFileNum(String ftpPath) {
    590. FTPClient ftpClient = ftpPoolService.borrowObject();
    591. try {
    592. Integer sum = 0;
    593. Map map = new HashMap<>();
    594. FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath);
    595. if (ftpFiles != null && ftpFiles.length > 0) {
    596. for (FTPFile file : ftpFiles) {
    597. if (file.isDirectory()) {
    598. sum += getFileNum(ftpPath + DIR_SPLIT + file.getName());
    599. map.put(file.getName(), String.valueOf(getFileNum(ftpPath + DIR_SPLIT + file.getName())));
    600. }
    601. }
    602. }else {
    603. log.error(String.format("路径有误,或目录【%s】为空", ftpPath));
    604. }
    605. map.put("sum", String.valueOf(sum));
    606. return map;
    607. } catch (IOException e) {
    608. log.error("文件获取异常:", e);
    609. } finally {
    610. ftpPoolService.returnObject(ftpClient);
    611. }
    612. return null;
    613. }
    614. /**
    615. * 下载指定文件夹到本地
    616. * @param ftpPath FTP服务器文件目录
    617. * @param localPath 下载后的文件路径
    618. * @param dirName 文件夹名称
    619. * @return
    620. */
    621. public void downloadDir(String ftpPath, String localPath, String dirName){
    622. FTPClient ftpClient = ftpPoolService.borrowObject();
    623. OutputStream outputStream = null;
    624. try {
    625. //判断是否存在该文件夹
    626. FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(dirName));
    627. if (ftpFiles != null && ftpFiles.length > 0) {
    628. if(ftpFiles[0].isDirectory()) {
    629. // 判断本地路径目录是否存在,不存在则创建
    630. File localFile = new File(localPath + DIR_SPLIT + dirName);
    631. if (!localFile.exists()) {
    632. localFile.mkdirs();
    633. }
    634. for (FTPFile file : ftpClient.listFiles(ftpPath + DIR_SPLIT + dirName)) {
    635. if (file.isDirectory()) {
    636. downloadDir(ftpPath + DIR_SPLIT + dirName, localPath + dirName + DIR_SPLIT, file.getName());
    637. } else {
    638. outputStream = Files.newOutputStream(new File(localPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName()).toPath());
    639. ftpClient.retrieveFile(ftpPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), outputStream);
    640. log.info("fileName:{},size:{}", file.getName(), file.getSize());
    641. outputStream.close();
    642. }
    643. }
    644. }
    645. }
    646. }catch (Exception e){
    647. log.error("下载文件夹失败,filePathname:{},", ftpPath + DIR_SPLIT + dirName, e);
    648. }finally {
    649. IOUtils.closeQuietly(outputStream);
    650. ftpPoolService.returnObject(ftpClient);
    651. }
    652. }
    653. /**
    654. * 从本地上传文件夹
    655. * @param uploadPath ftp服务器地址
    656. * @param localPath 本地文件夹地址
    657. * @param dirName 文件夹名称
    658. * @return
    659. */
    660. public boolean uploadDir(String uploadPath, String localPath, String dirName){
    661. FTPClient ftpClient = ftpPoolService.borrowObject();
    662. try{
    663. // 切换到工作目录
    664. if (!ftpClient.changeWorkingDirectory(uploadPath)) {
    665. ftpClient.makeDirectory(uploadPath);
    666. ftpClient.changeWorkingDirectory(uploadPath);
    667. }
    668. //创建文件夹
    669. ftpClient.makeDirectory(uploadPath + DIR_SPLIT + dirName);
    670. File src = new File(localPath);
    671. //获取该目录下的所有文件
    672. File[] files = src.listFiles();
    673. FileInputStream input = null;
    674. for(File file : files) {
    675. if (file.isDirectory()) {
    676. uploadDir(uploadPath + DIR_SPLIT + dirName, file.getAbsolutePath(), file.getName());
    677. } else {
    678. input = new FileInputStream(file);
    679. boolean storeFile = ftpClient.storeFile(uploadPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), input);
    680. if (storeFile) {
    681. log.info("文件:{}上传成功", file.getName());
    682. } else {
    683. throw new RuntimeException("ftp文件写入异常");
    684. }
    685. }
    686. }
    687. if(input != null){
    688. input.close();
    689. }
    690. }catch (Exception e){
    691. log.error("文件夹上传失败",e);
    692. }finally {
    693. ftpPoolService.returnObject(ftpClient);
    694. }
    695. return false;
    696. }
    697. }
  • 相关阅读:
    「技术人生」第9篇:如何设定业务目标
    动态链接库(扩展)--实际开发时的注意事项
    【ESD专题】金属外壳连接器的信号PIN脚需要进行ESD测试吗?
    史上最全,最详细SQL基础
    微服务拆分
    MySql的初识感悟,以及sql语句中的DDL和DML和DQL的基本语法
    STM32CubeMX串口通讯
    自行更换内存条的操作规范
    【java多种方式实现计时器】时分秒毫秒,附带代码+运行截图
    408 | 大纲知识点考点冲刺 复习整理 ——【计组】第一二三章
  • 原文地址:https://blog.csdn.net/qq_41481924/article/details/138192937