Java实现断点下载功能的示例代码

目录
  • 介绍
  • 效果
  • 前端代码
  • 后端代码

介绍

当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的。

具体原理:

利用indexedDb,将下载的数据存储到用户的本地中,这样用户就算是关电脑那么下次下载还是从上次的位置开始的

  • 先去看看本地缓存中是否存在这个文件的分片数据,如果存在那么就接着上一个分片继续下载(起始位置)
  • 下载前先去后端拿文件的大小,然后计算分多少次下载(n/(1024*1024*10)) (结束位置)
  • 每次下载的数据放入一个Blob中,然后存储到本地indexedDB
  • 当全部下载完毕后,将所有本地缓存的分片全部合并,然后给用户

有很多人说必须使用content-length、Accept-Ranges、Content-Range还有Range。 但是这只是一个前后端的约定而已,所有没必须非要遵守,只要你和后端约定好怎么拿取数据就行

难点都在前端:

  • 怎么存储
  • 怎么计算下载多少次
  • 怎么获取最后下载的分片是什么
  • 怎么判断下载完成了
  • 怎么保证下载的分片都是完整的
  • 下载后怎么合并然后给用户

效果

前端代码

<!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大文件断点下载传</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转文件并下载
   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
        // 触发点击
        document.body.appendChild(link)
        link.click()
        // 移除
        document.body.removeChild(link)
    }

}
export default BlobUtls;
//导包要从项目全路径开始,也就是最顶部
import BlobUtls  from '/web-js/src/blob/BlobUtls.js'
//导包
class FileSliceDownload{
    #m1=1024*1024*10 //1mb  每次下载多少
    #db   //indexedDB库对象
    #downloadUrl  // 下载文件的地址
    #fileSizeUrl  // 获取文件大小的url
    #fileSiez=0  //下载的文件大小
    #fileName  // 下载的文件名称
    #databaseName="dbDownload";  //默认库名称
    #tableDadaName="tableDada"  //用于存储数据的表
    #tableInfoName="tableInfo"  //用于存储信息的表
    #fIleReadCount=0 //文件读取次数
    #fIleStartReadCount=0//文件起始的位置
    #barId = "bar"; //进度条id
    #progressId = "progress";//进度数值ID
    #percent=0 //百分比
    #checkDownloadInterval=null; //检测下载是否完成定时器
    #mergeInterval=null;//检测是否满足合并分片要求
    #stop=false; //是否结束
    //下载地址
    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"   // 只支持异步,默认使用 text 作为默认值。
            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()
                    //记录下载了多少个分片了
                    store2.put({primary:"count",count:i})

                    //调整进度条
                    let percent1=   Math.ceil( (i/this.#fIleReadCount)*100)
                    if(this.#percent<percent1){
                        this.#percent=percent1;
                    }
                    this.#dynamicProgress()
                    resolve()

                }
            }
        })

    }

    #mergeCallback(){
        // 读取全部字节到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);
                   //多个Blob进行合并
                   let fileBlob = new Blob(arrayBlobs);//合并后的数组转成⼀个Blob对象。
                   BlobUtls.downloadFileByBlob(fileBlob,this.#fileName)

               //下载完毕后清除数据
                this. #clear()
           }

        },200)
    }
    #clear(){
        let store2 = this.#getTransactionDadaStore()
        let store3 = this.#getTransactionInfoStore()
        store2.clear() //清除本地全下载的数据
        store3.delete("count")//记录清除
        this.#fIleStartReadCount=0 //起始位置
        this.#db=null;
        this.#fileName=null;
        this.#fileSiez=0;
        this.#fIleReadCount=0 //文件读取次数
        this.#fIleStartReadCount=0//文件起始的位置

    }

    //检测是否有分片在本地
    #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){
                    //防止因为网络的原因导致分片损坏,所以不要最后一个分片
                    this.#fIleStartReadCount=count-1;
                }
                resolve();
            }
        })

    }

    /**
     *  样式可以进行修改
     * @param {*} progressId   需要将进度条添加到那个元素下面
     */
    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(){
        //调整进度
        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)

                   //自动调整分片,如果本地以下载了那么从上一次继续下载
               await    this.#checkSliceDoesIsExist()
                     //拿到文件的大小
               await    this.#getFileSize()
                   let begin=0; //开始读取的字节
                   let end=this.#m1; // 结束读取的字节
                   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)
                   }

                   //定时检测存下载的分片数量是否够了
                   this.#checkDownloadInterval= setInterval(()=> {
                       let store = this.#getTransactionDadaStore()
                       let result = store.count()
                       result.onsuccess = (data) => {
                           if (data.target.result === this.#fIleReadCount) {
                               clearInterval(this.#checkDownloadInterval);
                               //如果分片够了那么进行合并下载
                               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;//实际文件目录
    // 获取文件的大小
    @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  到什么位置结束
     * @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;
            //读取文件的指定字节
            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实现断点下载功能的示例代码的详细内容,更多关于Java断点下载的资料请关注我们其它相关文章!

(0)

相关推荐

  • 使用java实现http多线程断点下载文件(一)

    基本原理:利用URLConnection获取要下载文件的长度.头部等相关信息,并设置响应的头部信息.并且通过URLConnection获取输入流,将文件分成指定的块,每一块单独开辟一个线程完成数据的读取.写入.通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中.同时,每个线程写入的数据都文件指针也就是写入数据的长度,需要保存在一个临时文件中.这样当本次下载没有完成的时候,下次下载的时候就从这个文件中读取上一次下载的文件长度,然后继续接着上一次的位

  • 使用java实现http多线程断点下载文件(二)

    下载工具我想没有几个人不会用的吧,前段时间比较无聊,花了点时间用java写了个简单的http多线程下载程序,纯粹是无聊才写的,只实现了几个简单的功能,而且也没写界面,今天正好也是一个无聊日,就拿来写篇文章,班门弄斧一下,觉得好给个掌声,不好也不要喷,谢谢! 我实现的这个http下载工具功能很简单,就是一个多线程以及一个断点恢复,当然下载是必不可少的.那么大概先整理一下要做的事情: 1.连接资源服务器,获取资源信息,创建文件 2.切分资源,多线程下载 3.断点恢复功能 4.下载速率统计 大概就这几

  • 基于Java实现多线程下载并允许断点续传

    完整代码:https://github.com/iyuanyb/Downloader 多线程下载及断点续传的实现是使用 HTTP/1.1 引入的 Range 请求参数,可以访问Web资源的指定区间的内容.虽然实现了多线程及断点续传,但还有很多不完善的地方. 包含四个类: Downloader: 主类,负责分配任务给各个子线程,及检测进度DownloadFile: 表示要下载的哪个文件,为了能写输入到文件的指定位置,使用 RandomAccessFile 类操作文件,多个线程写同一个文件需要保证线

  • java实现文件断点续传下载功能

    本文实例为大家分享了java断点续传下载的代码,供大家参考,具体内容如下 1. Java代码     //实现文件下载功能 public String downloadFile(){ File dir = new File(filepath);//获取文件路劲 if(!dir.exists()) { System.out.println("文件路径错误"); log.debug("文件路径错误"); return "failed";// 判断文件

  • Java中 URL实现断点下载

    复制代码 代码如下: URL ur = new URL("http://localhost:8080/first/he.txt");HttpURLConnection conn = (HttpURLConnection) ur.openConnection();//URL.openConnection() -- >return URLCommection(直接子类HttpURLConnection)conn.setRequestProperty("Range"

  • Java实现多线程断点下载

    JAVA多线程断点下载原理如图: 代码如下: import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.n

  • Java实现断点下载功能的示例代码

    目录 介绍 效果 前端代码 后端代码 介绍 当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的. 具体原理: 利用indexedDb,将下载的数据存储到用户的本地中,这样用户就算是关电脑那么下次下载还是从上次的位置开始的 先去看看本地缓存中是否存在这个文件的分片数据,如果存在那么就接着上一个分片继续下载(起始位置) 下载前先去后端拿文件的大小,然后计算分多少次下载(n/(1024*1024*10)) (结束位置) 每次下载的数据放入一个Blob中,然后存储到本

  • SpringBoot实现文件上传与下载功能的示例代码

    目录 Spring Boot文件上传与下载 举例说明 1.引入Apache Commons FileUpload组件依赖 2.设置上传文件大小限制 3.创建选择文件视图页面 4.创建控制器 5.创建文件下载视图页面 6.运行 Spring Boot文件上传与下载 在实际的Web应用开发中,为了成功上传文件,必须将表单的method设置为post,并将enctype设置为multipart/form-data.只有这种设置,浏览器才能将所选文件的二进制数据发送给服务器. 从Servlet 3.0开

  • Java实现的断点续传功能的示例代码

    代码中已经加入了注释,需要的朋友可以直接参考代码中的注释.下面直接上功能实现的主要代码: import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.Malfor

  • Java实现断点下载服务端与客户端的示例代码

    目录 原理 扩展-大文件快速下载思路 代码 服务端 客户端 最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一下关于贴上关于实现服务端(Spring Boot)与客户端(Android)是如何实现下载续传功能 断点下载功能(下载续传)解释: 客户端由于突然性网络中断等原因,导致的下载失败,这个时候重新下载,可以继续从上次的地方进行下载,而不是重新下载 原理 首先,我们先说明了断点续传的功能,实际上的原理比较简单 客户端和服务端规定好一个规则,客户端传递一个

  • Java使用sftp定时下载文件的示例代码

    sftp简介 sftp是Secure File Transfer Protocol的缩写,安全文件传送协议.可以为传输文件提供一种安全的网络的加密方法.sftp 与 ftp 有着几乎一样的语法和功能.SFTP 为 SSH的其中一部分,是一种传输档案至 Blogger 伺服器的安全方式.其实在SSH软件包中,已经包含了一个叫作SFTP(Secure File Transfer Protocol)的安全文件信息传输子系统,SFTP本身没有单独的守护进程,它必须使用sshd守护进程(端口号默认是22)

  • Java spring boot 实现支付宝支付功能的示例代码

    一.准备工作: 1.登陆支付宝开发者中心,申请一个开发者账号. 地址:https://openhome.alipay.com/ 2.进入研发服务: 3.点击链接进入工具下载页面: 4.点击下载对应版本的RSA公钥生成器: 5.生成公钥密钥(记录你的应用私钥): 6.在支付宝配置公钥(点击保存): 二.搭建demo 1.引入jia包: <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alip

  • Java批量写入文件和下载图片的示例代码

    很久没有在WhitMe上写日记了,因为觉着在App上写私密日记的话肯定是不安全的,但是想把日记存下来.,然后看到有导出日记的功能,就把日记导出了(还好可以直接导出,不然就麻烦点).导出的是一个html文件.可以直接打开,排版都还在. 看了下源码,是把日记存在一个json数组里了,图片还是在服务器,利用url访问,文字是在本地了. 但是想把图片下载到本地,然后和文字对应,哪篇日记下的哪些图片. 大概是如下的json数组. 大概有几百条,分别是头像.内容:文字||内容:图片.时间. 简单明了的jso

  • Java实现断点续传功能的示例代码

    目录 一.题目描述 二.解题思路 三.代码详解 一.题目描述 题目实现:网络资源的断点续传功能. 二.解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节数 输入下载的起始位置和结束位置,开始下载网络资源 如果没有下载完成,可以接着上次的下载位置继续下载 创建一个类:BreakPointSuperveneFrame,继承JFrame窗体类. 定义一个download()方法:用于实现网络资源的断点续传. 核心重点:通过设置请求参数RANGE实现,通过RANGE

  • Java实现FTP文件的上传和下载功能的实例代码

    FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(Application).基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件.在FTP的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)."下载"文件就是从远程主机拷贝文件至自己

  • Java 批量文件压缩导出并下载到本地示例代码

    主要用的是org.apache.tools.zip.ZipOutputStream  这个zip流,这里以Execl为例子. 思路首先把zip流写入到http响应输出流中,再把excel的流写入zip流中(这里可以不用生成文件再打包,只需把execl模板读出写好数据输出到zip流中,并为每次的流设置文件名) 例如:在项目webapp下execl文件中 存在1.xls,2.xls,3.xls文件 1.Controller @RequestMapping(value = "/exportAll&qu

随机推荐