1. 程式人生 > >使用原生Java Web來實現大檔案的上傳

使用原生Java Web來實現大檔案的上傳

BigFileUpload

wen icon

目錄

背景介紹

這個專案是在朋友的一次面試中,面試人提出了一個問題.
我有一個100M的檔案,然後我的寬頻只有10M,我應該如何處理使用者上傳的檔案?
根據這個問題,我小試牛刀,寫了這個專案.

期間查閱了資料,借鑑了Fourwen的專案的前端框架和md寫法.

再次感謝.

專案介紹

專案採用如下:

  • 上層: Java, JDK8, Tomcat8,
  • 服務端: Jsp, 原生
  • 前端: webuploader, bootstrap, jquery

來進行開發,

針對檔案的上傳,一般可以考慮的功能點有

斷點續傳 在斷網或者在暫停的情況下,能夠在上傳斷點中繼續上傳。

分塊上傳 也是斷點續傳的基礎之一,把大檔案通過前端分塊,然後後臺在組在一起。

檔案秒傳 服務中已經有人上傳過檔案,其他人再上傳這個檔案直接記錄並放回成功。

其他功能 下面這些功能歸類到其他,是因為它們基本都是通過WebUploader來實現的,很簡單。

- 多執行緒上傳 多個執行緒上傳不同的塊檔案。 
- 檔案進度顯示 顯示檔案的上傳完成情況。 

使用說明

獲取程式碼

不會經常更新,下一步會做一個集合公司內部網址的專案.

需要知識點

  • 專案使用nio來進行檔案的讀取和建立
  • 使用原生web來開發,不使用任何框架
  • 使用Apache提供的fileupload來實現上傳資料的獲取
  • 使用Apache提供的codec來實現md5加密
  • 併發的理解

啟動專案

專案示範

功能分析

分塊上傳可以說是我們整個專案的基礎,像斷點續傳、暫停這些都是需要用到分塊。
分塊這塊相對來說比較簡單。前端是採用了webuploader,分塊等基礎功能已經封裝起來,使用方便。
藉助webUpload提供給我們的檔案API,前端就顯得異常簡單。

   var uploader = WebUploader.create({

        // swf檔案路徑
swf: '${ctx}/webuploader-0.1.5/Uploader.swf', // 檔案接收服務端。 server: '${ctx}/upload.do', //檔案上傳請求的引數表,每次傳送都會發送此物件中的引數 formData: { md5: '' }, // 選擇檔案的按鈕。可選。 // 內部根據當前執行是建立,可能是input元素,也可能是flash. pick: '#picker', // 不壓縮image, 預設如果是jpeg,檔案上傳前會壓縮一把再上傳! resize: false, chunked: true, // 分塊 chunkSize: 1 * 1024 * 1024, // 位元組 1M分塊 threads: 3, //開啟執行緒 auto: false, // 禁掉全域性的拖拽功能。這樣不會出現圖片拖進頁面的時候,把圖片開啟。 disableGlobalDnd: true, fileNumLimit: 1024, fileSizeLimit: 200 * 1024 * 1024, // 200 M fileSingleSizeLimit: 100 * 1024 * 1024 // 100 M });

上傳的檔案會被髮送到upload.do這個Controller,在裡面的邏輯如下:

  1. 判斷是檔案上傳請求,如果是繼續,否則退出
  2. 使用fileupload jar包解析request請求上傳的基礎資訊
  3. 使用FileUploadBean包裝上傳的基礎資訊.
  4. 拼裝父目錄,校驗是否存在
    4.1 不存在就建立
    4.2 存在就進入檢驗
    4.2.1 檢查md5值是否匹配, 應該建立資料庫,儲存檔案資訊才是更快 更好的解決辦法.
    4.2.2 若匹配直接返回成功.
    4.2.3 若不成功,刪除原始檔再次讀取
  1. 寫入該分片資料到指定目錄
    寫入規則如下:
    // 0.讀取上傳檔案到陣列
    // 1.寫到本地
    // 1.記錄分片數,檢查分片數
    // 2.當對應的md5讀取數量達到對應的檔案後,合併檔案
    // 3.刪除臨時檔案
  1. 完成

功能分析

分塊上傳

分塊上傳可以說是我們整個專案的基礎,像斷點續傳、暫停這些都是需要用到分塊。
分塊這塊相對來說比較簡單。前端是採用了webuploader,分塊等基礎功能已經封裝起來,使用方便。
藉助webUpload提供給我們的檔案API,前端就顯得異常簡單。

       var uploader = WebUploader.create({

            // swf檔案路徑
            swf: '${ctx}/webuploader-0.1.5/Uploader.swf',

            // 檔案接收服務端。
            server: '${ctx}/upload.do',
            //檔案上傳請求的引數表,每次傳送都會發送此物件中的引數
            formData: {
                md5: ''
            },

            // 選擇檔案的按鈕。可選。
            // 內部根據當前執行是建立,可能是input元素,也可能是flash.
            pick: '#picker',

            // 不壓縮image, 預設如果是jpeg,檔案上傳前會壓縮一把再上傳!
            resize: false,

            chunked: true, // 分塊
            chunkSize: 1 * 1024 * 1024, // 位元組 1M分塊
            threads: 3, //開啟執行緒
            auto: false,

            // 禁掉全域性的拖拽功能。這樣不會出現圖片拖進頁面的時候,把圖片開啟。
            disableGlobalDnd: true,
            fileNumLimit: 1024,
            fileSizeLimit: 200 * 1024 * 1024,    // 200 M
            fileSingleSizeLimit: 100 * 1024 * 1024    // 100 M
        });

伺服器先建立一個md5資料夾,然後按照上傳的檔名進行一套規範命名,寫入到一個臨時檔案中.
然後記錄這個臨時檔案.

    // 規範命名
    String fileName = param.getName();
    String uploadDirPath = finalDirPath + param.getMd5();
    String tempFileName = fileName + "_" + param.getChunk() + "_tmp";
    Path tmpDir = Paths.get(uploadDirPath);
    // 寫入臨時檔案
    Path path = Paths.get(uploadDirPath, tempFileName);
    byte[] fileData = FileUtils.read(param.getFile(), 2048);
    Files.write(path, fileData, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
    FileUtils.authorizationAll(path);
    // 記錄
    FileBean fileBean;
    if(fileMap.containsKey(param.getMd5())) {
        fileBean = fileMap.get(param.getMd5());
    } else {
        fileBean = new FileBean(param.getName(), param.getChunks(), param.getMd5());
        fileMap.put(param.getMd5(), fileBean);
    }
    fileBean.setChunk(param.getChunk());

然後當檔案分片都上傳完成後,在把分片合併為一個檔案,並且刪除所有臨時檔案.

    if(fileBean.isLoadComplate()) {
    // 合併檔案..
    Path realFile = Paths.get(uploadDirPath, fileBean.getName());
    realFile = Files.createFile(realFile);
    // 設定許可權
    FileUtils.authorizationAll(realFile);
    for(int i = 0 ; i < fileBean.getChunks(); i++) {
        // 獲取每個分片
        tempFileName = fileName + "_" + i + "_tmp";
        Path itemPath = Paths.get(uploadDirPath, tempFileName);
        byte[] bytes = Files.readAllBytes(itemPath);
        Files.write(realFile, bytes, StandardOpenOption.APPEND);
        //寫完後刪除掉臨時檔案.
        Files.delete(itemPath);
    }
    logger.info("合併檔案{}成功", fileName);
    }

秒傳功能

上傳檔案是若發現父目錄已經建立,並且目錄下有上傳的檔名,那麼進行md5比較,若相同,直接返回,若不相同,刪除目錄檔案,重新上傳.

    if (!Files.exists(tmpDir)) {
        Files.createDirectory(tmpDir);
    } else {
        // 資料夾已存在
        // 1.檢查是否有檔案,有進入2, 沒有進3
        Path localPath = Paths.get(uploadDirPath, fileName);

        // 2.檢查md5值是否匹配, 應該建立資料庫,儲存檔案資訊才是更快 更好的解決辦法.
        // 2.1.若匹配直接返回成功.
        // 2.2 若不成功,刪除原始檔再次讀取
        if(Files.exists(localPath)) {
            String nowMd5 = DigestUtils.md5Hex(Files.newInputStream(localPath, StandardOpenOption.READ));
            if(StringUtils.equals(param.getMd5(), nowMd5)) {
                // 比較相等,那麼直接返回成功.
                logger.info("已檢測到重複檔案{},並且比較md5相等,已直接返回", fileName);
                return;
            } else {
                // 刪除
                logger.info("已經存在的檔案的md5不匹配上傳上來的檔案的md5,刪除後重新下載");
                Files.delete(localPath);
            }
        }
        // 3. 直接寫入到具體目錄下.
    }

斷點續傳

斷點續傳,就是在檔案上傳的過程中發生了中斷,人為因素(暫停)或者不可抗力(斷網或者網路差)導致了檔案上傳到一半失敗了。然後在環境恢復的時候,重新上傳該檔案,而不至於是從新開始上傳的。

檔案上傳時,獲取分片大小,同伺服器目錄儲存的分片大小進行比較,若一直,直接返回成功.

    //寫入該分片資料
    Path path = Paths.get(uploadDirPath, tempFileName);
    //檔案上傳時,獲取是否有分片,如果有直接返回.
    if(!Files.exists(path)) {
        // 不存在
        byte[] fileData = FileUtils.read(param.getFile(), 2048);
        try {
            Files.write(path, fileData, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
        } catch (IOException e) {
            // 刪除上傳的檔案
            Files.delete(path);
            throw e;
        }
        FileUtils.authorizationAll(path);
    } else {
        return;
    }

總結

選擇使用原生是為了鍛鍊自己不要忘記基礎,前前後後寫了3天,複習了不少檔案相關的操作,並且對lambda表示式和流
有了進一步瞭解,還是很滿足的.

在併發的情況下進行檔案上傳,在使用一個例項的成員變數進行儲存的時候,在方法上面使用synchronized或程式碼段加synchronized
或Lock或使用AtomInteger去進行併發操作,都沒能達到正確統計的目的.最後使用ConcurrentHashMap才完成了正確的計數.

由此看來,多執行緒環境下,我還是個小菜鳥啊. 努力加油了.

相關推薦

使用原生Java Web實現檔案

BigFileUpload 目錄 背景介紹 專案介紹 使用說明 獲取程式碼 需要知識點 啟動專案 專案示範 核心講解 功能分析 分塊上傳 秒傳功能 斷點續傳 總結 背景介紹 這個專案是在朋友的一次面試中,面試人提出了一個問題.

Java 使用 FTP 實現檔案下載

Java 上傳下載 1G 以上的檔案可以通過 http 協議或 ftp 實現,但是 http 協議對檔案上傳大小有限制,而且還不穩定,因此這裡使用 ftp 上傳。 ftp 上傳方式有兩種: 一、ASCII 傳輸方式:假定使用者正在拷貝的檔案包含

java實現檔案

檔案上傳是最古老的網際網路操作之一,20多年來幾乎沒有怎麼變化,還是操作麻煩、缺乏互動、使用者體驗差。 一、前端程式碼 英國程式設計師Remy Sharp總結了這些新的介面 ,本文在他的基礎之上,討論在前端採用HTML5的API,對檔案上傳進行漸進式增強: * iframe上傳    *

C#實現檔案功能(二)---webuploader

                                        一、問題來源 近些時候,處理專案的時候發現如果使用者上傳大檔案的時候使用HtmlInputFile通過httppostfilebase 來實現上傳,如果檔案較小的話較小的話,上傳沒有問題(4M)

java 中 FtpClient 實現 FTP 檔案、下載

package ftp; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWrit

Java Web基礎知識之檔案檔案一窺究竟

其實檔案上傳的文章已經寫得很多了,但是好多文章都是都是說明了怎麼實現,沒有說這個過程到底發生了什麼(會不會引來仇恨。。),其實實現檔案上傳並不複雜,也沒有多少程式碼,但是要是清楚的明白其中的原理還是費點功夫的,這裡就還原檔案上傳的整個過程。 其實關於檔案上傳在最早之前是使用

JAVA 非同步ajax實現xls 檔案 並且解析xls

html:<!--upload html--> <input type="file" id="file" name="file" value="選擇檔案" /> <inpu

實現檔案的辦法

方法一: 對ini進行修改file_uploads = on;//是否允許通過http上傳檔案的開關(預設為開) upload_max_filesize = 8m // php允許最大上傳檔案大小 post_max_size = 8m;//表單post提交允許最大上傳檔案大

java 基於List實現檔案

       多檔案上傳這個問題以前困擾了我好久後來我經過我的不懈努力終於克服了,其實認真起來所有的事情都會變得特別簡單,直接上程式碼。 架包 (maven) 現在還在手動架包的我只能大寫的服 <dependency> <grou

webuploader實現檔案

目前在公司專案裡遇到了需要上傳大檔案(視訊、音訊)的情況,特此記錄。 此次專案引用了一款名為Webuploader的外掛。官網:http://fex.baidu.com/webuploader/getting-started.html html程式碼: <html

使用百度webuploader實現檔案

 寫在前面           後臺的上傳檔案,都是50M左右比較小,後面說需要上傳大一點500M以上的,一直沒整過這方面的,昨天在網上看下其他人的實現方式,東看看西借鑑之後就有了下面的東西 第一部分 頁面         頁面實現了簡單的分片上傳檔案        

利用WebUploader實現檔案和視訊

檔案上傳是網站開發必不可少的,常見的有圖片上傳。但是大檔案和視訊上傳不常見。這裡我將自己寫的視訊上傳demo貼出來供大家參考: 利用是最新的WebUploader外掛請 下載使用最新版即可 js程式碼 _extensions ='3gp,mp4,rmvb,mov,avi,

Java Web FormData格式資料流實現檔案

1.html <``input type="file" multiple="multiple" accept="image/gif, image/jpeg, image/png, image/jpg, image/bmp" /> 2.JS $(document).on("ch

Java實現FTP批量檔案下載(一)

本文介紹了在Java中,如何使用Java現有的可用的庫來編寫FTP客戶端程式碼,並開發成Applet控制元件,做成基於Web的批量、大檔案的上傳下載控制元件。文章在比較了一系列FTP客戶庫的基礎上,就其中一個比較通用且功能較強的j-ftp類庫,對一些比較常見的功能如進度條、

Java實現FTP批量檔案下載(四)

六、FTP埠對映 FTP的資料連線有PASV和PORT兩種,如果你的FTP伺服器位於內網中,需要做埠對映。筆者剛開始時對FTP的網外網對映也是不怎麼了解,因此開始走了不少的彎路,開始一直以為是自己的程式有問題,浪費了不少時間,希望通過這段,能讓大家在開發的時候少花或不花這些

Java實現FTP批量檔案下載(五) --執行效果圖

八、執行效果   1.上傳 (1).啟動上傳上面 (2).上傳中 (3).上傳中 (4).上傳成功 2.下載 (1)下載檔案的儲存路徑 (2)下載中 (3)下載中 (4)下載成功 九、小結 在本文中,筆者將在實際專案中的上傳下載

Java實現FTP批量檔案下載(五)

八、執行效果   1.上傳 (1).啟動上傳上面 (2).上傳中 (3).上傳中 (4).上傳成功 2.下載 (1)下載檔案的儲存路徑 (2)下載中  (3)下載中  (4)下載成功 九、小結 在本文中,筆者將在實際專案中的上傳下載問題的解決方案進行了闡述,通過採用FTP協

java jsch實現sftp檔案,並且控制的速度,同時監控進度

工作中,有些環境頻寬有限,比如說專線,通常頻寬比較小,又不便宜,當業務量大的時間,如果在專線上還要傳輸檔案的話,往往在檔案傳輸的時間會導致頻寬佔慢,就有可能導致時實交易進不來,有可能影響交易,今天貼一下 jsch實現sftp檔案上傳,並且控制上傳的速度,同時監控上傳進度,供大家參考。

java實現檔案01

1、html程式碼 <html> <head> <link rel="stylesheet" type="text/css" href="table.css" /> <link rel="stylesheet" type="text/css"

前端元件WebUploader檔案與Python結合的實現

Python實現大檔案分片上傳 引言 想借著這篇文章簡要談談WebUploader大檔案上傳與Python結合的實現。 WebUploader是百度團隊對大檔案上傳的前端實現,而後端需要根據不同的語言自己實現。這裡我採用Python語言的Flask框架搭建後端,