前端下载功能怎么实现?

1.项目使用window.open下载时,并没有携带token,容易被盗链下载,况且使用这个api,后端没有办法校验token,把token拼接在url后面还是一样的,前端直接去对应地址取文件,后端并没办法取到token;
2.使用正常的接口下载,blob格式的,由于文件比较大,经常会出现数百M或者上G的大小,在下载过程中,如果页面被刷新也会失败;好像还不能用分片下载,因为文件可能是安装包,有可能会在关键位置切断,导致安装包失效。
就是这样的问题,想咨询下大佬们,还有什么办法处理这个下载吗?
拜谢!!!

阅读 3k
5 个回答

服务器必须支持 HTTP Range 请求。可以发送一个 HEAD 请求来检查响应头中是不是包含 Accept-Ranges: bytes 来确认:

// 大概思路没有加错误处理和重试逻辑

const url = 'http://example.com/big-file';
const chunkSize = 1024 * 1024 * 10; // 10 MB
let start = 0;
let end = start + chunkSize;
let chunks = [];

fetchSize(url).then(size => {
    downloadChunk(url, start, end);

    function downloadChunk(url, start, end) {
        fetch(url, {
            headers: {
                'Range': `bytes=${start}-${end}`
            }
        })
        .then(response => response.arrayBuffer())
        .then(data => {
            chunks.push(data);

            if (end < size) {
                start += chunkSize;
                end = Math.min(end + chunkSize, size);
                downloadChunk(url, start, end);
            } else {
                const blob = new Blob(chunks);
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'big-file';
                a.click();
            }
        });
    }
});

function fetchSize(url) {
    return fetch(url, {
        method: 'HEAD'
    })
    .then(response => {
        const size = response.headers.get('Content-Length');
        return Number(size);
    });
}

用 HTTP Range Requests 来实现分片下载。这种方法可以让客户端请求文件的特定部分,而不是整个文件。这样,就算在下载过程中出现问题,也可以从上次的位置重新开始,而不是从头开始,服务端要支持,如果其中一个文件部分的下载失败,你应该让用户重试下载,直到成功才行,最后你要用一种方法来合并已下载的文件部分。可以用 File API、Blob 对象或者 Stream API 。

新手上路,请多包涵

点击按钮下载时,先调用接口创建一次性 token,然后 window.open 带上一次性 token 在 url 中去下载文件。这样既能鉴权,又能直接下载。

先鉴权,成功后返回文件地址,再 open ?

那就用户点击下载,你生成一个一次性的下载地址。可以解决问题嘛?

只需要动态生成一段form表单,target指向一个iframe,浏览器下载,刷新也没事,用form想传几个参数都没问题。

downFileIframe(url, data){//文件下载通过iframe实现
        let iframe = document.createElement('iframe')
        const name = 'xxx'//这里最好是动态的,避免多个文件同时下载冲突
        iframe.name = name
        iframe.style.position = 'fixed'
        iframe.style.left = '10000px'
        document.body.appendChild(iframe)
        let form = document.createElement('form')
        form.action = url
        form.method = data ? 'POST' : 'GET'
        form.target = name
        if (data){
            for (let k in data){
                const input = document.createElement('input')
                input.name = k
                input.value = data[k]
                form.appendChild(input)
            }
        }
        document.body.appendChild(form)
        form.submit()
        document.body.removeChild(form)//提交完这个form就可以删了
        //iframe想删的话,延迟点儿时间,应为删的过快会造成还没开始下载就中断了请求
    }

window.open也能实现,就不需要iframe了,新建一个_blank空的标签页,然后动态改变内容为form表单,表单提交,搞定。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题