检测用户何时同意下载文件

25

当用户点击一个允许其下载文件的超链接时,我希望能将其重定向到另一个网页。然而,由于他们需要在打开/保存文件对话框中进行选择,所以在用户接受下载前,我不想将其重定向。

如何检测用户是否执行了此操作?


2
在浏览器中,无法钩入文件保存对话框。这是由于明显的安全问题所致。 - charlietfl
很遗憾,你无法这样做,因为该接口不在浏览器内,所以你无法处理它!! - PacMan
正如之前所说,这是一个安全问题,但您可以找到并考虑许多解决方法,我在stackoverflow上看到过类似的解决方案:https://dev59.com/2XNA5IYBdhLWcg3wEZaT#4168965 - Amr Elgarhy
1
根据文件的不同,您可以挑战文件的一部分,例如“文档的第三个单词是什么”? - dandavis
7个回答

31

据我多年维护download.js的经验,从JavaScript(或一般情况下也如此)无法确定用户选择下载对话框后要执行的操作。虽然这是一个常见的功能请求,但我可以自信地说这是不可能的。如果有人可以演示出机械化的方法来确定任何文件后续动作,我将愉快地支付比这个赏金高10倍的费用。

此外,问题并不仅仅在于JS规则,浏览器下载和提示文件的方式也使问题更加复杂。这意味着即使服务器也不能总是知道发生了什么。可能有一些特定情况的解决方法,但它们并不美观或简单。

你可以强制用户使用<input type=file>重新上传已下载的文件进行验证,但这很繁琐,而且本地文件浏览对某些用户可能会产生警惕。这是确保下载的唯一可靠方法,但对于非敏感应用程序,它非常严格,并且在一些缺少文件支持的“移动”平台上无法使用。

你还可以在服务器端尝试监视并推送消息到客户端,告诉客户端文件被服务器访问了。但是,下载在打开/保存对话框出现时就开始下载,尽管在后台不可见。这就是为什么如果你等待一会儿才“接受”大文件,它看起来似乎很快下载完毕。从服务器的角度来看,无论用户做什么,活动都是相同的。

对于一个巨大的文件,你可能可以检测到整个文件未被传输,这意味着用户点击了“取消”,但这是一个复杂的同步过程,需要使用套接字、PHP消息传递、EventSource等进行大量定制编程,而收益却很小。它还存在时间竞赛的问题,而且时间长度也不确定;减慢下载速度并不利于用户满意度。

如果文件很小,它将在用户查看对话框之前进行物理下载,因此服务器将变得无用。还要考虑到一些下载管理扩展会接管该任务,它们不能保证与原始浏览器的行为相同。强制等待可能对那些需要“永远”才能“完成”下载的慢硬盘用户而言是危险的;我们都经历过这种情况,当“旋转”停下来时无法继续操作,这肯定会降低用户满意度。
简而言之,除了你知道会花费很长时间下载的大文件外,在一般情况下没有简单的方法可以实现下载进度条。我已经花费了很多心血尝试为我的download.js用户提供这个功能,但实际上没有好的选择。Ryan Dahl最初编写Node.js是为了提供上传进度条给他的用户,也许有人会制作一个服务器/客户端包,使得为下载提供相同功能变得容易。

9
这是一个不太正规的解决方案。
我的StreamSaver库不使用blob来通过a[download]下载文件。它使用service worker来模拟服务器使用content-disposition附件头处理下载的方式,将内容流式传输到磁盘上。
evt.respondWith(
  new Response(
    new ReadableStream({...})
  )
)

现在,您无法确切知道用户在对话框中按下了什么,但您可以获得有关流的一些信息。如果用户在对话框中按下取消或中止正在进行的下载,则流也会中止。
保存按钮更棘手。但让我们从流存储桶的高水位线能告诉我们什么开始。
在我的种子示例中,我记录writer.desiredSize。它是它愿意接收多少数据的相关性。当您向流写入内容时,它会降低所需大小(无论是计数还是字节策略)。如果它永远不增加,则表示用户可能已暂停下载。当它下降到0以下时,您正在写入比用户要求的更多数据。
每个块写操作都返回一个Promise。
writer.getWriter().write(uint8).then(() => {
  // Chunk have been sent to the destination bucket
  // and desiredSize increase again 
})

当桶未满时,该承诺将被解决。但这并不意味着块已经写入磁盘,它只意味着块已从一个流传递到另一个流(从写入 -> 可读取 -> 响应),通常会在流的开头和另一个更早的块被写入磁盘时这样做。

如果所有数据都可以适合桶(内存)中,则写入流甚至可以在用户作出选择之前完成

调整桶大小小于数据大小可能有所帮助

因此,您可以对以下内容进行假设

  • 下载开始
  • 完成
  • 暂停 但是您无法确定,因为您不会收到任何事件(除了关闭流的中止)

请注意,如果没有可转移流支持,则种子示例不会显示正确的大小,但如果您在服务工作者中完成所有操作(而不是在主线程中完成),则可以解决此问题。

检测流何时结束就像这样简单

readableStream.pipeTo(fileStream).then(done)

未来参考WICG/native-file-system可能会让您访问磁盘并写入文件,但在继续之前必须解决提示对话框承诺,这可能正是用户要求的。

如果您有兴趣,有保存blob为流的示例,甚至可以将多个 blob作为zip保存。


2
考虑到用户已经或应该知道在进程的下一步骤之前应下载文件,用户应该期望某种形式的确认已下载文件。
您可以通过使用具有设置为修改后的文件名的“download”属性的元素来创建唯一的标识符或时间戳,并将其包括在下载的文件名中。

一些想法:考虑验证日期和大小,这很容易只用元数据就能完成(不需要FileReader)。更安全的做法是对文件进行MD5处理,然后与服务器提供的值进行比较。或者,将密码包含在下载时压缩的文本文件中。 - dandavis
@dandavis 是的,正如所提到的,该程序可能需要进行调整、改进、精细化甚至加强。回答中的方法是一个概念验证的草稿,用户意识到并配合了这个过程。虽然有时候确实会出现这种情况,但人们经常试图绕过程序。 - guest271314
考虑到给定的条件,我认为这个解决方案很棒,尽管这只是我在非常罕见的情况下才会使用的方法。毕竟,对于用户来说非常笨拙。 - Magnus

0

最近我涉足了一个项目,需要我指定用户是否可以上传特定类型的文件,例如(用户可以上传png但不能上传pdf)。

也许我没有使用最有效的方法,但最终我做的是编写了一个小型内置“Web应用程序”,作为上传或下载的文件浏览器。

我想最接近的例子,而不泄露我的“秘密项目”,可能是https://encodable.com/filechucker/

也许你可以编写一个简单的集成文件浏览器,就像云服务有时使用的那样(例如Dropbox),并具有检测带有自定义框和其他内容的输入的一些功能。

这只是一些想法。


0

window.showSaveFilePicker 是文件系统访问 API 中的函数,可以满足你的需求。但不幸的是,目前只有 Chrome 和 Edge 支持它。它返回一个 Promise--如果用户选择下载文件,则 Promise 将被解析;如果他们取消,则会引发 AbortError


0

jquery.fileDownload 允许您做到这一点:

$(document).on("click", "a.fileDownloadPromise", function () {
    $.fileDownload($(this).prop('href'))
        .done(function () { alert('File download a success!'); })
        .fail(function () { alert('File download failed!'); });

    return false; 
});

请看Github:

https://github.com/johnculviner/jquery.fileDownload


抱歉,我认为在Plunker或JSFiddle上不可能实现。要运行此解决方案,您必须将可下载文件托管在同一域中,并且必须在下载响应中返回一个cookie。 - Rafael Z.
4
这个库只能告诉你在下载时是否出现了服务器错误,无法知道用户在对话框中做了什么。即使一个文件正在以200的状态下载,并不意味着用户正在保存或打开它。 - dandavis

-1

试试这个:

<html>
<head>
<script type="text/javascript">
    function Confirmation(pg) {
        var res = confirm("Do you want to download?");
        if(res){
            window.open(pg,"_blank");
        }
        return res;
    }
</script>
</head>
<body>
  <a href="http://yoursite/download.zip" onclick="return Confirmation('http://yoursite/redirect.html');">download</a>
</body>
</html>

这种方法是否强制下载文件? - guest271314
抱歉,您无法强制客户下载文件。 - Yousef Rahimy Akhondzadeh
此代码为您的客户提供选择,如果他们同意下载您的文件,则将重定向到目标URL,否则他们无法访问目标URL。但是,您无法强制他们下载。 - Yousef Rahimy Akhondzadeh
这个问题询问如何检测客户端是否实际下载了文件,是吗? - guest271314
没有问题是当用户“接受”下载文件时检测。这不是“强制”。 - Yousef Rahimy Akhondzadeh
是的,答案中的方法如何检测用户是否实际下载了文件? - guest271314

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接