Commons-net FTPClient上传下载的封装
??????? 在项目中使用到FTP功能,于是采用类似Spring的各种回调机制公用各种代码,减少代码的开发量,简化开发人员的学习难度.本文仅供学习交流使用,如有比较好的意见希望可以一起交流,再次表示感谢.
package easyway.tbs.transfer.ftp;import java.io.IOException;import org.apache.commons.net.ftp.FTPClient;/** * FTPCLient回调 * @author longgangabai * * @param <T> */public interface FTPClientCallback<T> {public T doTransfer(FTPClient ftp)throws IOException;}
?
?
package easyway.tbs.transfer.ftp;import java.io.IOException;import java.util.Collection;import org.apache.commons.net.ftp.FTPFile;/** * FTP操作的客户端操作上传下载 * @author longgangbai * */public interface FTPClientOperations {/** * 文件上传的方法 * @param remote * @param local * @return * @throws IOException */public UploadStatus upload(String remote, String local) throws IOException; /** * * 从远程服务器目录下载文件到本地服务器目录中 * @param localdir FTP服务器保存目录 * @param remotedir FTP下载服务器目录 * @param localTempFile临时下载记录文件 * @return 成功返回true,否则返回false */public Collection<String> downloadList(final String localdir, final String remotedir,final String localTmpFile) throws IOException;/** * 文件下载的方法 * @param remote * @param local * @return * @throws IOException */public DownloadStatus downloadFile( String local,String remote) throws IOException;/** * 查看服务器上文件列表方法 * @param remotedir * @return * @throws IOException */public FTPFile[] list(final String remotedir) throws IOException;}
?
?
package easyway.tbs.transfer.ftp;import java.io.File;import java.io.IOException;import java.net.ConnectException;import java.util.ArrayList;import java.util.Collection;import java.util.Properties;import org.apache.commons.collections.CollectionUtils;import org.apache.commons.lang.ArrayUtils;import org.apache.commons.lang.StringUtils;import org.apache.commons.lang.math.NumberUtils;import org.apache.commons.net.ftp.FTPClient;import org.apache.commons.net.ftp.FTPClientConfig;import org.apache.commons.net.ftp.FTPFile;import org.apache.commons.net.ftp.FTPReply;import org.apache.log4j.Logger;import easyway.tbs.commons.FileOperateUtils;/** * Ftp回调模板 * * @author longgangbai * */public class FTPClientTemplate implements FTPClientOperations{private static final Logger logger = Logger.getLogger(FTPClientTemplate.class);private static String DEAFULT_REMOTE_CHARSET="UTF-8";private static int DEAFULT_REMOTE_PORT=21;private static String separator = File.separator;private FTPClientConfig ftpClientConfig;private String host;private String username;private String password;private String port;public FTPClientTemplate(String host,String user,String pwd,String port){this.host=host;this.username=user;this.password=pwd;this.port=port;}/** * 查看服务器上文件列表方法 * @param remotedir * @return * @throws IOException */public FTPFile[] list(final String remotedir) throws IOException{ returnexecute(new FTPClientCallback<FTPFile[]>(){public FTPFile[] doTransfer(FTPClient ftp) throws IOException { ftp.changeWorkingDirectory(remotedir); FTPFile[] files= ftp.listFiles(remotedir);return files;}});}/** * 文件上传的方法 * @param remote * @param local * @return * @throws IOException */public UploadStatus upload(final String local,final String remote) throws IOException{return execute(new FTPClientCallback<UploadStatus>(){public UploadStatus doTransfer(FTPClient ftpClient)throws IOException {return FtpHelper.getInstance().upload(ftpClient, local, remote);}});}/** * 上传文件到服务器,新上传和断点续传 * @param remoteFile 远程文件名,在上传之前已经将服务器工作目录做了改变 * @param localFile 本地文件File句柄,绝对路径 * @param processStep 需要显示的处理进度步进值 * @param ftpClient FTPClient引用 * @return * @throws IOException */public UploadStatus uploadFile(String remoteFile, File localFile,FTPClient ftpClient, long remoteSize) throws IOException { return FtpHelper.getInstance().uploadFile(remoteFile, localFile, ftpClient, remoteSize);} /** * 从远程服务器目录下载文件到本地服务器目录中 * @param localdir FTP服务器保存目录 * @param remotedir FTP下载服务器目录 * @param localTempFile临时下载记录文件 * @return 成功下载记录 */public Collection<String> downloadList(final String localdir, final String remotedir,final String localTmpFile) throws IOException {return execute(new FTPClientCallback<Collection<String>>(){public Collection<String> doTransfer(final FTPClient ftp) throws IOException {//切换到下载目录的中ftp.changeWorkingDirectory(remotedir);//获取目录中所有的文件信息FTPFile[] ftpfiles=ftp.listFiles();Collection<String> fileNamesCol=new ArrayList<String>();//判断文件目录是否为空if(!ArrayUtils.isEmpty(ftpfiles)){for (FTPFile ftpfile : ftpfiles) {String remoteFilePath=remotedir+separator+ftpfile.getName();String localFilePath=localdir+separator+ftpfile.getName();System.out.println("remoteFilePath ="+remoteFilePath +" localFilePath="+localFilePath);//单个文件下载状态DownloadStatus downStatus=downloadFile(remoteFilePath, localFilePath);if(downStatus==DownloadStatus.Download_New_Success){//临时目录中添加记录信息fileNamesCol.add(remoteFilePath);}}}if(CollectionUtils.isNotEmpty(fileNamesCol)){FileOperateUtils.writeLinesToFile(fileNamesCol, localTmpFile);}return fileNamesCol;}});}/** * 从FTP服务器上下载文件,支持断点续传,上传百分比汇报 * @param remote 远程文件路径 * @param local 本地文件路径 * @return 上传的状态 * @throws IOException * */public DownloadStatus downloadFile(final String remote, final String local) throws IOException{return execute(new FTPClientCallback<DownloadStatus>(){public DownloadStatus doTransfer(FTPClient ftpClient) throws IOException {DownloadStatus result=FtpHelper.getInstance().download(ftpClient, remote, local);return result;}});}/** * 执行FTP回调操作的方法 * @param callback 回调的函数 * @throws IOException */public <T> T execute(FTPClientCallback<T> callback) throws IOException{FTPClient ftp=new FTPClient();try {/*if(getFtpClientConfig()!=null){ ftp.configure(getFtpClientConfig()); ftpClientConfig.setServerTimeZoneId(TimeZone.getDefault().getID());}*///登录FTP服务器 try { //设置超时时间 ftp.setDataTimeout(7200); //设置默认编码 ftp.setControlEncoding(DEAFULT_REMOTE_CHARSET); //设置默认端口 ftp.setDefaultPort(DEAFULT_REMOTE_PORT); //设置是否显示隐藏文件 ftp.setListHiddenFiles(false); //连接ftp服务器 if(StringUtils.isNotEmpty(port)&&NumberUtils.isDigits(port)){ ftp.connect(host, Integer.valueOf(port)); }else{ ftp.connect(host); } } catch(ConnectException e) { logger.error("连接FTP服务器失败:"+ftp.getReplyString()+ftp.getReplyCode()); throw new IOException("Problem connecting the FTP-server fail",e); } //得到连接的返回编码int reply=ftp.getReplyCode();if(!FTPReply.isPositiveCompletion(reply)){ftp.disconnect();}//登录失败权限验证失败 if(!ftp.login(getUsername(), getPassword())) { ftp.quit(); ftp.disconnect(); logger.error("连接FTP服务器用户或者密码失败::"+ftp.getReplyString()); throw new IOException("Cant Authentificate to FTP-Server"); } if(logger.isDebugEnabled()){ logger.info("成功登录FTP服务器:"+host+" 端口:"+port); } ftp.setFileType(FTPClient.BINARY_FILE_TYPE);//回调FTP的操作return callback.doTransfer(ftp);}finally{//FTP退出ftp.logout();//断开FTP连接if(ftp.isConnected()){ftp.disconnect();}}} protected String resolveFile(String file) { return null; //return file.replace(System.getProperty("file.separator").charAt(0), this.remoteFileSep.charAt(0)); }/** * 获取FTP的配置操作系统 * @return */public FTPClientConfig getFtpClientConfig() { //获得系统属性集 Properties props=System.getProperties();//操作系统名称String osname=props.getProperty("os.name"); //针对window系统if(osname.equalsIgnoreCase("Windows XP")){ftpClientConfig=new FTPClientConfig(FTPClientConfig.SYST_NT);//针对linux系统}else if(osname.equalsIgnoreCase("Linux")){ftpClientConfig=new FTPClientConfig(FTPClientConfig.SYST_UNIX);}if(logger.isDebugEnabled()){logger.info("the ftp client system os Name "+osname);}return ftpClientConfig;} public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}}
?
?
package easyway.tbs.transfer.ftp;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.RandomAccessFile;import java.util.StringTokenizer;import org.apache.commons.net.ftp.FTP;import org.apache.commons.net.ftp.FTPClient;import org.apache.commons.net.ftp.FTPFile;import org.apache.commons.net.ftp.FTPReply;/** * FTP下载工具类 * @author longgangbai * */public class FtpHelper {private static String DEAFULT_REMOTE_CHARSET="GBK";private static String DEAFULT_LOCAL_CHARSET="iso-8859-1";private static FtpHelper instance=new FtpHelper();public static FtpHelper getInstance(){ return instance;}/** * 连接到FTP服务器 * @param hostname主机名 * @param port 端口 * @param username 用户名 * @param password 密码 * @return 是否连接成功 * @throws IOException */public boolean connect(FTPClient ftpClient,String hostname, int port, String username,String password) throws IOException {ftpClient.connect(hostname, port);ftpClient.setControlEncoding(DEAFULT_REMOTE_CHARSET);if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {if (ftpClient.login(username, password)) {return true;}}disconnect(ftpClient);return false;}/** * 从FTP服务器上下载文件,支持断点续传,上传百分比汇报 * @param remoteFilePath 远程文件路径 * @param localFilePath 本地文件路径 * @return 上传的状态 * @throws IOException */public DownloadStatus download(FTPClient ftpClient,String remoteFilePath, String localFilePath)throws IOException {// 设置被动模式ftpClient.enterLocalPassiveMode();// 设置以二进制方式传输ftpClient.setFileType(FTP.BINARY_FILE_TYPE);DownloadStatus result;// 检查远程文件是否存在FTPFile[] files = ftpClient.listFiles(new String(remoteFilePath.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET));if (files.length != 1) {System.out.println("远程文件不存在");return DownloadStatus.Remote_File_Noexist;}long lRemoteSize = files[0].getSize();File f = new File(localFilePath);// 本地存在文件,进行断点下载if (f.exists()) {long localSize = f.length();// 判断本地文件大小是否大于远程文件大小if (localSize >= lRemoteSize) {System.out.println("本地文件大于远程文件,下载中止");return DownloadStatus.Local_Bigger_Remote;}// 进行断点续传,并记录状态FileOutputStream out = new FileOutputStream(f, true);ftpClient.setRestartOffset(localSize);InputStream in = ftpClient.retrieveFileStream(new String(remoteFilePath.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET));byte[] bytes = new byte[1024];long step = lRemoteSize / 100;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){System.out.println("下载进度:" + process);}// TODO 更新文件下载进度,值存放在process变量中}}in.close();out.close();boolean isDo = ftpClient.completePendingCommand();if (isDo) {result = DownloadStatus.Download_From_Break_Success;} else {result = DownloadStatus.Download_From_Break_Failed;}} else {OutputStream out = new FileOutputStream(f);InputStream in = ftpClient.retrieveFileStream(new String(remoteFilePath.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET));byte[] bytes = new byte[1024];long step = lRemoteSize / 100;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){System.out.println("下载进度:" + process);}// TODO 更新文件下载进度,值存放在process变量中}}in.close();out.close();boolean upNewStatus = ftpClient.completePendingCommand();if (upNewStatus) {result = DownloadStatus.Download_New_Success;} else {result = DownloadStatus.Download_New_Failed;}}return result;}/** * * 上传文件到FTP服务器,支持断点续传 * @param localFilePath 本地文件名称,绝对路径 * @param remoteFilePath 远程文件路径,使用/home/directory1/subdirectory/file.ext * 按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构 * @return 上传结果 * @throws IOException */public UploadStatus upload(FTPClient ftpClient,String localFilePath, String remoteFilePath) throws IOException {// 设置PassiveMode传输ftpClient.enterLocalPassiveMode();// 设置以二进制流的方式传输ftpClient.setFileType(FTP.BINARY_FILE_TYPE);ftpClient.setControlEncoding(DEAFULT_REMOTE_CHARSET);UploadStatus result;// 对远程目录的处理String remoteFileName = remoteFilePath;if (remoteFilePath.contains("/")) {remoteFileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);// 创建服务器远程目录结构,创建失败直接返回if (createDirecroty(remoteFilePath, ftpClient) == UploadStatus.Create_Directory_Fail) {return UploadStatus.Create_Directory_Fail;}}// ftpClient.feat();// System.out.println( ftpClient.getReply());// System.out.println( ftpClient.acct(null));// System.out.println(ftpClient.getReplyCode());// System.out.println(ftpClient.getReplyString());// 检查远程是否存在文件FTPFile[] files = ftpClient.listFiles(new String(remoteFileName.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET));if (files.length == 1) {long remoteSize = files[0].getSize();File f = new File(localFilePath);long localSize = f.length();if (remoteSize == localSize) { // 文件存在return UploadStatus.File_Exits;} else if (remoteSize > localSize) {return UploadStatus.Remote_Bigger_Local;}// 尝试移动文件内读取指针,实现断点续传result = uploadFile(remoteFileName, f, ftpClient, remoteSize);// 如果断点续传没有成功,则删除服务器上文件,重新上传if (result == UploadStatus.Upload_From_Break_Failed) {if (!ftpClient.deleteFile(remoteFileName)) {return UploadStatus.Delete_Remote_Faild;}result = uploadFile(remoteFileName, f, ftpClient, 0);}} else {result = uploadFile(remoteFileName, new File(localFilePath), ftpClient, 0);}return result;}/** * * 断开与远程服务器的连接 * @throws IOException */public void disconnect(FTPClient ftpClient) throws IOException {if (ftpClient.isConnected()) {ftpClient.disconnect();}}/** * 递归创建远程服务器目录 * @param remote 远程服务器文件绝对路径 * @param ftpClient FTPClient对象 * @return 目录创建是否成功 * @throws IOException */public UploadStatus createDirecroty(String remote, FTPClient ftpClient)throws IOException {UploadStatus status = UploadStatus.Create_Directory_Success;String directory = remote.substring(0, remote.lastIndexOf("/") + 1);if (!directory.equalsIgnoreCase("/")&& !ftpClient.changeWorkingDirectory(new String(directory.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET))) {// 如果远程目录不存在,则递归创建远程服务器目录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(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET);if (!ftpClient.changeWorkingDirectory(subDirectory)) {if (ftpClient.makeDirectory(subDirectory)) {ftpClient.changeWorkingDirectory(subDirectory);} else {System.out.println("创建目录失败");return UploadStatus.Create_Directory_Fail;}}start = end + 1;end = directory.indexOf("/", start);// 检查所有目录是否创建完毕if (end <= start) {break;}}}return status;}/** * 上传文件到服务器,新上传和断点续传 * @param remoteFile 远程文件名,在上传之前已经将服务器工作目录做了改变 * @param localFile 本地文件File句柄,绝对路径 * @param processStep 需要显示的处理进度步进值 * @param ftpClient FTPClient引用 * @return * @throws IOException */public UploadStatus uploadFile(String remoteFile, File localFile,FTPClient ftpClient, long remoteSize) throws IOException {UploadStatus status;// 显示进度的上传long step = localFile.length() / 100;long process = 0;long localreadbytes = 0L;RandomAccessFile raf = new RandomAccessFile(localFile, "r");String remote = new String(remoteFile.getBytes(DEAFULT_REMOTE_CHARSET), DEAFULT_LOCAL_CHARSET);OutputStream out = ftpClient.appendFileStream(remote);if (out == null){String message = ftpClient.getReplyString();throw new RuntimeException(message);}// 断点续传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;System.out.println("上传进度:" + process);// TODO 汇报上传状态}}out.flush();raf.close();out.close();boolean result = ftpClient.completePendingCommand();if (remoteSize > 0) {status = result ? UploadStatus.Upload_From_Break_Success: UploadStatus.Upload_From_Break_Failed;} else {status = result ? UploadStatus.Upload_New_File_Success: UploadStatus.Upload_New_File_Failed;}return status;} protected void makeRemoteDir(FTPClient ftp, String dir) throws IOException { String workingDirectory = ftp.printWorkingDirectory(); if (dir.indexOf("/") == 0) { ftp.changeWorkingDirectory("/"); } String subdir = new String(); StringTokenizer st = new StringTokenizer(dir, "/"); while (st.hasMoreTokens()) { subdir = st.nextToken(); if (!(ftp.changeWorkingDirectory(subdir))) { if (!(ftp.makeDirectory(subdir))) { int rc = ftp.getReplyCode(); if (((rc != 550) && (rc != 553) && (rc != 521))) { throw new IOException("could not create directory: " + ftp.getReplyString()); } } else { ftp.changeWorkingDirectory(subdir); } } } if (workingDirectory != null){ ftp.changeWorkingDirectory(workingDirectory); } }/** * 获取指定目录下的文件名称列表 * @param currentDir * 需要获取其子目录的当前目录名称 * @return 返回子目录字符串数组 */public String[] GetFileNames(FTPClient ftpClient,String currentDir) {String[] dirs = null;try {if (currentDir == null)dirs = ftpClient.listNames();elsedirs = ftpClient.listNames(currentDir);} catch (IOException e) {e.printStackTrace();}return dirs;}/** * 获取指定目录下的文件与目录信息集合 * @param currentDir * 指定的当前目录 * @return 返回的文件集合 */public FTPFile[] GetDirAndFilesInfo(FTPClient ftpClient,String currentDir){FTPFile[] files = null;try{if (currentDir == null)files = ftpClient.listFiles();elsefiles = ftpClient.listFiles(currentDir);}catch (IOException ex){ex.printStackTrace();}return files;}}
?
?
?
package easyway.tbs.transfer.ftp;import easyway.tbs.commons.EnumUtils;/** * 文件下载状态 * @author longgangbai * */public enum DownloadStatus { Remote_File_Noexist(0,"远程文件不存在"), // 远程文件不存在 //用于单个下载Download_New_Success(1,"下载文件成功"), // 下载文件成功Download_New_Failed(2,"下载文件失败"), // 下载文件失败Local_Bigger_Remote(3,"本地文件大于远程文件"), // 本地文件大于远程文件Download_From_Break_Success(4,"文件断点续传成功"), // 断点续传成功Download_From_Break_Failed(5,"文件断点续传失败"), // 断点续传失败//用于批量下载Download_Batch_Success(6,"文件批量下载成功"),Download_Batch_Failure(7,"文件批量下载失败"),Download_Batch_Failure_SUCCESS(8,"文件批量下载不完全成功"); private int code; private String description; private DownloadStatus(int code , String description) { this.code = code; this.description = description; }public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;} /** * 下载状态中中使用的code * @param code * @return */public static DownloadStatus fromCode(int code) {return EnumUtils.fromEnumProperty(DownloadStatus.class, "code", code);}}
?
?
?
package easyway.tbs.transfer.ftp;import easyway.tbs.commons.EnumUtils;/** * 文件上传状态 * @author longgangbai * */public enum UploadStatus {Create_Directory_Fail(0,"远程服务器相应目录创建失败"), // 远程服务器相应目录创建失败Create_Directory_Success(1,"远程服务器闯将目录成功"), // 远程服务器闯将目录成功Upload_New_File_Success(2,"上传新文件成功"), // 上传新文件成功Upload_New_File_Failed(3,"上传新文件失败"), // 上传新文件失败File_Exits(4,"文件已经存在"), // 文件已经存在Remote_Bigger_Local(5,"远程文件大于本地文件"), // 远程文件大于本地文件Upload_From_Break_Success(6," 断点续传成功"), // 断点续传成功Upload_From_Break_Failed(7,"断点续传失败"), // 断点续传失败Delete_Remote_Faild(8,"删除远程文件失败"); // 删除远程文件失败private int code; private String description; private UploadStatus(int code , String description) { this.code = code; this.description = description; }public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;} /** * 下载状态中中使用的code * @param code * @return */public static UploadStatus fromCode(int code) {return EnumUtils.fromEnumProperty(UploadStatus.class, "code", code);}}
?