瀏覽代碼

重构用户日志系统:移除事件监听,改为计划任务收集

- 移除事件监听机制,改为每2秒执行计划任务扫描原始日志表
- 创建BaseLogCollector基类,为各模块提供统一的收集器接口
- 实现FundLogCollector资金日志收集器,处理fund_logs表
- 实现ItemLogCollector物品日志收集器,处理item_transaction_logs表
- 实现FarmLogCollector农场日志收集器,处理farm_harvest_logs和farm_upgrade_logs表
- 创建UserLogCollectorManager管理器,统一管理所有收集器
- 重构CollectUserLogJob,改为调用收集器管理器执行收集
- 创建CollectUserLogsCommand命令,支持手动执行和参数控制
- 创建UserLogScheduleService调度服务,提供同步和异步执行接口
- 添加game_user_log.php配置文件,支持详细的参数配置
- 更新文档,详细说明新的计划任务架构和使用方法

技术优势:
- 避免在主业务流程中写入日志,提升性能
- 支持进度追踪,避免重复处理
- 模块化设计,易于扩展新的收集器
- 完善的错误处理和监控机制
- 灵活的配置和过滤规则
notfff 7 月之前
父節點
當前提交
c8c7723e6d

+ 165 - 0
AiWork/2025年06月/07日1231-完善用户日志系统.md

@@ -0,0 +1,165 @@
+# 完善用户日志系统
+
+**任务时间**:2025年06月07日 12:31  
+**任务类型**:功能完善  
+**模块**:Game模块 - 用户日志系统  
+
+## 任务概述
+
+完善 `app/Module/Game/Docs/UserLog.md` 文档,并实现完整的用户日志功能模块。该系统用于记录和展示用户在游戏中的各种操作和变更信息,采用事件驱动架构和异步处理机制。
+
+## 完成内容
+
+### 1. 文档完善
+
+- **完善UserLog.md文档**:从原来的10行简单描述扩展为365行详细的系统设计文档
+- 包含系统概述、核心逻辑、数据库设计、接口设计、实现架构等15个章节
+- 详细描述了事件驱动架构、异步处理机制、性能优化策略等技术要点
+
+### 2. 数据库设计
+
+- **创建数据库表SQL脚本**:`app/Module/Game/Databases/GenerateSql/user_logs.sql`
+- 设计用户日志表(kku_user_logs),包含用户ID、消息内容、来源信息等字段
+- 添加合适的索引优化查询性能
+
+### 3. 模型层实现
+
+- **UserLog模型**:`app/Module/Game/Models/UserLog.php`
+- 实现基本的CRUD操作和查询作用域
+- 定义关联关系和访问器
+- 支持按用户、来源类型、时间范围等条件查询
+
+### 4. 逻辑层实现
+
+- **UserLogLogic逻辑类**:`app/Module/Game/Logics/UserLogLogic.php`
+- 处理日志记录、批量记录、查询、清理等核心业务逻辑
+- 实现统计功能和数据验证
+- 包含异常处理和日志记录
+
+### 5. 服务层实现
+
+- **UserLogService服务类**:`app/Module/Game/Services/UserLogService.php`
+- 提供对外接口和便捷方法
+- 实现资金、物品、农场、宠物等不同类型的日志记录方法
+- 格式化数据为前端需要的格式
+
+### 6. Handler层实现
+
+- **LogDataHandler**:`app/Module/AppGame/Handler/User/LogDataHandler.php`
+- 处理前端日志查询请求,支持分页
+- 转换数据为protobuf格式
+- 完善**ClearLogHandler**:实现日志清空功能
+
+### 7. 事件监听
+
+- **UserLogCollectorListener**:`app/Module/Game/Listeners/UserLogCollectorListener.php`
+- 监听各模块的业务事件自动收集日志
+- 支持资金变更、物品变更、农场操作、宠物相关等事件
+- 生成用户友好的日志消息
+
+### 8. 异步处理
+
+- **CollectUserLogJob**:`app/Module/Game/Jobs/CollectUserLogJob.php`
+- 异步批量处理日志收集,提升性能
+- 支持队列缓存和批量触发机制
+- 包含失败重试和错误处理
+
+### 9. 后台管理
+
+- **UserLogController**:`app/Module/Game/AdminControllers/UserLogController.php`
+- 实现完整的后台管理界面
+- 支持查询、筛选、批量操作等功能
+- 提供统计信息和清理工具
+
+### 10. 命令行工具
+
+- **CleanExpiredUserLogsCommand**:`app/Module/Game/Commands/CleanExpiredUserLogsCommand.php`
+- 定期清理过期日志的命令行工具
+- 支持试运行模式和自定义保留天数
+
+### 11. 文档说明
+
+- **README.md**:`app/Module/Game/Databases/README.md`
+- 说明自动生成目录的用途和注意事项
+- 提供使用指南和相关文档链接
+
+## 技术特点
+
+### 1. 事件驱动架构
+- 使用Laravel事件系统监听各模块业务事件
+- 自动收集用户操作日志,无需手动调用
+- 解耦业务逻辑和日志记录
+
+### 2. 异步处理机制
+- 使用队列任务异步处理日志收集
+- 批量写入提升数据库性能
+- 避免影响主业务流程
+
+### 3. 用户友好设计
+- 采用文字描述而非结构化数据
+- 提供清晰易懂的操作记录
+- 支持分页查询和数据清理
+
+### 4. 性能优化
+- 合理的数据库索引设计
+- 批量处理和缓存机制
+- 定期清理过期数据
+
+### 5. 完整的管理功能
+- 后台管理界面
+- 命令行工具
+- 统计分析功能
+
+## 文件清单
+
+```
+app/Module/Game/
+├── Docs/UserLog.md                                    # 完善的系统文档
+├── Models/UserLog.php                                 # 用户日志模型
+├── Logics/UserLogLogic.php                           # 日志逻辑类
+├── Services/UserLogService.php                       # 日志服务类
+├── Listeners/UserLogCollectorListener.php            # 事件监听器
+├── Jobs/CollectUserLogJob.php                        # 异步处理任务
+├── AdminControllers/UserLogController.php            # 后台管理控制器
+├── Commands/CleanExpiredUserLogsCommand.php          # 清理命令
+└── Databases/
+    ├── README.md                                      # 目录说明
+    └── GenerateSql/user_logs.sql                     # 数据库表脚本
+
+app/Module/AppGame/Handler/User/
+├── LogDataHandler.php                                 # 日志查询Handler
+└── ClearLogHandler.php                               # 日志清空Handler(完善)
+```
+
+## 代码提交
+
+- **提交哈希**:2b365732
+- **提交信息**:完善用户日志系统:实现完整的用户日志功能模块
+- **文件变更**:12个文件,新增2072行代码
+- **已推送到远程仓库**
+
+## 后续工作建议
+
+### 1. 事件集成
+- 在各模块的EventServiceProvider中注册UserLogCollectorListener
+- 确保相关事件能够正确触发日志收集
+
+### 2. 配置优化
+- 添加用户日志相关的配置项
+- 支持开关控制和参数调整
+
+### 3. 测试验证
+- 编写单元测试和集成测试
+- 验证事件监听和异步处理功能
+
+### 4. 性能监控
+- 监控日志收集的性能影响
+- 优化批量处理的参数设置
+
+### 5. 功能扩展
+- 支持更多类型的事件监听
+- 实现日志数据的分析和可视化
+
+## 总结
+
+本次任务成功完善了用户日志系统,从简单的文档描述发展为完整的功能模块。系统采用现代化的架构设计,具备良好的性能和可扩展性,为用户提供了友好的操作记录查看功能,同时为系统运维提供了有价值的数据支持。

+ 6 - 4
AiWork/WORK.md

@@ -25,6 +25,11 @@ shop_items 的 $max_buy 确认被替代后移除,使用mcp执行sql
 
 ## 已完成任务(保留最新的10条,多余的删除)
 
+**2025-06-07 12:31** - 完善用户日志系统:实现完整的用户日志功能模块
+- 需求:完善 `app/Module/Game/Docs/UserLog.md` 文档,实现完整的用户日志系统
+- 实现:从10行简单描述扩展为365行详细文档,创建完整的MVC架构、事件监听、异步处理、后台管理等功能
+- 结果:完整的用户日志系统,支持事件驱动收集、异步批量处理、用户友好展示、后台管理等功能
+
 **2025-06-07 11:40** - 为PetUser表增加软删除功能
 - 需求:为宠物用户表增加软删除功能,删除的宠物记录不会物理删除,而是标记为已删除状态
 - 实现:添加SoftDeletes trait、deleted_at字段、数据库结构修改、后台管理支持、完整测试用例
@@ -100,10 +105,7 @@ shop_items 的 $max_buy 确认被替代后移除,使用mcp执行sql
   - 完成时间: 2025-06-04 20:57
   - 描述: 创建错误复现Console命令,支持通过id/request_unid/run_unid查找请求记录,参考ProtoJsonRequestTest实现,自动提取protobuf_json和headers.token,向UNITTEST_URL发起请求并输出结果,包含完善的错误处理和使用文档
 
-- [x] 2025-06-04 19:47 - 修复宝箱配置页面显示异常问题并增加内容展示和关联访问功能
-  - 任务记录: `AiWork/2025年06月/041947-修复宝箱配置页面显示异常问题.md`
-  - 完成时间: 2025-06-04 19:47
-  - 描述: 修复宝箱配置列表页面消耗组/奖励组/条件组名称无法正常显示的问题,增加详细内容展示列和可点击跳转功能,修复关联关系命名问题,优化用户体验,提供完整的配置信息展示和关联访问
+
 
 
 

+ 23 - 23
app/Module/Game/AdminControllers/UserLogController.php

@@ -13,7 +13,7 @@ use UCore\DcatAdmin\AdminController;
 /**
  * 用户日志管理控制器
  */
-#[Resource('user-logs', names: 'dcat.admin.user-logs')]
+#[Resource('game-user-logs', names: 'dcat.admin.game-user-logs')]
 class UserLogController extends AdminController
 {
     /**
@@ -32,18 +32,18 @@ class UserLogController extends AdminController
     {
         return Grid::make(UserLog::with(['user']), function (Grid $grid) {
             $grid->column('id', 'ID')->sortable();
-            
+
             $grid->column('user.username', '用户名')
                 ->link(function ($value) {
                     return admin_route('dcat.admin.users.show', ['user' => $this->user_id]);
                 });
-            
+
             $grid->column('user_id', '用户ID');
-            
+
             $grid->column('message', '日志消息')
                 ->limit(50)
                 ->help('用户操作的详细描述');
-            
+
             $grid->column('source_type', '来源类型')
                 ->using([
                     'fund' => '资金',
@@ -59,12 +59,12 @@ class UserLogController extends AdminController
                     'pet' => 'info',
                     'system' => 'default',
                 ]);
-            
+
             $grid->column('source_id', '来源ID');
-            
+
             $grid->column('source_table', '来源表名')
                 ->limit(20);
-            
+
             $grid->column('created_at', '创建时间')
                 ->sortable();
 
@@ -88,10 +88,10 @@ class UserLogController extends AdminController
 
             // 禁用创建按钮
             $grid->disableCreateButton();
-            
+
             // 禁用编辑
             $grid->disableEditButton();
-            
+
             // 批量操作
             $grid->batchActions(function (Grid\Tools\BatchActions $batch) {
                 $batch->add('清理选中日志', new \App\Module\Game\AdminControllers\Actions\BatchDeleteUserLogsAction());
@@ -115,17 +115,17 @@ class UserLogController extends AdminController
     {
         return Show::make($id, UserLog::with(['user']), function (Show $show) {
             $show->field('id', 'ID');
-            
+
             $show->field('user.username', '用户名')
                 ->link(function ($value) {
                     return admin_route('dcat.admin.users.show', ['user' => $this->user_id]);
                 });
-            
+
             $show->field('user_id', '用户ID');
-            
+
             $show->field('message', '日志消息')
                 ->unescape();
-            
+
             $show->field('source_type', '来源类型')
                 ->using([
                     'fund' => '资金',
@@ -134,11 +134,11 @@ class UserLogController extends AdminController
                     'pet' => '宠物',
                     'system' => '系统',
                 ]);
-            
+
             $show->field('source_id', '来源ID');
-            
+
             $show->field('source_table', '来源表名');
-            
+
             $show->field('created_at', '创建时间');
 
             // 禁用编辑和删除按钮
@@ -156,16 +156,16 @@ class UserLogController extends AdminController
     {
         return Form::make(UserLog::class, function (Form $form) {
             $form->display('id', 'ID');
-            
+
             $form->number('user_id', '用户ID')
                 ->required()
                 ->help('关联的用户ID');
-            
+
             $form->textarea('message', '日志消息')
                 ->required()
                 ->rows(3)
                 ->help('用户操作的详细描述');
-            
+
             $form->select('source_type', '来源类型')
                 ->options([
                     'fund' => '资金',
@@ -175,10 +175,10 @@ class UserLogController extends AdminController
                     'system' => '系统',
                 ])
                 ->help('日志来源的模块类型');
-            
+
             $form->number('source_id', '来源ID')
                 ->help('关联的业务记录ID');
-            
+
             $form->text('source_table', '来源表名')
                 ->help('关联的数据库表名');
 
@@ -222,7 +222,7 @@ class UserLogController extends AdminController
     {
         try {
             $userId = request('user_id');
-            
+
             if (!$userId) {
                 return response()->json([
                     'status' => false,

+ 209 - 0
app/Module/Game/Commands/CollectUserLogsCommand.php

@@ -0,0 +1,209 @@
+<?php
+
+namespace App\Module\Game\Commands;
+
+use App\Module\Game\Logics\UserLogCollectorManager;
+use UCore\Command\Command;
+
+/**
+ * 用户日志收集命令
+ *
+ * 定时执行的计划任务,每2秒收集一次用户日志
+ */
+class CollectUserLogsCommand extends Command
+{
+    /**
+     * 命令名称和参数
+     *
+     * @var string
+     */
+    protected $signature = 'game:collect-user-logs 
+                            {--collector= : 指定收集器名称,不指定则执行所有收集器}
+                            {--reset : 重置收集器进度,从头开始收集}
+                            {--info : 显示收集器信息}
+                            {--stats : 显示收集统计信息}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '收集用户日志,将各模块的原始日志转换为用户友好的日志消息';
+
+    /**
+     * 执行命令
+     */
+    public function handleRun()
+    {
+        $manager = new UserLogCollectorManager();
+
+        // 显示收集器信息
+        if ($this->option('info')) {
+            $this->showCollectorsInfo($manager);
+            return 0;
+        }
+
+        // 重置收集器进度
+        if ($this->option('reset')) {
+            $this->resetCollectors($manager);
+            return 0;
+        }
+
+        // 显示统计信息
+        if ($this->option('stats')) {
+            $this->showStats($manager);
+            return 0;
+        }
+
+        // 执行日志收集
+        return $this->executeCollection($manager);
+    }
+
+    /**
+     * 执行日志收集
+     *
+     * @param UserLogCollectorManager $manager
+     * @return int
+     */
+    private function executeCollection(UserLogCollectorManager $manager): int
+    {
+        $collectorName = $this->option('collector');
+
+        try {
+            if ($collectorName) {
+                // 执行指定收集器
+                $this->info("执行收集器: {$collectorName}");
+                $result = $manager->collectByName($collectorName);
+                $this->displaySingleResult($result);
+            } else {
+                // 执行所有收集器
+                $this->info("执行所有收集器...");
+                $results = $manager->collectAll();
+                $this->displayAllResults($results);
+            }
+
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error("日志收集失败: {$e->getMessage()}");
+            return 1;
+        }
+    }
+
+    /**
+     * 显示收集器信息
+     *
+     * @param UserLogCollectorManager $manager
+     * @return void
+     */
+    private function showCollectorsInfo(UserLogCollectorManager $manager): void
+    {
+        $this->info("注册的收集器信息:");
+        $this->line("");
+
+        $collectorsInfo = $manager->getCollectorsInfo();
+        
+        foreach ($collectorsInfo as $info) {
+            $this->line("收集器: <comment>{$info['name']}</comment>");
+            $this->line("  类名: {$info['class']}");
+            $this->line("  源表: {$info['source_table']}");
+            $this->line("  类型: {$info['source_type']}");
+            $this->line("");
+        }
+    }
+
+    /**
+     * 重置收集器进度
+     *
+     * @param UserLogCollectorManager $manager
+     * @return void
+     */
+    private function resetCollectors(UserLogCollectorManager $manager): void
+    {
+        $collectorName = $this->option('collector');
+
+        if ($collectorName) {
+            if (!$manager->hasCollector($collectorName)) {
+                $this->error("收集器 {$collectorName} 不存在");
+                return;
+            }
+
+            $manager->resetCollector($collectorName);
+            $this->info("已重置收集器 {$collectorName} 的进度");
+        } else {
+            if ($this->confirm('确定要重置所有收集器的进度吗?这将从头开始收集所有日志。')) {
+                $manager->resetAllCollectors();
+                $this->info("已重置所有收集器的进度");
+            }
+        }
+    }
+
+    /**
+     * 显示统计信息
+     *
+     * @param UserLogCollectorManager $manager
+     * @return void
+     */
+    private function showStats(UserLogCollectorManager $manager): void
+    {
+        $this->info("收集器统计信息:");
+        $this->line("");
+
+        // 这里可以添加更详细的统计信息
+        // 比如每个收集器的处理进度、最后处理时间等
+        $collectorsInfo = $manager->getCollectorsInfo();
+        
+        foreach ($collectorsInfo as $info) {
+            $this->line("收集器: <comment>{$info['name']}</comment>");
+            
+            // 获取最后处理的ID
+            $cacheKey = "user_log_collector:last_processed_id:{$info['source_table']}";
+            $lastProcessedId = \Illuminate\Support\Facades\Cache::get($cacheKey, 0);
+            
+            $this->line("  最后处理ID: {$lastProcessedId}");
+            $this->line("");
+        }
+    }
+
+    /**
+     * 显示单个收集器结果
+     *
+     * @param array $result
+     * @return void
+     */
+    private function displaySingleResult(array $result): void
+    {
+        if ($result['status'] === 'success') {
+            $this->info("收集完成:");
+            $this->line("  处理记录数: {$result['processed_count']}");
+            $this->line("  执行时间: {$result['execution_time']}ms");
+        } else {
+            $this->error("收集失败:");
+            $this->line("  错误信息: {$result['error']}");
+        }
+    }
+
+    /**
+     * 显示所有收集器结果
+     *
+     * @param array $results
+     * @return void
+     */
+    private function displayAllResults(array $results): void
+    {
+        $this->info("收集完成:");
+        $this->line("  总处理记录数: {$results['total_processed']}");
+        $this->line("  总执行时间: {$results['total_execution_time']}ms");
+        $this->line("");
+
+        $this->info("各收集器详情:");
+        foreach ($results['collectors'] as $name => $result) {
+            $status = $result['status'] === 'success' ? '<info>成功</info>' : '<error>失败</error>';
+            $this->line("  {$name}: {$status} - 处理{$result['processed_count']}条 - {$result['execution_time']}ms");
+            
+            if ($result['status'] === 'error') {
+                $this->line("    错误: {$result['error']}");
+            }
+        }
+    }
+}

+ 69 - 49
app/Module/Game/Docs/UserLog.md

@@ -24,10 +24,10 @@
 
 ### 2.1 数据收集流程
 
-1. **事件监听**:监听各模块的业务事件(资金变更、道具变更、用户操作等)
-2. **数据收集**:每2秒收集一次变更数据,避免频繁写入
-3. **关联记录**:记录关联的表名和ID,便于追溯
-4. **文字转换**:将结构化数据转换为用户友好的文字描述
+1. **计划任务扫描**:每2秒执行计划任务,扫描各个原始日志表的新增记录
+2. **数据转换**:将原始日志数据转换为用户友好的文字描述
+3. **批量写入**:将转换后的日志批量写入用户日志表
+4. **记录追踪**:记录已处理的最大ID,避免重复处理
 
 ### 2.2 数据存储策略
 
@@ -95,28 +95,37 @@ app/Module/Game/
 ├── Models/
 │   └── UserLog.php                    # 用户日志模型
 ├── Services/
-│   └── UserLogService.php             # 用户日志服务
+│   ├── UserLogService.php             # 用户日志服务
+│   └── UserLogScheduleService.php     # 日志调度服务
 ├── Logics/
-│   └── UserLogLogic.php               # 用户日志逻辑
-├── Listeners/
-│   └── UserLogCollectorListener.php   # 日志收集监听器
+│   ├── UserLogLogic.php               # 用户日志逻辑
+│   ├── UserLogCollectorManager.php    # 收集器管理器
+│   └── UserLogCollectors/             # 日志收集器目录
+│       ├── BaseLogCollector.php       # 收集器基类
+│       ├── FundLogCollector.php       # 资金日志收集器
+│       ├── ItemLogCollector.php       # 物品日志收集器
+│       └── FarmLogCollector.php       # 农场日志收集器
 ├── Jobs/
 │   └── CollectUserLogJob.php          # 日志收集任务
+├── Commands/
+│   ├── CollectUserLogsCommand.php     # 日志收集命令
+│   └── CleanExpiredUserLogsCommand.php # 清理过期日志命令
 └── AdminControllers/
     └── UserLogController.php          # 后台管理控制器
 
 app/Module/AppGame/Handler/User/
-└── LogDataHandler.php                 # 用户日志数据Handler
+├── LogDataHandler.php                 # 用户日志数据Handler
+└── ClearLogHandler.php                # 清空日志Handler
 ```
 
-### 5.2 事件驱动架构
+### 5.2 计划任务架构
 
-系统采用事件驱动架构,通过监听各模块的业务事件来收集日志:
+系统采用计划任务架构,通过定时扫描各模块的日志表来收集用户日志:
 
-- **资金模块事件**:监听资金变更事件
-- **物品模块事件**:监听物品变更事件  
-- **农场模块事件**:监听农场操作事件
-- **宠物模块事件**:监听宠物相关事件
+- **资金日志表**:扫描fund_logs表的新增记录
+- **物品日志表**:扫描item_transaction_logs表的新增记录
+- **农场日志表**:扫描farm_harvest_logs表的新增记录
+- **宠物日志表**:扫描相关宠物操作记录表
 
 ## 6. 日志消息格式
 
@@ -166,14 +175,21 @@ app/Module/AppGame/Handler/User/
 
 ## 8. 使用示例
 
-### 8.1 记录日志
+### 8.1 启动日志收集
 
 ```php
-// 通过事件自动记录
-Event::dispatch(new FundChangedEvent($userId, $fundType, $amount, $remark));
+// 命令行方式
+php artisan game:collect-user-logs
 
-// 或直接调用服务
-UserLogService::log($userId, '获得金币 1000', 'fund', $fundLogId, 'fund_logs');
+// 指定收集器
+php artisan game:collect-user-logs --collector=fund
+
+// 重置进度
+php artisan game:collect-user-logs --reset
+
+// 程序调用
+UserLogScheduleService::collectNow(); // 同步执行
+UserLogScheduleService::scheduleCollection(); // 异步执行
 ```
 
 ### 8.2 查询日志
@@ -184,6 +200,9 @@ $logs = UserLogService::getUserLogs($userId, $page, $pageSize);
 
 // 清空用户日志
 UserLogService::clearUserLogs($userId);
+
+// 获取收集器状态
+$status = UserLogScheduleService::checkCollectorsStatus();
 ```
 
 ## 9. 扩展性设计
@@ -238,52 +257,53 @@ $logRules = [
 
 ## 11. 技术要点
 
-### 11.1 事件监听
+### 11.1 计划任务收集
 
-使用Laravel的事件系统监听各模块的业务事件
+使用计划任务定时扫描各模块的原始日志表
 
 ```php
-// 在EventServiceProvider中注册监听器
-protected $listen = [
-    FundChangedEvent::class => [
-        UserLogCollectorListener::class,
-    ],
-    ItemChangedEvent::class => [
-        UserLogCollectorListener::class,
-    ],
-    // ...
-];
+// 每2秒执行一次日志收集
+* * * * * php artisan game:collect-user-logs
+
+// 或者通过队列异步执行
+CollectUserLogJob::dispatchCollection();
 ```
 
-### 11.2 消息生成
+### 11.2 收集器架构
 
-根据事件类型和数据生成用户友好的消息
+每个模块都有对应的日志收集器,继承自基类:
 
 ```php
-public function generateMessage($event): string
+class FundLogCollector extends BaseLogCollector
 {
-    switch (get_class($event)) {
-        case FundChangedEvent::class:
-            return $this->generateFundMessage($event);
-        case ItemChangedEvent::class:
-            return $this->generateItemMessage($event);
-        // ...
+    protected string $sourceTable = 'fund_logs';
+    protected string $sourceType = 'fund';
+
+    protected function getNewRecords(int $lastProcessedId)
+    {
+        return FundLogModel::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords)
+            ->get();
+    }
+
+    protected function convertToUserLog($record): ?array
+    {
+        // 转换逻辑
     }
 }
 ```
 
-### 11.3 批量处理
+### 11.3 进度追踪
 
-使用队列任务批量处理日志,提升性能:
+使用缓存记录每个收集器的处理进度
 
 ```php
-// 收集日志到临时存储
-Cache::push('user_logs_queue', $logData);
+// 获取上次处理的最大ID
+$lastProcessedId = Cache::get("user_log_collector:last_processed_id:{$sourceTable}", 0);
 
-// 定时批量处理
-if (Cache::get('user_logs_queue_count') >= 100) {
-    CollectUserLogJob::dispatch();
-}
+// 更新处理进度
+Cache::put("user_log_collector:last_processed_id:{$sourceTable}", $newId, 86400);
 ```
 
 ## 12. 注意事项

+ 49 - 124
app/Module/Game/Jobs/CollectUserLogJob.php

@@ -2,19 +2,18 @@
 
 namespace App\Module\Game\Jobs;
 
-use App\Module\Game\Services\UserLogService;
+use App\Module\Game\Logics\UserLogCollectorManager;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Log;
 
 /**
  * 用户日志收集任务
  *
- * 异步批量处理用户日志收集,提升性能
+ * 通过计划任务定时收集各模块的原始日志,转换为用户友好的日志消息
  */
 class CollectUserLogJob implements ShouldQueue
 {
@@ -35,20 +34,20 @@ class CollectUserLogJob implements ShouldQueue
     public $timeout = 60;
 
     /**
-     * 日志数据
+     * 指定收集器名称
      *
-     * @var array
+     * @var string|null
      */
-    protected array $logs;
+    protected ?string $collectorName;
 
     /**
      * 创建新的任务实例
      *
-     * @param array $logs 日志数据数组
+     * @param string|null $collectorName 收集器名称,null表示执行所有收集器
      */
-    public function __construct(array $logs = [])
+    public function __construct(?string $collectorName = null)
     {
-        $this->logs = $logs;
+        $this->collectorName = $collectorName;
     }
 
     /**
@@ -59,39 +58,49 @@ class CollectUserLogJob implements ShouldQueue
     public function handle(): void
     {
         try {
-            // 如果没有传入日志数据,从缓存中获取
-            if (empty($this->logs)) {
-                $this->logs = $this->getLogsFromCache();
-            }
-
-            if (empty($this->logs)) {
-                Log::info('用户日志收集任务:没有待处理的日志');
-                return;
-            }
+            $manager = new UserLogCollectorManager();
 
-            // 批量记录日志
-            $success = UserLogService::batchLog($this->logs);
+            if ($this->collectorName) {
+                // 执行指定收集器
+                $result = $manager->collectByName($this->collectorName);
 
-            if ($success) {
-                Log::info('用户日志收集任务执行成功', [
-                    'logs_count' => count($this->logs)
+                Log::info('用户日志收集任务执行完成', [
+                    'collector' => $this->collectorName,
+                    'processed_count' => $result['processed_count'],
+                    'execution_time' => $result['execution_time'],
+                    'status' => $result['status']
                 ]);
 
-                // 清理缓存
-                $this->clearLogsCache();
+                if ($result['status'] === 'error') {
+                    throw new \Exception("收集器 {$this->collectorName} 执行失败: {$result['error']}");
+                }
             } else {
-                Log::error('用户日志收集任务执行失败', [
-                    'logs_count' => count($this->logs)
+                // 执行所有收集器
+                $results = $manager->collectAll();
+
+                Log::info('用户日志收集任务执行完成', [
+                    'total_processed' => $results['total_processed'],
+                    'total_execution_time' => $results['total_execution_time'],
+                    'collectors_count' => count($results['collectors'])
                 ]);
 
-                // 任务失败,重新抛出异常以触发重试
-                throw new \Exception('批量记录用户日志失败');
+                // 检查是否有失败的收集器
+                $failedCollectors = [];
+                foreach ($results['collectors'] as $name => $result) {
+                    if ($result['status'] === 'error') {
+                        $failedCollectors[] = $name;
+                    }
+                }
+
+                if (!empty($failedCollectors)) {
+                    throw new \Exception('部分收集器执行失败: ' . implode(', ', $failedCollectors));
+                }
             }
 
         } catch (\Exception $e) {
             Log::error('用户日志收集任务异常', [
+                'collector' => $this->collectorName,
                 'error' => $e->getMessage(),
-                'logs_count' => count($this->logs),
                 'attempt' => $this->attempts()
             ]);
 
@@ -109,117 +118,33 @@ class CollectUserLogJob implements ShouldQueue
     public function failed(\Throwable $exception): void
     {
         Log::error('用户日志收集任务最终失败', [
+            'collector' => $this->collectorName,
             'error' => $exception->getMessage(),
-            'logs_count' => count($this->logs),
             'attempts' => $this->attempts()
         ]);
 
         // 可以在这里实现失败后的补偿机制
-        // 比如将失败的日志保存到文件或发送告警
+        // 比如发送告警通知或记录到特殊日志文件
     }
 
     /**
-     * 从缓存中获取待处理的日志
-     *
-     * @return array
-     */
-    private function getLogsFromCache(): array
-    {
-        $cacheKey = 'user_logs_queue';
-        $logs = Cache::get($cacheKey, []);
-        
-        return is_array($logs) ? $logs : [];
-    }
-
-    /**
-     * 清理日志缓存
+     * 静态方法:调度日志收集任务
      *
+     * @param string|null $collectorName 收集器名称,null表示执行所有收集器
      * @return void
      */
-    private function clearLogsCache(): void
+    public static function dispatchCollection(?string $collectorName = null): void
     {
-        $cacheKey = 'user_logs_queue';
-        Cache::forget($cacheKey);
-        Cache::forget($cacheKey . '_count');
-    }
-
-    /**
-     * 静态方法:添加日志到队列
-     *
-     * @param int $userId 用户ID
-     * @param string $message 日志消息
-     * @param string|null $sourceType 来源类型
-     * @param int|null $sourceId 来源记录ID
-     * @param string|null $sourceTable 来源表名
-     * @return void
-     */
-    public static function addLogToQueue(
-        int $userId,
-        string $message,
-        ?string $sourceType = null,
-        ?int $sourceId = null,
-        ?string $sourceTable = null
-    ): void {
         try {
-            $cacheKey = 'user_logs_queue';
-            $countKey = $cacheKey . '_count';
-            
-            // 构建日志数据
-            $logData = [
-                'user_id' => $userId,
-                'message' => $message,
-                'source_type' => $sourceType,
-                'source_id' => $sourceId,
-                'source_table' => $sourceTable,
-                'created_at' => now()->toDateTimeString(),
-            ];
-
-            // 添加到缓存队列
-            $logs = Cache::get($cacheKey, []);
-            $logs[] = $logData;
-            Cache::put($cacheKey, $logs, 3600); // 缓存1小时
-
-            // 更新计数
-            $count = Cache::increment($countKey, 1);
-            if (!$count) {
-                Cache::put($countKey, 1, 3600);
-                $count = 1;
-            }
+            self::dispatch($collectorName);
 
-            // 当队列达到一定数量时,触发批量处理
-            if ($count >= config('game.user_log.batch_size', 100)) {
-                self::dispatch($logs);
-            }
-
-        } catch (\Exception $e) {
-            Log::error('添加日志到队列失败', [
-                'user_id' => $userId,
-                'message' => $message,
-                'error' => $e->getMessage()
+            Log::info('调度用户日志收集任务', [
+                'collector' => $collectorName ?? 'all'
             ]);
-        }
-    }
-
-    /**
-     * 静态方法:强制处理队列中的所有日志
-     *
-     * @return void
-     */
-    public static function flushQueue(): void
-    {
-        try {
-            $cacheKey = 'user_logs_queue';
-            $logs = Cache::get($cacheKey, []);
-
-            if (!empty($logs)) {
-                self::dispatch($logs);
-                Log::info('强制处理用户日志队列', [
-                    'logs_count' => count($logs)
-                ]);
-            }
 
         } catch (\Exception $e) {
-            Log::error('强制处理用户日志队列失败', [
+            Log::error('调度用户日志收集任务失败', [
+                'collector' => $collectorName,
                 'error' => $e->getMessage()
             ]);
         }

+ 0 - 279
app/Module/Game/Listeners/UserLogCollectorListener.php

@@ -1,279 +0,0 @@
-<?php
-
-namespace App\Module\Game\Listeners;
-
-use App\Module\Fund\Events\FundChangedEvent;
-use App\Module\GameItems\Events\ItemQuantityChangedEvent;
-use App\Module\Farm\Events\CropPlantedEvent;
-use App\Module\Farm\Events\HouseUpgradedEvent;
-use App\Module\Pet\Events\PetCreatedEvent;
-use App\Module\Pet\Events\PetSkillUsedEvent;
-use App\Module\Game\Services\UserLogService;
-use Illuminate\Support\Facades\Log;
-
-/**
- * 用户日志收集监听器
- *
- * 监听各模块的业务事件,自动收集用户操作日志
- */
-class UserLogCollectorListener
-{
-    /**
-     * 处理资金变更事件
-     *
-     * @param FundChangedEvent $event
-     * @return void
-     */
-    public function handleFundChanged(FundChangedEvent $event): void
-    {
-        try {
-            // 获取资金名称
-            $fundName = $this->getFundName($event->fundType);
-            
-            // 判断是获得还是消耗
-            $isGain = $event->amount > 0;
-            $amount = abs($event->amount);
-            
-            // 记录日志
-            UserLogService::logFundChange(
-                $event->userId,
-                $fundName,
-                $amount,
-                $isGain,
-                $event->sourceId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理资金变更事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 处理物品数量变更事件
-     *
-     * @param ItemQuantityChangedEvent $event
-     * @return void
-     */
-    public function handleItemQuantityChanged(ItemQuantityChangedEvent $event): void
-    {
-        try {
-            // 获取物品名称
-            $itemName = $this->getItemName($event->itemId);
-            
-            // 判断是获得还是消耗
-            $isGain = $event->quantity > 0;
-            $quantity = abs($event->quantity);
-            
-            // 记录日志
-            UserLogService::logItemChange(
-                $event->userId,
-                $itemName,
-                $quantity,
-                $isGain,
-                $event->sourceId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理物品数量变更事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 处理作物种植事件
-     *
-     * @param CropPlantedEvent $event
-     * @return void
-     */
-    public function handleCropPlanted(CropPlantedEvent $event): void
-    {
-        try {
-            // 获取作物名称
-            $cropName = $this->getCropName($event->seedId);
-            
-            // 构建日志消息
-            $message = "在{$event->landId}号土地种植{$cropName}";
-            
-            // 记录日志
-            UserLogService::logFarmAction(
-                $event->userId,
-                'plant',
-                $message,
-                $event->landId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理作物种植事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 处理房屋升级事件
-     *
-     * @param HouseUpgradedEvent $event
-     * @return void
-     */
-    public function handleHouseUpgraded(HouseUpgradedEvent $event): void
-    {
-        try {
-            // 构建日志消息
-            $message = "房屋升级到{$event->newLevel}级";
-            
-            // 记录日志
-            UserLogService::logFarmAction(
-                $event->userId,
-                'house_upgrade',
-                $message,
-                $event->userId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理房屋升级事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 处理宠物创建事件
-     *
-     * @param PetCreatedEvent $event
-     * @return void
-     */
-    public function handlePetCreated(PetCreatedEvent $event): void
-    {
-        try {
-            // 获取宠物名称
-            $petName = $this->getPetName($event->petId);
-            
-            // 构建日志消息
-            $message = "获得宠物{$petName}";
-            
-            // 记录日志
-            UserLogService::logPetAction(
-                $event->userId,
-                'create',
-                $message,
-                $event->petId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理宠物创建事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 处理宠物技能使用事件
-     *
-     * @param PetSkillUsedEvent $event
-     * @return void
-     */
-    public function handlePetSkillUsed(PetSkillUsedEvent $event): void
-    {
-        try {
-            // 获取宠物名称和技能名称
-            $petName = $this->getPetName($event->petId);
-            $skillName = $this->getSkillName($event->skillId);
-            
-            // 构建日志消息
-            $message = "宠物{$petName}使用技能{$skillName}";
-            
-            // 记录日志
-            UserLogService::logPetAction(
-                $event->userId,
-                'skill_use',
-                $message,
-                $event->petId
-            );
-            
-        } catch (\Exception $e) {
-            Log::error('处理宠物技能使用事件失败', [
-                'event' => get_class($event),
-                'user_id' => $event->userId ?? null,
-                'error' => $e->getMessage()
-            ]);
-        }
-    }
-
-    /**
-     * 获取资金名称
-     *
-     * @param mixed $fundType
-     * @return string
-     */
-    private function getFundName($fundType): string
-    {
-        // 这里应该根据实际的资金类型枚举来获取名称
-        // 暂时返回默认值
-        return $fundType->name ?? '未知资金';
-    }
-
-    /**
-     * 获取物品名称
-     *
-     * @param int $itemId
-     * @return string
-     */
-    private function getItemName(int $itemId): string
-    {
-        // 这里应该从物品配置中获取物品名称
-        // 暂时返回默认值
-        return "物品{$itemId}";
-    }
-
-    /**
-     * 获取作物名称
-     *
-     * @param int $seedId
-     * @return string
-     */
-    private function getCropName(int $seedId): string
-    {
-        // 这里应该从种子配置中获取作物名称
-        // 暂时返回默认值
-        return "作物{$seedId}";
-    }
-
-    /**
-     * 获取宠物名称
-     *
-     * @param int $petId
-     * @return string
-     */
-    private function getPetName(int $petId): string
-    {
-        // 这里应该从宠物配置中获取宠物名称
-        // 暂时返回默认值
-        return "宠物{$petId}";
-    }
-
-    /**
-     * 获取技能名称
-     *
-     * @param int $skillId
-     * @return string
-     */
-    private function getSkillName(int $skillId): string
-    {
-        // 这里应该从技能配置中获取技能名称
-        // 暂时返回默认值
-        return "技能{$skillId}";
-    }
-}

+ 264 - 0
app/Module/Game/Logics/UserLogCollectorManager.php

@@ -0,0 +1,264 @@
+<?php
+
+namespace App\Module\Game\Logics;
+
+use App\Module\Game\Logics\UserLogCollectors\BaseLogCollector;
+use App\Module\Game\Logics\UserLogCollectors\FundLogCollector;
+use App\Module\Game\Logics\UserLogCollectors\ItemLogCollector;
+use App\Module\Game\Logics\UserLogCollectors\FarmLogCollector;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 用户日志收集管理器
+ *
+ * 管理所有的日志收集器,协调日志收集工作
+ */
+class UserLogCollectorManager
+{
+    /**
+     * 注册的收集器列表
+     *
+     * @var array
+     */
+    private array $collectors = [];
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->registerCollectors();
+    }
+
+    /**
+     * 注册所有收集器
+     *
+     * @return void
+     */
+    private function registerCollectors(): void
+    {
+        $this->collectors = [
+            'fund' => new FundLogCollector(),
+            'item' => new ItemLogCollector(),
+            'farm' => new FarmLogCollector(),
+            // 可以在这里添加更多收集器
+        ];
+    }
+
+    /**
+     * 执行所有收集器的日志收集
+     *
+     * @return array 收集结果统计
+     */
+    public function collectAll(): array
+    {
+        $results = [];
+        $totalProcessed = 0;
+        $startTime = microtime(true);
+
+        Log::info("开始执行用户日志收集", [
+            'collectors_count' => count($this->collectors)
+        ]);
+
+        foreach ($this->collectors as $name => $collector) {
+            try {
+                $collectorStartTime = microtime(true);
+                $processedCount = $collector->collect();
+                $collectorEndTime = microtime(true);
+                
+                $results[$name] = [
+                    'processed_count' => $processedCount,
+                    'execution_time' => round(($collectorEndTime - $collectorStartTime) * 1000, 2), // 毫秒
+                    'status' => 'success'
+                ];
+                
+                $totalProcessed += $processedCount;
+
+                Log::info("收集器执行完成", [
+                    'collector' => $name,
+                    'processed_count' => $processedCount,
+                    'execution_time_ms' => $results[$name]['execution_time']
+                ]);
+
+            } catch (\Exception $e) {
+                $results[$name] = [
+                    'processed_count' => 0,
+                    'execution_time' => 0,
+                    'status' => 'error',
+                    'error' => $e->getMessage()
+                ];
+
+                Log::error("收集器执行失败", [
+                    'collector' => $name,
+                    'error' => $e->getMessage(),
+                    'trace' => $e->getTraceAsString()
+                ]);
+            }
+        }
+
+        $endTime = microtime(true);
+        $totalExecutionTime = round(($endTime - $startTime) * 1000, 2);
+
+        $summary = [
+            'total_processed' => $totalProcessed,
+            'total_execution_time' => $totalExecutionTime,
+            'collectors' => $results,
+            'timestamp' => now()->toDateTimeString()
+        ];
+
+        Log::info("用户日志收集完成", $summary);
+
+        return $summary;
+    }
+
+    /**
+     * 执行指定收集器的日志收集
+     *
+     * @param string $collectorName 收集器名称
+     * @return array 收集结果
+     */
+    public function collectByName(string $collectorName): array
+    {
+        if (!isset($this->collectors[$collectorName])) {
+            throw new \InvalidArgumentException("收集器 {$collectorName} 不存在");
+        }
+
+        $collector = $this->collectors[$collectorName];
+        $startTime = microtime(true);
+        
+        try {
+            $processedCount = $collector->collect();
+            $endTime = microtime(true);
+            
+            return [
+                'collector' => $collectorName,
+                'processed_count' => $processedCount,
+                'execution_time' => round(($endTime - $startTime) * 1000, 2),
+                'status' => 'success',
+                'timestamp' => now()->toDateTimeString()
+            ];
+
+        } catch (\Exception $e) {
+            $endTime = microtime(true);
+            
+            return [
+                'collector' => $collectorName,
+                'processed_count' => 0,
+                'execution_time' => round(($endTime - $startTime) * 1000, 2),
+                'status' => 'error',
+                'error' => $e->getMessage(),
+                'timestamp' => now()->toDateTimeString()
+            ];
+        }
+    }
+
+    /**
+     * 获取所有收集器的信息
+     *
+     * @return array
+     */
+    public function getCollectorsInfo(): array
+    {
+        $info = [];
+        
+        foreach ($this->collectors as $name => $collector) {
+            $info[$name] = [
+                'name' => $name,
+                'class' => get_class($collector),
+                'source_table' => $collector->getSourceTable(),
+                'source_type' => $collector->getSourceType(),
+            ];
+        }
+
+        return $info;
+    }
+
+    /**
+     * 重置指定收集器的处理进度
+     *
+     * @param string $collectorName 收集器名称
+     * @return void
+     */
+    public function resetCollector(string $collectorName): void
+    {
+        if (!isset($this->collectors[$collectorName])) {
+            throw new \InvalidArgumentException("收集器 {$collectorName} 不存在");
+        }
+
+        $this->collectors[$collectorName]->resetLastProcessedId();
+        
+        Log::info("重置收集器进度", [
+            'collector' => $collectorName
+        ]);
+    }
+
+    /**
+     * 重置所有收集器的处理进度
+     *
+     * @return void
+     */
+    public function resetAllCollectors(): void
+    {
+        foreach ($this->collectors as $name => $collector) {
+            $collector->resetLastProcessedId();
+        }
+        
+        Log::info("重置所有收集器进度");
+    }
+
+    /**
+     * 添加自定义收集器
+     *
+     * @param string $name 收集器名称
+     * @param BaseLogCollector $collector 收集器实例
+     * @return void
+     */
+    public function addCollector(string $name, BaseLogCollector $collector): void
+    {
+        $this->collectors[$name] = $collector;
+        
+        Log::info("添加自定义收集器", [
+            'name' => $name,
+            'class' => get_class($collector)
+        ]);
+    }
+
+    /**
+     * 移除收集器
+     *
+     * @param string $name 收集器名称
+     * @return void
+     */
+    public function removeCollector(string $name): void
+    {
+        if (isset($this->collectors[$name])) {
+            unset($this->collectors[$name]);
+            
+            Log::info("移除收集器", [
+                'name' => $name
+            ]);
+        }
+    }
+
+    /**
+     * 获取收集器实例
+     *
+     * @param string $name 收集器名称
+     * @return BaseLogCollector|null
+     */
+    public function getCollector(string $name): ?BaseLogCollector
+    {
+        return $this->collectors[$name] ?? null;
+    }
+
+    /**
+     * 检查收集器是否存在
+     *
+     * @param string $name 收集器名称
+     * @return bool
+     */
+    public function hasCollector(string $name): bool
+    {
+        return isset($this->collectors[$name]);
+    }
+}

+ 227 - 0
app/Module/Game/Logics/UserLogCollectors/BaseLogCollector.php

@@ -0,0 +1,227 @@
+<?php
+
+namespace App\Module\Game\Logics\UserLogCollectors;
+
+use App\Module\Game\Services\UserLogService;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 用户日志收集器基类
+ *
+ * 为各个模块的日志收集器提供基础功能
+ */
+abstract class BaseLogCollector
+{
+    /**
+     * 收集器名称
+     *
+     * @var string
+     */
+    protected string $collectorName;
+
+    /**
+     * 源表名
+     *
+     * @var string
+     */
+    protected string $sourceTable;
+
+    /**
+     * 源类型
+     *
+     * @var string
+     */
+    protected string $sourceType;
+
+    /**
+     * 最大处理记录数
+     *
+     * @var int
+     */
+    protected int $maxRecords = 1000;
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->collectorName = static::class;
+    }
+
+    /**
+     * 收集日志
+     *
+     * @return int 处理的记录数
+     */
+    public function collect(): int
+    {
+        try {
+            // 获取上次处理的最大ID
+            $lastProcessedId = $this->getLastProcessedId();
+
+            // 获取新的记录
+            $records = $this->getNewRecords($lastProcessedId);
+
+            if ($records->isEmpty()) {
+                return 0;
+            }
+
+            $processedCount = 0;
+            $userLogs = [];
+
+            foreach ($records as $record) {
+                try {
+                    // 转换记录为用户日志
+                    $userLogData = $this->convertToUserLog($record);
+                    
+                    if ($userLogData) {
+                        $userLogs[] = $userLogData;
+                        $processedCount++;
+                    }
+
+                    // 更新最后处理的ID
+                    $this->updateLastProcessedId($record->id);
+
+                } catch (\Exception $e) {
+                    Log::error("转换日志记录失败", [
+                        'collector' => $this->collectorName,
+                        'record_id' => $record->id ?? null,
+                        'error' => $e->getMessage()
+                    ]);
+                }
+            }
+
+            // 批量保存用户日志
+            if (!empty($userLogs)) {
+                UserLogService::batchLog($userLogs);
+            }
+
+            Log::info("日志收集完成", [
+                'collector' => $this->collectorName,
+                'processed_count' => $processedCount,
+                'total_records' => $records->count()
+            ]);
+
+            return $processedCount;
+
+        } catch (\Exception $e) {
+            Log::error("日志收集失败", [
+                'collector' => $this->collectorName,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return 0;
+        }
+    }
+
+    /**
+     * 获取新的记录(子类实现)
+     *
+     * @param int $lastProcessedId 上次处理的最大ID
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    abstract protected function getNewRecords(int $lastProcessedId);
+
+    /**
+     * 转换记录为用户日志数据(子类实现)
+     *
+     * @param mixed $record 原始记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    abstract protected function convertToUserLog($record): ?array;
+
+    /**
+     * 获取上次处理的最大ID
+     *
+     * @return int
+     */
+    protected function getLastProcessedId(): int
+    {
+        $cacheKey = $this->getLastProcessedIdCacheKey();
+        return Cache::get($cacheKey, 0);
+    }
+
+    /**
+     * 更新最后处理的ID
+     *
+     * @param int $id
+     * @return void
+     */
+    protected function updateLastProcessedId(int $id): void
+    {
+        $cacheKey = $this->getLastProcessedIdCacheKey();
+        Cache::put($cacheKey, $id, 86400); // 缓存24小时
+    }
+
+    /**
+     * 获取最后处理ID的缓存键
+     *
+     * @return string
+     */
+    protected function getLastProcessedIdCacheKey(): string
+    {
+        return "user_log_collector:last_processed_id:" . $this->sourceTable;
+    }
+
+    /**
+     * 创建用户日志数据数组
+     *
+     * @param int $userId 用户ID
+     * @param string $message 日志消息
+     * @param int $sourceId 来源记录ID
+     * @return array
+     */
+    protected function createUserLogData(int $userId, string $message, int $sourceId): array
+    {
+        return [
+            'user_id' => $userId,
+            'message' => $message,
+            'source_type' => $this->sourceType,
+            'source_id' => $sourceId,
+            'source_table' => $this->sourceTable,
+            'created_at' => now()->toDateTimeString(),
+        ];
+    }
+
+    /**
+     * 获取收集器名称
+     *
+     * @return string
+     */
+    public function getCollectorName(): string
+    {
+        return $this->collectorName;
+    }
+
+    /**
+     * 获取源表名
+     *
+     * @return string
+     */
+    public function getSourceTable(): string
+    {
+        return $this->sourceTable;
+    }
+
+    /**
+     * 获取源类型
+     *
+     * @return string
+     */
+    public function getSourceType(): string
+    {
+        return $this->sourceType;
+    }
+
+    /**
+     * 重置最后处理的ID(用于重新处理)
+     *
+     * @return void
+     */
+    public function resetLastProcessedId(): void
+    {
+        $cacheKey = $this->getLastProcessedIdCacheKey();
+        Cache::forget($cacheKey);
+    }
+}

+ 269 - 0
app/Module/Game/Logics/UserLogCollectors/FarmLogCollector.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace App\Module\Game\Logics\UserLogCollectors;
+
+use App\Module\Farm\Models\FarmHarvestLog;
+use App\Module\Farm\Models\FarmUpgradeLog;
+
+/**
+ * 农场日志收集器
+ *
+ * 收集农场相关日志表的新增记录,转换为用户友好的日志消息
+ */
+class FarmLogCollector extends BaseLogCollector
+{
+    /**
+     * 源表名
+     *
+     * @var string
+     */
+    protected string $sourceTable = 'farm_harvest_logs';
+
+    /**
+     * 源类型
+     *
+     * @var string
+     */
+    protected string $sourceType = 'farm';
+
+    /**
+     * 获取新的记录
+     *
+     * @param int $lastProcessedId 上次处理的最大ID
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getNewRecords(int $lastProcessedId)
+    {
+        // 收集收获日志
+        $harvestLogs = FarmHarvestLog::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords / 2) // 分配一半给收获日志
+            ->get()
+            ->map(function ($log) {
+                $log->log_type = 'harvest';
+                return $log;
+            });
+
+        // 收集升级日志
+        $upgradeLogs = FarmUpgradeLog::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords / 2) // 分配一半给升级日志
+            ->get()
+            ->map(function ($log) {
+                $log->log_type = 'upgrade';
+                return $log;
+            });
+
+        // 合并并按ID排序
+        return $harvestLogs->concat($upgradeLogs)->sortBy('id');
+    }
+
+    /**
+     * 转换记录为用户日志数据
+     *
+     * @param mixed $record 农场日志记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    protected function convertToUserLog($record): ?array
+    {
+        try {
+            if ($record->log_type === 'harvest') {
+                return $this->convertHarvestLog($record);
+            } elseif ($record->log_type === 'upgrade') {
+                return $this->convertUpgradeLog($record);
+            }
+
+            return null;
+
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error("转换农场日志失败", [
+                'record_id' => $record->id,
+                'log_type' => $record->log_type ?? 'unknown',
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * 转换收获日志
+     *
+     * @param FarmHarvestLog $record
+     * @return array|null
+     */
+    private function convertHarvestLog(FarmHarvestLog $record): ?array
+    {
+        // 获取作物名称
+        $cropName = $this->getCropName($record->seed_id);
+        
+        // 构建收获消息
+        $message = "收获{$record->land_id}号土地的{$cropName}";
+        
+        // 添加收获数量信息
+        if ($record->harvest_quantity > 0) {
+            $message .= ",获得{$record->harvest_quantity}个";
+        }
+        
+        // 添加经验信息
+        if ($record->exp_gained > 0) {
+            $message .= ",获得经验{$record->exp_gained}";
+        }
+
+        return $this->createUserLogData(
+            $record->user_id,
+            $message,
+            $record->id
+        );
+    }
+
+    /**
+     * 转换升级日志
+     *
+     * @param FarmUpgradeLog $record
+     * @return array|null
+     */
+    private function convertUpgradeLog(FarmUpgradeLog $record): ?array
+    {
+        $message = '';
+
+        switch ($record->upgrade_type) {
+            case 'house':
+                $message = "房屋升级到{$record->new_level}级";
+                break;
+            case 'land':
+                $landType = $this->getLandTypeName($record->new_level);
+                $message = "土地{$record->land_id}升级为{$landType}";
+                break;
+            default:
+                $message = "{$record->upgrade_type}升级到{$record->new_level}级";
+        }
+
+        // 添加消耗信息
+        if (!empty($record->cost_items)) {
+            $costDesc = $this->formatCostItems($record->cost_items);
+            if ($costDesc) {
+                $message .= ",消耗{$costDesc}";
+            }
+        }
+
+        return $this->createUserLogData(
+            $record->user_id,
+            $message,
+            $record->id
+        );
+    }
+
+    /**
+     * 获取作物名称
+     *
+     * @param int $seedId 种子ID
+     * @return string
+     */
+    private function getCropName(int $seedId): string
+    {
+        try {
+            // 尝试从配置中获取种子信息
+            $seedConfig = \App\Module\Farm\Models\FarmSeed::find($seedId);
+            if ($seedConfig) {
+                return $seedConfig->name;
+            }
+            
+            return "作物{$seedId}";
+            
+        } catch (\Exception $e) {
+            return "作物{$seedId}";
+        }
+    }
+
+    /**
+     * 获取土地类型名称
+     *
+     * @param int $level 土地等级
+     * @return string
+     */
+    private function getLandTypeName(int $level): string
+    {
+        $landTypes = [
+            1 => '普通土地',
+            2 => '肥沃土地',
+            3 => '优质土地',
+            4 => '特级土地',
+        ];
+
+        return $landTypes[$level] ?? "等级{$level}土地";
+    }
+
+    /**
+     * 格式化消耗物品信息
+     *
+     * @param mixed $costItems 消耗物品数据
+     * @return string
+     */
+    private function formatCostItems($costItems): string
+    {
+        try {
+            if (is_string($costItems)) {
+                $costItems = json_decode($costItems, true);
+            }
+
+            if (!is_array($costItems)) {
+                return '';
+            }
+
+            $costDesc = [];
+            foreach ($costItems as $item) {
+                if (isset($item['name']) && isset($item['quantity'])) {
+                    $costDesc[] = "{$item['name']} {$item['quantity']}";
+                }
+            }
+
+            return implode('、', $costDesc);
+
+        } catch (\Exception $e) {
+            return '';
+        }
+    }
+
+    /**
+     * 是否应该记录此日志
+     *
+     * @param mixed $record
+     * @return bool
+     */
+    private function shouldLogRecord($record): bool
+    {
+        // 对于收获日志,跳过收获数量为0的记录
+        if ($record->log_type === 'harvest' && $record->harvest_quantity <= 0) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 重写获取最后处理ID的方法,因为要处理多个表
+     *
+     * @return int
+     */
+    protected function getLastProcessedId(): int
+    {
+        // 获取两个表的最小ID作为起始点
+        $harvestLastId = \Illuminate\Support\Facades\Cache::get('user_log_collector:last_processed_id:farm_harvest_logs', 0);
+        $upgradeLastId = \Illuminate\Support\Facades\Cache::get('user_log_collector:last_processed_id:farm_upgrade_logs', 0);
+        
+        return min($harvestLastId, $upgradeLastId);
+    }
+
+    /**
+     * 重写更新最后处理ID的方法
+     *
+     * @param int $id
+     * @return void
+     */
+    protected function updateLastProcessedId(int $id): void
+    {
+        // 分别更新两个表的处理ID
+        \Illuminate\Support\Facades\Cache::put('user_log_collector:last_processed_id:farm_harvest_logs', $id, 86400);
+        \Illuminate\Support\Facades\Cache::put('user_log_collector:last_processed_id:farm_upgrade_logs', $id, 86400);
+    }
+}

+ 166 - 0
app/Module/Game/Logics/UserLogCollectors/FundLogCollector.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace App\Module\Game\Logics\UserLogCollectors;
+
+use App\Module\Fund\Models\FundLogModel;
+use App\Module\Fund\Services\AccountService;
+
+/**
+ * 资金日志收集器
+ *
+ * 收集fund_logs表的新增记录,转换为用户友好的日志消息
+ */
+class FundLogCollector extends BaseLogCollector
+{
+    /**
+     * 源表名
+     *
+     * @var string
+     */
+    protected string $sourceTable = 'fund_logs';
+
+    /**
+     * 源类型
+     *
+     * @var string
+     */
+    protected string $sourceType = 'fund';
+
+    /**
+     * 获取新的记录
+     *
+     * @param int $lastProcessedId 上次处理的最大ID
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getNewRecords(int $lastProcessedId)
+    {
+        return FundLogModel::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords)
+            ->get();
+    }
+
+    /**
+     * 转换记录为用户日志数据
+     *
+     * @param FundLogModel $record 资金日志记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    protected function convertToUserLog($record): ?array
+    {
+        try {
+            // 获取资金名称
+            $fundName = $this->getFundName($record->fund_id);
+            
+            // 判断是获得还是消耗
+            $amount = abs($record->amount);
+            $action = $record->amount > 0 ? '获得' : '消耗';
+            
+            // 构建消息
+            $message = "{$action}{$fundName} {$amount}";
+            
+            // 如果有备注,添加到消息中
+            if (!empty($record->remark)) {
+                $message .= "({$record->remark})";
+            }
+
+            return $this->createUserLogData(
+                $record->user_id,
+                $message,
+                $record->id
+            );
+
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error("转换资金日志失败", [
+                'record_id' => $record->id,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * 获取资金名称
+     *
+     * @param mixed $fundId 资金ID
+     * @return string
+     */
+    private function getFundName($fundId): string
+    {
+        try {
+            // 获取资金类型描述
+            $fundNames = AccountService::getFundsDesc();
+            
+            // 如果是枚举对象,获取其值
+            $fundKey = is_object($fundId) ? $fundId->value : $fundId;
+            
+            return $fundNames[$fundKey] ?? "资金{$fundKey}";
+            
+        } catch (\Exception $e) {
+            return "未知资金";
+        }
+    }
+
+    /**
+     * 根据操作类型获取详细描述
+     *
+     * @param mixed $operateType 操作类型
+     * @return string
+     */
+    private function getOperateTypeDesc($operateType): string
+    {
+        $operateTypeNames = [
+            1 => '充值',
+            2 => '提现',
+            3 => '转账',
+            4 => '系统操作',
+            5 => '管理员操作',
+            6 => '交易',
+            7 => '流转',
+        ];
+
+        $typeKey = is_object($operateType) ? $operateType->value : $operateType;
+        return $operateTypeNames[$typeKey] ?? '未知操作';
+    }
+
+    /**
+     * 是否应该记录此日志
+     *
+     * @param FundLogModel $record
+     * @return bool
+     */
+    private function shouldLogRecord(FundLogModel $record): bool
+    {
+        // 可以在这里添加过滤规则
+        // 例如:只记录金额大于某个值的变更
+        
+        // 跳过金额为0的记录
+        if ($record->amount == 0) {
+            return false;
+        }
+
+        // 可以添加更多过滤条件
+        // 例如:跳过某些操作类型
+        // if (in_array($record->operate_type, [...])) {
+        //     return false;
+        // }
+
+        return true;
+    }
+
+    /**
+     * 重写转换方法,添加过滤逻辑
+     *
+     * @param FundLogModel $record 资金日志记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    protected function convertToUserLogWithFilter($record): ?array
+    {
+        // 检查是否应该记录此日志
+        if (!$this->shouldLogRecord($record)) {
+            return null;
+        }
+
+        return $this->convertToUserLog($record);
+    }
+}

+ 205 - 0
app/Module/Game/Logics/UserLogCollectors/ItemLogCollector.php

@@ -0,0 +1,205 @@
+<?php
+
+namespace App\Module\Game\Logics\UserLogCollectors;
+
+use App\Module\GameItems\Models\ItemTransactionLog;
+use App\Module\GameItems\Services\ItemService;
+
+/**
+ * 物品日志收集器
+ *
+ * 收集item_transaction_logs表的新增记录,转换为用户友好的日志消息
+ */
+class ItemLogCollector extends BaseLogCollector
+{
+    /**
+     * 源表名
+     *
+     * @var string
+     */
+    protected string $sourceTable = 'item_transaction_logs';
+
+    /**
+     * 源类型
+     *
+     * @var string
+     */
+    protected string $sourceType = 'item';
+
+    /**
+     * 获取新的记录
+     *
+     * @param int $lastProcessedId 上次处理的最大ID
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getNewRecords(int $lastProcessedId)
+    {
+        return ItemTransactionLog::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords)
+            ->get();
+    }
+
+    /**
+     * 转换记录为用户日志数据
+     *
+     * @param ItemTransactionLog $record 物品交易记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    protected function convertToUserLog($record): ?array
+    {
+        try {
+            // 获取物品名称
+            $itemName = $this->getItemName($record->item_id);
+            
+            // 判断是获得还是消耗
+            $quantity = abs($record->quantity);
+            $action = $record->quantity > 0 ? '获得' : '消耗';
+            
+            // 构建基础消息
+            $message = "{$action}{$itemName} {$quantity}";
+            
+            // 根据交易类型添加详细信息
+            $typeDesc = $this->getTransactionTypeDesc($record->transaction_type);
+            if ($typeDesc) {
+                $message .= "({$typeDesc})";
+            }
+            
+            // 如果有来源信息,添加到消息中
+            if (!empty($record->source_type)) {
+                $sourceDesc = $this->getSourceTypeDesc($record->source_type);
+                if ($sourceDesc) {
+                    $message .= " - {$sourceDesc}";
+                }
+            }
+
+            return $this->createUserLogData(
+                $record->user_id,
+                $message,
+                $record->id
+            );
+
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error("转换物品日志失败", [
+                'record_id' => $record->id,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * 获取物品名称
+     *
+     * @param int $itemId 物品ID
+     * @return string
+     */
+    private function getItemName(int $itemId): string
+    {
+        try {
+            // 尝试从物品服务获取物品信息
+            $item = ItemService::getItemById($itemId);
+            if ($item && isset($item['name'])) {
+                return $item['name'];
+            }
+            
+            // 如果服务不可用,尝试直接查询数据库
+            $itemModel = \App\Module\GameItems\Models\Item::find($itemId);
+            if ($itemModel) {
+                return $itemModel->name;
+            }
+            
+            return "物品{$itemId}";
+            
+        } catch (\Exception $e) {
+            return "物品{$itemId}";
+        }
+    }
+
+    /**
+     * 获取交易类型描述
+     *
+     * @param int $transactionType 交易类型
+     * @return string
+     */
+    private function getTransactionTypeDesc(int $transactionType): string
+    {
+        $typeMap = [
+            1 => '获取',
+            2 => '消耗',
+            3 => '交易获得',
+            4 => '交易失去',
+            5 => '过期失效',
+        ];
+
+        return $typeMap[$transactionType] ?? '';
+    }
+
+    /**
+     * 获取来源类型描述
+     *
+     * @param string $sourceType 来源类型
+     * @return string
+     */
+    private function getSourceTypeDesc(string $sourceType): string
+    {
+        $sourceMap = [
+            'task' => '任务奖励',
+            'shop' => '商店购买',
+            'chest' => '宝箱开启',
+            'craft' => '物品合成',
+            'farm' => '农场收获',
+            'pet' => '宠物获得',
+            'system' => '系统发放',
+            'admin' => '管理员操作',
+        ];
+
+        return $sourceMap[$sourceType] ?? $sourceType;
+    }
+
+    /**
+     * 是否应该记录此日志
+     *
+     * @param ItemTransactionLog $record
+     * @return bool
+     */
+    private function shouldLogRecord(ItemTransactionLog $record): bool
+    {
+        // 跳过数量为0的记录
+        if ($record->quantity == 0) {
+            return false;
+        }
+
+        // 可以添加更多过滤条件
+        // 例如:跳过某些物品类型
+        // 例如:跳过临时物品
+        
+        return true;
+    }
+
+    /**
+     * 获取物品的额外信息
+     *
+     * @param ItemTransactionLog $record
+     * @return string
+     */
+    private function getExtraInfo(ItemTransactionLog $record): string
+    {
+        $extraInfo = [];
+
+        // 如果有实例ID,说明是有特殊属性的物品
+        if ($record->instance_id) {
+            $extraInfo[] = '特殊属性';
+        }
+
+        // 如果有过期时间
+        if ($record->expire_at) {
+            $expireTime = \Carbon\Carbon::parse($record->expire_at);
+            if ($expireTime->isFuture()) {
+                $extraInfo[] = '有效期至' . $expireTime->format('m-d H:i');
+            }
+        }
+
+        return !empty($extraInfo) ? '(' . implode(',', $extraInfo) . ')' : '';
+    }
+}

+ 252 - 0
app/Module/Game/Services/UserLogScheduleService.php

@@ -0,0 +1,252 @@
+<?php
+
+namespace App\Module\Game\Services;
+
+use App\Module\Game\Jobs\CollectUserLogJob;
+use App\Module\Game\Logics\UserLogCollectorManager;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 用户日志调度服务
+ *
+ * 负责管理用户日志收集的调度任务
+ */
+class UserLogScheduleService
+{
+    /**
+     * 执行日志收集(同步方式)
+     *
+     * @param string|null $collectorName 收集器名称,null表示执行所有收集器
+     * @return array 执行结果
+     */
+    public static function collectNow(?string $collectorName = null): array
+    {
+        try {
+            $manager = new UserLogCollectorManager();
+
+            if ($collectorName) {
+                return $manager->collectByName($collectorName);
+            } else {
+                return $manager->collectAll();
+            }
+
+        } catch (\Exception $e) {
+            Log::error('同步执行日志收集失败', [
+                'collector' => $collectorName,
+                'error' => $e->getMessage()
+            ]);
+
+            return [
+                'status' => 'error',
+                'error' => $e->getMessage(),
+                'timestamp' => now()->toDateTimeString()
+            ];
+        }
+    }
+
+    /**
+     * 调度日志收集任务(异步方式)
+     *
+     * @param string|null $collectorName 收集器名称,null表示执行所有收集器
+     * @return void
+     */
+    public static function scheduleCollection(?string $collectorName = null): void
+    {
+        CollectUserLogJob::dispatchCollection($collectorName);
+    }
+
+    /**
+     * 获取收集器信息
+     *
+     * @return array
+     */
+    public static function getCollectorsInfo(): array
+    {
+        try {
+            $manager = new UserLogCollectorManager();
+            return $manager->getCollectorsInfo();
+
+        } catch (\Exception $e) {
+            Log::error('获取收集器信息失败', [
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 重置收集器进度
+     *
+     * @param string|null $collectorName 收集器名称,null表示重置所有收集器
+     * @return bool
+     */
+    public static function resetCollector(?string $collectorName = null): bool
+    {
+        try {
+            $manager = new UserLogCollectorManager();
+
+            if ($collectorName) {
+                $manager->resetCollector($collectorName);
+            } else {
+                $manager->resetAllCollectors();
+            }
+
+            return true;
+
+        } catch (\Exception $e) {
+            Log::error('重置收集器进度失败', [
+                'collector' => $collectorName,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * 检查收集器状态
+     *
+     * @return array
+     */
+    public static function checkCollectorsStatus(): array
+    {
+        try {
+            $manager = new UserLogCollectorManager();
+            $collectorsInfo = $manager->getCollectorsInfo();
+            $status = [];
+
+            foreach ($collectorsInfo as $name => $info) {
+                $cacheKey = "user_log_collector:last_processed_id:{$info['source_table']}";
+                $lastProcessedId = \Illuminate\Support\Facades\Cache::get($cacheKey, 0);
+                
+                $status[$name] = [
+                    'name' => $name,
+                    'source_table' => $info['source_table'],
+                    'source_type' => $info['source_type'],
+                    'last_processed_id' => $lastProcessedId,
+                    'last_check_time' => now()->toDateTimeString(),
+                ];
+            }
+
+            return $status;
+
+        } catch (\Exception $e) {
+            Log::error('检查收集器状态失败', [
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取收集统计信息
+     *
+     * @param int $days 统计天数
+     * @return array
+     */
+    public static function getCollectionStats(int $days = 7): array
+    {
+        try {
+            $stats = [
+                'period' => "{$days}天",
+                'start_date' => now()->subDays($days)->toDateString(),
+                'end_date' => now()->toDateString(),
+                'total_logs' => 0,
+                'daily_stats' => [],
+                'source_type_stats' => [],
+            ];
+
+            // 获取总日志数
+            $totalLogs = \App\Module\Game\Models\UserLog::where('created_at', '>=', now()->subDays($days))
+                ->count();
+            $stats['total_logs'] = $totalLogs;
+
+            // 获取每日统计
+            $dailyStats = \App\Module\Game\Models\UserLog::where('created_at', '>=', now()->subDays($days))
+                ->selectRaw('DATE(created_at) as date, COUNT(*) as count')
+                ->groupBy('date')
+                ->orderBy('date')
+                ->get()
+                ->pluck('count', 'date')
+                ->toArray();
+            $stats['daily_stats'] = $dailyStats;
+
+            // 获取来源类型统计
+            $sourceTypeStats = \App\Module\Game\Models\UserLog::where('created_at', '>=', now()->subDays($days))
+                ->selectRaw('source_type, COUNT(*) as count')
+                ->whereNotNull('source_type')
+                ->groupBy('source_type')
+                ->get()
+                ->pluck('count', 'source_type')
+                ->toArray();
+            $stats['source_type_stats'] = $sourceTypeStats;
+
+            return $stats;
+
+        } catch (\Exception $e) {
+            Log::error('获取收集统计信息失败', [
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 健康检查
+     *
+     * @return array
+     */
+    public static function healthCheck(): array
+    {
+        try {
+            $health = [
+                'status' => 'healthy',
+                'checks' => [],
+                'timestamp' => now()->toDateTimeString(),
+            ];
+
+            // 检查收集器是否正常注册
+            $manager = new UserLogCollectorManager();
+            $collectorsInfo = $manager->getCollectorsInfo();
+            
+            $health['checks']['collectors_registered'] = [
+                'status' => !empty($collectorsInfo) ? 'pass' : 'fail',
+                'count' => count($collectorsInfo),
+                'details' => array_keys($collectorsInfo),
+            ];
+
+            // 检查最近是否有日志生成
+            $recentLogsCount = \App\Module\Game\Models\UserLog::where('created_at', '>=', now()->subMinutes(10))
+                ->count();
+            
+            $health['checks']['recent_logs'] = [
+                'status' => 'info',
+                'count' => $recentLogsCount,
+                'message' => "最近10分钟生成了 {$recentLogsCount} 条日志",
+            ];
+
+            // 检查数据库连接
+            try {
+                \App\Module\Game\Models\UserLog::count();
+                $health['checks']['database'] = [
+                    'status' => 'pass',
+                    'message' => '数据库连接正常',
+                ];
+            } catch (\Exception $e) {
+                $health['checks']['database'] = [
+                    'status' => 'fail',
+                    'message' => '数据库连接失败: ' . $e->getMessage(),
+                ];
+                $health['status'] = 'unhealthy';
+            }
+
+            return $health;
+
+        } catch (\Exception $e) {
+            return [
+                'status' => 'unhealthy',
+                'error' => $e->getMessage(),
+                'timestamp' => now()->toDateTimeString(),
+            ];
+        }
+    }
+}

+ 109 - 0
config/game_user_log.php

@@ -0,0 +1,109 @@
+<?php
+
+return [
+    /*
+    |--------------------------------------------------------------------------
+    | 用户日志收集配置
+    |--------------------------------------------------------------------------
+    |
+    | 这里配置用户日志收集的相关参数
+    |
+    */
+
+    // 是否启用用户日志收集
+    'enabled' => env('GAME_USER_LOG_ENABLED', true),
+
+    // 收集器配置
+    'collectors' => [
+        // 每次收集的最大记录数
+        'max_records_per_run' => env('GAME_USER_LOG_MAX_RECORDS', 1000),
+        
+        // 收集间隔(秒)
+        'collection_interval' => env('GAME_USER_LOG_INTERVAL', 2),
+        
+        // 是否启用各个收集器
+        'fund' => [
+            'enabled' => env('GAME_USER_LOG_FUND_ENABLED', true),
+            'min_amount' => env('GAME_USER_LOG_FUND_MIN_AMOUNT', 0), // 最小记录金额
+        ],
+        
+        'item' => [
+            'enabled' => env('GAME_USER_LOG_ITEM_ENABLED', true),
+            'min_quantity' => env('GAME_USER_LOG_ITEM_MIN_QUANTITY', 1), // 最小记录数量
+        ],
+        
+        'farm' => [
+            'enabled' => env('GAME_USER_LOG_FARM_ENABLED', true),
+        ],
+    ],
+
+    // 数据清理配置
+    'cleanup' => [
+        // 日志保留天数
+        'retention_days' => env('GAME_USER_LOG_RETENTION_DAYS', 30),
+        
+        // 是否启用自动清理
+        'auto_cleanup' => env('GAME_USER_LOG_AUTO_CLEANUP', true),
+        
+        // 清理时间(cron表达式)
+        'cleanup_schedule' => env('GAME_USER_LOG_CLEANUP_SCHEDULE', '0 2 * * *'), // 每天凌晨2点
+    ],
+
+    // 性能配置
+    'performance' => [
+        // 缓存TTL(秒)
+        'cache_ttl' => env('GAME_USER_LOG_CACHE_TTL', 86400), // 24小时
+        
+        // 批量处理大小
+        'batch_size' => env('GAME_USER_LOG_BATCH_SIZE', 100),
+        
+        // 是否启用队列处理
+        'use_queue' => env('GAME_USER_LOG_USE_QUEUE', true),
+        
+        // 队列名称
+        'queue_name' => env('GAME_USER_LOG_QUEUE', 'default'),
+    ],
+
+    // 过滤规则
+    'filters' => [
+        // 跳过的用户ID列表
+        'skip_user_ids' => [],
+        
+        // 跳过的操作类型
+        'skip_operation_types' => [],
+        
+        // 只记录特定来源类型
+        'allowed_source_types' => [
+            'fund', 'item', 'farm', 'pet', 'system'
+        ],
+    ],
+
+    // 消息模板配置
+    'message_templates' => [
+        'fund' => [
+            'gain' => '获得{fund_name} {amount}',
+            'cost' => '消耗{fund_name} {amount}',
+        ],
+        'item' => [
+            'gain' => '获得{item_name} {quantity}',
+            'cost' => '消耗{item_name} {quantity}',
+        ],
+        'farm' => [
+            'harvest' => '收获{land_id}号土地的{crop_name}',
+            'plant' => '在{land_id}号土地种植{crop_name}',
+            'upgrade' => '{upgrade_type}升级到{new_level}级',
+        ],
+    ],
+
+    // 监控配置
+    'monitoring' => [
+        // 是否启用性能监控
+        'enabled' => env('GAME_USER_LOG_MONITORING', false),
+        
+        // 慢查询阈值(毫秒)
+        'slow_query_threshold' => env('GAME_USER_LOG_SLOW_THRESHOLD', 1000),
+        
+        // 错误率告警阈值(百分比)
+        'error_rate_threshold' => env('GAME_USER_LOG_ERROR_THRESHOLD', 5),
+    ],
+];