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

如何实现S3预签名URL下载的用户及下载时间记录?

我来给你几个实用的解决方案,完美解决你这个下载记录的问题:

方案1:服务器端代理下载(最可靠)

不要直接把S3预签名URL给到用户,而是通过你的PHP接口做一层中转——先记录用户的下载请求,再从S3拉取文件转发给用户。这种方式完全在服务器端控制,不会出现前端脚本中断的问题,而且能100%捕获下载行为。

代码示例

<?php
session_start();
// 从会话获取当前用户ID(根据你的认证方式调整)
$userId = $_SESSION['user_id'] ?? null;
$fileKey = $_GET['file_key']; // 要下载的S3文件键

if (!$userId || !$fileKey) {
    http_response_code(403);
    exit("权限不足");
}

// 1. 先插入下载记录到数据库
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'db_user', 'db_pass');
$stmt = $pdo->prepare("INSERT INTO download_logs (user_id, file_key, download_time) VALUES (?, ?, NOW())");
$stmt->execute([$userId, $fileKey]);

// 2. 生成S3预签名URL(使用AWS SDK for PHP)
require 'vendor/autoload.php';
use Aws\S3\S3Client;

$s3 = new S3Client([
    'version' => 'latest',
    'region'  => 'your-s3-region',
]);

$presignedCmd = $s3->getCommand('GetObject', [
    'Bucket' => 'your-bucket-name',
    'Key'    => $fileKey,
]);
$presignedUrl = $presignedCmd->createPresignedUrl('+10 minutes'); // URL有效期10分钟

// 3. 流式转发文件给用户(避免把大文件读入内存)
$ch = curl_init($presignedUrl);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode == 200) {
    // 转发S3返回的响应头
    foreach (explode("\r\n", $headers) as $header) {
        if (!empty($header) && strpos($header, 'HTTP/') !== 0) {
            header($header);
        }
    }
    // 强制浏览器下载
    header('Content-Disposition: attachment; filename="' . basename($fileKey) . '"');
    echo $body;
} else {
    http_response_code(404);
    echo "文件不存在或已过期";
}
?>

优点

  • 完全可靠,只要用户发起下载请求就能记录,还能根据HTTP状态码补充“下载是否成功”的记录
  • 不需要前端做任何复杂逻辑

缺点

  • 服务器会消耗带宽,大文件场景下建议优化为流式输出(上面的代码已经做了处理)

方案2:前端异步记录+触发下载(解决你之前的脚本终止问题)

之前的问题是JS触发下载后页面被中断,导致记录请求没完成。可以用navigator.sendBeacon(浏览器专门为这种场景设计的API,即使页面卸载也会保证请求发送完成)先提交记录,再触发下载。

前端代码

<button onclick="initDownload('report-2024.pdf')">下载月度报告</button>

<script>
function initDownload(fileKey) {
    // 1. 用sendBeacon异步提交下载记录
    const formData = new FormData();
    formData.append('user_id', '<?php echo $_SESSION['user_id']; ?>');
    formData.append('file_key', fileKey);
    navigator.sendBeacon('/record-download.php', formData);

    // 2. 触发下载(预签名URL可以提前从PHP渲染到页面,或者异步请求获取)
    const presignedUrl = '<?php echo $generatedPresignedUrl; ?>';
    const link = document.createElement('a');
    link.href = presignedUrl;
    link.download = fileKey;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}
</script>

后端记录接口(record-download.php)

<?php
session_start();
$userId = $_SESSION['user_id'] ?? null;
$fileKey = $_POST['file_key'] ?? null;

if ($userId && $fileKey) {
    $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'db_user', 'db_pass');
    $stmt = $pdo->prepare("INSERT INTO download_logs (user_id, file_key, download_time) VALUES (?, ?, NOW())");
    $stmt->execute([$userId, $fileKey]);
}

http_response_code(200);
exit;
?>

优点

  • 服务器不用代理文件,节省带宽
  • 实现简单,对现有代码改动小

缺点

  • 极端情况下可能丢失记录(比如用户点击后立刻关闭浏览器,但sendBeacon已经把这种概率降到最低)

方案3:S3访问日志+预签名URL关联(适合高流量场景)

如果你的网站用户量很大,不想消耗服务器带宽,可以开启S3的访问日志,再结合预签名URL的生成记录来补全下载日志:

  1. 开启S3访问日志:在你的S3桶设置中开启访问日志,把日志存储到另一个专门的日志桶
  2. 记录预签名URL生成记录:用户请求下载时,PHP生成预签名URL的同时,把user_id、presigned_url、create_time存入数据库的presigned_urls表
  3. 定期解析日志:用PHP脚本或者AWS Lambda定期读取S3日志,解析日志中的请求URL,匹配presigned_urls表中的记录,找到对应的用户ID,插入到download_logs表,同时标记该URL已被使用(避免重复记录)

优点

  • 完全不消耗业务服务器的带宽和资源
  • 适合大规模下载场景

缺点

  • 日志解析有一定复杂度,而且记录有延迟(无法实时看到下载记录)

方案4:预签名URL生成时记录(简单但不够精准)

如果你的业务可以接受“用户请求下载即视为已下载”,那这个方案最简单:在生成预签名URL的同时直接插入记录,然后跳转到预签名URL即可。

<?php
session_start();
$userId = $_SESSION['user_id'];
$fileKey = $_GET['file_key'];

// 插入记录
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'db_user', 'db_pass');
$stmt = $pdo->prepare("INSERT INTO download_logs (user_id, file_key, request_time) VALUES (?, ?, NOW())");
$stmt->execute([$userId, $fileKey]);

// 生成预签名URL并跳转
$s3 = new S3Client([...]);
$presignedUrl = $s3->getCommand('GetObject', [...])->createPresignedUrl('+10 minutes');
header("Location: $presignedUrl");
exit;
?>

优点

  • 实现成本极低,几行代码就能搞定

缺点

  • 记录的是“请求下载”而非“实际下载”,如果用户拿到URL后没点击,记录会不准确

根据你的业务需求推荐优先级:如果对记录准确性要求高,选方案1;如果想节省带宽,选方案2;如果是高流量场景,选方案3。

内容的提问来源于stack exchange,提问作者Nick Fallows

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:23:59