Java实现断点下载功能的示例代码(java断点续传下载)

网友投稿 437 2022-07-27


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

介绍

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

具体原理:

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

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

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

难点都在前端:

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

效果

前端代码

class BlobUtls{

// blob转文件并下载

static downloadFileByBlob(blob, fileName = "file") {

let blobUrl = window.URL.createObjectYTxAUvrIURL(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.#fileNaYTxAUvrIme=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(){

lehttp://t 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

this.#percent=percent1;

}

this.#dynamicProgress()

resolve()

}

}

})

}

#mergeCallback(){

// 读取全部字节到blob里,处理合并

let arrayBlobs = [];

let store1 = this.#getTransactionDadaStore()

//按顺序找到全部的分片

for (let i = 0; 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.onYTxAUvrIsuccess=(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断点下载的资料请关注我们其它相关文章!

this.#percent=percent1;

}

this.#dynamicProgress()

resolve()

}

}

})

}

#mergeCallback(){

// 读取全部字节到blob里,处理合并

let arrayBlobs = [];

let store1 = this.#getTransactionDadaStore()

//按顺序找到全部的分片

for (let i = 0; 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.onYTxAUvrIsuccess=(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断点下载的资料请关注我们其它相关文章!

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.onYTxAUvrIsuccess=(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断点下载的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:MQ的消息模型及在工作上应用场景(mqtt模型)
下一篇:JAVA使用Ip2region获取IP定位信息的操作方法
相关文章