You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP如何实现后台倒计时 延迟输出下载直链防止审查元素获取

核心认知纠正

你当前的思路存在一个本质错误:只要是最终发送到用户浏览器的内容,不管是PHP执行完一次性输出、还是JS/AJAX延迟加载的,用户都能通过审查元素、网络抓包拿到,不存在「PHP输出的内容用户无法提前查看」的情况。
你写的带sleep(10)的代码之所以达不到预期,是因为PHP作为服务端语言,会把整个脚本执行完成后才把完整响应返回给浏览器,加sleep()只会导致用户打开页面时白屏等待10秒,之后所有内容(包括前面的段落、延迟输出的链接脚本)才会一起渲染,根本做不到先显示提示、延迟出链接的效果,这是HTTP请求响应的基本逻辑,靠前端隐藏、服务端延迟输出的方式藏直链本质都是掩耳盗铃。

正确实现思路

不要试图「藏住固定直链」,要把思路转成让用户提前拿到链接也无法非法使用:

  • 真实文件不要放在Web可直接访问的目录下,从根源上杜绝永久直链泄露的可能
  • 不输出固定的文件直链,所有下载请求都经过服务端接口校验
  • 倒计时结束后才给用户生成绑定身份、短时效、一次性的下载令牌,就算用户拿到令牌链接,提前访问、转发他人都无法正常下载
简化实现代码

1. 前端下载页(index.php)

页面加载时先展示倒计时,纯前端实现,不需要服务端延迟执行,倒计时结束后再向服务端申请下载令牌:

<?php
session_start();
// 记录用户进入页面的时间,用于后续校验是否真的等待了足够时长
$_SESSION['page_enter_time'] = time();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
    <p>请等待15秒后开始下载</p>
    <a id="downloadBtn" href="javascript:alert('请等待倒计时结束')">等待中...</a>

    <script>
        let waitSeconds = 15;
        const downloadBtn = document.getElementById('downloadBtn');
        const countDown = setInterval(() => {
            waitSeconds--;
            if (waitSeconds <= 0) {
                clearInterval(countDown);
                downloadBtn.innerText = '立即下载';
                // 倒计时结束,向服务端申请一次性下载令牌
                fetch('get_token.php')
                    .then(res => {
                        if (!res.ok) throw new Error('获取链接失败');
                        return res.text();
                    })
                    .then(token => {
                        downloadBtn.href = `download.php?token=${token}`;
                    })
                    .catch(err => alert(err.message));
                return;
            }
            downloadBtn.innerText = `请等待${waitSeconds}秒后下载`;
        }, 1000);
    </script>
</body>
</html>

2. 令牌生成接口(get_token.php)

校验用户等待时长,生成随机、短时效、一次性的下载令牌,不暴露任何真实文件信息:

<?php
session_start();
// 校验是否合法访问、是否等够了15秒
if (
    !isset($_SESSION['page_enter_time'])
    || time() - $_SESSION['page_enter_time'] < 15
) {
    http_response_code(403);
    exit('非法访问');
}
// 生成16位随机令牌
$downloadToken = bin2hex(random_bytes(16));
// 把令牌信息存在Session(高并发场景可以换成Redis缓存)
$_SESSION['valid_download_token'] = [
    'token_value' => $downloadToken,
    'expire_time' => time() + 20, // 令牌20秒后过期
    'is_used' => false, // 标记是否已使用
    'target_file' => '/your/non-web-dir/target_file.zip' // 真实文件路径,存在服务端不发给前端
];
echo $downloadToken;
?>

3. 实际下载处理接口(download.php)

校验令牌合法性,校验通过后才输出文件内容给用户:

<?php
session_start();
// 基础参数校验
if (!isset($_GET['token']) || !isset($_SESSION['valid_download_token'])) {
    http_response_code(403);
    exit('下载链接无效');
}
$tokenConfig = $_SESSION['valid_download_token'];
// 校验令牌是否匹配、是否过期、是否已使用
if (
    $tokenConfig['token_value'] !== $_GET['token']
    || $tokenConfig['expire_time'] < time()
    || $tokenConfig['is_used']
) {
    http_response_code(403);
    exit('下载链接已过期或已被使用');
}
// 标记令牌为已使用,防止重复盗用
$tokenConfig['is_used'] = true;
$_SESSION['valid_download_token'] = $tokenConfig;

// 读取文件输出给浏览器
$filePath = $tokenConfig['target_file'];
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filePath).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
?>
额外优化建议
  • 真实文件一定要存在网站根目录之外的非Web访问目录,禁止通过静态URL直接访问
  • 如果需要更高的防刷强度,可以在令牌校验逻辑里加上IP匹配、单用户下载频率限制、验证码校验等规则
  • 不要尝试用PHP flush、前端代码混淆这类方式藏链接,所有浏览器侧的内容对用户都是完全透明的,这类操作没有实际防护效果

内容的提问来源于stack exchange,提问作者Genisys Mythic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:48:23