浇水成功Response的LastData缺失DataLands修复说明.md 5.6 KB

浇水成功 Response 的 LastData 缺失 DataLands 修复说明

问题描述

用户浇水成功后,Response 的 LastData 中缺失 DataLands 信息,导致客户端无法及时更新土地状态。

问题原因分析

通过代码分析发现,系统使用了土地暂存系统(LandTemp)来处理土地状态变更,然后通过 AppGameProtobufResponseListener 监听器在响应中自动添加 LastData。但是浇水操作成功后,存在以下问题:

  1. 灾害清理时未触发土地状态变更事件:在 CropLogic::clearDisaster 方法中,虽然更新了土地状态,但没有触发 LandStatusChangedEvent 事件。

  2. 缺少灾害清理事件监听器:虽然触发了 DisasterClearedEvent 事件,但没有相应的监听器来处理土地暂存数据的更新。

  3. 灾害生成时也存在同样问题:在 DisasterLogic::applyDisastersToCrop 方法中,更新土地状态时也没有触发相应的事件。

修复方案

1. 修复灾害清理时的土地状态变更事件触发

文件: app/Module/Farm/Logics/CropLogic.php

clearDisaster 方法中添加土地状态变更事件的触发:

// 如果没有其他活跃灾害,更新土地状态
$oldLandStatus = $land->status;
if (!$hasActiveDisaster) {
    $land->status = LAND_STATUS::PLANTING->value;
}

// 保存更改
$crop->save();
$land->save();

// 如果土地状态发生了变化,触发土地状态变更事件
if ($oldLandStatus !== $land->status) {
    event(new LandStatusChangedEvent($userId, $landId, $oldLandStatus, $land->status));
}

2. 创建灾害清理事件监听器

文件: app/Module/Game/Listeners/DisasterClearedListener.php

创建专门的监听器来处理 DisasterClearedEvent 事件,更新土地暂存数据:

public function handle(DisasterClearedEvent $event): void
{
    // 获取土地信息并构建暂存数据
    $land = $event->crop->land;
    $landStatusData = [
        'land_id' => $land->id,
        'status' => $land->status,
        'crop' => $event->crop->toArray(),
        'updated_at' => now()->toDateTimeString()
    ];

    // 存储到暂存系统
    $tempKey = LandTemp::TEMP_KEY_STATUS_PREFIX . $event->userId;
    $userLandsStatusTemp = \UCore\Helper\Cache::get($tempKey, []);
    $userLandsStatusTemp[$land->id] = \App\Module\Game\Dtos\LandStatusTempDto::fromArray($landStatusData);
    \UCore\Helper\Cache::put($tempKey, $userLandsStatusTemp, LandTemp::TEMP_TTL);
}

3. 注册灾害清理事件监听器

文件: app/Module/Game/Providers/GameServiceProvider.php

在 Game 模块的服务提供者中注册新的监听器:

// 注册灾害清理事件监听器
Event::listen(
    DisasterClearedEvent::class,
    DisasterClearedListener::class
);

4. 修复灾害生成时的土地状态变更事件触发

文件: app/Module/Farm/Logics/DisasterLogic.php

applyDisastersToCrop 方法中添加土地状态变更事件的触发:

// 更新土地状态为灾害
$land = $crop->land;
$oldLandStatus = $land->status;
$land->status = LAND_STATUS::DISASTER->value;

// 保存更改
$crop->save();
$land->save();

// 如果土地状态发生了变化,触发土地状态变更事件
if ($oldLandStatus !== $land->status) {
    event(new \App\Module\Farm\Events\LandStatusChangedEvent($crop->user_id, $land->id, $oldLandStatus, $land->status));
}

修复效果

修复后,当用户进行浇水操作时:

  1. 成功浇水

    • 灾害被清理,土地状态从 DISASTER 更新为 PLANTING
    • 触发 LandStatusChangedEventDisasterClearedEvent 事件
    • 监听器更新土地暂存数据
    • AppGameProtobufResponseListener 自动将暂存数据添加到 Response 的 LastData 中
    • 客户端收到完整的土地状态更新
  2. 失败浇水

    • 物品被消耗,但灾害状态不变
    • 不触发土地状态变更事件
    • 返回错误信息

数据流程

用户浇水操作
    ↓
WateringHandler 处理请求
    ↓
CropService::removeDisasterWithItem (概率判断)
    ↓
DisasterRemovalLogic::removeDisaster
    ↓
CropLogic::clearDisaster
    ↓
触发 LandStatusChangedEvent + DisasterClearedEvent
    ↓
LandStatusChangedListener + DisasterClearedListener 处理事件
    ↓
更新土地暂存数据 (LandTemp)
    ↓
AppGameProtobufResponseListener 监听响应事件
    ↓
从暂存数据构建 LastData.lands
    ↓
客户端收到包含 DataLands 的响应

测试验证

创建了功能测试 tests/Feature/WateringResponseTest.php 来验证:

  1. 浇水成功后 Response 包含 LastData.lands
  2. 土地状态正确更新
  3. 灾害状态正确清理
  4. 浇水失败时的错误处理

相关文件

修改的文件

  • app/Module/Farm/Logics/CropLogic.php
  • app/Module/Farm/Logics/DisasterLogic.php
  • app/Module/Game/Providers/GameServiceProvider.php

新增的文件

  • app/Module/Game/Listeners/DisasterClearedListener.php
  • tests/Feature/WateringResponseTest.php

注意事项

  1. 事件触发顺序:确保在保存数据库更改后再触发事件
  2. 状态比较:只有在土地状态真正发生变化时才触发事件,避免不必要的处理
  3. 异常处理:监听器中包含完整的异常处理,确保单个事件处理失败不影响整体流程
  4. 缓存一致性:暂存数据的 TTL 设置合理,避免数据过期导致的不一致

后续优化建议

  1. 考虑将土地状态变更事件的触发逻辑抽取为通用方法
  2. 添加更多的单元测试覆盖边界情况
  3. 监控事件处理的性能,确保不影响响应速度
  4. 考虑添加事件处理失败的重试机制