# 用户日志系统 - 原时间排序版本 ## 系统概述 用户日志系统是一个高性能的日志收集和展示系统,专门用于将各模块的原始业务日志转换为用户友好的日志消息。系统采用**原时间排序**设计理念,确保用户看到的日志顺序与实际业务发生的时间顺序完全一致。 ### 核心特性 - **原时间排序**:日志按业务发生的原始时间排序,而非收集时间 - **多模块支持**:支持资金、物品、农场、积分等多个业务模块 - **高性能收集**:采用批量处理和时间线追踪,支持高频数据收集 - **实时性保证**:2秒延迟收集机制,平衡实时性和系统稳定性 - **完整监控**:提供详细的收集统计和健康检查功能 ## 系统架构 ### 整体架构图 ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 原始日志表 │ │ 收集器系统 │ │ 用户日志表 │ │ │ │ │ │ │ │ fund_logs │───▶│ FundCollector │───▶│ │ │ item_logs │───▶│ ItemCollector │───▶│ user_logs │ │ farm_logs │───▶│ FarmCollector │───▶│ │ │ point_logs │───▶│ PointCollector │───▶│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ 调度管理器 │ │ │ │ CollectorManager│ │ ScheduleService │ └─────────────────┘ ``` ### 核心组件 #### 1. 收集器系统 (Collectors) - **BaseLogCollector**: 收集器基类,提供通用功能 - **FundLogCollector**: 资金日志收集器 - **ItemLogCollector**: 物品日志收集器 - **FarmLogCollector**: 农场日志收集器 - **PointLogCollector**: 积分日志收集器 #### 2. 管理层 (Management) - **UserLogCollectorManager**: 收集器管理器,统一调度所有收集器 - **UserLogService**: 对外服务接口 #### 3. 数据层 (Data) - **UserLog**: 用户日志模型 - **UserLogLogic**: 日志业务逻辑 - **用户日志表**: 统一的日志存储表 #### 4. 控制层 (Control) - **CollectUserLogsCommand**: 命令行工具 - **UserLogController**: 后台管理界面 - **LogDataHandler**: 前端接口处理 ## 原时间排序实现 ### 设计理念 传统的日志系统通常按收集时间排序,这会导致用户看到的日志顺序与实际业务发生顺序不一致。本系统采用**原时间排序**机制,确保: 1. **时间一致性**:日志显示顺序 = 业务发生顺序 2. **跨模块同步**:不同模块的日志能够按统一时间线排序 3. **数据完整性**:基于ID进度追踪,确保不遗漏任何记录 ### 实现机制 #### 1. 双时间戳设计 ```php // 收集时同时记录原始时间和收集时间 $userLogData = [ 'user_id' => $record->user_id, 'message' => $this->generateMessage($record), 'source_type' => $this->sourceType, 'source_id' => $record->id, 'source_table' => $this->sourceTable, 'original_time' => $record->created_at, // 原始业务时间 'collected_at' => now(), // 收集时间 'created_at' => now(), // 兼容字段 ]; ``` #### 2. ID进度追踪 每个收集器基于自增ID维护进度,确保数据完整性: ```php // 获取最后处理的记录ID $lastProcessedId = $this->getLastProcessedId(); // 按ID顺序获取新记录 $records = $sourceModel::where('id', '>', $lastProcessedId) ->orderBy('id') ->limit($this->maxRecords) ->get(); ``` #### 3. 原时间排序 收集到的记录按原始时间排序后批量保存: ```php // 按原始时间排序 usort($userLogs, function($a, $b) { return strtotime($a['original_time']) <=> strtotime($b['original_time']); }); // 批量保存 UserLogService::batchLog($userLogs); ``` ### 进度追踪优势 #### 基于ID vs 基于时间 | 特性 | 基于ID追踪 | 基于时间追踪 | |------|------------|--------------| | **数据完整性** | ✅ 保证不遗漏 | ❌ 可能遗漏相同时间戳的记录 | | **时钟容错** | ✅ 不受时钟调整影响 | ❌ 受服务器时间影响 | | **并发安全** | ✅ 自增ID唯一 | ❌ 并发时时间戳可能重复 | | **恢复能力** | ✅ 可精确断点续传 | ❌ 时间重叠可能重复处理 | ## 收集流程详解 ### 主流程 ``` 启动脚本 → 遍历所有收集器 → 执行收集 → 输出结果 │ │ │ │ │ │ │ └─ 统计信息 │ │ │ └─ 错误报告 │ │ │ └─ 性能指标 │ │ │ │ │ └─ 批量写入user_logs │ │ └─ 更新进度追踪 │ │ │ └─ 获取收集进度 │ └─ 读取原始日志 │ └─ 转换消息格式 │ └─ 初始化收集器 └─ 检查系统状态 ``` ### 单个收集器流程 #### 1. 进度确认阶段 ```php // 从user_logs表获取本收集器最新记录的时间戳 $lastTimestamp = UserLog::where('source_type', $this->sourceType) ->where('source_table', $this->sourceTable) ->max('created_at'); ``` #### 2. 数据获取阶段 ```php // 获取未收集的记录(延迟2秒 + 限制数量) $cutoffTime = now()->subSeconds(2); $records = $this->getNewRecordsByTime($lastTimestamp) ->where('created_at', '<=', $cutoffTime) ->limit($this->maxRecords); ``` #### 3. 消息转换阶段 ```php foreach ($records as $record) { $userLogData = $this->convertToUserLog($record); if ($userLogData) { $userLogs[] = $userLogData; } } ``` #### 4. 批量保存阶段 ```php if (!empty($userLogs)) { UserLogService::batchLog($userLogs); } ``` ## 配置系统 ### 数据库配置 用户日志系统的配置已迁移到数据库,存储在 `kku_game_configs` 表中,支持运行时动态修改。 #### 配置项说明 | 配置键 | 名称 | 类型 | 默认值 | 说明 | |--------|------|------|--------|------| | `user_log.enabled` | 用户日志系统启用 | 布尔值 | true | 控制整个用户日志系统是否启用 | | `user_log.auto_collect_enabled` | 自动收集日志启用 | 布尔值 | true | 控制是否允许自动收集用户日志 | | `user_log.max_records_per_run` | 单次最大处理记录数 | 整数 | 1000 | 每次收集日志时的最大处理记录数 | | `user_log.collection_interval` | 收集间隔 | 整数 | 2 | 日志收集的间隔时间(秒) | | `user_log.retention_days` | 日志保留天数 | 整数 | 30 | 用户日志的保留天数 | | `user_log.auto_cleanup` | 自动清理启用 | 布尔值 | true | 是否启用自动清理过期日志 | #### 配置访问方式 ```php use App\Module\Game\Services\GameConfigService; // 获取完整的用户日志配置 $config = GameConfigService::getUserLogConfig(); // 配置结构示例 [ 'enabled' => true, 'auto_collect_enabled' => true, 'max_records_per_run' => 1000, 'collection_interval' => 2, 'retention_days' => 30, 'auto_cleanup' => true, ] ``` #### 后台管理 可以通过后台管理界面修改配置: - 访问路径:`/admin/game-system-configs` - 支持在线修改配置值 - 支持重置为默认值 - 自动缓存管理 ## 数据库设计 ### 用户日志表 (kku_user_logs) ```sql CREATE TABLE `kku_user_logs` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_id` int NOT NULL COMMENT '用户ID', `message` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日志消息内容', `source_type` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源类型(fund, item, farm等)', `source_id` int DEFAULT NULL COMMENT '来源记录ID', `source_table` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源表名', `original_time` timestamp NULL DEFAULT NULL COMMENT '原始日志时间(业务发生时间)', `collected_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '收集时间(日志收集时间)', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(兼容字段,等同于collected_at)', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_original_time` (`original_time`), KEY `idx_collected_at` (`collected_at`), KEY `idx_created_at` (`created_at`), KEY `idx_source` (`source_type`,`source_id`), KEY `idx_user_original_time` (`user_id`, `original_time`), KEY `idx_user_collected_at` (`user_id`, `collected_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户日志表'; ``` ### 索引设计说明 1. **主键索引** (`id`): 自增主键,保证唯一性 2. **用户索引** (`user_id`): 按用户查询日志的核心索引 3. **原始时间索引** (`original_time`): 支持按业务发生时间查询和排序 4. **收集时间索引** (`collected_at`): 支持按收集时间查询和监控 5. **兼容时间索引** (`created_at`): 保持向后兼容性 6. **来源索引** (`source_type`, `source_id`): 支持按来源类型和ID查询 7. **复合索引** (`user_id`, `original_time`): 优化用户日志按原始时间排序查询 8. **复合索引** (`user_id`, `collected_at`): 优化按收集时间的查询 ### 字段说明 - **original_time**: 存储业务发生的原始时间,用于排序和展示 - **collected_at**: 存储日志收集时间,用于监控和追踪收集进度 - **created_at**: 兼容字段,等同于collected_at,保持向后兼容性 - **source_type**: 来源模块类型,如 fund、item、farm、point - **source_table**: 原始日志表名,便于追溯和调试 - **message**: 用户友好的日志消息,已经过格式化处理 ### 时间字段设计理念 #### 双时间戳设计 系统采用双时间戳设计,分别记录: 1. **原始时间** (`original_time`): 业务实际发生的时间 - 用于用户界面展示和排序 - 确保日志顺序与业务发生顺序一致 - 支持跨模块的统一时间线 2. **收集时间** (`collected_at`): 日志被收集系统处理的时间 - 用于监控收集系统的性能 - 追踪收集延迟和系统健康状况 - 支持收集进度管理 #### 时间一致性保证 ```php // 收集器创建日志时的时间设置 $userLogData = [ 'original_time' => $record->created_at, // 使用原始业务时间 'collected_at' => now(), // 使用当前收集时间 'created_at' => now(), // 兼容字段 ]; ``` ## 命令行工具 ### 基本用法 ```bash # 执行日志收集 php artisan game:collect-user-logs # 显示详细处理过程 php artisan game:collect-user-logs --detail # 限制单次处理记录数 php artisan game:collect-user-logs --limit=500 # 显示收集器信息 php artisan game:collect-user-logs --info # 显示统计信息 php artisan game:collect-user-logs --statistics ``` ### 命令选项详解 #### --detail 详细模式 显示每个收集器的详细执行过程: ``` 🚀 开始按时间线收集用户日志... 📊 执行各收集器的日志收集... fund: 处理了 45 条记录 item: 处理了 23 条记录 farm: 处理了 12 条记录 point: 处理了 8 条记录 ✅ 收集完成,总共处理了 88 条记录,耗时 234.56ms ``` #### --info 信息模式 显示所有收集器的状态信息: ``` 📋 收集器信息: 💰 fund: FundLogCollector (fund_logs → user_logs) 📦 item: ItemLogCollector (item_transaction_logs → user_logs) 🌾 farm: FarmLogCollector (farm_harvest_logs, farm_upgrade_logs → user_logs) ⭐ point: PointLogCollector (point_logs → user_logs) ``` #### --statistics 统计模式 显示系统统计信息: ``` 📊 用户日志统计: 📝 总日志数: 12,345 📊 按类型统计: 💰 资金日志: 5,678 📦 物品日志: 3,456 🌾 农场日志: 2,345 ⭐ 积分日志: 866 ``` #### 进度管理 系统采用基于已处理记录的进度追踪机制: - 进度通过`user_logs`表中的记录自动追踪 - 每个收集器会查找最后处理的记录,从该位置继续收集 - 无需手动重置,系统会自动从上次中断的位置继续 - 如需重新收集,可直接删除`user_logs`表中对应的记录 ## 计划任务配置 ### Laravel 调度器配置 如需启用自动日志收集,可在 `routes/console.php` 中添加: ```php use Illuminate\Support\Facades\Schedule; // 每分钟执行用户日志收集(高频收集) Schedule::command('game:collect-user-logs --limit=100')->everyMinute(); // 每天凌晨2点清理过期日志 Schedule::command('game:clean-expired-user-logs')->dailyAt('02:00'); ``` **注意**:默认情况下计划任务已被注释,需要根据实际需求启用。系统通过数据库配置 `user_log.auto_collect_enabled` 控制是否允许自动收集。 ### Crontab 配置 如果需要更高频率的收集,可以直接配置 crontab: ```bash # 每30秒执行一次(需要注意服务器负载) * * * * * cd /path/to/project && php artisan game:collect-user-logs --limit=50 >/dev/null 2>&1 * * * * * sleep 30; cd /path/to/project && php artisan game:collect-user-logs --limit=50 >/dev/null 2>&1 ``` ### 执行方式 日志收集系统采用同步执行方式,通过命令行工具直接执行: ```bash # 手动执行日志收集 php artisan game:collect-user-logs # 通过计划任务定时执行 # 在 routes/console.php 中配置调度任务 ``` ## 监控和告警 ### 健康检查 系统提供基本的健康检查功能: ```bash # 检查收集器状态 php artisan game:collect-user-logs --info # 查看统计信息 php artisan game:collect-user-logs --statistics ``` 通过命令行工具可以检查: - 收集器注册状态 - 日志收集统计 - 系统运行状态 ### 性能监控 #### 收集统计 ```bash # 查看收集统计信息 php artisan game:collect-user-logs --statistics # 输出示例 📊 收集器统计信息: 收集器数量: 5 - fund: ⚙️ 资金日志收集器 - item: ⚙️ 物品日志收集器 - farm_harvest: 🌾 农场收获日志收集器 - farm_upgrade: 🌾 农场升级日志收集器 - point: ⭐ 积分日志收集器 📋 用户日志表统计: 📝 总日志数: 119163 🕐 最新日志时间: 2025-06-22 23:35:56 🕐 最旧日志时间: 2025-06-21 16:30:12 ``` #### 执行时间监控 每次收集都会记录详细的执行时间: ```php [ 'total_processed' => 88, 'total_execution_time' => 234.56, // 毫秒 'collectors' => [ 'fund' => [ 'processed_count' => 45, 'execution_time' => 123.45, 'status' => 'success' ], // ... ] ] ``` ### 错误监控 系统会自动记录所有收集过程中的错误: ```php // 错误日志示例 Log::error("日志收集失败", [ 'collector' => 'FundLogCollector', 'error' => 'Database connection timeout', 'trace' => '...', 'timestamp' => '2025-06-13 11:16:04' ]); ``` ## API 接口 ### 前端日志查询接口 #### LogDataHandler 处理前端的日志查询请求: ```php // 请求参数 [ 'page' => 1, // 页码 'page_size' => 20, // 每页数量 'source_type' => '', // 来源类型过滤 'start_time' => '', // 开始时间 'end_time' => '' // 结束时间 ] // 响应格式(protobuf) message LogDataResponse { repeated LogItem logs = 1; int32 total = 2; int32 page = 3; int32 page_size = 4; } message LogItem { int64 id = 1; string message = 2; string time = 3; // 格式化时间 "MM-dd HH:mm" string source_type = 4; } ``` #### ClearLogHandler 处理用户清空日志请求: ```php // 请求:空 // 响应:标准成功响应 // 注意:实际不删除数据,只记录清理时间 ``` ### 后台管理接口 #### UserLogController 提供完整的后台管理功能: - **列表查询**:支持按用户、类型、时间范围筛选 - **详情查看**:查看单条日志的详细信息 - **批量操作**:批量查看和分析(不支持删除) - **统计分析**:提供各种统计图表 ## 扩展开发 ### 添加新的收集器 #### 1. 创建收集器类 ```php ', $lastProcessedId) ->orderBy('id') ->limit($this->maxRecords) ->get(); } protected function getNewRecordsByTime(int $lastProcessedTimestamp) { $cutoffTime = now()->subSeconds(2); return CustomLog::where('created_at', '>', date('Y-m-d H:i:s', $lastProcessedTimestamp)) ->where('created_at', '<=', $cutoffTime) ->orderBy('created_at') ->orderBy('id') ->limit($this->maxRecords) ->get(); } protected function getRecordTimestamp($record): int { return $record->created_at->timestamp; } protected function convertToUserLog($record): ?array { // 转换逻辑 return $this->createUserLogData( $record->user_id, "自定义操作:{$record->action}", $record->id, $record->created_at->toDateTimeString() ); } } ``` #### 2. 注册收集器 在 `UserLogCollectorManager` 中注册: ```php private function registerCollectors(): void { $this->collectors = [ 'fund' => new FundLogCollector(), 'item' => new ItemLogCollector(), 'farm' => new FarmLogCollector(), 'point' => new PointLogCollector(), 'custom' => new CustomLogCollector(), // 新增 ]; } ``` #### 3. 添加配置 在数据库中添加配置项: ```sql INSERT INTO `kku_game_configs` (`key`, `name`, `description`, `group`, `type`, `value`, `default_value`, `sort_order`, `remark`) VALUES ('user_log.custom_enabled', '自定义收集器启用', '控制自定义收集器是否启用', 'user_log', 1, '1', '1', 70, ''); ``` 或通过后台管理界面添加配置项。 ### 自定义消息模板 #### 1. 配置模板 ```php 'message_templates' => [ 'custom' => [ 'action1' => '执行了{action_name}操作', 'action2' => '完成了{task_name}任务,获得{reward}奖励', ], ], ``` #### 2. 使用模板 ```php protected function convertToUserLog($record): ?array { $template = config('game_user_log.message_templates.custom.' . $record->action_type); $message = str_replace( ['{action_name}', '{task_name}', '{reward}'], [$record->action_name, $record->task_name, $record->reward], $template ); return $this->createUserLogData( $record->user_id, $message, $record->id, $record->created_at->toDateTimeString() ); } ``` ## 性能优化 ### 数据库优化 #### 1. 索引优化 - 确保所有查询都有对应的索引 - 定期分析慢查询并优化 - 考虑分区表设计(按时间分区) #### 2. 查询优化 ```php // 使用复合索引优化用户日志查询 UserLog::where('user_id', $userId) ->where('created_at', '>=', $startTime) ->orderBy('created_at', 'desc') ->limit(20) ->get(); ``` #### 3. 批量处理优化 ```php // 使用批量插入减少数据库连接 UserLog::insert($userLogs); // 而不是逐条插入 ``` ### 内存优化 #### 1. 分批处理 ```php // 避免一次性加载大量数据 $records->chunk(100, function ($chunk) { $this->processChunk($chunk); }); ``` #### 2. 及时释放内存 ```php unset($records, $userLogs); // 处理完成后释放变量 ``` ### 缓存优化 #### 1. 进度缓存 ```php // 缓存收集进度,减少数据库查询 Cache::remember("collector_progress:{$this->sourceType}", 300, function () { return $this->getLastProcessedTimestamp(); }); ``` #### 2. 配置缓存 ```php // 配置自动缓存,通过GameConfigService访问 $config = GameConfigService::getUserLogConfig(); // 手动清除配置缓存(如果需要) GameConfigLogic::clearCache('user_log.enabled'); ``` ## 系统清理说明 ### 已移除的废弃组件 为了保持系统整洁,以下组件已被移除: #### 1. 废弃的配置文件 - `config/game_user_log.php` - 配置已迁移到数据库 #### 2. 废弃的服务类 - `UserLogScheduleService` - 未被实际使用,功能已由命令行工具替代 - `CollectUserLogJob` - 异步队列任务,未被实际使用 #### 3. 废弃的protobuf类 - `Request_RequestUserLogData` - 已被新命名空间替代 - `Request_RequestUserClearLog` - 已被新命名空间替代 ### 迁移说明 如果您的代码中引用了上述废弃组件,请按以下方式迁移: 1. **配置访问**:使用 `GameConfigService::getUserLogConfig()` 替代文件配置 2. **日志收集**:直接使用 `CollectUserLogsCommand` 或 `UserLogCollectorManager` 3. **异步处理**:如需异步处理,可以自行实现队列任务调用 `UserLogCollectorManager` 4. **protobuf类**:使用新的命名空间下的类 ## 故障排除 ### 常见问题 #### 1. 收集器不工作 **症状**:命令执行但没有新日志生成 **排查步骤**: ```bash # 检查收集器状态 php artisan game:collect-user-logs --info # 检查原始日志表是否有新数据 # 检查配置是否正确 # 检查数据库连接是否正常 ``` #### 2. 时间顺序错误 **症状**:用户日志时间顺序混乱 **排查步骤**: - 检查 `created_at` 字段是否使用原始时间 - 检查时区设置是否正确 - 检查延迟收集是否生效 #### 3. 性能问题 **症状**:收集速度慢,影响系统性能 **优化方案**: - 调整 `max_records_per_run` 参数 - 启用队列异步处理 - 优化数据库索引 - 增加服务器资源 #### 4. 内存溢出 **症状**:收集过程中出现内存不足错误 **解决方案**: ```php // 减少批量处理大小 'batch_size' => 50, // 从100减少到50 // 增加内存限制 ini_set('memory_limit', '512M'); // 使用分块处理 $records->chunk(50, function ($chunk) { // 处理逻辑 }); ``` ### 调试工具 #### 1. 详细日志 ```bash # 启用详细模式查看执行过程 php artisan game:collect-user-logs --detail ``` #### 2. 性能分析 ```php // 在收集器中添加性能监控 $startTime = microtime(true); // 执行收集逻辑 $endTime = microtime(true); Log::info("收集器性能", [ 'collector' => $this->collectorName, 'execution_time' => ($endTime - $startTime) * 1000, 'processed_count' => $processedCount ]); ``` #### 3. 数据验证 ```bash # 验证数据一致性 SELECT source_type, COUNT(*) FROM kku_user_logs GROUP BY source_type; # 检查时间范围 SELECT MIN(created_at), MAX(created_at) FROM kku_user_logs; ``` ## 总结 用户日志系统采用**原时间排序**设计,确保用户看到的日志顺序与业务发生顺序完全一致。系统具有以下优势: ### 核心优势 1. **时间一致性**:日志按业务原始时间排序,保证时序正确性 2. **高性能**:批量处理 + 时间线追踪,支持高频数据收集 3. **模块化设计**:收集器独立运行,易于扩展和维护 4. **完整监控**:提供详细的统计和健康检查功能 5. **灵活配置**:支持多种配置方式和环境变量 ### 技术特点 - **延迟收集**:2秒延迟机制,平衡实时性和稳定性 - **进度追踪**:基于时间戳的进度管理,支持断点续传 - **批量处理**:减少数据库连接,提升处理效率 - **错误恢复**:完善的异常处理和错误恢复机制 ### 适用场景 - 游戏用户行为日志收集 - 业务操作审计日志 - 多模块统一日志展示 - 实时数据分析和监控 通过合理的配置和部署,本系统能够稳定高效地处理大量用户日志数据,为业务分析和用户体验提供强有力的支持。