目 录CONTENT

文章目录

Nginx autoindex 多线程分段下载

ABin
2025-04-21 / 0 评论 / 0 点赞 / 29 阅读 / 0 字

主域名 nginx配置


server
{
    listen 80 default_server;
    server_name s*****c;
	index index.html;
    root /smb;
    #CERT-APPLY-CHECK--START
    # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
    include /www/server/panel/vhost/nginx/well-known/smb.abin.cc.conf;
    #CERT-APPLY-CHECK--END
    add_header Cache-Control no-cache;
    add_before_body /DownJS/header.html;
    add_after_body /DownJS/footer.html;
    auth_basic "User Authentication";# 添加这一行
    auth_basic_user_file /******tpasswd; //配置密码路径

    
    #文件访问
    charset utf-8;
    autoindex on;
    autoindex_exact_size off;


    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    #error_page 404/404.html;
    #SSL-END

    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    error_page 404 /404.html;
    #error_page 502 /502.html;
    #ERROR-PAGE-END

    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-80.conf;
    #PHP-INFO-END

    #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    include /www/server/panel/vhost/rewrite/s*****c.conf;
    #REWRITE-END

    #禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }

    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }

    #禁止在证书验证目录放入敏感文件
    if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
        return 403;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }

    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log /dev/null;
        access_log /dev/null;
    }
    access_log  /www/wwwlogs/s*****c.log;
    error_log  /www/wwwlogs/s*****c.error.log;
}

其他双域名配置

server
{
    listen 80;
    server_name h*****c h*****c;
	  index index.html;
    root /smb;

    location / {
      add_header Access-Control-Allow-Origin *;
      add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
      add_header Access-Control-Allow-Headers 'Range';
      add_header Access-Control-Expose-Headers 'Content-Length, Content-Range';
    
      # 如果你需要支持 OPTIONS 预检请求,可以加上:
      if ($request_method = 'OPTIONS') {
          add_header Content-Length 0;
          add_header Content-Type text/plain;
          return 204;
      }
    }

    error_page 404 /404.html;


    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-80.conf;
    #PHP-INFO-END

    #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    include /www/server/panel/vhost/rewrite/s*****c.conf;
    #REWRITE-END

    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }

    access_log  /www/wwwlogs/h*****c.log;
    error_log  /www/wwwlogs/h*****c.error.log;
}

根目录文件

  • DownJS

footer.html

<script>
  document.querySelectorAll('a').forEach(a=>{
    a.addEventListener('click', e=>{
      e.preventDefault();
      parallelDownload(a.href);
    });
  });
</script>

header.html

<script src="/DownJS/parallel.js" defer></script>
<link rel="stylesheet" href="/DownJS/styles.css">

parallel.js

(() => {
    // 创建按钮容器
    const buttonContainer = document.createElement("div");
    buttonContainer.style.cssText = "position:absolute;top:10px;right:10px;z-index:9999;display:flex;gap:10px;";
    
    // 退出按钮
    const exitButton = document.createElement("button");
    exitButton.innerText = "退出";
    exitButton.style.cssText = "padding:5px 10px;background:#f44336;color:white;border:none;border-radius:3px;cursor:pointer;";
    exitButton.onclick = () => {
        window.location.href="http://1@smb.abin.cc:880";
    };
    
    // 清除缓存按钮
    const clearCacheButton = document.createElement("button");
    clearCacheButton.innerText = "清除缓存";
    clearCacheButton.style.cssText = "padding:5px 10px;background:#2196F3;color:white;border:none;border-radius:3px;cursor:pointer;";
    clearCacheButton.onclick = async () => {
        try {
            clearCacheButton.disabled = true;
            clearCacheButton.innerText = "正在清除...";
            
            // 打开数据库
            const db = await openDB();
            
            // 清除所有缓存数据
            await clearAllCache(db);
            
            clearCacheButton.innerText = "缓存已清除";
            setTimeout(() => {
                clearCacheButton.innerText = "清除缓存";
                clearCacheButton.disabled = false;
            }, 2000);
        } catch (error) {
            console.error("清除缓存失败:", error);
            clearCacheButton.innerText = "清除失败";
            setTimeout(() => {
                clearCacheButton.innerText = "清除缓存";
                clearCacheButton.disabled = false;
            }, 2000);
        }
    };
    
    // 添加按钮到容器
    buttonContainer.appendChild(clearCacheButton);
    buttonContainer.appendChild(exitButton);
    
    // 添加容器到页面
    document.body.appendChild(buttonContainer);
})();

async function parallelDownload(url) {
        try {
            // 重置进度条和速度显示
            resetProgress();
            stopSpeedMonitor();
            
            const urlObj = new URL(url);
            const path = urlObj.pathname;
            
            // 判断路径是否以"/"结尾,如果是则直接跳转
            if (path.endsWith('/')) {
                console.log("检测到目录路径,直接跳转:", url);
                window.location.href = url;
                return;
            }
            
            const mirrors = [
                "http://home.abin.cc:880",
                "http://home2.abin.cc:880"
            ];
            const fullUrls = mirrors.map(base => new URL(path, base).toString());

            // 获取文件大小
            const db = await openDB();
            
            // 断点续传:尝试获取已存储的文件大小
            let size = await getStoredFileSize(db, path);
            let isResume = false;
            
            if (!size) {
                // 新下载:获取文件大小
                size = await getFileSize(fullUrls[0]);
                console.log("获取文件大小", size);
                if (isNaN(size) || size <= 0) {
                    throw new Error("文件大小获取失败");
                }
                // 存储文件大小以便断点续传
                await storeFileSize(db, path, size);
            } else {
                console.log("检测到未完成的下载,继续下载文件,大小:", size);
                isResume = true;
            }

            // 对于小于1MB的文件,直接下载
            if (size < 1024 * 1024) {
                console.log("文件小于1MB,直接下载");
                showProgress();
                updateProgress(0);
                
                const response = await fetch(fullUrls[0]);
                if (!response.ok) {
                    throw new Error(`下载失败: ${response.status}`);
                }
                
                const blob = await response.blob();
                const a = document.createElement("a");
                a.href = URL.createObjectURL(blob);
                a.download = decodeURIComponent(path.split("/").pop());
                a.click();
                
                updateProgress(100);
                setTimeout(() => {
                    resetProgress();
                }, 3000);
                return;
            }
            
            // 重新创建进度条UI
            removeProgressBar();
            showProgress(); // 显示进度条
            updateProgress(0); // 确保进度从0开始

            // 为大文件优化分片数量
            let numChunks = 100;
            // 对于大文件(>500MB),增加分片数量,以便更频繁地更新进度
            if (size > 500 * 1024 * 1024) { // 500MB
                numChunks = 200;
            }
            if (size > 1024 * 1024 * 1024) { // 1GB
                numChunks = 500;
            }

            const chunkSize = Math.ceil(size / numChunks);
            console.log(`文件大小: ${formatSize(size)}, 分片数: ${numChunks}, 每个分片大小: ${formatSize(chunkSize)}`);
            
            // 使用双精度浮点数进行进度计算
            const progressState = { 
                downloaded: 0, 
                total: size,
                lastProgressUpdate: Date.now(),
                lastDownloadedBytes: 0,
                bytesPerSecond: 0
            };

            let downloadedChunks = new Array(numChunks).fill(false);
            let chunkData = new Array(numChunks).fill(null);
            // 用于跟踪正在下载的分片
            const downloadingChunks = new Set();

            // 获取已下载的进度(断点续传)
            let totalDownloaded = 0;
            for (let i = 0; i < numChunks; i++) {
                const storedData = await getChunk(db, path, i);
                if (storedData) {
                    chunkData[i] = storedData;
                    downloadedChunks[i] = true;
                    // 确保使用byteLength而不是length
                    totalDownloaded += storedData.byteLength; 
                }
            }
            
            // 正确设置已下载大小
            progressState.downloaded = totalDownloaded;
            progressState.lastDownloadedBytes = totalDownloaded;

            if (progressState.total <= 0) {
                throw new Error("文件大小无效");
            }

            // 计算初始进度百分比
            const initialPercentComplete = (progressState.downloaded / progressState.total * 100).toFixed(2);
            updateProgress(initialPercentComplete);
            console.log("当前下载进度:", initialPercentComplete + "%", `(${formatSize(progressState.downloaded)}/${formatSize(progressState.total)})`);
            
            // 启动高精度进度条更新
            startProgressUpdater(progressState);
            
            // 启动速度监控
            startSpeedMonitor(progressState);

            // 计算待下载的分片索引
            const chunksToDownload = [];
            for (let i = 0; i < numChunks; i++) {
                if (!downloadedChunks[i]) {
                    chunksToDownload.push(i);
                }
            }
            
            console.log(`需要下载 ${chunksToDownload.length} 个分片,共 ${numChunks} 个`);
            
            // 如果没有分片需要下载,直接完成
            if (chunksToDownload.length === 0) {
                console.log("所有分片已下载完成,直接合并");
            } else {
                // 创建下载队列和互斥锁
                // 使用共享队列让所有镜像抢占式地下载分片
                const downloadQueue = [...chunksToDownload];
                
                // 启动多个线路同时下载
                const downloadPromises = mirrors.map((mirror, index) => {
                    return downloadFromMirror(
                        mirror, 
                        path, 
                        downloadQueue, 
                        downloadingChunks,
                        chunkSize, 
                        size, 
                        db, 
                        chunkData, 
                        downloadedChunks, 
                        progressState,
                        index
                    );
                });
                
                // 等待所有下载线路完成
                await Promise.all(downloadPromises);
            }

            // 停止高精度进度条更新
            stopProgressUpdater();
            
            // 检查是否所有分片都已下载
            const allChunksDownloaded = downloadedChunks.every(status => status === true);
            if (!allChunksDownloaded) {
                throw new Error("部分分片下载失败,请重试");
            }

            // ✅ 合并下载的所有分片
            const blobParts = chunkData.map(chunk => new Blob([chunk]));
            const finalBlob = new Blob(blobParts);

            const a = document.createElement("a");
            a.href = URL.createObjectURL(finalBlob);
            a.download = decodeURIComponent(path.split("/").pop());
            a.click();

            updateProgress(100);
            stopSpeedMonitor();

            // 清除缓存
            console.log("下载完成,正在清除缓存...");
            await clearChunks(db, path);
            console.log("缓存清除完成");

            // 延迟重置进度条,让用户能看到100%完成的状态
            setTimeout(() => {
                resetProgress();
            }, 3000);

        } catch (e) {
            console.error("下载出错:", e);
            alert("下载失败:" + e.message);
            stopSpeedMonitor();
            stopProgressUpdater();
        } finally {
            // 确保即使出错也会尝试清理定时器
            stopProgressUpdater();
            
            // 确保即使出错也会尝试清理缓存
            try {
                const db = await openDB();
                await clearChunks(db, path);
                console.log("缓存清理完成");
            } catch (e) {
                console.error("清理缓存出错:", e);
            }
        }
    }
    
    // 从指定镜像抢占式下载分片
    async function downloadFromMirror(mirrorBase, path, downloadQueue, downloadingChunks, chunkSize, totalSize, db, chunkData, downloadedChunks, progressState, mirrorIndex) {
        const mirrorUrl = new URL(path, mirrorBase).toString();
        console.log(`镜像 ${mirrorIndex+1}(${mirrorBase}) 开始下载`);
        
        // 并发数量 - 为大文件增加并发数
        const concurrentLimit = totalSize > 1024 * 1024 * 1024 ? 10 : 5; // 大于1GB时使用10个并发
        let activeDownloads = 0;
        
        // 下载单个分片
        async function downloadChunk(chunkIndex) {
            // 如果该分片已经完成或正在下载,跳过
            if (downloadedChunks[chunkIndex] || downloadingChunks.has(chunkIndex)) {
                return true;
            }
            
            // 标记为正在下载
            downloadingChunks.add(chunkIndex);
            
            try {
                const start = chunkIndex * chunkSize;
                const end = Math.min(start + chunkSize - 1, totalSize - 1);
                
                const res = await fetch(mirrorUrl, {
                    headers: { Range: `bytes=${start}-${end}` }
                });
                
                if (!res.ok && res.status !== 206) {
                    throw new Error(`分段请求失败: 状态码 ${res.status}`);
                }
                
                // 使用流式读取响应,实时更新进度
                const reader = res.body.getReader();
                let receivedLength = 0;
                const chunks = [];
                
                while (true) {
                    const { done, value } = await reader.read();
                    
                    if (done) {
                        break;
                    }
                    
                    chunks.push(value);
                    receivedLength += value.length;
                    
                    // 实时更新下载进度
                    progressState.downloaded += value.length;
                    
                    // 计算下载速度
                    const now = Date.now();
                    const elapsed = now - progressState.lastProgressUpdate;
                    if (elapsed > 1000) { // 每秒更新一次速度
                        const bytesDownloaded = progressState.downloaded - progressState.lastDownloadedBytes;
                        progressState.bytesPerSecond = Math.round(bytesDownloaded * 1000 / elapsed);
                        progressState.lastDownloadedBytes = progressState.downloaded;
                        progressState.lastProgressUpdate = now;
                    }
                }
                
                // 合并所有块
                const chunksAll = new Uint8Array(receivedLength);
                let position = 0;
                for (const chunk of chunks) {
                    chunksAll.set(chunk, position);
                    position += chunk.length;
                    
                }
                
                // 转换为ArrayBuffer
                const buffer = chunksAll.buffer;
                
                // 存储和标记为完成
                chunkData[chunkIndex] = buffer;
                await storeChunk(db, path, chunkIndex, buffer);
                downloadedChunks[chunkIndex] = true;
                
                console.log(`镜像 ${mirrorIndex+1} 下载分片 ${chunkIndex} 完成,总进度:${(progressState.downloaded / progressState.total * 100).toFixed(2)}%`);
                
                return true;
            } catch (error) {
                console.error(`镜像 ${mirrorIndex+1} 下载分片 ${chunkIndex} 失败:`, error);
                return false;
            } finally {
                // 无论成功失败,都移除正在下载标记
                downloadingChunks.delete(chunkIndex);
                activeDownloads--;
            }
        }
        
        // 主下载循环
        while (downloadQueue.length > 0 || activeDownloads > 0) {
            // 如果当前下载数量未达到上限且队列中有任务
            while (activeDownloads < concurrentLimit && downloadQueue.length > 0) {
                // 从队列中取出一个分片索引
                const chunkIndex = downloadQueue.shift();
                
                // 如果该分片已完成,跳过
                if (downloadedChunks[chunkIndex]) {
                    continue;
                }
                
                // 如果该分片正在被其他镜像下载,放回队列末尾
                if (downloadingChunks.has(chunkIndex)) {
                    downloadQueue.push(chunkIndex);
                    continue;
                }
                
                // 开始下载
                activeDownloads++;
                downloadChunk(chunkIndex).catch(err => {
                    console.error(`处理分片下载异常:`, err);
                    // 下载失败,将分片放回队列
                    if (!downloadedChunks[chunkIndex]) {
                        downloadQueue.push(chunkIndex);
                    }
                });
            }
            
            // 等待一小段时间再继续检查
            await new Promise(resolve => setTimeout(resolve, 100));
            
            // 如果队列为空但还有活动下载,等待它们完成
            if (downloadQueue.length === 0 && activeDownloads > 0) {
                await new Promise(resolve => setTimeout(resolve, 500));
            }
            
            // 如果所有分片都已下载完成,退出循环
            if (downloadedChunks.every(status => status === true)) {
                break;
            }
        }
        
        console.log(`镜像 ${mirrorIndex+1}(${mirrorBase}) 下载任务完成`);
    }

    // 获取文件大小
    async function getFileSize(url) {
        try {
            const res = await fetch(url, { 
                method: "GET",
                headers: { Range: "bytes=0-0" }  // 只请求第一个字节
            });
            if (!res.ok) throw new Error("无法获取文件大小");
            const contentRange = res.headers.get("content-range");
            if (contentRange) {
                // 从Content-Range头中提取总大小
                const totalSize = parseInt(contentRange.split("/")[1], 10);
                if (!isNaN(totalSize)) {
                    return totalSize;
                }
            }
            // 如果没有Content-Range,尝试从Content-Length获取
            const contentLength = res.headers.get("content-length");
            if (contentLength) {
                return parseInt(contentLength, 10);
            }
            throw new Error("无法获取文件大小");
        } catch (e) {
            console.error("获取文件大小失败:", e);
            throw new Error("无法获取文件大小");
        }
    }

    // 显示进度条
    function showProgress() {
        let container = document.getElementById("progress-container");
        if (!container) {
            container = document.createElement("div");
            container.id = "progress-container";
            container.style.cssText = "width:100%;background:#eee;border-radius:5px;overflow:hidden;height:24px;font-family:sans-serif;";
            
            const bar = document.createElement("div");
            bar.id = "progress-bar";
            bar.style.cssText = "height:100%;width:0%;background:#4caf50;text-align:center;color:white;line-height:24px;float:left;";
            
            const speed = document.createElement("div");
            speed.id = "speed-display";
            speed.style.cssText = "position: absolute;right:0;float:right;padding-right:10px;color:#333;font-size:14px;line-height:24px;";
            speed.innerText = "速度:0 KB/s";

            container.appendChild(bar);
            container.appendChild(speed);
             // ✅ 插入到 <h1> 和 <hr> 之间
            const h1 = document.querySelector("h1");
            const hr = document.querySelector("hr");
            if (h1 && hr) {
                hr.parentNode.insertBefore(container, hr);
            } else {
                document.body.appendChild(container); // fallback
            }
        }
    }

    // 更新下载进度
    function updateProgress(percent) {
        const bar = document.getElementById("progress-bar");
        if (bar) {
            // 确保百分比是数字并且精确到小数点后2位
            const displayPercent = typeof percent === 'string' ? percent : percent.toFixed(2);
            
            bar.style.width = displayPercent + "%";
            
            // 进度条文字内容
            if (parseFloat(displayPercent) >= 100) {
                bar.innerText = '下载完成。正在弹窗中...';
            } else if (parseFloat(displayPercent) <= 0) {
                bar.innerText = "准备下载...";
            } else {
                bar.innerText = displayPercent + "%";
            }
        }
    }

    // 打开 IndexedDB
    function openDB() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open("downloadDB", 2);
            request.onupgradeneeded = (event) => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains("chunks")) {
                    db.createObjectStore("chunks");
                }
                if (!db.objectStoreNames.contains("metadata")) {
                    db.createObjectStore("metadata");
                }
            };
            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event.target.error);
        });
    }

    // 存储文件大小
    function storeFileSize(db, path, size) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(["metadata"], "readwrite");
            const store = transaction.objectStore("metadata");
            store.put(size, `${path}_size`);
            transaction.oncomplete = () => resolve();
            transaction.onerror = (event) => reject(event.target.error);
        });
    }

    // 获取存储的文件大小
    function getStoredFileSize(db, path) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(["metadata"]);
            const store = transaction.objectStore("metadata");
            const request = store.get(`${path}_size`);
            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event.target.error);
        });
    }

    // 存储数据到 IndexedDB
    function storeChunk(db, path, chunkIndex, chunkData) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(["chunks"], "readwrite");
            const store = transaction.objectStore("chunks");
            store.put(chunkData, `${path}_chunk_${chunkIndex}`);
            transaction.oncomplete = () => resolve();
            transaction.onerror = (event) => reject(event.target.error);
        });
    }

    // 从 IndexedDB 获取数据
    function getChunk(db, path, chunkIndex) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(["chunks"]);
            const store = transaction.objectStore("chunks");
            const request = store.get(`${path}_chunk_${chunkIndex}`);
            request.onsuccess = (event) => {
                const result = event.target.result;
                resolve(result); // 可能是null,这是正常的
            };
            request.onerror = (event) => {
                console.error(`获取分片${chunkIndex}失败:`, event.target.error);
                reject(event.target.error);
            };
        });
    }

    // 清除下载的分片和元数据
    function clearChunks(db, path) {
        return new Promise((resolve, reject) => {
            try {
                // 清除分片数据
                const chunksTransaction = db.transaction(["chunks"], "readwrite");
                const chunksStore = chunksTransaction.objectStore("chunks");
                
                // 获取所有键
                const getAllKeysRequest = chunksStore.getAllKeys();
                getAllKeysRequest.onsuccess = () => {
                    const keys = getAllKeysRequest.result;
                    const pathPrefix = `${path}_chunk_`;
                    let deletedCount = 0;
                    
                    // 删除与当前路径相关的所有分片
                    keys.forEach(key => {
                        if (typeof key === 'string' && key.startsWith(pathPrefix)) {
                            chunksStore.delete(key);
                            deletedCount++;
                        }
                    });
                    
                    console.log(`已清除${deletedCount}个分片缓存`);
                };
                
                chunksTransaction.oncomplete = () => {
                    // 清除元数据
                    const metadataTransaction = db.transaction(["metadata"], "readwrite");
                    const metadataStore = metadataTransaction.objectStore("metadata");
                    metadataStore.delete(`${path}_size`);
                    
                    metadataTransaction.oncomplete = () => {
                        console.log(`已清除文件大小元数据缓存`);
                        resolve();
                    };
                    metadataTransaction.onerror = (event) => {
                        console.error("清除元数据失败:", event.target.error);
                        reject(event.target.error);
                    };
                };
                
                chunksTransaction.onerror = (event) => {
                    console.error("清除分片数据失败:", event.target.error);
                    reject(event.target.error);
                };
            } catch (error) {
                console.error("清除缓存时出错:", error);
                reject(error);
            }
        });
    }

    // 速度监控
    let speedTimer = null;
    let lastDownloaded = 0;

    function startSpeedMonitor(progressState) {
        lastDownloaded = progressState.downloaded;
        speedTimer = setInterval(() => {
            const delta = progressState.downloaded - lastDownloaded;
            lastDownloaded = progressState.downloaded;

            const speedInBytes = delta;
            progressState.bytesPerSecond = speedInBytes;
            updateSpeedDisplay(speedInBytes);
        }, 1000); // 每秒更新一次
    }

    function stopSpeedMonitor() {
        clearInterval(speedTimer);
        speedTimer = null;
    }

    // 重置进度条
    function resetProgress() {
        const bar = document.getElementById("progress-bar");
        const speedDisplay = document.getElementById("speed-display");
        
        if (bar) {
            bar.style.width = "0%";
            bar.innerText = "准备下载...";
        }
        
        if (speedDisplay) {
            speedDisplay.innerText = "速度:0 KB/s";
        }
        
        lastDownloaded = 0;
    }

    // 完全移除进度条,下次重新创建
    function removeProgressBar() {
        const container = document.getElementById("progress-container");
        if (container) {
            container.remove();
        }
    }

    // 高精度进度条更新器
    let progressUpdateTimer = null;
    
    function startProgressUpdater(progressState) {
        // 停止现有定时器
        stopProgressUpdater();
        
        // 创建新的高频率定时器
        progressUpdateTimer = setInterval(() => {
            if (progressState && progressState.total > 0) {
                const percent = (progressState.downloaded / progressState.total * 100).toFixed(2);
                updateProgress(percent);
                
                // 更新速度显示
                updateSpeedDisplay(progressState.bytesPerSecond);
            }
        }, 10); // 每10毫秒更新一次
    }
    
    function stopProgressUpdater() {
        if (progressUpdateTimer) {
            clearInterval(progressUpdateTimer);
            progressUpdateTimer = null;
        }
    }

    function updateSpeedDisplay(bytesPerSecond) {
        const speedDisplay = document.getElementById("speed-display");
        if (!speedDisplay) return;
        
        const speedText = formatSpeed(bytesPerSecond);
        speedDisplay.innerText = speedText;
    }

    // 格式化文件大小
    function formatSize(bytes) {
        if (bytes === 0) return '0 B';
        
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
    
    // 格式化速度
    function formatSpeed(bytesPerSecond) {
        if (bytesPerSecond === 0) return '速度:0 KB/s';
        
        if (bytesPerSecond >= 1024 * 1024) {
            return `速度:${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
        } else {
            return `速度:${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
        }
    }

    // 清除所有缓存数据
    async function clearAllCache(db) {
        return new Promise((resolve, reject) => {
            try {
                console.log("开始清除所有缓存...");
                
                // 清除所有分片数据
                const chunksTransaction = db.transaction(["chunks"], "readwrite");
                const chunksStore = chunksTransaction.objectStore("chunks");
                const chunksRequest = chunksStore.clear();
                
                chunksTransaction.oncomplete = () => {
                    // 清除所有元数据
                    const metadataTransaction = db.transaction(["metadata"], "readwrite");
                    const metadataStore = metadataTransaction.objectStore("metadata");
                    const metadataRequest = metadataStore.clear();
                    
                    metadataTransaction.oncomplete = () => {
                        console.log("所有缓存已清除");
                        // alert("所有下载缓存已清除!");
                        resolve();
                    };
                    
                    metadataTransaction.onerror = (event) => {
                        console.error("清除元数据失败:", event.target.error);
                        reject(event.target.error);
                    };
                };
                
                chunksTransaction.onerror = (event) => {
                    console.error("清除分片数据失败:", event.target.error);
                    reject(event.target.error);
                };
            } catch (error) {
                console.error("清除缓存时出错:", error);
                reject(error);
            }
        });
    }

style.css

body {
    font-size: x-large;
}
button {
    position:relative;
    top:10px;
    right:10px;
    z-index:9999;
    padding:8px 14px;
    font-size:14px;
    background:red;
    color:white;
    border:none;
    border-radius:6px;
    cursor:pointer;
    box-shadow:0 2px 4px rgba(0,0,0,0.2);
}

0

评论区