Переглянути джерело

增加日志收集器启用/禁用配置功能

- 在game_configs表中添加5个日志收集器的开关配置项
- 在GameConfigService中添加收集器状态管理方法
- 在GameConfigLogic中添加setBool方法
- 修改UserLogCollectorManager支持检查收集器启用状态
- 修改CollectUserLogsCommand显示收集器启用状态
- 支持通过后台管理界面控制各收集器的启用/禁用
AI Assistant 6 місяців тому
батько
коміт
19d121f062

+ 243 - 0
AiWork/2025年06月/23日0130-重构用户日志收集器架构消除重复MaxId获取逻辑.md

@@ -0,0 +1,243 @@
+# 重构用户日志收集器架构,消除重复MaxId获取逻辑
+
+**时间**: 2025年06月23日 01:30-01:32  
+**状态**: ✅ 已完成
+
+## 问题描述
+
+在`app/Module/Game/Commands/CollectUserLogsCommand.php`中维护了一套获取MaxId的方法,这是重复的,应该使用收集器的方法。
+
+## 问题分析
+
+### 1. 重复逻辑问题
+CollectUserLogsCommand中的`getTableMaxId`方法:
+```php
+private function getTableMaxId(string $tableName): int
+{
+    try {
+        // 根据表名使用对应的模型
+        switch ($tableName) {
+            case 'fund_logs':
+                return \App\Module\Fund\Models\FundLogModel::max('id') ?: 0;
+            case 'item_transaction_logs':
+                return \App\Module\GameItems\Models\ItemTransactionLog::max('id') ?: 0;
+            // ... 更多case
+        }
+    } catch (\Exception $e) {
+        return 0;
+    }
+}
+```
+
+**问题**:
+- 维护了与收集器重复的逻辑
+- 每次新增收集器都需要修改Command代码
+- 违反了单一职责原则
+- 适用性差,扩展性不好
+
+### 2. 架构设计问题
+- Command层不应该知道具体的模型实现
+- 收集器应该自己负责提供源表信息
+- 缺乏统一的接口设计
+
+## 解决方案
+
+### 1. 在BaseLogCollector中添加通用方法
+```php
+/**
+ * 获取源表的最大ID
+ * 
+ * 子类可以重写此方法以提供更高效的实现
+ * 默认使用通用的DB查询方式
+ *
+ * @return int
+ */
+public function getSourceTableMaxId(): int
+{
+    try {
+        // 使用通用的DB查询方式作为默认实现
+        $result = \Illuminate\Support\Facades\DB::table($this->sourceTable)->max('id');
+        return $result ? (int)$result : 0;
+    } catch (\Exception $e) {
+        Log::error("获取源表最大ID失败", [
+            'collector' => $this->collectorName,
+            'source_table' => $this->sourceTable,
+            'error' => $e->getMessage()
+        ]);
+        return 0;
+    }
+}
+```
+
+### 2. 各收集器重写方法提供高效实现
+```php
+// FundLogCollector
+public function getSourceTableMaxId(): int
+{
+    return FundLogModel::max('id') ?: 0;
+}
+
+// ItemLogCollector  
+public function getSourceTableMaxId(): int
+{
+    return ItemTransactionLog::max('id') ?: 0;
+}
+```
+
+### 3. 在UserLogCollectorManager中添加代理方法
+```php
+/**
+ * 获取收集器的源表最大ID
+ *
+ * @param string $name 收集器名称
+ * @return int
+ */
+public function getCollectorSourceTableMaxId(string $name): int
+{
+    if (!isset($this->collectors[$name])) {
+        return 0;
+    }
+
+    return $this->collectors[$name]->getSourceTableMaxId();
+}
+```
+
+### 4. 修改Command使用收集器方法
+```php
+// 修改前
+$maxId = $this->getTableMaxId($info['source_table']);
+
+// 修改后  
+$maxId = $manager->getCollectorSourceTableMaxId($name);
+```
+
+## 实施过程
+
+### 1. 添加BaseLogCollector方法
+- 在BaseLogCollector中添加getSourceTableMaxId方法
+- 提供通用的DB查询实现作为默认方案
+- 添加异常处理和日志记录
+
+### 2. 各收集器重写方法
+- FundLogCollector: 使用FundLogModel::max('id')
+- ItemLogCollector: 使用ItemTransactionLog::max('id')  
+- FarmHarvestLogCollector: 使用FarmHarvestLog::max('id')
+- FarmUpgradeLogCollector: 使用FarmUpgradeLog::max('id')
+- PointLogCollector: 使用PointLogModel::max('id')
+
+### 3. 修改UserLogCollectorManager
+- 添加getCollectorSourceTableMaxId方法
+- 提供统一的接口访问收集器功能
+
+### 4. 重构CollectUserLogsCommand
+- 修改showCollectorProgress方法使用新接口
+- 修改showTimelineProgress方法使用新接口
+- 删除重复的getTableMaxId方法
+
+### 5. 测试验证
+```bash
+php artisan game:collect-user-logs --detail
+```
+
+输出正常:
+```
+📈 收集器进度状态:
+  🔧 fund: 最后处理ID 515845, 待处理 0 条
+  🔧 item: 最后处理ID 11802, 待处理 0 条
+  🔧 farm_harvest: 最后处理ID 432, 待处理 0 条
+  🔧 farm_upgrade: 最后处理ID 432, 待处理 0 条
+  🔧 point: 最后处理ID 372, 待处理 0 条
+```
+
+## 技术优势
+
+### 1. 单一职责原则
+- 每个收集器负责自己的源表信息
+- Command层只负责协调和显示
+- Manager层提供统一接口
+
+### 2. 开放封闭原则
+- 新增收集器无需修改Command代码
+- 收集器可以重写方法提供优化实现
+- 基类提供通用的默认实现
+
+### 3. 依赖倒置原则
+- Command依赖抽象接口而非具体实现
+- 通过Manager层解耦Command和收集器
+
+### 4. 性能优化
+- 各收集器使用模型查询,比通用DB查询更高效
+- 避免了switch-case的性能开销
+- 支持收集器级别的缓存优化
+
+## 架构改进
+
+### 修改前
+```
+CollectUserLogsCommand
+├── getTableMaxId() (重复逻辑)
+│   ├── switch case for fund_logs
+│   ├── switch case for item_transaction_logs
+│   └── ...
+└── 直接使用模型查询
+```
+
+### 修改后
+```
+CollectUserLogsCommand
+└── UserLogCollectorManager
+    └── BaseLogCollector
+        ├── getSourceTableMaxId() (通用实现)
+        └── 各收集器重写 (优化实现)
+            ├── FundLogCollector
+            ├── ItemLogCollector
+            └── ...
+```
+
+## 扩展性提升
+
+### 1. 新增收集器
+只需要:
+1. 继承BaseLogCollector
+2. 实现必要的抽象方法
+3. 可选择重写getSourceTableMaxId方法
+
+无需修改:
+- CollectUserLogsCommand
+- UserLogCollectorManager的核心逻辑
+
+### 2. 性能优化
+各收集器可以独立优化:
+- 添加缓存机制
+- 使用更高效的查询
+- 实现批量操作
+
+## 提交信息
+
+```bash
+git commit -m "重构用户日志收集器架构,消除重复的MaxId获取逻辑
+
+- 在BaseLogCollector中添加getSourceTableMaxId方法,提供通用的DB查询实现
+- 各收集器重写此方法,使用模型查询以获得更好的性能
+- 在UserLogCollectorManager中添加getCollectorSourceTableMaxId方法
+- 修改CollectUserLogsCommand使用收集器的方法而不是维护单独的getTableMaxId
+- 删除CollectUserLogsCommand中重复的getTableMaxId方法
+- 提高了代码的可维护性和扩展性,新增收集器无需修改Command代码"
+```
+
+## 总结
+
+✅ **重构完成**:
+1. 消除了Command层的重复逻辑
+2. 提高了代码的可维护性和扩展性
+3. 遵循了SOLID设计原则
+4. 保持了向后兼容性
+5. 提升了性能和可测试性
+
+**关键改进**:
+- 单一职责:每个组件负责自己的功能
+- 开放封闭:易于扩展,无需修改现有代码
+- 依赖倒置:依赖抽象而非具体实现
+- 性能优化:使用模型查询替代通用DB查询
+
+这次重构为用户日志收集系统奠定了更好的架构基础,为后续功能扩展提供了良好的支持。

+ 2 - 1
AiWork/now.md

@@ -1,11 +1,12 @@
 # 当前工作状态
 
-**最后更新**: 2025年06月23日 01:21
+**最后更新**: 2025年06月23日 01:32
 
 ## 正在进行的任务
 - 无
 
 ## 最近完成的任务
+- ✅ 重构用户日志收集器架构,消除重复MaxId获取逻辑(2025-06-23 01:32)
 - ✅ 修复用户日志收集器fund_logs进度显示问题(2025-06-23 01:21)
 - ✅ 修复用户日志收集器重复收集问题(2025-06-23 01:09)
 - ✅ 梳理种子生长周期的逻辑,解读萝卜的发芽期为什么是4小时

+ 12 - 1
app/Module/Game/Commands/CollectUserLogsCommand.php

@@ -213,9 +213,11 @@ class CollectUserLogsCommand extends Command
         }
 
         $info = $collectorsInfo[$collectorName];
+        $enabledStatus = $info['enabled'] ? '<info>✅ 启用</info>' : '<comment>❌ 禁用</comment>';
 
         $this->line("📝 收集器详情:");
         $this->line("  名称: <comment>{$info['name']}</comment>");
+        $this->line("  状态: {$enabledStatus}");
         $this->line("  类名: {$info['class']}");
         $this->line("  源表: <info>{$info['source_table']}</info>");
         $this->line("  类型: <info>{$info['source_type']}</info>");
@@ -305,7 +307,8 @@ class CollectUserLogsCommand extends Command
         $collectorsInfo = $manager->getCollectorsInfo();
 
         foreach ($collectorsInfo as $info) {
-            $this->line("收集器: <comment>{$info['name']}</comment>");
+            $enabledStatus = $info['enabled'] ? '<info>✅ 启用</info>' : '<comment>❌ 禁用</comment>';
+            $this->line("收集器: <comment>{$info['name']}</comment> ({$enabledStatus})");
             $this->line("  类名: {$info['class']}");
             $this->line("  源表: <info>{$info['source_table']}</info>");
             $this->line("  类型: <info>{$info['source_type']}</info>");
@@ -626,6 +629,10 @@ class CollectUserLogsCommand extends Command
                 $avgTime = round($result['execution_time'] / $result['processed_count'], 2);
                 $this->line("  📈 平均处理时间: <info>{$avgTime}ms/条</info>");
             }
+        } elseif ($result['status'] === 'disabled') {
+            $this->comment("⏸️ 收集器已禁用!");
+            $this->line("💡 <comment>提示</comment>:");
+            $this->line("  收集器已通过游戏配置禁用,如需启用请修改相关配置项");
         } else {
             $this->error("❌ 收集失败!");
             $this->line("🚨 <comment>错误信息</comment>:");
@@ -670,6 +677,8 @@ class CollectUserLogsCommand extends Command
             if ($result['status'] === 'success') {
                 $status = '<info>✅ 成功</info>';
                 $successCount++;
+            } elseif ($result['status'] === 'disabled') {
+                $status = '<comment>⏸️ 禁用</comment>';
             } else {
                 $status = '<error>❌ 失败</error>';
                 $failureCount++;
@@ -681,6 +690,8 @@ class CollectUserLogsCommand extends Command
 
             if ($result['status'] === 'error') {
                 $this->line("    🚨 错误信息: <error>{$result['error']}</error>");
+            } elseif ($result['status'] === 'disabled') {
+                $this->line("    💡 收集器已禁用,可通过游戏配置启用");
             } elseif ($result['processed_count'] > 0) {
                 $avgTime = round($result['execution_time'] / $result['processed_count'], 2);
                 $this->line("    📈 平均时间: <info>{$avgTime}</info> ms/条");

+ 12 - 0
app/Module/Game/Logics/GameConfigLogic.php

@@ -107,6 +107,18 @@ class GameConfigLogic
         return (bool) self::get($key, $default);
     }
 
+    /**
+     * 设置布尔值配置
+     *
+     * @param string $key 配置键名
+     * @param bool $value 配置值
+     * @return bool
+     */
+    public static function setBool(string $key, bool $value): bool
+    {
+        return self::set($key, $value ? '1' : '0');
+    }
+
     /**
      * 获取整数配置
      *

+ 33 - 6
app/Module/Game/Logics/UserLogCollectorManager.php

@@ -8,6 +8,7 @@ use App\Module\Game\Logics\UserLogCollectors\ItemLogCollector;
 use App\Module\Game\Logics\UserLogCollectors\FarmHarvestLogCollector;
 use App\Module\Game\Logics\UserLogCollectors\FarmUpgradeLogCollector;
 use App\Module\Game\Logics\UserLogCollectors\PointLogCollector;
+use App\Module\Game\Services\GameConfigService;
 use Illuminate\Support\Facades\Log;
 
 /**
@@ -67,6 +68,20 @@ class UserLogCollectorManager
 
         foreach ($this->collectors as $name => $collector) {
             try {
+                // 检查收集器是否启用
+                if (!GameConfigService::isCollectorEnabled($name)) {
+                    $results[$name] = [
+                        'processed_count' => 0,
+                        'execution_time' => 0,
+                        'status' => 'disabled'
+                    ];
+
+                    Log::info("收集器已禁用,跳过执行", [
+                        'collector' => $name
+                    ]);
+                    continue;
+                }
+
                 // 如果指定了限制,设置收集器的最大记录数
                 if ($limit !== null) {
                     $collector->setMaxRecords($limit);
@@ -75,13 +90,13 @@ class UserLogCollectorManager
                 $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("收集器执行完成", [
@@ -133,13 +148,24 @@ class UserLogCollectorManager
             throw new \InvalidArgumentException("收集器 {$collectorName} 不存在");
         }
 
+        // 检查收集器是否启用
+        if (!GameConfigService::isCollectorEnabled($collectorName)) {
+            return [
+                'collector' => $collectorName,
+                'processed_count' => 0,
+                'execution_time' => 0,
+                'status' => 'disabled',
+                'timestamp' => now()->toDateTimeString()
+            ];
+        }
+
         $collector = $this->collectors[$collectorName];
         $startTime = microtime(true);
-        
+
         try {
             $processedCount = $collector->collect();
             $endTime = microtime(true);
-            
+
             return [
                 'collector' => $collectorName,
                 'processed_count' => $processedCount,
@@ -150,7 +176,7 @@ class UserLogCollectorManager
 
         } catch (\Exception $e) {
             $endTime = microtime(true);
-            
+
             return [
                 'collector' => $collectorName,
                 'processed_count' => 0,
@@ -170,13 +196,14 @@ class UserLogCollectorManager
     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(),
+                'enabled' => GameConfigService::isCollectorEnabled($name),
             ];
         }
 

+ 37 - 0
app/Module/Game/Services/GameConfigService.php

@@ -168,6 +168,43 @@ class GameConfigService
         return GameConfigLogic::getInt('reward.log_retention_days', 90);
     }
 
+    /**
+     * 检查指定收集器是否启用
+     */
+    public static function isCollectorEnabled(string $collectorName): bool
+    {
+        $key = "user_log.collector.{$collectorName}.enabled";
+        return GameConfigLogic::getBool($key, true);
+    }
+
+    /**
+     * 获取所有收集器的启用状态
+     */
+    public static function getCollectorStates(): array
+    {
+        return [
+            'fund' => self::isCollectorEnabled('fund'),
+            'item' => self::isCollectorEnabled('item'),
+            'farm_harvest' => self::isCollectorEnabled('farm_harvest'),
+            'farm_upgrade' => self::isCollectorEnabled('farm_upgrade'),
+            'point' => self::isCollectorEnabled('point'),
+        ];
+    }
+
+    /**
+     * 批量设置收集器启用状态
+     */
+    public static function setCollectorStates(array $states): bool
+    {
+        foreach ($states as $collectorName => $enabled) {
+            $key = "user_log.collector.{$collectorName}.enabled";
+            if (!GameConfigLogic::setBool($key, $enabled)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * 获取条件缓存时间
      */