Преглед изворни кода

修复用户日志收集器重复收集问题

问题分析:
- 用户39078查询到的日志全是钻石消耗,与后台和数据库查询结果不一致
- 根本原因:BaseLogCollector的getLastProcessedId方法使用source_type查询最大source_id
- FundLogCollector会根据不同操作类型生成不同source_type(如farm_upgrade、system等)
- 导致进度追踪不准确,同一fund_log记录被重复收集数千次

修复内容:
1. 修改BaseLogCollector.getLastProcessedId()方法,只按source_table查询最大source_id
2. 在collectById()中添加重复检查,避免重复收集已存在记录
3. 修改isDuplicateRecord()方法,不依赖source_type进行重复检查
4. 修复FundLogCollector中operate_type字段的枚举转换问题
5. 清理数据库中的重复记录,只保留每个source_id的第一条记录

测试结果:
- 用户39078的日志现在显示多样化内容:农场收获、物品交易、积分获得等
- 后台管理界面正常显示各种类型的日志
- 重复收集问题已解决,进度追踪机制正常工作
AI Assistant пре 6 месеци
родитељ
комит
c99285ec7b

+ 15 - 4
app/Module/Game/Logics/UserLogCollectors/BaseLogCollector.php

@@ -125,6 +125,12 @@ abstract class BaseLogCollector
         $maxId = 0;
 
         foreach ($records as $record) {
+            // 检查是否已经收集过此记录,避免重复收集
+            if ($this->isDuplicateRecord($this->sourceTable, $record->id)) {
+                $maxId = max($maxId, $record->id);
+                continue;
+            }
+
             $userLogData = $this->convertToUserLog($record);
             if ($userLogData) {
                 $userLogs[] = $userLogData;
@@ -196,14 +202,17 @@ abstract class BaseLogCollector
      * 获取上次处理的最大ID
      * 从user_logs表中查询该收集器最后处理的记录ID
      *
+     * 注意:对于同一个source_table可能有多个source_type的情况(如FundLogCollector),
+     * 需要查询该表的所有记录的最大source_id,而不是仅查询当前source_type的记录
+     *
      * @return int
      */
     protected function getLastProcessedId(): int
     {
         try {
-            // 直接查询对应表的最大source_id
+            // 查询该source_table的所有记录的最大source_id
+            // 这样可以避免因为动态source_type导致的重复收集问题
             $maxId = \App\Module\Game\Models\UserLog::where('source_table', $this->sourceTable)
-                ->where('source_type', $this->sourceType)
                 ->max('source_id');
 
             return $maxId ?: 0;
@@ -280,6 +289,9 @@ abstract class BaseLogCollector
     /**
      * 检查是否为重复记录
      *
+     * 注意:不使用source_type进行检查,因为同一个source_table的记录可能有不同的source_type
+     * 只要source_table和source_id相同,就认为是重复记录
+     *
      * @param string $sourceTable 源表名
      * @param int $sourceId 源记录ID
      * @return bool
@@ -287,8 +299,7 @@ abstract class BaseLogCollector
     protected function isDuplicateRecord(string $sourceTable, int $sourceId): bool
     {
         try {
-            $exists = \App\Module\Game\Models\UserLog::where('source_type', $this->sourceType)
-                ->where('source_table', $sourceTable)
+            $exists = \App\Module\Game\Models\UserLog::where('source_table', $sourceTable)
                 ->where('source_id', $sourceId)
                 ->exists();
 

+ 26 - 7
app/Module/Game/Logics/UserLogCollectors/FundLogCollector.php

@@ -77,10 +77,17 @@ class FundLogCollector extends BaseLogCollector
      */
     protected function getNewRecords(int $lastProcessedId)
     {
-        $records = FundLogModel::where('id', '>', $lastProcessedId)
+        // 使用原始查询避免枚举转换问题
+        $records = FundLogModel::selectRaw('*')
+            ->where('id', '>', $lastProcessedId)
             ->orderBy('id')
             ->limit($this->maxRecords)
-            ->get();
+            ->get()
+            ->map(function ($record) {
+                // 手动设置原始属性,避免枚举转换
+                $record->setRawAttributes($record->getAttributes(), true);
+                return $record;
+            });
 
         // 预加载商店商品信息
         $this->preloadShopItems($records);
@@ -155,12 +162,24 @@ class FundLogCollector extends BaseLogCollector
         }
 
         // 特殊处理operate_type=3(管理员操作)的情况
-        $operateTypeValue = is_object($record->operate_type) ? $record->operate_type->value : $record->operate_type;
-        if ($operateTypeValue == 3) {
-            $sourceMessage = $this->getAdminOperationMessage($record, $action);
-            if ($sourceMessage) {
-                return "{$sourceMessage}{$action}{$fundName} {$amount}";
+        try {
+            // 直接从属性数组获取值,避免枚举转换问题
+            $operateTypeValue = $record->getAttributes()['operate_type'] ?? null;
+
+            if ($operateTypeValue == 3 || $operateTypeValue === '3') {
+                $sourceMessage = $this->getAdminOperationMessage($record, $action);
+                if ($sourceMessage) {
+                    return "{$sourceMessage}{$action}{$fundName} {$amount}";
+                }
             }
+        } catch (\Exception $e) {
+            // 如果类型转换失败,记录错误但继续处理
+            \Illuminate\Support\Facades\Log::warning("处理operate_type失败", [
+                'record_id' => $record->id,
+                'operate_type_raw' => $record->getAttributes()['operate_type'] ?? 'null',
+                'error' => $e->getMessage(),
+                'collector' => $this->collectorName
+            ]);
         }
 
         // 如果无法解析来源信息,使用默认格式