最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)現(xiàn)斷點(diǎn)下載功能的示例代碼

 更新時(shí)間:2022年05月27日 14:48:51   作者:胡安民  
當(dāng)下載一個(gè)很大的文件時(shí),如果下載到一半暫停,如果繼續(xù)下載呢?斷點(diǎn)下載就是解決這個(gè)問題的。本文將用Java語言實(shí)現(xiàn)斷點(diǎn)下載,需要的可以參考一下

介紹

當(dāng)下載一個(gè)很大的文件時(shí),如果下載到一半暫停,如果繼續(xù)下載呢?斷點(diǎn)下載就是解決這個(gè)問題的。

具體原理:

利用indexedDb,將下載的數(shù)據(jù)存儲(chǔ)到用戶的本地中,這樣用戶就算是關(guān)電腦那么下次下載還是從上次的位置開始的

  • 先去看看本地緩存中是否存在這個(gè)文件的分片數(shù)據(jù),如果存在那么就接著上一個(gè)分片繼續(xù)下載(起始位置)
  • 下載前先去后端拿文件的大小,然后計(jì)算分多少次下載(n/(1024*1024*10)) (結(jié)束位置)
  • 每次下載的數(shù)據(jù)放入一個(gè)Blob中,然后存儲(chǔ)到本地indexedDB
  • 當(dāng)全部下載完畢后,將所有本地緩存的分片全部合并,然后給用戶

有很多人說必須使用content-length、Accept-Ranges、Content-Range還有Range。 但是這只是一個(gè)前后端的約定而已,所有沒必須非要遵守,只要你和后端約定好怎么拿取數(shù)據(jù)就行

難點(diǎn)都在前端:

  • 怎么存儲(chǔ)
  • 怎么計(jì)算下載多少次
  • 怎么獲取最后下載的分片是什么
  • 怎么判斷下載完成了
  • 怎么保證下載的分片都是完整的
  • 下載后怎么合并然后給用戶

效果

前端代碼

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

<h1>html5大文件斷點(diǎn)下載傳</h1>
<div id="progressBar"></div>
<Button id="but">下載</Button>
<Button id="stop">暫停</Button>

<script type="module">

    import FileSliceDownload from '/src/file/FileSliceDownload.js'
    let downloadUrl = "http://localhost:7003/fileslice/dwnloadsFIleSlice"
    let fileSizeUrl = "http://localhost:7003/fileslice/fIleSliceDownloadSize"
    let fileName = "Downloads.zip"
    let but = document.querySelector("#but")
    let stop = document.querySelector("#stop")
    let fileSliceDownload = new FileSliceDownload(downloadUrl, fileSizeUrl);
    fileSliceDownload.addProgress("#progressBar")
    but.addEventListener("click",  function () {
        fileSliceDownload.startDownload(fileName)
    })
    stop.addEventListener("click",  function () {
        fileSliceDownload.stop()
    })


</script>


</body>

</html>
 class BlobUtls{

    // blob轉(zhuǎn)文件并下載
   static  downloadFileByBlob(blob, fileName = "file")  {
        let blobUrl = window.URL.createObjectURL(blob)
        let link = document.createElement('a')
        link.download = fileName || 'defaultName'
        link.style.display = 'none'
        link.href = blobUrl
        // 觸發(fā)點(diǎn)擊
        document.body.appendChild(link)
        link.click()
        // 移除
        document.body.removeChild(link)
    }


}
export default BlobUtls;
//導(dǎo)包要從項(xiàng)目全路徑開始,也就是最頂部
import BlobUtls  from '/web-js/src/blob/BlobUtls.js'
//導(dǎo)包
class FileSliceDownload{
    #m1=1024*1024*10 //1mb  每次下載多少
    #db   //indexedDB庫對(duì)象
    #downloadUrl  // 下載文件的地址
    #fileSizeUrl  // 獲取文件大小的url
    #fileSiez=0  //下載的文件大小
    #fileName  // 下載的文件名稱
    #databaseName="dbDownload";  //默認(rèn)庫名稱
    #tableDadaName="tableDada"  //用于存儲(chǔ)數(shù)據(jù)的表
    #tableInfoName="tableInfo"  //用于存儲(chǔ)信息的表
    #fIleReadCount=0 //文件讀取次數(shù)
    #fIleStartReadCount=0//文件起始的位置
    #barId = "bar"; //進(jìn)度條id
    #progressId = "progress";//進(jìn)度數(shù)值ID
    #percent=0 //百分比
    #checkDownloadInterval=null; //檢測(cè)下載是否完成定時(shí)器
    #mergeInterval=null;//檢測(cè)是否滿足合并分片要求
    #stop=false; //是否結(jié)束
    //下載地址
    constructor(downloadUrl,fileSizeUrl) {
        this.check()
        this.#downloadUrl=downloadUrl;
        this.#fileSizeUrl=fileSizeUrl;
    }

    check(){
       let indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB ;
        if(!indexedDB){
            alert('不支持');
        }
    }

    //初始化
    #init(fileName){
        return   new Promise((resolve,reject)=>{
            this.#fileName=fileName;
            this.#percent=0;
            this.#stop=false;
            const request = window.indexedDB.open(this.#databaseName, 1)
            request.onupgradeneeded = (e) => {
                const db = e.target.result
                if (!db.objectStoreNames.contains(this.#tableDadaName)) {
                    db.createObjectStore(this.#tableDadaName, { keyPath: 'serial',autoIncrement:false })
                    db.createObjectStore(this.#tableInfoName, { keyPath: 'primary',autoIncrement:false })
                }
            }
            request.onsuccess = e => {
                this.#db = e.target.result
                resolve()
            }
        })

    }


    #getFileSize(){
        return   new Promise((resolve,reject)=>{
            let ref=this;
            var xhr = new XMLHttpRequest();
            //同步
            xhr.open("GET", this.#fileSizeUrl+"/"+this.#fileName,false)
            xhr.send()
            if (xhr.readyState === 4 && xhr.status === 200) {
                let ret = JSON.parse(xhr.response)
                if (ret.code === 20000) {
                    ref.#fileSiez=ret.data
                }
                resolve()
            }
        })
    }

    #getTransactionDadaStore(){
        let transaction =  this.#db.transaction([this.#tableDadaName], 'readwrite')
        let store = transaction.objectStore(this.#tableDadaName)
        return store;
    }
    #getTransactionInfoStore(){
        let transaction =  this.#db.transaction([this.#tableInfoName], 'readwrite')
        let store = transaction.objectStore(this.#tableInfoName)
        return store;
    }

    #setBlob(begin,end,i,last){
        return   new Promise((resolve,reject)=>{
            var xhr = new XMLHttpRequest();
            xhr.open("GET", this.#downloadUrl+"/"+this.#fileName+"/"+begin+"/"+end+"/"+last)
            xhr.responseType="blob"   // 只支持異步,默認(rèn)使用 text 作為默認(rèn)值。
            xhr.send()
            xhr.onload = ()=> {
                if (xhr.status === 200) {
                    let store= this.#getTransactionDadaStore()
                    let obj={serial:i,blob:xhr.response}
                    //添加分片到用戶本地的庫中
                    store.add(obj)
                    let store2= this.#getTransactionInfoStore()
                    //記錄下載了多少個(gè)分片了
                    store2.put({primary:"count",count:i})

                    //調(diào)整進(jìn)度條
                    let percent1=   Math.ceil( (i/this.#fIleReadCount)*100)
                    if(this.#percent<percent1){
                        this.#percent=percent1;
                    }
                    this.#dynamicProgress()
                    resolve()

                }
            }
        })

    }


    #mergeCallback(){
        // 讀取全部字節(jié)到blob里,處理合并
        let arrayBlobs = [];
        let store1 = this.#getTransactionDadaStore()
        //按順序找到全部的分片
        for (let i = 0; i <this.#fIleReadCount; i++) {
           let result= store1.get(IDBKeyRange.only(i))
            result.onsuccess=(data)=>{
                arrayBlobs.push(data.target.result.blob)


            }
        }
        //分片合并下載
       this.#mergeInterval= setInterval(()=> {
           if(arrayBlobs.length===this.#fIleReadCount){
               clearInterval(this.#mergeInterval);
                   //多個(gè)Blob進(jìn)行合并
                   let fileBlob = new Blob(arrayBlobs);//合并后的數(shù)組轉(zhuǎn)成?個(gè)Blob對(duì)象。
                   BlobUtls.downloadFileByBlob(fileBlob,this.#fileName)

               //下載完畢后清除數(shù)據(jù)
                this. #clear()
           }

        },200)
    }
    #clear(){
        let store2 = this.#getTransactionDadaStore()
        let store3 = this.#getTransactionInfoStore()
        store2.clear() //清除本地全下載的數(shù)據(jù)
        store3.delete("count")//記錄清除
        this.#fIleStartReadCount=0 //起始位置
        this.#db=null;
        this.#fileName=null;
        this.#fileSiez=0;
        this.#fIleReadCount=0 //文件讀取次數(shù)
        this.#fIleStartReadCount=0//文件起始的位置

    }

    //檢測(cè)是否有分片在本地
    #checkSliceDoesIsExist(){
        return   new Promise((resolve,reject)=>{
            let store1 = this.#getTransactionInfoStore()
            let result= store1.get(IDBKeyRange.only("count"))
            result.onsuccess=(data)=>{
                let count= data.target.result?.count
                if(count){
                    //防止因?yàn)榫W(wǎng)絡(luò)的原因?qū)е路制瑩p壞,所以不要最后一個(gè)分片
                    this.#fIleStartReadCount=count-1;
                }
                resolve();
            }
        })

    }

    /**
     *  樣式可以進(jìn)行修改
     * @param {*} progressId   需要將進(jìn)度條添加到那個(gè)元素下面
     */
    addProgress (progressSelect) {
        let bar = document.createElement("div")
        bar.setAttribute("id", this.#barId);
        let num = document.createElement("div")
        num.setAttribute("id", this.#progressId);
        num.innerText = "0%"
        bar.appendChild(num);
        document.querySelector(progressSelect).appendChild(bar)
    }
    #dynamicProgress(){
        //調(diào)整進(jìn)度
        let bar = document.getElementById(this.#barId)
        let progressEl = document.getElementById(this.#progressId)
        bar.style.width = this.#percent + '%';
        bar.style.backgroundColor = 'red';
        progressEl.innerHTML =  this.#percent + '%'
    }

    stop(){
        this.#stop=true;
    }

   startDownload(fileName){
        //同步代碼塊
        ;(async ()=>{
                 //初始化
               await this.#init(fileName)

                   //自動(dòng)調(diào)整分片,如果本地以下載了那么從上一次繼續(xù)下載
               await    this.#checkSliceDoesIsExist()
                     //拿到文件的大小
               await    this.#getFileSize()
                   let begin=0; //開始讀取的字節(jié)
                   let end=this.#m1; // 結(jié)束讀取的字節(jié)
                   let last=false; //是否是最后一次讀取
                   this.#fIleReadCount= Math.ceil( this.#fileSiez/this.#m1)
                   for (let i =  this.#fIleStartReadCount; i < this.#fIleReadCount; i++) {
                       if(this.#stop){
                            return
                       }
                       begin=i*this.#m1;
                       end=begin+this.#m1
                       if(i===this.#fIleReadCount-1){
                           last=true;
                       }
                       //添加分片
                       await  this.#setBlob(begin,end,i,last)
                   }

                   //定時(shí)檢測(cè)存下載的分片數(shù)量是否夠了
                   this.#checkDownloadInterval= setInterval(()=> {
                       let store = this.#getTransactionDadaStore()
                       let result = store.count()
                       result.onsuccess = (data) => {
                           if (data.target.result === this.#fIleReadCount) {
                               clearInterval(this.#checkDownloadInterval);
                               //如果分片夠了那么進(jìn)行合并下載
                               this.#mergeCallback()
                           }
                       }
                   },200)


       })()





   }


}

export default FileSliceDownload;

后端代碼

package com.controller.commontools.fileDownload;

import com.application.Result;
import com.container.ArrayByteUtil;
import com.file.FileWebDownLoad;
import com.file.ReadWriteFileUtils;
import com.path.ResourceFileUtil;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.net.URLEncoder;

@RestController
@RequestMapping("/fileslice")
public class FIleSliceDownloadController {
    private  final  String uploaddir="uploads"+ File.separator+"real"+File.separator;//實(shí)際文件目錄
    // 獲取文件的大小
    @GetMapping("/fIleSliceDownloadSize/{fileName}")
    public Result getFIleSliceDownloadSize(@PathVariable String fileName){
        String absoluteFilePath = ResourceFileUtil.getAbsoluteFilePathAndCreate(uploaddir)+File.separator+fileName;
         File file= new File(absoluteFilePath);
        if(file.exists()&&file.isFile()){
            return  Result.Ok(file.length(),Long.class);
        }

        return  Result.Error();
    }

    /**
     * 分段下載文件
     * @param fileName  文件名稱
     * @param begin  從文件什么位置開始讀取
     * @param end  到什么位置結(jié)束
     * @param last  是否是最后一次讀取
     * @param response
     */
    @GetMapping("/dwnloadsFIleSlice/{fileName}/{begin}/{end}/{last}")
    public void dwnloadsFIleSlice(@PathVariable String fileName, @PathVariable long begin, @PathVariable long end, @PathVariable boolean last, HttpServletResponse response){
        String absoluteFilePath = ResourceFileUtil.getAbsoluteFilePathAndCreate(uploaddir)+File.separator+fileName;
        File file= new File(absoluteFilePath);
        try(OutputStream toClient = new BufferedOutputStream(response.getOutputStream())) {
            long readSize = end - begin;
            //讀取文件的指定字節(jié)
            byte[] bytes =  new byte[(int)readSize];
            ReadWriteFileUtils.randomAccessFileRead(file.getAbsolutePath(),(int)begin,bytes);
            if(readSize<=file.length()||last){
                bytes=ArrayByteUtil.getActualBytes(bytes); //去掉多余的
            }

            response.setContentType("application/octet-stream");
            response.addHeader("Content-Length", String.valueOf(bytes.length));
            response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + URLEncoder.encode(fileName, "UTF-8"));
            toClient.write(bytes);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

以上就是Java實(shí)現(xiàn)斷點(diǎn)下載功能的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Java斷點(diǎn)下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何用Spring發(fā)送電子郵件

    如何用Spring發(fā)送電子郵件

    這篇文章主要介紹了如何用Spring發(fā)送電子郵件,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-02-02
  • Java?HashMap詳解及實(shí)現(xiàn)原理

    Java?HashMap詳解及實(shí)現(xiàn)原理

    Java?HashMap是Java集合框架中最常用的實(shí)現(xiàn)Map接口的數(shù)據(jù)結(jié)構(gòu),它使用哈希表實(shí)現(xiàn),允許null作為鍵和值,可以存儲(chǔ)不同類型的鍵值對(duì),在Java中,HashMap被廣泛應(yīng)用于各種場(chǎng)景,如緩存、數(shù)據(jù)庫連接池、路由器等,文中有詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-05-05
  • java中CompletableFuture異步執(zhí)行方法

    java中CompletableFuture異步執(zhí)行方法

    本文主要介紹了java中CompletableFuture異步執(zhí)行方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java中indexOf()的用法小結(jié)

    Java中indexOf()的用法小結(jié)

    這篇文章主要介紹了Java中indexOf()的用法小結(jié),indexOf()有四種方法,用來查找某個(gè)字符或字符串的位置,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • springboot用戶數(shù)據(jù)修改的詳細(xì)實(shí)現(xiàn)

    springboot用戶數(shù)據(jù)修改的詳細(xì)實(shí)現(xiàn)

    用戶管理功能作為所有的系統(tǒng)是必不可少的一部分,下面這篇文章主要給大家介紹了關(guān)于springboot用戶數(shù)據(jù)修改的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • JVM的基本介紹以及垃圾回收

    JVM的基本介紹以及垃圾回收

    垃圾回收(Garbage Collection,GC),顧名思義就是釋放垃圾占用的空間,防止內(nèi)存泄露,這篇文章主要給大家介紹了關(guān)于JVM垃圾回收的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • Java之BigDecimal的坑及解決

    Java之BigDecimal的坑及解決

    這篇文章主要介紹了Java之BigDecimal的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Spark SQL的自定義函數(shù)UDF使用

    Spark SQL的自定義函數(shù)UDF使用

    Spark Sql可以通過UDF來對(duì)DataFrame的Column進(jìn)行自定義操作。在特定場(chǎng)景下定義UDF可能需要用到Spark Context以外的資源或數(shù)據(jù)。比如從List或Map中取值,或是通過連接池從外部的數(shù)據(jù)源中讀取數(shù)據(jù),然后再參與Column的運(yùn)算
    2023-02-02
  • Servlet和Spring?MVC的區(qū)別及使用說明

    Servlet和Spring?MVC的區(qū)別及使用說明

    這篇文章詳細(xì)介紹了Servlet和SpringMVC的基本概念、工作原理、功能對(duì)比和應(yīng)用場(chǎng)景,Servlet是JavaWeb開發(fā)的基礎(chǔ),而SpringMVC是一個(gè)基于Servlet的高級(jí)框架,提供了更強(qiáng)大的功能和易用性,文章通過定義、原理和示例代碼,幫助讀者理解這兩個(gè)技術(shù)的區(qū)別與聯(lián)系
    2025-01-01
  • Java單鏈表基本操作的實(shí)現(xiàn)

    Java單鏈表基本操作的實(shí)現(xiàn)

    鏈表是一種數(shù)據(jù)結(jié)構(gòu),和數(shù)組同級(jí)。接下來通過本文給大家介紹Java單鏈表基本操作的實(shí)現(xiàn),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看下吧
    2016-07-07

最新評(píng)論

颍上县| 崇信县| 巴南区| 民权县| 永川市| 疏勒县| 中江县| 无为县| 德钦县| 双辽市| 三亚市| 麻阳| 九龙坡区| 和林格尔县| 时尚| 夏津县| 鄂伦春自治旗| 尼木县| 宽甸| 莱芜市| 囊谦县| 额济纳旗| 浦江县| 信丰县| 平远县| 海淀区| 金秀| 威远县| 邵阳县| 胶州市| 滨州市| 明溪县| 侯马市| 巴楚县| 平舆县| 锦州市| 临沧市| 乐平市| 大厂| 双牌县| 枣强县|