- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的。
具体原理:
利用indexedDb,将下载的数据存储到用户的本地中,这样用户就算是关电脑那么下次下载还是从上次的位置开始的
(起始位置)
(n/(1024*1024*10))
(结束位置)有很多人说必须使用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 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!