Java連接FTP服務(wù)器并使用ftp連接池進(jìn)行文件操作指南
前言
使用Java連接FTP服務(wù)器進(jìn)行文件相關(guān)操作,并且使用FTP連接池降低資源消耗,提高響應(yīng)速率。
1、導(dǎo)入Pom依賴
<!-- https://mvnrepository.com/artifact/commons-net/commons-net --> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.9.0</version> </dependency>
2、創(chuàng)建FTP的配置
ftp: # 服務(wù)器地址 host: xx.xxx.xx.xxx # 端口號 port: 21 # 用戶名 userName: xxx # 密碼 password: xxxxxxx # 工作目錄 workingDirectory: /ftpTest # 編碼 encoding: utf-8 #被動模式 passiveMode: true #連接超時時間 clientTimeout: 30000 # 線程數(shù) threaNum: 1 # 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二進(jìn)制文件) transferFileType: 2 # 是否重命名 renameUploaded: true # 重新連接時間 retryTimes: 1200 # 緩存大小 bufferSize: 8192 # 最大數(shù) maxTotal: 50 # 最小空閑 minldle: 10 # 最大空閑 maxldle: 50 # 最大等待時間 maxWait: 30000 # 池對象耗盡之后是否阻塞,maxWait < 0 時一直等待 blockWhenExhausted: true # 取對象時驗(yàn)證 testOnBorrow: true # 回收驗(yàn)證 testOnReturn: true # 創(chuàng)建時驗(yàn)證 testOnCreate: true # 空閑驗(yàn)證 testWhileldle: false # 后進(jìn)先出 lifo: false
3、創(chuàng)建FTP配置類
import lombok.Getter; import lombok.Setter; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.apache.commons.net.ftp.FTPClient; /** * Ftp配置類 */ @Configuration @ConfigurationProperties(prefix = "ftp") @Getter @Setter public class FtpConfig extends GenericObjectPoolConfig<FTPClient> { /** * FTP服務(wù)器地址 */ private String host; /** * FTP服務(wù)器端口 */ private Integer port; /** * FTP用戶名 */ private String userName; /** * FTP密碼 */ private String password; /** * FTP服務(wù)器根目錄 */ private String workingDirectory; /** * 傳輸編碼 */ String encoding; /** * 被動模式:在這種模式下,數(shù)據(jù)連接是由客戶程序發(fā)起的 */ boolean passiveMode; /** * 連接超時時間 */ int clientTimeout; /** * 線程數(shù) */ int threaNum; /** * 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二進(jìn)制文件) */ int transferFileType; /** * 是否重命名 */ boolean renameUploaded; /** * 重新連接時間 */ int retryTimes; /** * 緩存大小 */ int bufferSize; /** * 最大數(shù) */ int maxTotal; /** * 最小空閑 */ int minldle; /** * 最大空閑 */ int maxldle; /** * 最大等待時間 */ int maxWait; /** * 池對象耗盡之后是否阻塞,maxWait < 0 時一直等待 */ boolean blockWhenExhausted; /** * 取對象時驗(yàn)證 */ boolean testOnBorrow; /** * 回收驗(yàn)證 */ boolean testOnReturn; /** * 創(chuàng)建時驗(yàn)證 */ boolean testOnCreate; /** * 空閑驗(yàn)證 */ boolean testWhileldle; /** * 后進(jìn)先出 */ boolean lifo; }
4、創(chuàng)建工廠連接對象并注入配置
import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * FtpClient 工廠連接對象 */ @Component @Slf4j public class FTPClientFactory implements PooledObjectFactory<FTPClient> { /** * 注入 ftp 連接配置 */ @Autowired FtpConfig config; /** * 創(chuàng)建連接到池中 * * @return * @throws Exception */ @Override public PooledObject<FTPClient> makeObject() throws Exception { FTPClient ftpClient = new FTPClient(); ftpClient.setConnectTimeout(config.getClientTimeout()); ftpClient.connect(config.getHost(), config.getPort()); int reply = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { ftpClient.disconnect(); return null; } boolean success; if (StringUtils.isBlank(config.getUserName())) { success = ftpClient.login("anonymous", "anonymous"); } else { success = ftpClient.login(config.getUserName(), config.getPassword()); } if (!success) { return null; } ftpClient.setFileType(config.getTransferFileType()); ftpClient.setBufferSize(1024); ftpClient.setControlEncoding(config.getEncoding()); if (config.isPassiveMode()) { ftpClient.enterLocalPassiveMode(); } log.debug("創(chuàng)建ftp連接"); return new DefaultPooledObject<>(ftpClient); } /** * 鏈接狀態(tài)檢查 * * @param pool * @return */ @Override public boolean validateObject(PooledObject<FTPClient> pool) { FTPClient ftpClient = pool.getObject(); try { return ftpClient != null && ftpClient.sendNoOp(); } catch (Exception e) { return false; } } /** * 銷毀連接,當(dāng)連接池空閑數(shù)量達(dá)到上限時,調(diào)用此方法銷毀連接 * * @param pool * @throws Exception */ @Override public void destroyObject(PooledObject<FTPClient> pool) throws Exception { FTPClient ftpClient = pool.getObject(); if (ftpClient != null) { try { ftpClient.disconnect(); log.debug("銷毀ftp連接"); } catch (Exception e) { log.error("銷毀ftpClient異常,error:", e.getMessage()); } } } /** * 鈍化連接,是連接變?yōu)榭捎脿顟B(tài) * * @param p * @throws Exception */ @Override public void passivateObject(PooledObject<FTPClient> p) throws Exception{ FTPClient ftpClient = p.getObject(); try { ftpClient.changeWorkingDirectory(config.getWorkingDirectory()); ftpClient.logout(); if (ftpClient.isConnected()) { ftpClient.disconnect(); } } catch (Exception e) { throw new RuntimeException("Could not disconnect from server.", e); } } /** * 初始化連接 * * @param pool * @throws Exception */ @Override public void activateObject(PooledObject<FTPClient> pool) throws Exception { FTPClient ftpClient = pool.getObject(); ftpClient.connect(config.getHost(),config.getPort()); ftpClient.login(config.getUserName(), config.getPassword()); ftpClient.setControlEncoding(config.getEncoding()); ftpClient.changeWorkingDirectory(config.getWorkingDirectory()); //設(shè)置上傳文件類型為二進(jìn)制,否則將無法打開文件 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); } /** * 獲取 FTP 連接配置 * @return */ public FtpConfig getConfig(){ return config; } }
5、創(chuàng)建客戶端對象service接口
import com.aicut.monitor.config.FtpConfig; import org.apache.commons.net.ftp.FTPClient; /** * 獲取 ftp 客戶端對象的接口 */ public interface FTPPoolService { /** * 獲取ftpClient * @return */ FTPClient borrowObject(); /** * 歸還ftpClient * @param ftpClient * @return */ void returnObject(FTPClient ftpClient); /** * 獲取 ftp 配置信息 * @return */ FtpConfig getFtpPoolConfig(); }
6、創(chuàng)建FTP接口實(shí)現(xiàn)類
import com.aicut.monitor.config.FTPClientFactory; import com.aicut.monitor.config.FtpConfig; import com.aicut.monitor.service.FTPPoolService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.pool2.impl.GenericObjectPool; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component @Slf4j public class FTPPoolServiceImpl implements FTPPoolService { /** * ftp 連接池生成 */ private GenericObjectPool<FTPClient> pool; /** * ftp 客戶端配置文件 */ @Autowired private FtpConfig config; /** * ftp 客戶端工廠 */ @Autowired private FTPClientFactory factory; /** * 初始化pool */ @PostConstruct private void initPool() { this.pool = new GenericObjectPool<FTPClient>(this.factory, this.config); } /** * 獲取ftpClient */ @Override public FTPClient borrowObject() { if (this.pool != null) { try { return this.pool.borrowObject(); } catch (Exception e) { log.error("獲取 FTPClient 失敗 ", e); } } return null; } /** * 歸還 ftpClient */ @Override public void returnObject(FTPClient ftpClient) { if (this.pool != null && ftpClient != null) { this.pool.returnObject(ftpClient); } } @Override public FtpConfig getFtpPoolConfig() { return config; } }
7、FTP工具類
import cn.hutool.core.util.CharsetUtil; import com.aicut.monitor.enums.DownloadStatus; import com.aicut.monitor.enums.UploadStatus; import com.aicut.monitor.enums.uploadImageType; import com.aicut.monitor.service.FTPPoolService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator; import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.parallel.InputStreamSupplier; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.NullInputStream; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.nio.file.Files; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * FTP工具類 * * @author YJ2023085043 */ @Component @Slf4j public class FtpUtil { /** * ftp 連接池 */ @Autowired FTPPoolService ftpPoolService; public static final String DIR_SPLIT = "/"; public static final String HTTP_protocol = "http://"; /** * 上傳單個文件 * * @param uploadPath 上傳路徑 * @param fileName 文件名 * @param input 文件輸入流 * @return 上傳結(jié)果 */ public UploadStatus upload(String uploadPath, String fileName, InputStream input) { FTPClient ftpClient = ftpPoolService.borrowObject(); try { // 切換到工作目錄 if (!ftpClient.changeWorkingDirectory(uploadPath)) { ftpClient.makeDirectory(uploadPath); ftpClient.changeWorkingDirectory(uploadPath); } // 文件寫入 boolean storeFile = ftpClient.storeFile(fileName, input); if (storeFile) { log.info("文件:{}上傳成功", fileName); return UploadStatus.UploadNewFileSuccess; } else { throw new RuntimeException("ftp文件寫入異常"); } } catch (IOException e) { log.error("文件:{}上傳失敗", fileName, e); return UploadStatus.UploadNewFileFailed; } finally { IOUtils.closeQuietly(input); ftpPoolService.returnObject(ftpClient); } } /** * 從FTP服務(wù)器上下載文件,支持?jǐn)帱c(diǎn)續(xù)傳,下載百分比匯報 * * @param ftpPath 遠(yuǎn)程文件路徑 * @param fileName 遠(yuǎn)程文件名 * @param local 本地文件完整絕對路徑 * @return 下載的狀態(tài) * @throws IOException */ public DownloadStatus downloadFile(String ftpPath, String fileName, String local) throws IOException { FTPClient ftpClient = ftpPoolService.borrowObject(); // 設(shè)置被動模式,由于Linux安全性考慮,端口沒有全部放開,所有被動模式不能用 ftpClient.enterLocalPassiveMode(); // 設(shè)置以二進(jìn)制方式傳輸 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); DownloadStatus result; try { // 檢查遠(yuǎn)程文件是否存在 FTPFile[] files = ftpClient.listFiles(ftpPath,file -> file.getName().equals(fileName)); if (files.length != 1) { log.info("遠(yuǎn)程文件不存在"); return DownloadStatus.RemoteFileNotExist; } long lRemoteSize = files[0].getSize(); File f = new File(local+DIR_SPLIT+fileName); // 本地存在文件,進(jìn)行斷點(diǎn)下載 if (f.exists()) { long localSize = f.length(); // 判斷本地文件大小是否大于遠(yuǎn)程文件大小 if (localSize >= lRemoteSize) { log.info("本地文件大于遠(yuǎn)程文件,下載中止"); return DownloadStatus.LocalFileBiggerThanRemoteFile; } // 進(jìn)行斷點(diǎn)續(xù)傳,并記錄狀態(tài) FileOutputStream out = new FileOutputStream(f, true); ftpClient.setRestartOffset(localSize); InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName); byte[] bytes = new byte[1024]; long step = lRemoteSize / 100; // 文件過小,step可能為0 step = step == 0 ? 1 : step; long process = localSize / step; int c; while ((c = in.read(bytes)) != -1) { out.write(bytes, 0, c); localSize += c; long nowProcess = localSize / step; if (nowProcess > process) { process = nowProcess; if (process % 10 == 0) { log.info("下載進(jìn)度:" + process); } } } in.close(); out.close(); boolean isDo = ftpClient.completePendingCommand(); if (isDo) { result = DownloadStatus.DownloadFromBreakSuccess; } else { result = DownloadStatus.DownloadFromBreakFailed; } } else { OutputStream out = new FileOutputStream(f); InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName); byte[] bytes = new byte[1024]; long step = lRemoteSize / 100; // 文件過小,step可能為0 step = step == 0 ? 1 : step; long process = 0; long localSize = 0L; int c; while ((c = in.read(bytes)) != -1) { out.write(bytes, 0, c); localSize += c; long nowProcess = localSize / step; if (nowProcess > process) { process = nowProcess; if (process % 10 == 0) { log.info("下載進(jìn)度:" + process); } } } in.close(); out.close(); boolean upNewStatus = ftpClient.completePendingCommand(); if (upNewStatus) { result = DownloadStatus.DownloadNewSuccess; } else { result = DownloadStatus.DownloadNewFailed; } } } catch (Exception e) { log.error("download error", e); } finally { ftpPoolService.returnObject(ftpClient); } return DownloadStatus.DownloadNewFailed; } /** * 下載文件到本地 * * * @param ftpPath FTP服務(wù)器文件目錄 * * @param ftpFileName 文件名稱 * * @param localPath 下載后的文件路徑 * * @return */ public boolean download(String ftpPath, String ftpFileName, String localPath) { FTPClient ftpClient = ftpPoolService.borrowObject(); OutputStream outputStream = null; try { FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName)); if (ftpFiles != null && ftpFiles.length > 0) { FTPFile ftpFile = ftpFiles[0]; File localFile = new File(localPath + DIR_SPLIT + ftpFile.getName()); // 判斷本地路徑目錄是否存在,不存在則創(chuàng)建 if (!localFile.getParentFile().exists()) { localFile.getParentFile().mkdirs(); } outputStream = Files.newOutputStream(localFile.toPath()); ftpClient.retrieveFile(ftpFile.getName(), outputStream); log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize()); log.info("下載文件成功..."); return true; } else { log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName); } } catch (Exception e) { log.error("下載文件失敗...",e); } finally { IOUtils.closeQuietly(outputStream); ftpPoolService.returnObject(ftpClient); } return false; } /** * 下載文件到瀏覽器 * * * @param ftpPath FTP服務(wù)器文件目錄 * * @param ftpFileName 文件名稱 * * @param response * @return */ public void download(HttpServletResponse response, String ftpPath, String ftpFileName) { FTPClient ftpClient = ftpPoolService.borrowObject(); OutputStream outputStream = null; try { FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName)); response.setContentType("application/octet-stream"); response.setCharacterEncoding("utf8"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(ftpFileName,"UTF-8") ); outputStream = response.getOutputStream(); if (ftpFiles != null && ftpFiles.length > 0) { FTPFile ftpFile = ftpFiles[0]; ftpClient.retrieveFile(ftpPath+DIR_SPLIT+ftpFile.getName(), outputStream); log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize()); log.info("下載文件成功..."); } else { log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName); } } catch (Exception e) { log.error("下載文件失敗...",e); } finally { IOUtils.closeQuietly(outputStream); ftpPoolService.returnObject(ftpClient); } } public void ftpZipFileDownload (HttpServletResponse response,String ftpPath) { //從FTP上下載文件并打成ZIP包給用戶下載 ZipOutputStream zipOut = null; try { //文件名稱 String zipFileName = "導(dǎo)出數(shù)據(jù).zip"; response.reset(); // 設(shè)置導(dǎo)出文件頭 response.setContentType("application/octet-stream"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(zipFileName,"UTF-8") ); // 定義Zip輸出流 zipOut = new ZipOutputStream(response.getOutputStream()); zipFTPFile(ftpPath,zipOut,""); } catch (IOException e) { log.error("當(dāng)前:"+ftpPath+"下載FTP文件--->下載文件失?。?+e.getMessage()); } finally { // 關(guān)閉zip文件輸出流 if (null != zipOut) { try { zipOut.closeEntry(); zipOut.close(); } catch (IOException e) { log.error("當(dāng)前:"+ftpPath+"下載FTP文件--->關(guān)閉zip文件輸出流出錯:"+e.getMessage()); } } } } public void zipFTPFile(String ftpPath, ZipOutputStream zipOut,String foldPath){ FTPClient ftpClient = ftpPoolService.borrowObject(); try { // 切換到指定目錄中,如果切換失敗說明目錄不存在 if(!ftpClient.changeWorkingDirectory(ftpPath)){ log.error("切換目錄失敗"); throw new RuntimeException("切換目錄失敗"); } // 每次數(shù)據(jù)連接之前,ftp client告訴ftp server開通一個端口來傳輸數(shù)據(jù) ftpClient.enterLocalPassiveMode(); // 遍歷路徑下的所有文件 FTPFile[] fileList = ftpClient.listFiles(); byte[] byteReader = new byte[1024]; ByteArrayOutputStream os = null; for (FTPFile tempFile : fileList) { if (tempFile.isFile()) { os = new ByteArrayOutputStream(); // 從FTP上下載downFileName該文件把該文件轉(zhuǎn)化為字節(jié)數(shù)組的輸出流 ftpClient.retrieveFile(tempFile.getName(), os); byte[] bytes = os.toByteArray(); InputStream ins = new ByteArrayInputStream(bytes); int len; zipOut.putNextEntry(new ZipEntry(foldPath + tempFile.getName())); // 讀入需要下載的文件的內(nèi)容,打包到zip文件 while ((len = ins.read(byteReader)) > 0) { zipOut.write(byteReader, 0, len); } }else{ //如果是文件夾,則遞歸調(diào)用該方法 zipOut.putNextEntry(new ZipEntry(tempFile.getName() + DIR_SPLIT)); zipFTPFile(ftpPath + DIR_SPLIT + tempFile.getName(), zipOut, tempFile.getName() + DIR_SPLIT); } } zipOut.flush(); } catch (IOException e) { e.printStackTrace(); } finally { ftpPoolService.returnObject(ftpClient); } } /** * 得到某個目錄下的文件名列表 * * @param ftpDirPath FTP上的目標(biāo)文件路徑 * @return * @throws IOException */ public List<String> getFileList(String ftpDirPath) { FTPClient ftpClient = ftpPoolService.borrowObject(); try { FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath); if (ftpFiles != null && ftpFiles.length > 0) { return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList()); } log.error(String.format("路徑有誤,或目錄【%s】為空", ftpDirPath)); } catch (Exception e) { log.error("獲取目錄下文件列表失敗", e); } finally { ftpPoolService.returnObject(ftpClient); } return null; } /** * 刪除文件 * * @param ftpPath 服務(wù)器文件存儲路徑 * @param fileName 文件名 * @return * @throws IOException */ public boolean deleteFile(String ftpPath, String fileName) { FTPClient ftpClient = ftpPoolService.borrowObject(); try { // 在 ftp 目錄下獲取文件名與 fileName 匹配的文件信息 FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(fileName)); // 刪除文件 if (ftpFiles != null && ftpFiles.length > 0) { boolean del; String deleteFilePath = ftpPath + DIR_SPLIT + fileName; FTPFile ftpFile = ftpFiles[0]; if (ftpFile.isDirectory()) { //遞歸刪除該目錄下的所有文件后刪除目錄 FTPFile[] files = ftpClient.listFiles(ftpPath + DIR_SPLIT + fileName); for (FTPFile file : files) { if(file.isDirectory()){ deleteFile(ftpPath + DIR_SPLIT + fileName,file.getName()); }else{ del = ftpClient.deleteFile(deleteFilePath + DIR_SPLIT + file.getName()); log.info(del ? "文件:{}刪除成功" : "文件:{}刪除失敗", file.getName()); } } del = ftpClient.removeDirectory(deleteFilePath); } else { del = ftpClient.deleteFile(deleteFilePath); } log.info(del ? "文件:{}刪除成功" : "文件:{}刪除失敗", fileName); return del; } else { log.warn("文件:{}未找到", fileName); } } catch (IOException e) { e.printStackTrace(); } finally { ftpPoolService.returnObject(ftpClient); } return false; } /** * 上傳文件到FTP服務(wù)器,支持?jǐn)帱c(diǎn)續(xù)傳 * @param uploadPath 遠(yuǎn)程文件存放路徑 * @param fileName 上傳文件名 * @param input 文件輸入流 * @return 上傳結(jié)果 * @throws IOException */ public UploadStatus uploadFile(String uploadPath, String fileName, InputStream input) { FTPClient ftpClient = ftpPoolService.borrowObject(); UploadStatus result = UploadStatus.UploadNewFileFailed; try { // 設(shè)置PassiveMode傳輸 ftpClient.enterLocalPassiveMode(); // 設(shè)置以二進(jìn)制流的方式傳輸 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); ftpClient.setControlEncoding(CharsetUtil.UTF_8); //切換到工作目錄 if(!ftpClient.changeWorkingDirectory(uploadPath)){ ftpClient.makeDirectory(uploadPath); ftpClient.changeWorkingDirectory(uploadPath); } // 檢查遠(yuǎn)程是否存在文件 FTPFile[] files = ftpClient.listFiles(uploadPath,file -> file.getName().equals(fileName)); if (files.length == 1) { long remoteSize = files[0].getSize(); //根據(jù)文件輸入流獲取文件對象 File f = getFileFromInputStream(input); long localSize = f.length(); // 文件存在 if (remoteSize == localSize) { return UploadStatus.FileExits; } else if (remoteSize > localSize) { return UploadStatus.RemoteFileBiggerThanLocalFile; } // 嘗試移動文件內(nèi)讀取指針,實(shí)現(xiàn)斷點(diǎn)續(xù)傳 result = uploadFile(fileName, f, ftpClient, remoteSize); // 如果斷點(diǎn)續(xù)傳沒有成功,則刪除服務(wù)器上文件,重新上傳 if (result == UploadStatus.UploadFromBreakFailed) { if (!ftpClient.deleteFile(fileName)) { return UploadStatus.DeleteRemoteFaild; } result = uploadFile(fileName, f, ftpClient, 0); } } else { result = uploadFile(fileName, getFileFromInputStream(input), ftpClient, 0); } } catch (Exception e) { log.error("上傳文件失敗", e); } finally { ftpPoolService.returnObject(ftpClient); } return result; } /** * 從輸入流中獲取文件對象 * @param inputStream * @return */ public static File getFileFromInputStream(InputStream inputStream) { File file = null; try { // 創(chuàng)建臨時文件 file = File.createTempFile("temp", null); // 將輸入流寫入臨時文件 byte[] buffer = new byte[1024]; int bytesRead; try (FileOutputStream outputStream = new FileOutputStream(file)) { while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } } catch (IOException e) { e.printStackTrace(); } return file; } /** * 遞歸創(chuàng)建遠(yuǎn)程服務(wù)器目錄 * * @param remote 遠(yuǎn)程服務(wù)器文件絕對路徑 * @param ftpClient FTPClient對象 * @return 目錄創(chuàng)建是否成功 * @throws IOException */ public UploadStatus createDirectory(String remote, FTPClient ftpClient) throws IOException { UploadStatus status = UploadStatus.CreateDirectorySuccess; String directory = remote.substring(0, remote.lastIndexOf("/") + 1); if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1))) { // 如果遠(yuǎn)程目錄不存在,則遞歸創(chuàng)建遠(yuǎn)程服務(wù)器目錄 int start = 0; int end = 0; if (directory.startsWith("/")) { start = 1; } else { start = 0; } end = directory.indexOf("/", start); while (true) { String subDirectory = new String(remote.substring(start, end).getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1); if (!ftpClient.changeWorkingDirectory(subDirectory)) { if (ftpClient.makeDirectory(subDirectory)) { ftpClient.changeWorkingDirectory(subDirectory); } else { log.info("創(chuàng)建目錄失敗"); return UploadStatus.CreateDirectoryFail; } } start = end + 1; end = directory.indexOf("/", start); // 檢查所有目錄是否創(chuàng)建完畢 if (end <= start) { break; } } } return status; } /** * 上傳文件到服務(wù)器,新上傳和斷點(diǎn)續(xù)傳 * * @param remoteFileName 遠(yuǎn)程文件名,在上傳之前已經(jīng)將服務(wù)器工作目錄做了改變,一定要注意這里的 remoteFile 已經(jīng)別被編碼 ISO-8859-1 * @param localFile 本地文件File句柄,絕對路徑 * @param ftpClient FTPClient引用 * @return * @throws IOException */ public UploadStatus uploadFile(String remoteFileName, File localFile, FTPClient ftpClient, long remoteSize) { if (null == ftpClient) { ftpClient = ftpPoolService.borrowObject(); } if (null == ftpClient) { return null; } UploadStatus status = UploadStatus.UploadNewFileFailed; try (RandomAccessFile raf = new RandomAccessFile(localFile, "r"); OutputStream out = ftpClient.appendFileStream(remoteFileName);) { // 顯示進(jìn)度的上傳 log.info("localFile.length():" + localFile.length()); long step = localFile.length() / 100; // 文件過小,step可能為0 step = step == 0 ? 1 : step; long process = 0; long localreadbytes = 0L; // 斷點(diǎn)續(xù)傳 if (remoteSize > 0) { ftpClient.setRestartOffset(remoteSize); process = remoteSize / step; raf.seek(remoteSize); localreadbytes = remoteSize; } byte[] bytes = new byte[1024]; int c; while ((c = raf.read(bytes)) != -1) { out.write(bytes, 0, c); localreadbytes += c; if (localreadbytes / step != process) { process = localreadbytes / step; if (process % 10 == 0) { log.info("上傳進(jìn)度:" + process); } } } out.flush(); raf.close(); out.close(); // FTPUtil的upload方法在執(zhí)行ftpClient.completePendingCommand()之前應(yīng)該先關(guān)閉OutputStream,否則主線程會在這里卡死執(zhí)行不下去。 // 原因是completePendingCommand()會一直在等FTP Server返回226 Transfer complete,但是FTP Server只有在接受到OutputStream執(zhí)行close方法時,才會返回。 boolean result = ftpClient.completePendingCommand(); if (remoteSize > 0) { status = result ? UploadStatus.UploadFromBreakSuccess : UploadStatus.UploadFromBreakFailed; } else { status = result ? UploadStatus.UploadNewFileSuccess : UploadStatus.UploadNewFileFailed; } } catch (Exception e) { log.error("uploadFile error ", e); } return status; } /** * 獲取FTP某一特定目錄下的文件數(shù)量 * * @param ftpDirPath FTP上的目標(biāo)文件路徑 */ public Integer getFileNum(String ftpDirPath) { FTPClient ftpClient = ftpPoolService.borrowObject(); try { FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath); if (ftpFiles != null && ftpFiles.length > 0) { return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList()).size(); } log.error(String.format("路徑有誤,或目錄【%s】為空", ftpDirPath)); } catch (IOException e) { log.error("文件獲取異常:", e); } finally { ftpPoolService.returnObject(ftpClient); } return null; } /** * 獲取文件夾下文件數(shù)量 * @param ftpPath * @return */ public Map<String,String> getDirFileNum(String ftpPath) { FTPClient ftpClient = ftpPoolService.borrowObject(); try { Integer sum = 0; Map<String,String> map = new HashMap<>(); FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath); if (ftpFiles != null && ftpFiles.length > 0) { for (FTPFile file : ftpFiles) { if (file.isDirectory()) { sum += getFileNum(ftpPath + DIR_SPLIT + file.getName()); map.put(file.getName(), String.valueOf(getFileNum(ftpPath + DIR_SPLIT + file.getName()))); } } }else { log.error(String.format("路徑有誤,或目錄【%s】為空", ftpPath)); } map.put("sum", String.valueOf(sum)); return map; } catch (IOException e) { log.error("文件獲取異常:", e); } finally { ftpPoolService.returnObject(ftpClient); } return null; } /** * 下載指定文件夾到本地 * @param ftpPath FTP服務(wù)器文件目錄 * @param localPath 下載后的文件路徑 * @param dirName 文件夾名稱 * @return */ public void downloadDir(String ftpPath, String localPath, String dirName){ FTPClient ftpClient = ftpPoolService.borrowObject(); OutputStream outputStream = null; try { //判斷是否存在該文件夾 FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(dirName)); if (ftpFiles != null && ftpFiles.length > 0) { if(ftpFiles[0].isDirectory()) { // 判斷本地路徑目錄是否存在,不存在則創(chuàng)建 File localFile = new File(localPath + DIR_SPLIT + dirName); if (!localFile.exists()) { localFile.mkdirs(); } for (FTPFile file : ftpClient.listFiles(ftpPath + DIR_SPLIT + dirName)) { if (file.isDirectory()) { downloadDir(ftpPath + DIR_SPLIT + dirName, localPath + dirName + DIR_SPLIT, file.getName()); } else { outputStream = Files.newOutputStream(new File(localPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName()).toPath()); ftpClient.retrieveFile(ftpPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), outputStream); log.info("fileName:{},size:{}", file.getName(), file.getSize()); outputStream.close(); } } } } }catch (Exception e){ log.error("下載文件夾失敗,filePathname:{},", ftpPath + DIR_SPLIT + dirName, e); }finally { IOUtils.closeQuietly(outputStream); ftpPoolService.returnObject(ftpClient); } } /** * 從本地上傳文件夾 * @param uploadPath ftp服務(wù)器地址 * @param localPath 本地文件夾地址 * @param dirName 文件夾名稱 * @return */ public boolean uploadDir(String uploadPath, String localPath, String dirName){ FTPClient ftpClient = ftpPoolService.borrowObject(); try{ // 切換到工作目錄 if (!ftpClient.changeWorkingDirectory(uploadPath)) { ftpClient.makeDirectory(uploadPath); ftpClient.changeWorkingDirectory(uploadPath); } //創(chuàng)建文件夾 ftpClient.makeDirectory(uploadPath + DIR_SPLIT + dirName); File src = new File(localPath); //獲取該目錄下的所有文件 File[] files = src.listFiles(); FileInputStream input = null; for(File file : files) { if (file.isDirectory()) { uploadDir(uploadPath + DIR_SPLIT + dirName, file.getAbsolutePath(), file.getName()); } else { input = new FileInputStream(file); boolean storeFile = ftpClient.storeFile(uploadPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), input); if (storeFile) { log.info("文件:{}上傳成功", file.getName()); } else { throw new RuntimeException("ftp文件寫入異常"); } } } if(input != null){ input.close(); } }catch (Exception e){ log.error("文件夾上傳失敗",e); }finally { ftpPoolService.returnObject(ftpClient); } return false; } }
總結(jié)
到此這篇關(guān)于Java連接FTP服務(wù)器并使用ftp連接池進(jìn)行文件操作指南的文章就介紹到這了,更多相關(guān)Java連接FTP服務(wù)器進(jìn)行文件操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot、mybatis返回樹結(jié)構(gòu)的數(shù)據(jù)實(shí)現(xiàn)
本文主要介紹了SpringBoot、mybatis返回樹結(jié)構(gòu)的數(shù)據(jù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Java如何通過反射將map轉(zhuǎn)換為實(shí)體對象
在Java開發(fā)中,常需要將XML配置數(shù)據(jù)轉(zhuǎn)為Map,并最終映射到實(shí)體對象上,通過單例模式管理XML轉(zhuǎn)換后的Map,并利用Java反射機(jī)制,通過屬性名稱匹配將Map的值賦給實(shí)體對象的對應(yīng)屬性,這種方法忽略了數(shù)據(jù)類型轉(zhuǎn)換,適用于數(shù)據(jù)類型一致的簡單場景,需要類型轉(zhuǎn)換時2024-09-09Spark隨機(jī)森林實(shí)現(xiàn)票房預(yù)測
這篇文章主要為大家詳細(xì)介紹了Spark隨機(jī)森林實(shí)現(xiàn)票房預(yù)測,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08ByteArrayOutputStream與InputStream互相轉(zhuǎn)換方式
這篇文章主要介紹了ByteArrayOutputStream與InputStream互相轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12