文件下载及进度条

2022/12/12

# 文件下载

# 带进度条下载

本地启动服务器,且准备下载文件 video.mp4






















 








const express = require('express');
const app = express();
const path = require("path");
const fs = require("fs");
app.listen('8888', () => console.log('listen 8888'));

const bdParser = require('body-parser');
app.use(bdParser.urlencoded({ extended: true }));
app.use(bdParser.json());

// 允许跨域中间件
const allowCors = function (req, res, next) {
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Credentials', 'true');
    next();
};
app.use(allowCors); // 使用跨域中间件

//
const file = fs.readFileSync(path.join(__dirname, "./video.mp4"));
app.get("/size", (req, res) => {
    const size = file.length;
    res.send({ size: file.length, type: "video/mp4" })
})
app.get("/file", (req, res) => {
    res.send(file);
});
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
















 
 
 
 
 
 
 
 
 
 
 
 




































<body>
    <button id="downloadBtn1">下载</button>
    <input type="range" value="0" id="process1" disabled>

    <button id="downloadBtn2">下载</button>
    <input type="range" value="0" id="process2" disabled>


    <script>
        const listener = (btn, processEl) => {
            let totleSize;

            const loopLoad = async (reader) => {
                let len = 0;
                const chunks = [];

                // 重点:轮询拿到 chunks 返回 Uint8Array 数据
                while (true) {
                    const { value, done } = await reader.read();
                    if (done) {
                        const u8Arr = new Uint8Array(len);
                        chunks.reduce((offset, chunk) => {
                            u8Arr.set(chunk, offset);
                            return offset + chunk.length;
                        }, 0);
                        processEl.value = 100;
                        return u8Arr;
                    }
                    chunks.push(value);
                    len += value.length;
                    processEl.value = Math.floor(len / totleSize * 100);
                }
            }
            const downloadData = async () => {
                const { size, type } = await fetch("http://127.0.0.1:8888/size").then(res => res.json());
                totleSize = size;
                const response = await fetch("http://127.0.0.1:8888/file");

                // 重点:读取进度,但是body只能读取一次,所以不能执行 res.json
                const reader = response.body.getReader();
                const unit8Array = await loopLoad(reader); // 直接拿到 u8Array 类型
                const blob = new Blob([unit8Array], { type });
                //

                const a = document.createElement("a");
                a.download = `p${fileNameMap[type]}`;
                a.href = window.URL.createObjectURL(blob); // 将缓存转化成 url
                a.click();
            };
            btn.addEventListener("click", downloadData);
            return downloadData;
        }
        listener(downloadBtn1, process1);
        listener(downloadBtn2, process2);
    </script>

    <script>
        // 文件类型和尾缀 map
        const fileNameMap = {
            "video/mp4": ".mp4",
        };
    </script>
</body>
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
上次更新: 6/13/2025