防止惡意爬蟲、濫用 API 或限制免費用戶每日存取次數,是常見的業務需求。本文提供基於 MySQL 的完整實作方案:資料表設計、記錄邏輯、限流判斷、定時清理。
CREATE TABLE `ip_log` (
`ip_log_ip` VARCHAR(45) NOT NULL COMMENT 'IPv6 相容長度',
`ip_log_date` DATE NOT NULL COMMENT '訪問日期',
`ip_log_visits` SMALLINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '訪問次數',
`ip_log_page` VARCHAR(255) NOT NULL COMMENT '訪問頁面/路由',
PRIMARY KEY (`ip_log_page`, `ip_log_ip`, `ip_log_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IP 訪問日誌限流表';
設計要點:
(page, ip, date) 確保同一 IP 同一天同一頁面只有一條記錄,避免重複插入。VARCHAR(45) 支援 IPv6 (最長 45 字元)。SMALLINT UNSIGNED 足夠儲存每日訪問上限 (65535)。<?php
function checkIpLimit(PDO $pdo, string $ip, string $page, int $dailyLimit = 100): bool {
$today = date('Y-m-d');
// 1. 嘗試插入新記錄 (訪問次數 = 1)
$sql = "INSERT INTO ip_log (ip_log_ip, ip_log_date, ip_log_visits, ip_log_page)
VALUES (?, ?, 1, ?)
ON DUPLICATE KEY UPDATE ip_log_visits = ip_log_visits + 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([$ip, $today, $page]);
// 2. 查詢當前訪問次數
$sql = "SELECT ip_log_visits FROM ip_log
WHERE ip_log_ip = ? AND ip_log_date = ? AND ip_log_page = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$ip, $today, $page]);
$visits = $stmt->fetchColumn();
// 3. 判斷是否超過限制
return $visits <= $dailyLimit;
}
// 使用範例
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$clientIp = $_SERVER['REMOTE_ADDR'];
$currentPage = $_SERVER['REQUEST_URI'];
if (!checkIpLimit($pdo, $clientIp, $currentPage, 100)) {
http_response_code(429); // Too Many Requests
exit('今日訪問次數已超過限制 (100次/天)');
}
// 正常業務邏輯...
echo '歡迎訪問!';
?>
建立 Cron 排程,每日凌晨清理歷史資料:
# crontab -e 0 0 * * * /usr/bin/php /path/to/cleanup_ip_log.php
<?php
// cleanup_ip_log.php
require 'db_config.php';
$pdo = new PDO(...);
// 保留最近 30 天,刪除更早資料
$cutoff = date('Y-m-d', strtotime('-30 days'));
$pdo->exec("DELETE FROM ip_log WHERE ip_log_date < '$cutoff'");
// 或截斷全表 (若不需歷史分析)
// $pdo->exec("TRUNCATE TABLE ip_log");
?>
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Redis 計數器 + 過期時間 | 極高效能、原子操作、自動過期 | 需額外基建、資料非持久 | 高併發、海量存取 |
| Memcached + 定時落地 DB | 緩衝寫入壓力 | 複雜度高、可能丟失計數 | 寫入極大、可容忍微小誤差 |
| 資料庫分區表 (按日期) | 查詢/清理效能好 | 維護複雜 | 長期保留全量日誌 |
$_SERVER['HTTP_X_FORWARDED_FOR']、$_SERVER['HTTP_CLIENT_IP'],並驗證格式。Retry-After 標頭。