介绍

当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的。
具体原理:
利用indexedDb,将下载的数据存储到用户的本地中,这样用户就算是关电脑那么下次下载还是从上次的位置开始的

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

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

难点都在前端:

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

效果

前端代码

<!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.resultif (!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.resultresolve()}})}#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?.countif(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.#m1if(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(end>=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-断点下载(分片下载)相关推荐

  1. java断点上传下载_java实现多线程断点续传,上传下载 分享

    程序采用的ftp工具, apache 的 commons-net-ftp-ftpclient package com.ftp; import java.io.File; import java.io. ...

  2. java多线程下载源码_Java多线程文件分片下载实现的示例代码

    多线程下载介绍 多线程下载技术是很常见的一种下载方案,这种方式充分利用了多线程的优势,在同一时间段内通过多个线程发起下载请求,将需要下载的数据分割成多个部分,每一个线程只负责下载其中一个部分,然后将下 ...

  3. Java Web 实现文件多线程分片下载方案

    背景需求 最近发现系统中有不少功能的下载文件涉及到较大文件 当超过1G的文件下载时,直接通过浏览器下载,可能出现下载失败现象 下载失败表现为下载文件损坏,或重复重试下载 大文件的下载会因为网络波动.会 ...

  4. SpringBoot Java实现Http方式分片下载断点续传+实现H5大视频渐进式播放

    项目Git地址:h5-video 一.功能目的 SpringBoot 实现Http分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文 ...

  5. java断点下载工具,RxDownload

    RxDownload The download tool based on RxJava . Support multi-threaded download and breakpoint downlo ...

  6. Java字节流与字符流,断点续传实现分片下载、上传、合并

    文章目录 一:字节流和字符流详解 1.1 流的概念 1.2 流的分类 1.3 字节流,字符流区别与使用场景 1.3.1 区别 1.3.2 使用场景 1.3.3 顶级父类 1.3.4 对比--总结对比字 ...

  7. python 断点下载_python多进程断点续传分片下载器

    标签:python 下载器 多进程 因为爬虫要用到下载器,但是直接用urllib下载很慢,所以找了很久终于找到一个让我欣喜的下载器.他能够断点续传分片下载,极大提高下载速度. #! /usr/bin/ ...

  8. Springboot分片下载实现

    DownloadClient  是模拟前端,若前端实现需要worker线程JS插件方法 DownLoadController 是后端服务 package org.xxx.wx.web;import o ...

  9. 用Electron开发企业网盘(二)--分片下载

    书接上文,背景见:https://www.cnblogs.com/shawnyung/p/10060119.html HTTP请求头  Range 请求资源的部分内容(不包括响应头的大小),单位是by ...

  10. python写http文件下载器_http分片请求-python分片下载文件

    源文件 http://theday.guohongfu.top/letter.txt内容为abcdefghijklmnopqrstuvwxyz 获取第20字节及以后的内容import requests ...

最新文章

  1. OpenCv 005---图像像素的算术操作
  2. Linux安装solr
  3. qr分解求线性方程组_计算方法/数值分析第三章 线性方程组的数值解法
  4. ado mysql 读写_C#使用Ado.Net读写数据库
  5. [转载]JSONP跨域的原理解析
  6. DeepDream网络
  7. 已经被废弃的 tcp_tw_recycle
  8. 《微观经济学》第三章相互依存性与贸易的好处
  9. php微信实现红包雨,怎么制作微信红包雨(微信红包雨特效)
  10. SQL Server数据类型一览表
  11. RiruEdxposed学习研究(四)Magisk(面具)源码下载编译详细实战教程
  12. 大话西游2人数最多服务器,大话西游2:全服最火服务器!凌烟阁人山人海遍地200级玩家...
  13. Python错误集锦:除法运算时提示ZeroDivisionError: division by zero
  14. 计算机桌面屏幕显示不到右边,电脑回收站打不开怎么办 电脑显示器右边有黑边怎么办...
  15. 【转】加油站压力/真空阀(PV阀)的工作原理及安全注意事项
  16. Mac上使用Emacs
  17. 【java】java学习笔记之java oop(面向对象)
  18. 如何通俗的理解函数的极限_(高等数学笔记)萌新也能理解的函数极限求法
  19. Java8里不得不说的那些常用日期处理,码起来~
  20. 用化学绘图软件写带括号的分子式的方法

热门文章

  1. c语言城市交通灯优化,城市智能交通灯系统(本科)毕业论文.doc
  2. 学做‘视频剪辑’攻略
  3. 【23】数据可视化:基于 Echarts + Python 动态实时大屏范例 - Redis 数据源
  4. 浅学transcad(与表格链接以及创建矩阵OD并显示期望线)
  5. 如何在网上买到下铺票2020_网上订票怎么选下铺
  6. Java实现中国象棋(人机对战)
  7. 美观实用的BeautifulReport测试报告
  8. Live reload
  9. Cat 6 的网线确实不好做
  10. SMB/CIFS--NetBOIS/Browser/NBNS 协议