提供檔案下載連結時,需統計每個檔案的下載次數。直接連結無法攔截請求,故引入中介頁面 ddload.php:記錄計數後再導向真實下載網址。
CREATE TABLE book (
id INT PRIMARY KEY AUTO_INCREMENT,
downnum INT DEFAULT 0 -- 下載次數
-- 其他欄位:檔名、路徑、描述等
);
<?php while ($myrow = mysql_fetch_array($result)) { ?>
<tr>
<td><?= empty($myrow['downnum']) ? 0 : $myrow['downnum'] ?></td>
<td>
<a href="ddload.php?purl=<?= urlencode($myrow['fileurl']) ?>&dnnum=<?= $myrow['downnum'] ?>&id=<?= $myrow['id'] ?>">
點此下載
</a>
</td>
</tr>
<?php } ?>
<?php
include("data.inc.php"); // 資料庫連線設定
if ($incerror) { echo $incerror; exit; }
$dnnum = empty($dnnum) ? 1 : $dnnum + 1;
$sql = "UPDATE book SET downnum = '$dnnum' WHERE id = $id";
mysql_query($sql);
mysql_close($connect);
header("Location: $purl"); // 重導向至真實檔案
?>
| 問題 | 風險 | 修正 |
|---|---|---|
直接信任 $id, $dnnum 參數 | SQL Injection、計數竄改 | PDO 預處理、intval() 強制整數 |
| 計數邏輯競態條件 | 高併發下計數遺漏 | UPDATE book SET downnum = downnum + 1 WHERE id = ? 原子操作 |
導向目標 $purl 未驗證 | 開放式重導向攻擊 | 白名單驗證、或僅允許本站路徑 |
使用 mysql_* 已移除函式 | PHP 7+ 不可用 | 遷移至 PDO / MySQLi |
| 無下載完成確認 | 用戶取消下載仍計數 | 前端 JS 回報 / 伺服端檢查 Content-Length 傳輸完成 |
<?php
// ddload_v2.php
require_once 'pdo_config.php';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$purl = isset($_GET['purl']) ? $_GET['purl'] : '';
if (!$id || !$purl) { http_response_code(400); exit('參數錯誤'); }
// 白名單驗證:確保 purl 指向本站允許目錄
$allowedBase = 'https://mysite.com/downloads/';
if (strpos($purl, $allowedBase) !== 0) { http_response_code(403); exit('非法下載連結'); }
try {
$stmt = $pdo->prepare("UPDATE book SET downnum = downnum + 1 WHERE id = ?");
$stmt->execute([$id]);
header("Location: $purl");
} catch (PDOException $e) {
error_log($e->getMessage());
http_response_code(500); exit('系統錯誤');
}
?>
INCR download:{id} 極速計數,定期同步落地 MySQL。中介頁面模式是下載計數的經典解法。核心在於「原子更新計數」與「安全重導向」。現代架構可引入 Redis 快取層與非同步隊列,支撐百萬級下載統計。