FTP文件上传 支持断点续传 并 打印下载进度(二) —— 单线程实现
这个就看代码,哈哈哈哈哈 需要用到的jar包是:
<dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.3</version> </dependency>
一:定义我们可能会返回的状态值。两个枚举类 一个异常类
public enum UploadStatus { Create_Directory_Fail, //远程服务器相应目录创建失败 Create_Directory_Success, //远程服务器闯将目录成功 Upload_New_File_Success, //上传新文件成功 Upload_New_File_Failed, //上传新文件失败 File_Exits, //文件已经存在 Remote_Bigger_Local, //远程文件大于本地文件 Upload_From_Break_Success, //断点续传成功 Upload_From_Break_Failed, //断点续传失败 Delete_Remote_Faild; //删除远程文件失败 }
public enum DownloadStatus { Remote_File_Noexist, //远程文件不存在 Local_Bigger_Remote, //本地文件大于远程文件 Download_From_Break_Success, //断点下载文件成功 Download_From_Break_Failed, //断点下载文件失败 Download_New_Success, //全新下载文件成功 Download_New_Failed; //全新下载文件失败 }
//用于记录创建时候的异常
public class CreateException extends Exception{ private static Logger log = LoggerFactory.getLogger(CreateException.class); private static final long serialVersionUID = 1L; private Integer errCode; private String errMessage; public CreateException(Throwable cause, Integer errCode, String errMessage) { super(cause); this.errCode = errCode; this.errMessage = errMessage; } public CreateException(Integer errCode, String errMessage) { this.errCode = errCode; this.errMessage = errMessage; } public CreateException(Integer errCode, UploadStatus uploadStatus) { this.errCode = errCode; this.errMessage = uploadStatus.toString(); } public Integer getErrCode() { return errCode; } public String getErrMessage() { return errMessage; } }
二:创建连接ftp服务器的类
package com.utils.study.ftpCenter; import com.coocaa.core.generation.service.CreateBeanService; import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PrintWriter; /** * Created by yugaofeng on 2017/9/5. */ public class ContinueFTP { private static Logger logger = LoggerFactory.getLogger(CreateBeanService.class); //定义一个客户端 private static FTPClient ftpClient ; //单例模式 public FTPClient getFtpClient(){ if(ftpClient == null){ ftpClient = new FTPClient(); } return ftpClient; } public ContinueFTP(){ getFtpClient().addProtocolCommandListener(new PrintCommandListener( new PrintWriter(System.out))); } /** * 连接到FTP服务器 * @param hostname 主机名 * @param port 端口 * @param username 用户名 * @param password 密码 * @return 是否连接成功 * @throws IOException */ public boolean connect(String hostname, int port, String username, String password) throws IOException { getFtpClient().connect(hostname, port); if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode())) { if (getFtpClient().login(username, password)) { getFtpClient().enterLocalPassiveMode(); getFtpClient().setFileType(FTP.BINARY_FILE_TYPE); return true; } } disconnect(); return false; } /** * 断开与服务器的连接 * @throws IOException */ public void disconnect() throws IOException { if (getFtpClient().isConnected()) { getFtpClient().disconnect(); System.out.println("ftp is disconnect!"); } } }
这个时候 先测试一下 你能不能连接到服务器
public static void main(String[] args) throws IOException { ContinueFTP ftp = new ContinueFTP(); System.out.println("<<<<<<<<<<<<<<<<<1"+ftp.getFtpClient().isConnected()); ftp.connect("172.20.139.217", 21, "ftp01", "ftp111"); System.out.println("<<<<<<<<<<<<<<<<<3"+ftp.getFtpClient().isConnected()); ftp.getFtpClient().disconnect(); }
表示连接成功
三:实现文件上传 和断点续传
由于设置了观察者 在观察当前上传的进度变化 ,本次代码中 没有添加观察者模式的代码,所以这个地方 可能需要先注释掉观察者
package com.utils.study.ftpCenter; import com.utils.study.CreateException; import com.utils.study.enums.UploadStatus; import com.utils.study.observerModel.FileObserverAble; import com.utils.study.observerModel.FilePercentObserver; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import java.io.*; /** * Created by yugaofeng on 2017/9/5. */ public class FileOperateByFtp { private FTPClient ftpClient; FileObserverAble fileObserverAble; public FileOperateByFtp(FTPClient ftpClient) { //添加观察者对象 fileObserverAble = new FileObserverAble(); FilePercentObserver filePercentObserver = new FilePercentObserver(fileObserverAble); this.ftpClient = ftpClient; } /** * 上传文件到FTP服务器,支持断点续传 并返回上传文件进度 * @param local 本地文件名称,绝对路径 * @param remote 远程文件路径,使用/home/directory1/subdirectory/file.ext * 按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构 * @return 上传结果 * @throws IOException */ public UploadStatus upload(String local, String remote) throws Exception{ try { if (!ftpClient.isConnected()) { throw new CreateException(-1, "远程服务器相应目录创建失败"); } UploadStatus result; // 对远程目录的处理 并返回文件的名称 String remoteFileName = createDirectory(remote, ftpClient); // 检查远程是否存在文件 FTPFile[] files = ftpClient.listFiles(remoteFileName); File localFile = new File(local); if(localFile.length() <=0){ throw new CreateException(-1,"本地文件不存在"); } if (files.length == 1) { //判断文件是否存在 long remoteSize = files[0].getSize(); long localSize = localFile.length(); if(remoteSize==localSize){ return UploadStatus.File_Exits; }else if(remoteSize > localSize){ return UploadStatus.Remote_Bigger_Local; } result = this.writeByUnit(remoteFileName,localFile,ftpClient,remoteSize,localFile.length()); } else { result = this.writeByUnit(remoteFileName,localFile,ftpClient,0,localFile.length()); } return result; }catch (CreateException e){ throw e; }finally { //上传完成之后 切回到根目录 ftpClient.changeWorkingDirectory("/"); } } /** * 判断目录 * @param remoteFilePath 远程服务器上面的 文件目录 * @param ftpClient ftp客户端 * @return * @throws Exception */ private String createDirectory(String remoteFilePath,FTPClient ftpClient) throws Exception { if(ftpClient == null){ throw new CreateException(-1,"FTP客户端为空,请先连接到客户端"); } String fileName = remoteFilePath; if(remoteFilePath.contains("/")){ fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1); String directory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1); if(directory.startsWith("/")){ directory = directory.substring(1); } while (true){ if(!directory.contains("/")){ break; } String subDirectory = directory.substring(0, directory.indexOf("/")); directory = directory.substring(directory.indexOf("/")+1); if (!ftpClient.changeWorkingDirectory(subDirectory)) { if (ftpClient.makeDirectory(subDirectory)) { ftpClient.changeWorkingDirectory(subDirectory); } else { throw new CreateException(-1,"创建目录失败"); } } } } return fileName; } /** * 上传文件到服务器,新上传和断点续传 * @param remoteFile 远程文件名,在上传之前已经将服务器工作目录做了改变 * @param localFile 本地文件File句柄,绝对路径 * @param ftpClient FTPClient引用 beginSize是指文件长传开始指针位置 endSize是结束的位置 为多线程上传下载提供接口 不过该方法还需要修改 * @return * @throws IOException */ private UploadStatus writeByUnit(String remoteFile,File localFile,FTPClient ftpClient,long beginSize,long endSize) throws Exception { long localSize = localFile.length(); if(endSize > localSize){ endSize = localSize; } if(beginSize < 0){ beginSize = 0; } //等待写入的文件大小 long writeSize = endSize - beginSize; if(writeSize <= 0){ throw new CreateException(1,"文件指针参数出错"); } //获取百分单位是 1-100 RandomAccessFile raf = new RandomAccessFile(localFile,"r"); OutputStream out = ftpClient.appendFileStream(new String(remoteFile.getBytes("GBK"),"iso-8859-1")); //把文件指针移动到 开始位置 ftpClient.setRestartOffset(beginSize); raf.seek(beginSize); //定义最小移动单位是 1024字节 也就是1kb byte[] bytes = new byte[1024]; int c; double finishSize = 0; double finishPercent = 0; //存在一个bug 当分布移动的时候 可能会出现下载重复的问题 后期需要修改 while ((c = raf.read(bytes)) != -1) { out.write(bytes, 0, c); finishSize += c; if(finishSize > writeSize){ finishPercent = 1; //System.out.println(">>>>>完成进度:" + finishPercent); fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"upload"); break; } if ((finishSize / writeSize) - finishPercent > 0.01) { finishPercent = finishSize / writeSize; //System.out.println(">>>>>完成进度:" + finishPercent); fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"upload"); } } out.flush(); raf.close(); out.close(); boolean result =ftpClient.completePendingCommand(); return result?UploadStatus.Upload_From_Break_Success:UploadStatus.Upload_From_Break_Failed; } /** * 从FTP服务器上下载文件 * @param remote 远程文件路径 * @param local 本地文件路径 * @return 是否成功 * @throws IOException */ public boolean download(String remote,String local) throws Exception{ FTPFile[] files = ftpClient.listFiles(remote); if(files == null || files.length < 0){ throw new CreateException(-1,"远程文件不存在"); } if(files.length != 1){ throw new CreateException(-1,"远程文件不唯一"); } File localFile = new File(local); if(localFile.exists()){ long localBeginSize = localFile.length(); if(localBeginSize == files[0].getSize()){ throw new CreateException(-1,"文件已经存在"); }else if(localBeginSize > files[0].getSize()){ throw new CreateException(-1,"下载文件出错"); } return downloadByUnit(remote,local,localBeginSize,files[0].getSize()); }else { return downloadByUnit(remote,local,0,files[0].getSize()); } } private Boolean downloadByUnit(String remote,String local,long beginSize,long endSize) throws Exception { File localFile = new File(local); long waitSize = endSize - beginSize; //进行断点续传,并记录状态 FileOutputStream out = new FileOutputStream(localFile,true); //把文件指针移动到 开始位置 ftpClient.setRestartOffset(beginSize); InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes("GBK"),"iso-8859-1")); byte[] bytes = new byte[1024]; int c; double finishSize =0; double finishPercent = 0; while((c = in.read(bytes))!= -1){ out.write(bytes,0,c); finishSize += c; if(finishSize > waitSize){ //System.out.println(">>>>>完成进度:" + 1); fileObserverAble.setKeyValue(localFile.getName(),1,"download"); } if ((finishSize / waitSize) - finishPercent > 0.01) { finishPercent = finishSize / waitSize; //System.out.println(">>>>>完成进度:" + finishPercent); fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"download"); } } in.close(); out.close(); return ftpClient.completePendingCommand(); } }
测试上传 并在控制台打印出 上传百分百
public static void main(String[] args) { ContinueFTP ftp = new ContinueFTP(); try { ftp.connect("172.20.139.217", 21, "ftp01", "ftp111"); FileOperateByFtp fileOperateByFtp = new FileOperateByFtp(ftp.getFtpClient()); fileOperateByFtp.upload("F:\\upload7.temp","/upload2/f3/upload7.temp"); fileOperateByFtp.upload("F:\\upload6.temp","/upload2/f3/upload6.temp"); /* fileOperateByFtp.download("/upload2/f3/upload7.temp","F:\\upload6.temp"); fileOperateByFtp.download("//upload2/f3/upload6.temp","F:\\upload7.temp");*/ if(ftp.getFtpClient() != null){ ftp.getFtpClient().disconnect(); } } catch (Exception e) { if(e instanceof CreateException){ System.out.println(((CreateException) e).getErrMessage()); } } }
上传结果:
下载就不做演示
四:总结
ftp文件长传其实很简单,,实现断点续传也不能
ftp里面提供了一个 ftpClient.setRestartOffset(beginSize); 方法 实现了文件指针移动的开始位置 为后面的 分布式断点 多点上传 提供了 基础 .
另外关于文件显示进度比例,在这里实现也不能,但要是与前端进度条进行实时数据交互式不现实的。。。后来通过查阅资料发现有些还很有道理的。
比如我们服务器一般也会限制文件上传的大小,所以一般显示进度条是在前端做的,通过比较浏览器发送出去的数据量 和带上传的文件大小 进行比较来显示 进度条,但这种方法还没有测试成功,后面会进行验证。