資料表設計
CREATE TABLE mycounter (
id INT PRIMARY KEY DEFAULT 1, -- 單列計數器,固定 id=1
total_visits BIGINT UNSIGNED NOT NULL DEFAULT 0,
today_visits INT UNSIGNED NOT NULL DEFAULT 0,
yesterday_visits INT UNSIGNED NOT NULL DEFAULT 0,
record_date DATE NOT NULL, -- 最後統計日期
UNIQUE KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO mycounter (id, record_date) VALUES (1, CURDATE());
核心統計邏輯(單一檔案版,含 Cookie 去重)
<?php
session_start();
// 1. 連線資料庫(建議用 PDO)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
// 2. 讀取計數器
$stmt = $pdo->query('SELECT * FROM mycounter WHERE id = 1');
$row = $stmt->fetch();
// 3. 判斷是否跨日
$today = date('Y-m-d');
$isNewDay = ($row['record_date'] !== $today);
if ($isNewDay) {
// 歸檔昨日、歸零今日
$pdo->prepare('UPDATE mycounter SET yesterday_visits = today_visits, today_visits = 0, record_date = ? WHERE id = 1')
->execute([$today]);
$row['today_visits'] = 0;
$row['yesterday_visits'] = $row['today_visits']; // 注意:這裡已更新,實際應先存舊值
}
// 4. 訪客去重:以 Cookie 標記「今日已計數」
$cookieName = 'visit_counted';
$counted = isset($_COOKIE[$cookieName]);
if (!$counted) {
// 原子遞增
$pdo->prepare('UPDATE mycounter SET total_visits = total_visits + 1, today_visits = today_visits + 1 WHERE id = 1')
->execute();
$row['total_visits']++;
$row['today_visits']++;
// 設定 Cookie 過期至今日 23:59:59
$expire = strtotime('today 23:59:59');
setcookie($cookieName, '1', $expire, '/', '', false, true); // Secure, HttpOnly
}
// 5. 顯示
echo "總訪問量:{$row['total_visits']}<br>";
echo "今日流量:{$row['today_visits']}<br>";
echo "昨日流量:{$row['yesterday_visits']}<br>";
?>
效能與擴展建議
- 高併發:單表單列 `UPDATE ... + 1` 會產生行鎖爭用。改用 Redis `INCR` 計數,定時同步落地 MySQL。
- 分佈式:多台 Web 伺服器共用 Redis,Key 設計 `counter:total`、`counter:today:{date}`。
- 精確去重:Cookie 可被清除/偽造。進階方案用「用戶 ID + 日期」唯一索引寫入 `visit_log` 表,`INSERT IGNORE` 成功才計數。
- 排除爬蟲:解析 `User-Agent`、IP 信譽庫,過濾已知 Bot 流量。
Redis 版本核心片段
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$todayKey = 'counter:today:' . date('Ymd');
$totalKey = 'counter:total';
$isNew = !$redis->exists($todayKey);
if ($isNew) {
$redis->set($todayKey, 0);
$redis->expire($todayKey, 86400 * 2);
}
$visitorKey = 'visitor:' . md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']) . ':' . date('Ymd');
if (!$redis->exists($visitorKey)) {
$redis->incr($totalKey);
$redis->incr($todayKey);
$redis->set($visitorKey, 1, 86400);
}
echo "總計:{$redis->get($totalKey)} | 今日:{$redis->get($todayKey)}";
?>