灾害系统重构说明.md 6.5 KB

农场灾害系统重构说明

重构背景

原有的灾害生成系统存在以下问题:

  1. 代码重复:灾害生成逻辑在 DisasterLogicGenerateDisasterListenerGenerateDisastersCommand 三个地方重复实现
  2. 性能问题GenerateDisastersCommand 每分钟执行时会扫描所有发芽期和生长期的作物,没有状态标识
  3. 维护困难:相同逻辑分散在多个地方,修改时容易遗漏

重构内容

1. 数据库优化

新增字段

farm_crops 表中添加两个字段:

  1. last_disaster_check_time

    • 类型timestamp NULL
    • 用途:记录上次灾害检查时间,控制检查频率
  2. can_disaster

    • 类型tinyint(1) NOT NULL DEFAULT '1'
    • 用途:标识当前阶段是否可以产生灾害,每个阶段只能产生一次灾害

索引优化

  • 复合索引idx_disaster_check (can_disaster, last_disaster_check_time)
  • 单独索引idx_can_disaster (can_disaster)

2. 服务层优化

DisasterService 增强

  • 添加 getDisasterKey() 公共方法,统一灾害类型键名映射
  • 添加 getCheckInterval() 方法,配置灾害检查间隔(默认5分钟)

DisasterLogic 重构

  • 新增方法

    • tryGenerateDisasterForCrop() - 核心灾害生成逻辑
    • applyDisasterToCrop() - 将灾害应用到作物
    • batchGenerateDisasters() - 批量生成灾害(优化版)
  • 优化特性

    • 统一的灾害生成逻辑
    • 支持批量处理,减少数据库查询
    • 使用 with() 预加载关联数据
    • 基于时间戳的智能过滤

3. 命令优化

GenerateDisastersCommand 重构

  • 性能提升

    • 只处理需要检查的作物(基于 last_disaster_check_time
    • 使用预加载减少 N+1 查询问题
    • 批量处理替代逐个处理
  • 代码简化

    • 移除重复的灾害生成逻辑
    • 直接调用 DisasterLogic::batchGenerateDisasters()

4. 事件监听器优化

GenerateDisasterListener 重构

  • 逻辑统一:移除重复代码,调用统一的 DisasterLogic::generateDisaster()
  • 状态管理:在作物生长阶段变化时重置 last_disaster_check_time,确保能立即检查

5. 模型更新

FarmCrop 模型

  • 添加 last_disaster_check_time 字段到 $fillable$casts
  • 更新字段注释

性能改进

执行效率提升

原有方式

// 每次都查询所有发芽期和生长期的作物
$crops = FarmCrop::whereIn('growth_stage', [GROWTH_STAGE::SPROUT, GROWTH_STAGE::GROWTH])->get();

// 对每个作物都要查询关联数据
foreach ($crops as $crop) {
    $land = $crop->land;
    $seed = $crop->seed;
    $activeBuffs = FarmGodBuff::where('user_id', $userId)->get();
    // ... 重复的灾害生成逻辑
}

优化后方式

// 只查询需要检查的作物(基于时间戳)
$crops = FarmCrop::whereIn('growth_stage', [GROWTH_STAGE::SPROUT, GROWTH_STAGE::GROWTH])
    ->where(function ($query) use ($checkTime) {
        $query->whereNull('last_disaster_check_time')
              ->orWhere('last_disaster_check_time', '<', $checkTime);
    })
    ->with(['land.landType', 'seed', 'user.buffs'])  // 预加载关联数据
    ->get();

查询优化

  • 减少查询次数:使用 with() 预加载,避免 N+1 查询
  • 智能过滤:只处理需要检查的作物
  • 索引优化:为 last_disaster_check_time 添加索引

配置参数

检查间隔

  • 默认值:5分钟
  • 配置位置DisasterService::getCheckInterval()
  • 说明:可根据服务器性能和游戏需求调整

灾害概率

  • 基础概率:90%(所有灾害类型)
  • 影响因素
    • 种子抵抗属性(百分比格式,1=1%,需要除以100转换为小数)
    • 土地抵抗属性(百分比格式,1=1%,需要除以100转换为小数)
    • 神灵加持效果(直接设置概率为0)

概率计算公式

最终概率 = 基础概率(0.9) - 种子抵抗/100 - 土地抵抗/100

示例

  • 基础概率:90% (0.9)
  • 种子抵抗:20% (数据库存储20,转换为0.2)
  • 土地抵抗:10% (数据库存储10,转换为0.1)
  • 最终概率:0.9 - 0.2 - 0.1 = 0.6 (60%)

使用方式

命令执行

# 手动执行灾害生成
php artisan farm:generate-disasters

定时任务

// 在 Kernel.php 中配置
$schedule->command('farm:generate-disasters')->everyMinute();

代码调用

// 单个作物灾害生成
$disasterLogic = new DisasterLogic();
$disasterInfo = $disasterLogic->generateDisaster($cropId);

// 批量灾害生成
$result = $disasterLogic->batchGenerateDisasters();

兼容性说明

  • 向后兼容:现有的灾害数据结构保持不变
  • API 兼容:公共方法签名保持一致
  • 数据迁移:新字段为可空,不影响现有数据

监控和日志

性能监控

  • 记录每次批量处理的统计信息
  • 监控处理时间和作物数量

错误处理

  • 统一的异常处理和日志记录
  • 详细的错误信息便于调试

多灾害功能

功能特性

  • 并发灾害:一个作物可以同时遭受多种不同类型的灾害(干旱、虫害、杂草)
  • 独立判定:每种灾害类型都有独立的概率计算和判定过程
  • 完整记录:所有灾害信息存储在作物的 disasters JSON字段中

实现原理

// 对每种灾害类型都进行独立判定
foreach ($disasterTypes as $disasterType => $baseProb) {
    // 计算该类型灾害的最终概率
    $finalProb = $baseProb - $seedResistance - $landResistance;

    // 独立的随机数判定
    if (mt_rand(1, 100) <= $finalProb * 100) {
        $generatedDisasters[] = $disasterInfo;
    }
}

数据结构

{
  "disasters": [
    {
      "type": 1,
      "generated_ts": "2025-05-24 11:46:51",
      "status": "active"
    },
    {
      "type": 3,
      "generated_ts": "2025-05-24 11:46:51",
      "status": "active"
    }
  ]
}

测试验证

  • ✅ 成功实现一个作物同时生成多种灾害
  • ✅ 每种灾害类型独立计算概率
  • ✅ 数据结构和事件触发正常

后续优化建议

  1. 缓存优化:考虑缓存神灵加持信息,减少重复查询
  2. 配置化:将灾害概率等参数移到配置文件
  3. 队列处理:对于大量作物的情况,考虑使用队列异步处理
  4. 监控告警:添加性能监控和异常告警机制
  5. 灾害处理:优化多灾害的处理和恢复机制