如何实现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的生成记录来补全下载日志:
- 开启S3访问日志:在你的S3桶设置中开启访问日志,把日志存储到另一个专门的日志桶
- 记录预签名URL生成记录:用户请求下载时,PHP生成预签名URL的同时,把
user_id、presigned_url、create_time存入数据库的presigned_urls表 - 定期解析日志:用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
相关产品推荐
相关产品推荐

