服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java实现断点下载功能的示例代码

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

2022-12-29 16:17胡安民 Java教程

当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的。本文将用Java语言实现断点下载,需要的可以参考一下

介绍

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

具体原理:

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

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

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

难点都在前端:

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

效果

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

前端代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!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>
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//导包要从项目全路径开始,也就是最顶部
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;

后端代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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断点下载的资料请关注服务器之家其它相关文章!

原文链接:https://blog.csdn.net/weixin_45203607/article/details/124998112

延伸 · 阅读

精彩推荐