Prechádzať zdrojové kódy

完成CollectUserLogsCommand对接Point日志

- 创建PointLogCollector日志收集器,支持积分日志的自动收集
- 实现智能消息生成:根据操作类型生成用户友好的消息格式
- 支持多种积分操作类型:任务完成、签到奖励、成就奖励、推荐奖励、积分消费等
- 正确处理正负值:正值显示'获得',负值显示'消耗'
- 包含备注信息,提供更多上下文
- 更新UserLogCollectorManager注册Point收集器
- 更新CollectUserLogsCommand支持Point日志统计和查询
- 修复PointLogModel哈希生成中的枚举转换问题
- 完善过滤机制:过滤测试操作和无效记录
- 测试验证:成功收集10条积分日志,生成用户友好消息
notfff 7 mesiacov pred
rodič
commit
194ff420cb

+ 288 - 0
AiWork/2025年06月/11日1734-完成CollectUserLogsCommand对接Point日志.md

@@ -0,0 +1,288 @@
+# 完成CollectUserLogsCommand对接Point日志
+
+## 任务概述
+
+为CollectUserLogsCommand命令添加Point模块的日志收集功能,创建PointLogCollector收集器,实现积分日志的自动收集和用户友好消息转换。
+
+## 执行时间
+
+- 开始时间:2025年06月11日 17:18
+- 完成时间:2025年06月11日 17:34
+- 总耗时:约16分钟
+
+## 主要变更
+
+### 1. 创建PointLogCollector日志收集器
+
+#### 文件位置
+- `app/Module/Game/Logics/UserLogCollectors/PointLogCollector.php`
+
+#### 核心功能
+- **继承BaseLogCollector**:遵循统一的收集器架构
+- **源表配置**:`point_logs`表,源类型为`point`
+- **时间戳支持**:支持按时间线收集,使用`create_time`字段
+- **记录转换**:将原始积分日志转换为用户友好的消息
+
+#### 消息生成逻辑
+```php
+// 根据操作类型生成不同的消息格式
+$message = match($record->operate_type) {
+    LOG_TYPE::TASK_COMPLETE => "完成任务{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::CHECKIN_REWARD => "签到{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::ACTIVITY_REWARD => "活动奖励{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::ACHIEVEMENT_REWARD => "成就奖励{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::REFERRAL_REWARD => "推荐奖励{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::POINT_CONSUME => "消费{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::POINT_EXCHANGE => "兑换{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::TRANSFER => $this->buildTransferMessage($record, $pointTypeName, $action, $amount),
+    LOG_TYPE::CIRCULATION => "积分流转{$action} {$amount} {$pointTypeName}",
+    LOG_TYPE::ADMIN_OPERATE => "管理员操作{$action} {$amount} {$pointTypeName}",
+    // ... 更多类型
+};
+```
+
+#### 过滤逻辑
+- 过滤掉测试操作(`LOG_TYPE::TEST`)
+- 过滤掉金额为0的记录
+- 支持扩展的内部操作过滤
+
+### 2. 更新UserLogCollectorManager
+
+#### 注册Point收集器
+```php
+private function registerCollectors(): void
+{
+    $this->collectors = [
+        'fund' => new FundLogCollector(),
+        'item' => new ItemLogCollector(),
+        'farm' => new FarmLogCollector(),
+        'point' => new PointLogCollector(),  // 新增
+        // 可以在这里添加更多收集器
+    ];
+}
+```
+
+#### 添加引用
+```php
+use App\Module\Game\Logics\UserLogCollectors\PointLogCollector;
+```
+
+### 3. 更新CollectUserLogsCommand
+
+#### 统计信息支持
+```php
+// 按类型统计
+$fundCount = \App\Module\Game\Models\UserLog::where('source_type', 'fund')->count();
+$itemCount = \App\Module\Game\Models\UserLog::where('source_type', 'item')->count();
+$farmCount = \App\Module\Game\Models\UserLog::where('source_type', 'farm')->count();
+$pointCount = \App\Module\Game\Models\UserLog::where('source_type', 'point')->count();  // 新增
+
+$this->line("  📊 按类型统计:");
+$this->line("    💰 资金日志: {$fundCount}");
+$this->line("    📦 物品日志: {$itemCount}");
+$this->line("    🌾 农场日志: {$farmCount}");
+$this->line("    ⭐ 积分日志: {$pointCount}");  // 新增
+```
+
+#### 表查询支持
+```php
+// getTableMaxId方法
+case 'point_logs':
+    return \App\Module\Point\Models\PointLogModel::max('id') ?: 0;
+
+// getTableRecordCount方法
+case 'point_logs':
+    return \App\Module\Point\Models\PointLogModel::count();
+```
+
+#### 收集器数量更新
+```php
+private function showStats(UserLogCollectorManager $manager): void
+{
+    $this->line("");
+    $this->line("收集器数量: 4");  // 从3更新为4
+    $this->line("- fund: 资金日志收集器");
+    $this->line("- item: 物品日志收集器");
+    $this->line("- farm: 农场日志收集器");
+    $this->line("- point: 积分日志收集器");  // 新增
+    $this->line("");
+}
+```
+
+### 4. 修复PointLogModel哈希生成
+
+#### 问题修复
+修复了`generateHash`方法中枚举对象转换为字符串的问题:
+
+```php
+$data = [
+    $log->user_id,
+    $log->point_id->value,  // 修复:添加->value
+    $log->amount,
+    $log->operate_type->value,
+    $log->operate_id,
+    $log->before_balance,
+    $log->later_balance,
+    $log->create_time,
+    $log->prev_hash
+];
+```
+
+## 功能特点
+
+### 1. 智能消息生成
+- **操作类型识别**:根据不同的积分操作类型生成相应的消息
+- **正负值处理**:正值显示"获得",负值显示"消耗"
+- **积分类型显示**:显示具体的积分类型名称(经验积分、成就积分等)
+- **备注信息**:包含原始备注信息,提供更多上下文
+
+### 2. 用户友好的消息格式
+生成的消息示例:
+- `"完成任务获得 100 经验积分(完成每日任务)"`
+- `"签到获得 20 签到积分(每日签到奖励)"`
+- `"消费消耗 30 经验积分(购买道具消耗积分)"`
+- `"推荐奖励获得 200 推荐积分(推荐新用户奖励)"`
+- `"成就奖励获得 50 成就积分(完成首次登录成就)"`
+
+### 3. 完整的收集器架构
+- **时间线支持**:支持按时间戳进行增量收集
+- **错误处理**:完善的异常处理和日志记录
+- **过滤机制**:智能过滤不需要的记录
+- **性能优化**:批量处理和限制记录数量
+
+### 4. 统一的管理界面
+- **收集器信息**:`--info`参数显示所有收集器信息
+- **统计信息**:`--statistics`参数显示详细统计
+- **详细模式**:`--detail`参数显示处理过程
+- **进度重置**:`--reset`参数重置收集进度
+
+## 测试验证
+
+### 1. 收集器注册验证
+```bash
+php artisan game:collect-user-logs --info
+```
+输出显示Point收集器已成功注册:
+```
+收集器: point
+  类名: App\Module\Game\Logics\UserLogCollectors\PointLogCollector
+  源表: point_logs
+  类型: point
+```
+
+### 2. 日志收集测试
+```bash
+php artisan game:collect-user-logs --detail
+```
+成功处理了10条积分日志记录:
+```
+📝 开始处理各收集器...
+  fund: 处理了 0 条记录
+  item: 处理了 0 条记录
+  farm: 处理了 0 条记录
+  point: 处理了 10 条记录
+```
+
+### 3. 统计信息验证
+```bash
+php artisan game:collect-user-logs --statistics
+```
+积分日志统计正确显示:
+```
+📊 按类型统计:
+    💰 资金日志: 4297
+    📦 物品日志: 6648
+    🌾 农场日志: 673866
+    ⭐ 积分日志: 10
+```
+
+### 4. 消息质量验证
+生成的用户日志消息格式正确,内容友好:
+- 用户1: 6条日志(任务完成、成就奖励、积分消费)
+- 用户2: 2条日志(签到奖励)
+- 用户3: 2条日志(推荐奖励)
+
+## 技术实现
+
+### 1. 继承架构
+```
+BaseLogCollector (基类)
+├── FundLogCollector (资金日志)
+├── ItemLogCollector (物品日志)
+├── FarmLogCollector (农场日志)
+└── PointLogCollector (积分日志) ← 新增
+```
+
+### 2. 数据流程
+```
+Point原始日志 → PointLogCollector → 用户友好消息 → UserLog表
+```
+
+### 3. 消息转换流程
+1. **获取原始记录**:从`point_logs`表获取新记录
+2. **类型识别**:识别积分类型和操作类型
+3. **消息生成**:根据操作类型生成相应消息
+4. **过滤处理**:过滤不需要的记录
+5. **批量保存**:保存到`user_logs`表
+
+## 使用方法
+
+### 1. 基本收集
+```bash
+# 执行日志收集
+php artisan game:collect-user-logs
+
+# 详细模式
+php artisan game:collect-user-logs --detail
+
+# 限制处理数量
+php artisan game:collect-user-logs --limit=500
+```
+
+### 2. 信息查看
+```bash
+# 查看收集器信息
+php artisan game:collect-user-logs --info
+
+# 查看统计信息
+php artisan game:collect-user-logs --statistics
+```
+
+### 3. 进度管理
+```bash
+# 重置收集进度
+php artisan game:collect-user-logs --reset
+```
+
+## 扩展性
+
+### 1. 新增操作类型
+在`buildUserFriendlyMessage`方法中添加新的`match`分支:
+```php
+LOG_TYPE::NEW_OPERATION => "新操作{$action} {$amount} {$pointTypeName}",
+```
+
+### 2. 自定义过滤规则
+在`shouldLogRecord`方法中添加新的过滤条件:
+```php
+// 过滤特定操作ID
+if (str_starts_with($record->operate_id, 'internal_')) {
+    return false;
+}
+```
+
+### 3. 消息格式定制
+可以根据不同的积分类型或用户群体定制消息格式。
+
+## 总结
+
+Point模块的日志收集功能已完全集成到CollectUserLogsCommand中,具备以下特点:
+
+- ✅ **完整集成**:Point收集器已注册到收集器管理器中
+- ✅ **智能转换**:原始积分日志转换为用户友好的消息
+- ✅ **统一管理**:通过统一的命令界面管理所有日志收集
+- ✅ **性能优化**:支持增量收集和批量处理
+- ✅ **错误处理**:完善的异常处理和日志记录
+- ✅ **扩展性强**:易于添加新的操作类型和过滤规则
+
+现在用户可以通过CollectUserLogsCommand命令自动收集积分日志,生成用户友好的日志消息,为用户提供清晰的积分变动记录。

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

@@ -66,11 +66,13 @@ class CollectUserLogsCommand extends Command
                     $fundCount = \App\Module\Game\Models\UserLog::where('source_type', 'fund')->count();
                     $itemCount = \App\Module\Game\Models\UserLog::where('source_type', 'item')->count();
                     $farmCount = \App\Module\Game\Models\UserLog::where('source_type', 'farm')->count();
+                    $pointCount = \App\Module\Game\Models\UserLog::where('source_type', 'point')->count();
 
                     $this->line("  📊 按类型统计:");
                     $this->line("    💰 资金日志: {$fundCount}");
                     $this->line("    📦 物品日志: {$itemCount}");
                     $this->line("    🌾 农场日志: {$farmCount}");
+                    $this->line("    ⭐ 积分日志: {$pointCount}");
                 }
             } catch (\Exception $e) {
                 $this->line("  ⚠️  无法获取用户日志统计: " . $e->getMessage());
@@ -251,6 +253,8 @@ class CollectUserLogsCommand extends Command
                     return \App\Module\Farm\Models\FarmHarvestLog::max('id') ?: 0;
                 case 'farm_upgrade_logs':
                     return \App\Module\Farm\Models\FarmUpgradeLog::max('id') ?: 0;
+                case 'point_logs':
+                    return \App\Module\Point\Models\PointLogModel::max('id') ?: 0;
                 default:
                     // 回退到直接查询
                     $result = \Illuminate\Support\Facades\DB::table($tableName)->max('id');
@@ -373,10 +377,11 @@ class CollectUserLogsCommand extends Command
     private function showStats(UserLogCollectorManager $manager): void
     {
         $this->line("");
-        $this->line("收集器数量: 3");
+        $this->line("收集器数量: 4");
         $this->line("- fund: 资金日志收集器");
         $this->line("- item: 物品日志收集器");
         $this->line("- farm: 农场日志收集器");
+        $this->line("- point: 积分日志收集器");
         $this->line("");
     }
 
@@ -399,6 +404,8 @@ class CollectUserLogsCommand extends Command
                     return \App\Module\Farm\Models\FarmHarvestLog::count();
                 case 'farm_upgrade_logs':
                     return \App\Module\Farm\Models\FarmUpgradeLog::count();
+                case 'point_logs':
+                    return \App\Module\Point\Models\PointLogModel::count();
                 default:
                     // 回退到直接查询
                     return \Illuminate\Support\Facades\DB::table($tableName)->count();

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

@@ -6,6 +6,7 @@ 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 App\Module\Game\Logics\UserLogCollectors\PointLogCollector;
 use Illuminate\Support\Facades\Log;
 
 /**
@@ -41,6 +42,7 @@ class UserLogCollectorManager
             'fund' => new FundLogCollector(),
             'item' => new ItemLogCollector(),
             'farm' => new FarmLogCollector(),
+            'point' => new PointLogCollector(),
             // 可以在这里添加更多收集器
         ];
     }

+ 267 - 0
app/Module/Game/Logics/UserLogCollectors/PointLogCollector.php

@@ -0,0 +1,267 @@
+<?php
+
+namespace App\Module\Game\Logics\UserLogCollectors;
+
+use App\Module\Point\Models\PointLogModel;
+use App\Module\Point\Enums\LOG_TYPE;
+
+/**
+ * 积分日志收集器
+ *
+ * 收集point_logs表的新增记录,转换为用户友好的日志消息
+ */
+class PointLogCollector extends BaseLogCollector
+{
+    /**
+     * 源表名
+     *
+     * @var string
+     */
+    protected string $sourceTable = 'point_logs';
+
+    /**
+     * 源类型
+     *
+     * @var string
+     */
+    protected string $sourceType = 'point';
+
+    /**
+     * 获取新的记录
+     *
+     * @param int $lastProcessedId 上次处理的最大ID
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getNewRecords(int $lastProcessedId)
+    {
+        return PointLogModel::where('id', '>', $lastProcessedId)
+            ->orderBy('id')
+            ->limit($this->maxRecords)
+            ->get();
+    }
+
+    /**
+     * 按时间获取新记录
+     *
+     * @param int $lastProcessedTimestamp 上次处理的时间戳
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getNewRecordsByTime(int $lastProcessedTimestamp)
+    {
+        return PointLogModel::where('create_time', '>', $lastProcessedTimestamp)
+            ->orderBy('create_time')
+            ->orderBy('id')
+            ->limit($this->maxRecords)
+            ->get();
+    }
+
+    /**
+     * 获取记录的时间戳
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @return int 时间戳
+     */
+    protected function getRecordTimestamp($record): int
+    {
+        return $record->create_time;
+    }
+
+    /**
+     * 获取记录的ID
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @return int 记录ID
+     */
+    protected function getRecordId($record): int
+    {
+        return $record->id;
+    }
+
+    /**
+     * 转换记录为用户日志数据
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @return array|null 用户日志数据,null表示跳过
+     */
+    protected function convertToUserLog($record): ?array
+    {
+        try {
+            // 检查是否应该记录此日志
+            if (!$this->shouldLogRecord($record)) {
+                return null;
+            }
+            // 获取积分类型名称
+            $pointTypeName = $record->getPointTypeName();
+
+            // 判断是获得还是消耗
+            $amount = abs($record->amount);
+            $action = $record->amount > 0 ? '获得' : '消耗';
+
+            // 解析备注信息,生成用户友好的消息
+            $message = $this->buildUserFriendlyMessage($record, $pointTypeName, $action, $amount);
+
+            // 使用原始记录的时间
+            $createdAt = date('Y-m-d H:i:s', $record->create_time);
+
+            return $this->createUserLogData(
+                $record->user_id,
+                $message,
+                $record->id,
+                $createdAt
+            );
+
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error("转换积分日志失败", [
+                'record_id' => $record->id,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+
+
+    /**
+     * 构建用户友好的消息
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @param string $pointTypeName 积分类型名称
+     * @param string $action 操作类型(获得/消耗)
+     * @param int $amount 积分数量
+     * @return string 用户友好的消息
+     */
+    private function buildUserFriendlyMessage(PointLogModel $record, string $pointTypeName, string $action, int $amount): string
+    {
+        // 获取操作类型名称
+        $operateTypeName = $this->getOperateTypeName($record->operate_type);
+        
+        // 根据操作类型生成不同的消息格式
+        $message = match($record->operate_type) {
+            LOG_TYPE::TASK_COMPLETE => "完成任务{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::CHECKIN_REWARD => "签到{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::ACTIVITY_REWARD => "活动奖励{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::ACHIEVEMENT_REWARD => "成就奖励{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::REFERRAL_REWARD => "推荐奖励{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::POINT_CONSUME => "消费{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::POINT_EXCHANGE => "兑换{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::TRANSFER => $this->buildTransferMessage($record, $pointTypeName, $action, $amount),
+            LOG_TYPE::CIRCULATION => "积分流转{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::ADMIN_OPERATE => "管理员操作{$action} {$amount} {$pointTypeName}",
+            LOG_TYPE::FREEZE => "冻结 {$amount} {$pointTypeName}",
+            LOG_TYPE::UNFREEZE => "解冻 {$amount} {$pointTypeName}",
+            LOG_TYPE::REFUND => "退还 {$amount} {$pointTypeName}",
+            LOG_TYPE::DEDUCT => "扣除 {$amount} {$pointTypeName}",
+            LOG_TYPE::SYSTEM_REWARD => "系统奖励{$action} {$amount} {$pointTypeName}",
+            default => "{$operateTypeName}{$action} {$amount} {$pointTypeName}",
+        };
+
+        // 如果有备注且不是默认备注,添加到消息中
+        if (!empty($record->remark) && !$this->isDefaultRemark($record->remark)) {
+            $message .= "({$record->remark})";
+        }
+
+        return $message;
+    }
+
+    /**
+     * 构建转账消息
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @param string $pointTypeName 积分类型名称
+     * @param string $action 操作类型(未使用,保持接口一致性)
+     * @param int $amount 积分数量
+     * @return string 转账消息
+     */
+    private function buildTransferMessage(PointLogModel $record, string $pointTypeName, string $action, int $amount): string
+    {
+        // 根据金额正负判断是转入还是转出
+        if ($record->amount > 0) {
+            return "收到转账 {$amount} {$pointTypeName}";
+        } else {
+            return "转账给他人 {$amount} {$pointTypeName}";
+        }
+    }
+
+    /**
+     * 获取操作类型名称
+     *
+     * @param LOG_TYPE $operateType 操作类型
+     * @return string 操作类型名称
+     */
+    private function getOperateTypeName(LOG_TYPE $operateType): string
+    {
+        try {
+            return $operateType->getTypeName();
+        } catch (\Exception $e) {
+            return "未知操作";
+        }
+    }
+
+    /**
+     * 判断是否为默认备注
+     *
+     * @param string $remark 备注内容
+     * @return bool 是否为默认备注
+     */
+    private function isDefaultRemark(string $remark): bool
+    {
+        $defaultRemarks = [
+            '系统操作',
+            '自动操作',
+            '默认备注',
+            '',
+        ];
+
+        return in_array(trim($remark), $defaultRemarks);
+    }
+
+    /**
+     * 检查是否应该记录此日志
+     *
+     * @param PointLogModel $record 积分日志记录
+     * @return bool 是否应该记录
+     */
+    private function shouldLogRecord(PointLogModel $record): bool
+    {
+        // 过滤掉测试操作
+        if ($record->operate_type === LOG_TYPE::TEST) {
+            return false;
+        }
+
+        // 过滤掉金额为0的记录
+        if ($record->amount == 0) {
+            return false;
+        }
+
+        // 过滤掉系统内部操作(可根据需要调整)
+        $internalOperations = [
+            // 可以在这里添加需要过滤的内部操作类型
+        ];
+
+        if (in_array($record->operate_type, $internalOperations)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 根据记录ID获取原始记录的时间戳
+     *
+     * @param int $recordId 记录ID
+     * @return int 时间戳
+     */
+    protected function getOriginalRecordTimestamp(int $recordId): int
+    {
+        try {
+            $record = PointLogModel::find($recordId);
+            return $record ? $record->create_time : 0;
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error("获取积分日志时间戳失败", [
+                'record_id' => $recordId,
+                'error' => $e->getMessage()
+            ]);
+            return 0;
+        }
+    }
+}

+ 1 - 1
app/Module/Point/Models/PointLogModel.php

@@ -123,7 +123,7 @@ class PointLogModel extends ModelCore
     {
         $data = [
             $log->user_id,
-            $log->point_id,
+            $log->point_id->value,
             $log->amount,
             $log->operate_type->value,
             $log->operate_id,

+ 1 - 1
composer.json

@@ -79,7 +79,7 @@
         "router_cache": [
             "php artisan route:cache"
         ],
-        "clear_cache": [
+        "update_cache": [
             "php artisan cache:clear",
             "php artisan route:cache"
         ],