Skip to content

访问日志系统

访问日志系统提供灵活的网站访问记录功能,支持 CMS 和 API 双模式,可独立控制启用状态。

系统架构

继承关系

Anon_System_AccessLog (基类 - 通用功能)

Anon_Cms_AccessLog (CMS 模式扩展)

设计特点

  • 通用性:基类 Anon_System_AccessLog 提供核心功能
  • 可扩展:CMS 模式可继承并扩展特定功能
  • 独立性:与评论统计、文章阅读量等功能完全独立

快速开始

启用访问日志

php
// 方法 1:代码启用
Anon_System_AccessLog::enable();

// 方法 2:数据库设置
UPDATE options SET value = '1' WHERE name = 'access_log_enabled';

禁用访问日志

php
Anon_System_AccessLog::disable();

检查状态

php
if (Anon_System_AccessLog::isEnabled()) {
    // 访问日志已启用
}

配置选项

数据库配置

访问日志配置存储在 options 表中:

配置项键名类型默认值说明
启用状态access_log_enabledbooleantrue是否启用访问日志
排除路径access_log_exclude_pathsarray[]不记录的路径列表
保留天数access_log_retention_daysint30日志保留天数

CMS 模式配置

在 CMS 管理后台「设置」→「权限设置」中可视化配置:

  • 访问日志开关:控制 access_log_enabled
  • 排除路径设置:每行一个路径,支持通配符
  • 统计设置:配置统计数据的更新频率

记录内容

每条访问日志包含以下字段:

字段类型说明
idint日志 ID
ipstring访问者 IP 地址
pathstring访问路径
methodstringHTTP 方法
user_agentstring用户代理
refererstring来源 URL
created_atdatetime访问时间
typestring访问类型(page, api, static)

示例数据

json
{
    "id": 12345,
    "ip": "192.168.1.100",
    "path": "/post/123",
    "method": "GET",
    "user_agent": "Mozilla/5.0...",
    "referer": "https://google.com",
    "created_at": "2024-03-22 10:30:00",
    "type": "page"
}

自动排除规则

系统会自动排除以下路径,不记录访问日志:

默认排除

php
$excludePaths = [
    '/anon/static/*',      // 静态资源
    '/anon/debug/*',       // 调试相关
    '*.css',               // CSS 文件
    '*.js',                // JS 文件
    '*.png', '*.jpg',      // 图片文件
    '*.gif', '*.svg',      // 其他图片
    '*.ico', '*.woff*',    // 字体文件
];

自定义排除

php
// 添加排除路径
Anon_System_AccessLog::addExcludedPath('/api/health');
Anon_System_AccessLog::addExcludedPath('/admin/ajax/*');

// 批量添加
Anon_System_AccessLog::setExcludedPaths([
    '/api/health',
    '/admin/ajax/*',
    '/cron/*',
]);

// 移除排除路径
Anon_System_AccessLog::removeExcludedPath('/api/health');

// 清空排除路径
Anon_System_AccessLog::clearExcludedPaths();

使用场景

1. 流量分析

php
// 获取今日访问量
$db = Anon_Database::getInstance();
$today = date('Y-m-d');

$count = $db->db('access_logs')
    ->where('DATE(created_at)', '=', $today)
    ->count();

echo "今日访问:{$count}";

2. 热门页面统计

php
// 获取最热门的 10 个页面
$popular = $db->db('access_logs')
    ->select('path, COUNT(*) as views')
    ->where('type', '=', 'page')
    ->group('path')
    ->order('views', 'DESC')
    ->limit(10)
    ->all();

foreach ($popular as $page) {
    echo "{$page['path']}: {$page['views']} 次访问\n";
}

3. IP 分析

php
// 获取访问最多的 IP
$ips = $db->db('access_logs')
    ->select('ip, COUNT(*) as visits')
    ->group('ip')
    ->order('visits', 'DESC')
    ->limit(100)
    ->all();

4. 来源分析

php
// 统计来源网站
$referers = $db->db('access_logs')
    ->select('referer')
    ->where('referer', '!=', '')
    ->all();

$domains = [];
foreach ($referers as $ref) {
    $domain = parse_url($ref['referer'], PHP_URL_HOST);
    if ($domain) {
        $domains[$domain] = ($domains[$domain] ?? 0) + 1;
    }
}

arsort($domains);
print_r(array_slice($domains, 0, 10));

性能优化

1. 批量插入

避免频繁单条插入,使用批量操作:

php
class AccessLogBatch
{
    private $logs = [];
    private $batchSize = 100;
    
    public function log($data)
    {
        $this->logs[] = $data;
        
        if (count($this->logs) >= $this->batchSize) {
            $this->flush();
        }
    }
    
    public function flush()
    {
        if (empty($this->logs)) {
            return;
        }
        
        $db = Anon_Database::getInstance();
        foreach ($this->logs as $log) {
            $db->db('access_logs')->insert($log);
        }
        
        $this->logs = [];
    }
    
    public function __destruct()
    {
        $this->flush();
    }
}

2. 定期清理

设置定时任务清理过期日志:

php
Anon_System_Console::command('logs:clean', function($args) {
    $days = isset($args[0]) ? (int)$args[0] : 30;
    $cutoff = date('Y-m-d', strtotime("-{$days} days"));
    
    $db = Anon_Database::getInstance();
    $deleted = $db->db('access_logs')
        ->where('created_at', '<', $cutoff)
        ->delete();
    
    Anon_System_Console::success("清理 {$deleted} 条旧日志");
    return 0;
}, '清理过期访问日志');

3. 索引优化

为常用查询字段添加索引:

sql
-- 为日期查询添加索引
ALTER TABLE access_logs ADD INDEX idx_created_at (created_at);

-- 为路径查询添加索引
ALTER TABLE access_logs ADD INDEX idx_path (path);

-- 为 IP 查询添加索引
ALTER TABLE access_logs ADD INDEX idx_ip (ip);

-- 组合索引(用于统计查询)
ALTER TABLE access_logs ADD INDEX idx_date_type (created_at, type);

统计数据

基础统计

php
class AccessLogStatistics
{
    /**
     * 获取今日访问量
     */
    public static function getTodayViews(): int
    {
        $db = Anon_Database::getInstance();
        return (int)$db->db('access_logs')
            ->where('DATE(created_at)', '=', date('Y-m-d'))
            ->count();
    }
    
    /**
     * 获取昨日访问量
     */
    public static function getYesterdayViews(): int
    {
        $db = Anon_Database::getInstance();
        return (int)$db->db('access_logs')
            ->where('DATE(created_at)', '=', date('Y-m-d', strtotime('-1 day')))
            ->count();
    }
    
    /**
     * 获取总访问量
     */
    public static function getTotalViews(): int
    {
        $db = Anon_Database::getInstance();
        return (int)$db->db('access_logs')->count();
    }
    
    /**
     * 获取独立访客数
     */
    public static function getUniqueVisitors(int $days = 30): int
    {
        $db = Anon_Database::getInstance();
        $cutoff = date('Y-m-d', strtotime("-{$days} days"));
        
        return (int)$db->db('access_logs')
            ->select('COUNT(DISTINCT ip) as uv')
            ->where('created_at', '>=', $cutoff)
            ->first()['uv'];
    }
}

趋势分析

php
/**
 * 获取最近 N 天的访问趋势
 */
public static function getTrend(int $days = 7): array
{
    $db = Anon_Database::getInstance();
    $cutoff = date('Y-m-d', strtotime("-{$days} days"));
    
    return $db->db('access_logs')
        ->select('DATE(created_at) as date, COUNT(*) as views')
        ->where('created_at', '>=', $cutoff)
        ->group('DATE(created_at)')
        ->order('date', 'ASC')
        ->all();
}

// 使用示例
$trend = AccessLogStatistics::getTrend(30);
foreach ($trend as $day) {
    echo "{$day['date']}: {$day['views']} 次访问\n";
}

API 接口

管理端 API

CMS 模式下提供完整的 RESTful API:

GET    /anon/cms/admin/access-logs          # 日志列表
GET    /anon/cms/admin/access-logs/{id}     # 日志详情
DELETE /anon/cms/admin/access-logs/{id}     # 删除日志
GET    /anon/cms/admin/access-logs/statistics # 统计数据
POST   /anon/cms/admin/access-logs/clean    # 清理日志

请求参数

列表接口

GET /anon/cms/admin/access-logs?filter={
    "ip": "192.168.1.1",
    "path": "/post/*",
    "type": "page",
    "startDate": "2024-01-01",
    "endDate": "2024-01-31",
    "page": 1,
    "limit": 20
}

统计接口

json
{
    "total": 12345,
    "today": 567,
    "yesterday": 543,
    "uniqueVisitors": 8901,
    "topPages": [
        {"path": "/", "views": 1234},
        {"path": "/post/1", "views": 567}
    ],
    "topIps": [
        {"ip": "192.168.1.1", "visits": 100}
    ]
}

最佳实践

1. 合理设置保留期

根据存储容量设置合适的保留天数:

php
// 小型网站:保留 90 天
Anon_Cms_Options::update('access_log_retention_days', 90);

// 中型网站:保留 30 天
Anon_Cms_Options::update('access_log_retention_days', 30);

// 大型网站:保留 7 天
Anon_Cms_Options::update('access_log_retention_days', 7);

2. 排除无关路径

减少不必要的日志记录:

php
$excludes = [
    '/api/health',           // 健康检查
    '/cron/*',               // 定时任务
    '/admin/ajax/*',         // 后台 AJAX
    '/uploads/*',            // 上传文件
    '*.xml',                 // RSS 订阅
    '*.json',                // JSON 数据
];

Anon_System_AccessLog::setExcludedPaths($excludes);

3. 监控异常

设置告警监控异常访问:

php
// 检测短时间内大量访问
$suspicious = $db->db('access_logs')
    ->select('ip, COUNT(*) as count')
    ->where('created_at', '>=', DATE_SUB(NOW(), INTERVAL 1 MINUTE))
    ->group('ip')
    ->having('count > 100')
    ->all();

foreach ($suspicious as $record) {
    Anon_Debug::warning("可疑 IP: {$record['ip']} - {$record['count']} 次/分钟");
}

4. 数据导出

定期导出历史数据:

php
Anon_System_Console::command('logs:export', function($args) {
    $month = $args[0] ?? date('Y-m');
    $filename = "access_logs_{$month}.csv";
    
    $db = Anon_Database::getInstance();
    $logs = $db->db('access_logs')
        ->where('MONTH(created_at)', '=', (int)substr($month, 5, 2))
        ->where('YEAR(created_at)', '=', (int)substr($month, 0, 4))
        ->all();
    
    $fp = fopen(ANON_ROOT . 'exports/' . $filename, 'w');
    fputcsv($fp, ['id', 'ip', 'path', 'method', 'created_at']);
    
    foreach ($logs as $log) {
        fputcsv($fp, $log);
    }
    
    fclose($fp);
    Anon_System_Console::success("导出完成:{$filename}");
    return 0;
}, '导出访问日志为 CSV');

与其他系统的关系

独立性

访问日志系统与以下功能完全独立:

  • 评论系统:评论数量统计不受影响
  • 文章阅读量posts.views 字段独立更新
  • 用户会话:不依赖 session 或 cookie
  • 缓存系统:日志记录不影响缓存

协同工作

可以与以下功能配合使用:

  • 🔧 安全审计:记录所有访问用于安全分析
  • 🔧 性能监控:结合慢查询日志分析性能
  • 🔧 SEO 分析:分析搜索引擎爬虫访问
  • 🔧 用户行为分析:了解用户访问路径

故障排查

常见问题

1. 日志未记录

检查点:

  • 确认 access_log_enabled 是否为 true
  • 检查路径是否在排除列表中
  • 验证数据库连接是否正常
  • 检查 access_logs 表是否存在

2. 日志量过大

解决方案:

  • 增加排除路径
  • 缩短保留期限
  • 降低统计更新频率
  • 使用抽样记录(如只记录 10% 的访问)

3. 查询性能慢

优化方案:

  • 添加合适的索引
  • 分区存储(按月分表)
  • 定期归档历史数据
  • 使用专门的日志存储(如 Elasticsearch)

相关文档

Released under the MIT License.