本文記錄一套完整的網站計數器系統設計,支援頁面級訪問統計、IP 級訪問分析、Session 架構下的獨立訪客識別,並提供報表生成基礎。系統採用 MySQL 儲存,核心包含兩張資料表與一個可複用的 PHP 函式庫 counter.php。
| 欄位名 | 型別 | 說明 |
|---|---|---|
| id | INT(10) AUTO_INCREMENT | 主鍵序號 |
| pagename | VARCHAR(20) | 頁面唯一標識,預設為檔案名 |
| count | INT(10) | 累計訪問次數 |
特殊約定: 站級總訪問數以 pagename='0' 記錄。
| 欄位名 | 型別 | 說明 |
|---|---|---|
| id | INT(10) AUTO_INCREMENT | 主鍵序號 |
| ip | VARCHAR(20) | 訪客 IP 位址 |
| count | INT(10) | 該 IP 總訪問次數 |
| date | DATETIME | 最近訪問時間 |
| pages | TEXT | 歷史訪問頁面 ID 序列,格式:||YYYY-MM-DD HH:MM:SS|pid1|pid2||... |
tpCounter 無對應 pagename,自動插入新記錄。tiCounter 記錄每個 IP 的訪問次數、最近時間、歷史頁面軌跡(pages 欄位以 | 分隔頁面 ID,雙管線 || 分隔訪問時段)。| 函式 | 功能 | 參數 | 返回值 |
|---|---|---|---|
check_session() | 檢查/建立 Session,註冊 existing 旗標 | 無 | Boolean (true=既有, false=新建) |
site_count($conn) | 站級計數(新訪客含 IP 計數) | 資料庫連線 | 站總訪問數 (int) |
page_count($conn, $page_name, $flag=true) | 頁級計數,可選是否遞增 | 連線, 頁面名, 旗標 | 該頁訪問數 (int) |
ip_count($conn) | IP 計數與軌跡記錄(需在 page_count 前呼叫) | 連線 | IP 訪問數 (int) |
show_site_count($num, $len=6, $type=2) | 顯示計數器(圖形/文字) | 數值, 長度, 類型 | 直接輸出 HTML |
0. 權限檢查(防止直接存取 include 檔)
1. 連接資料庫
2. check_session() → 新訪客則 site_count()(內含 ip_count + 頁計數)
3. 顯示站級計數 show_site_count()
4. 若定義 $page_name → page_count() 執行頁級計數
5. 關閉資料庫連線(腳本結束自動)
<?php
/** counter.php v1.0 by Amio 2001-5-1
* 功能:全站/單頁/IP 計數,Session 控制,圖形/文字顯示
* 使用:各頁面頂部 require 本檔,引用前設定 $inc 變數指向 include 目錄
*/
function check_session() {
$existing = true;
session_start();
if (!isset($_SESSION['existing'])) { // 現代寫法:session_is_registered 已棄用
$_SESSION['existing'] = true;
return false;
}
return true;
}
function page_count($connect, $page_name, $flag = true) {
$ip = $_SERVER['REMOTE_ADDR'];
$page_name = mysql_real_escape_string($page_name, $connect); // 防注入
$query = @mysql_query("SELECT id, count FROM tpcounter WHERE pagename='$page_name'", $connect) or die("invalid page query!");
if (!mysql_num_rows($query)) {
mysql_query("INSERT INTO tpcounter (pagename, count) VALUES ('$page_name', 1)", $connect) or die("insert page failed");
$pid = mysql_insert_id($connect);
$return_num = 1;
} else {
$row = mysql_fetch_assoc($query);
$num = $row['count'];
$pid = $row['id'];
if ($flag) $num++;
mysql_query("UPDATE tpcounter SET count=$num WHERE pagename='$page_name'", $connect) or die("update page failed");
$return_num = $num;
}
// 記錄 IP 訪問頁面軌跡
if ($flag) {
$pquery = @mysql_query("SELECT pages FROM ticounter WHERE ip='$ip'", $connect) or die("invalid pages query!");
if (mysql_num_rows($pquery)) {
$parray = mysql_fetch_assoc($pquery);
$pstr = $parray['pages'] . $pid . "|";
mysql_query("UPDATE ticounter SET pages='$pstr' WHERE ip='$ip'", $connect) or die("update ip failed");
}
}
return $return_num;
}
function ip_count($connect) {
$ip = $_SERVER['REMOTE_ADDR'];
$visit_time = date("Y-m-d H:i:s");
$visit_pages = "||" . $visit_time . "|";
$ipquery = @mysql_query("SELECT count, pages FROM ticounter WHERE ip='$ip'", $connect) or die("invalid ip query!");
if (!mysql_num_rows($ipquery)) {
$pageStr = "|" . $visit_pages;
mysql_query("INSERT INTO ticounter (ip, count, date, pages) VALUES ('$ip', 1, '$visit_time', '$pageStr')", $connect) or die("insert ip failed");
return 1;
} else {
$parray = mysql_fetch_assoc($ipquery);
$ipnum = $parray['count'] + 1;
$pageStr = $parray['pages'] . $visit_pages;
mysql_query("UPDATE ticounter SET count=$ipnum, date='$visit_time', pages='$pageStr' WHERE ip='$ip'", $connect) or die("update ip failed");
return $ipnum;
}
}
function site_count($connect) {
if (!check_session()) {
ip_count($connect);
return page_count($connect, "website", true);
} else {
return page_count($connect, "website", false);
}
}
function displayCount($num) {
return "countpng.php?count=" . $num;
}
function show_site_count($num, $length = 6, $type = 2) {
$outStr = str_pad((string)$num, $length, '0', STR_PAD_LEFT);
if ($type == 1) {
echo '<img src="' . displayCount($outStr) . '">';
} else {
echo $outStr;
}
}
// 自動執行區塊(需在引用前設定 $inc 非空)
if (!isset($inc)) exit;
$connect = mysql_connect('localhost', 'root', '');
mysql_select_db('damio', $connect);
$sitecount = site_count($connect);
if (isset($page_name)) page_count($connect, $page_name);
?>
$_SESSION 超全域陣列,配合 session_set_save_handler 可擴展至 Redis/Memcached 叢集。VARBINARY(16) 或 VARCHAR(45)。pages 欄位以 TEXT 追加方式寫入,長期運行會導致行體積膨脹、更新緩慢。建議拆分為獨立的 visit_log 表(ip, page_id, visit_time),並建立索引,利於分析查詢。countpng.php 可替換為前端 JavaScript 從 API 取得數值後動態渲染,減少伺服器端圖片生成開銷。UPDATE ... SET count=count+1 可能產生競態條件,建議使用資料庫原子操作或 Redis INCR 先行計數,定期持久化落庫。這套設計在 2001 年屬於較完整的訪問統計方案,涵蓋 Session 隔離、多維度計數、行為軌跡記錄。如今雖然底層技術棧已過時,但其核心邏輯——「以 Session 區分首次/再次訪問、以 IP 追蹤用戶軌跡、多表協同支援報表分析」——在現代分析系統(如 Google Analytics、Matomo)中依然是基石。重構時保留業務邏輯,替換底層儲存與存取層,即可平滑遷移至現代架構。