FTP文件上傳 支持斷點續傳 並 打印下載進度(二) —— 單線程實現

 

這個就看代碼,哈哈哈哈哈  須要用到的jar包是:前端

    <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.3</version>
        </dependency>

一:定義咱們可能會返回的狀態值。兩個枚舉類 一個異常類java

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; //全新下載文件失敗
}

//用於記錄建立時候的異常apache

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();
    }

表示鏈接成功多線程

三:實現文件上傳 和斷點續傳app

 

因爲設置了觀察者 在觀察當前上傳的進度變化 ,本次代碼中 沒有添加觀察者模式的代碼,因此這個地方 可能須要先註釋掉觀察者dom

 

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); 方法 實現了文件指針移動的開始位置  爲後面的 分佈式斷點 多點上傳 提供了 基礎 .

另外關於文件顯示進度比例,在這裏實現也不能,但要是與前端進度條進行實時數據交互式不現實的。。。後來經過查閱資料發現有些還頗有道理的。

好比咱們服務器通常也會限制文件上傳的大小,因此通常顯示進度條是在前端作的,經過比較瀏覽器發送出去的數據量 和帶上傳的文件大小 進行比較來顯示 進度條,但這種方法尚未測試成功,後面會進行驗證。

相關文章
相關標籤/搜索