23日0109-修复用户日志收集器重复收集问题.md 6.2 KB

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

时间: 2025-06-23 01:09
任务类型: Bug修复
影响模块: Game模块 - 用户日志系统

问题描述

用户反馈查询到的user_log数据全是钻石消耗,这不正常,与后台管理界面 /admin/game-user-logs?user_id=39078 和数据库查询到的数据有差异。

问题现象

  • 用户39078的日志查询结果只显示钻石消耗记录
  • 后台管理界面显示为空或数据不一致
  • 数据库中存在大量重复的日志记录

问题分析

根本原因

  1. 进度追踪机制缺陷: BaseLogCollector::getLastProcessedId() 方法使用 source_typesource_table 联合查询最大 source_id
  2. 动态source_type问题: FundLogCollector 会根据不同操作类型生成不同的 source_type(如 farm_upgradesystem 等)
  3. 重复收集: 当查询 source_type='system' 的最大ID时,无法获取到 source_type='farm_upgrade' 的记录,导致重复收集

数据验证

-- 发现同一个fund_log记录被重复收集2938次
SELECT COUNT(*) FROM kku_user_logs WHERE source_table = 'fund_logs' AND source_id = 515842;
-- 结果: 2938条重复记录

-- 不同source_type的分布
SELECT source_type, COUNT(*) FROM kku_user_logs WHERE source_table = 'fund_logs' GROUP BY source_type;
-- farm_upgrade: 66992条
-- system: 188条

修复方案

1. 修改进度追踪逻辑

文件: app/Module/Game/Logics/UserLogCollectors/BaseLogCollector.php

// 修改前:按source_type和source_table查询
$maxId = UserLog::where('source_table', $this->sourceTable)
    ->where('source_type', $this->sourceType)
    ->max('source_id');

// 修改后:只按source_table查询,避免动态source_type问题
$maxId = UserLog::where('source_table', $this->sourceTable)
    ->max('source_id');

2. 添加重复检查机制

collectById() 方法中添加重复检查:

foreach ($records as $record) {
    // 检查是否已经收集过此记录,避免重复收集
    if ($this->isDuplicateRecord($this->sourceTable, $record->id)) {
        $maxId = max($maxId, $record->id);
        continue;
    }
    // ... 处理逻辑
}

3. 优化重复检查方法

修改 isDuplicateRecord() 方法,不依赖 source_type

// 只要source_table和source_id相同,就认为是重复记录
$exists = UserLog::where('source_table', $sourceTable)
    ->where('source_id', $sourceId)
    ->exists();

4. 修复枚举转换问题

文件: app/Module/Game/Logics/UserLogCollectors/FundLogCollector.php

解决 operate_type 字段的枚举转换错误:

  • 使用原始查询避免枚举转换
  • 直接从属性数组获取值
  • 添加异常处理机制

数据清理

清理重复的用户日志记录:

-- 删除重复记录,只保留每个source_table+source_id组合的第一条记录
DELETE ul1 FROM kku_user_logs ul1
INNER JOIN (
    SELECT source_table, source_id, MIN(id) as min_id
    FROM kku_user_logs 
    WHERE source_table = 'fund_logs'
    GROUP BY source_table, source_id
    HAVING COUNT(*) > 1
) ul2 ON ul1.source_table = ul2.source_table 
    AND ul1.source_id = ul2.source_id 
    AND ul1.id > ul2.min_id;

测试验证

1. 命令行测试

# 查看收集器状态
php artisan game:collect-user-logs --info

# 执行收集测试
php artisan game:collect-user-logs --detail --limit=20

2. 数据验证

-- 验证用户39078的日志多样性
SELECT * FROM kku_user_logs WHERE user_id = 39078 ORDER BY original_time DESC LIMIT 10;

结果: 现在显示多样化内容:

  • 农场收获日志(farm)
  • 物品交易日志(system、farm_plant)
  • 积分获得日志(point)

3. 后台界面测试

访问 http://kku_laravel.local.gd/admin/game-user-logs?user_id=39078

结果: 正常显示各种类型的日志,包含:

  • ⚙️ 系统:farm_harvest获得西瓜、消耗高级化肥等
  • 🌿 农场种植:播种消耗神秘种子
  • farm:收获土地的神秘种子
  • point:种植作物获得积分

代码提交

提交哈希: c99285ec
提交信息: 修复用户日志收集器重复收集问题
文件变更: 2个文件,新增41行,删除11行
已推送到远程仓库: ✅

影响评估

正面影响

  1. 数据准确性: 用户日志不再重复收集,数据更加准确
  2. 性能提升: 避免重复处理,提高收集效率
  3. 用户体验: 用户查询到的日志内容更加丰富和真实

风险控制

  1. 向后兼容: 修改不影响现有数据结构
  2. 渐进式修复: 重复检查机制确保不会产生新的重复数据
  3. 可回滚: 修改逻辑简单,易于回滚

后续优化建议

  1. 监控机制: 添加日志收集的监控和告警
  2. 性能优化: 考虑批量重复检查以提高性能
  3. 数据清理: 定期清理历史重复数据
  4. 测试覆盖: 增加单元测试覆盖收集器逻辑

后续发现的排序问题

问题现象

即使修复了重复收集问题,用户39078查询到的日志仍然全是钻石消耗,与后台管理界面显示不一致。

根本原因

UserLogLogic 中的查询方法使用 latest()created_at 排序,而不是按 original_time 排序。由于重复收集导致最新的 created_at 记录都是钻石消耗,掩盖了其他类型的日志。

最终修复

文件: app/Module/Game/Logics/UserLogLogic.php

// 修改前:按创建时间排序
$query = UserLog::byUser($userId)->latest();

// 修改后:按原始业务时间排序
$query = UserLog::byUser($userId)->orderBy('original_time', 'desc');

最终测试结果

  1. API接口:返回多样化日志内容,按业务发生时间排序
  2. 后台管理界面:正常显示各种类型的日志
  3. 数据一致性:API和后台显示的数据完全一致

总结

本次修复解决了用户日志系统的两个关键问题:

  1. 重复收集问题:修复了进度追踪机制,避免同一记录被重复收集
  2. 排序问题:修复了查询排序逻辑,确保按业务发生时间而非收集时间排序

通过这些修复,用户现在可以看到按正确时间顺序排列的多样化日志内容,数据的准确性和完整性得到了保障。