PHP 純檔案儲存計數器:無資料庫、相容虛擬主機
許多虛擬主機禁用 exec()、無資料庫權限,使用檔案儲存計數器是最相容的方案。本文提供完整、經驗證可行的計數器實作,含圖片顯示與文字顯示兩種模式。
目錄結構
counter/
├── counter.php # 核心計數邏輯
├── counter.txt # 計數儲存檔 (需可寫入權限)
└── digits/ # 數字圖片目錄
├── 0.gif
├── 1.gif
...
└── 9.gif
核心代碼 counter/counter.php
<?php
// 計數檔案路徑 (相對於此檔案)
$countFile = __DIR__ . '/counter.txt';
// 1. 首次執行:建立檔案、初始化為 1
if (!file_exists($countFile)) {
$fp = fopen($countFile, 'w');
fwrite($fp, '1');
fclose($fp);
$count = 1;
} else {
// 2. 讀取、遞增、寫回
$fp = fopen($countFile, 'r+'); // 讀寫模式
// 獨佔鎖定 (防併發競態)
if (flock($fp, LOCK_EX)) {
$count = (int)fread($fp, 20); // 讀取最多 20 位數
$count++;
ftruncate($fp, 0); // 清空檔案
rewind($fp); // 指標歸零
fwrite($fp, (string)$count);
fflush($fp); // 刷新緩衝區
flock($fp, LOCK_UN); // 釋放鎖
} else {
$count = 0; // 鎖定失敗備用
}
fclose($fp);
}
// 3. 輸出模式選擇
// 模式 A: 圖片顯示 (預設)
$output = '';
$countStr = (string)$count;
for ($i = 0; $i < strlen($countStr); $i++) {
$digit = $countStr[$i];
$output .= "
";
}
echo $output;
// 模式 B: 文字顯示 (取消註解下行並註解上方迴圈)
// echo "$count";
?>
使用方式
1. 直接引入 (同目錄)
<?php
echo "您是第 ";
require_once 'counter/counter.php';
echo " 位訪客";
?>
2. 跨目錄引入
<?php
// 修改 counter.php 中的 $countFile 路徑為絕對路徑
$countFile = '/home/user/public_html/counter/counter.txt';
// 或在引入前定義常數
define('COUNTER_FILE', '/absolute/path/counter.txt');
require_once '/path/to/counter/counter.php';
?>
關鍵技術點解析
| 技術點 |
實作方式 |
重要性 |
| 檔案鎖定 |
flock($fp, LOCK_EX) |
⭐⭐⭐⭐⭐ 防併發導致計數遺漏 |
| 原子寫入 |
ftruncate + rewind + fwrite |
⭐⭐⭐⭐ 避免寫入中斷導致檔案損壞 |
| 權限設定 |
Linux: chmod 666 counter.txt 或 644 + 目錄可寫 |
⭐⭐⭐⭐⭐ 必須可讀寫 |
| 路徑處理 |
使用 __DIR__ 確保路徑正確 |
⭐⭐⭐ 避免引入時路徑錯誤 |
| 錯誤抑制 |
生產環境移除 @,改用錯誤處理 |
⭐⭐ 方便除錯 |
進階優化:高流量網站方案
🚀 檔案計數器效能瓶頸
- 磁碟 I/O 瓶頸:每次請求都讀寫檔案
- 鎖競爭:高併發下進程等待鎖
- 檔案系統 inode 限制
優化方案
- 記憶體快取 + 定期寫入:Redis/Memcached 計數,每分鐘同步至檔案
- 分散式計數:多台伺服器各自計數,定期彙總
- 非同步寫入:請求寫入 Queue,Worker 批次處理
- 資料庫計數:
UPDATE counter SET count=count+1 WHERE id=1 (行鎖較檔案鎖效能好)
常見問題排查
| 現象 |
可能原因 |
解決方案 |
| 計數不增加 |
counter.txt 無寫入權限 |
設定檔案權限 666,目錄 755 |
| 計數歸零/亂碼 |
併發寫入衝突、檔案損壞 |
啟用 flock、檢查 ftruncate 邏輯 |
| 圖片不顯示 |
digits/ 目錄路徑錯誤、檔案不存在 |
確認 0-9.gif 存在、路徑正確 |
| 重複計數 |
同一用戶短時間重複請求 |
加入 Session/Cookie 去重 (見下方) |
加入簡單去重 (防刷新重複計數)
<?php
session_start();
$visitedKey = 'counter_visited_' . md5($_SERVER['REQUEST_URI']);
if (!isset($_SESSION[$visitedKey])) {
// 執行計數邏輯...
$_SESSION[$visitedKey] = true;
// 可選:設定過期時間 (1小時)
// $_SESSION[$visitedKey . '_expire'] = time() + 3600;
}
// 顯示計數 (不論是否計數都顯示)
require_once 'counter/counter.php';
?>