# 农场灾害系统重构说明 ## 重构背景 原有的灾害生成系统存在以下问题: 1. **代码重复**:灾害生成逻辑在 `DisasterLogic`、`GenerateDisasterListener`、`GenerateDisastersCommand` 三个地方重复实现 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` - 更新字段注释 ## 性能改进 ### 执行效率提升 #### 原有方式 ```php // 每次都查询所有发芽期和生长期的作物 $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(); // ... 重复的灾害生成逻辑 } ``` #### 优化后方式 ```php // 只查询需要检查的作物(基于时间戳) $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%) ## 使用方式 ### 命令执行 ```bash # 手动执行灾害生成 php artisan farm:generate-disasters ``` ### 定时任务 ```php // 在 Kernel.php 中配置 $schedule->command('farm:generate-disasters')->everyMinute(); ``` ### 代码调用 ```php // 单个作物灾害生成 $disasterLogic = new DisasterLogic(); $disasterInfo = $disasterLogic->generateDisaster($cropId); // 批量灾害生成 $result = $disasterLogic->batchGenerateDisasters(); ``` ## 兼容性说明 - **向后兼容**:现有的灾害数据结构保持不变 - **API 兼容**:公共方法签名保持一致 - **数据迁移**:新字段为可空,不影响现有数据 ## 监控和日志 ### 性能监控 - 记录每次批量处理的统计信息 - 监控处理时间和作物数量 ### 错误处理 - 统一的异常处理和日志记录 - 详细的错误信息便于调试 ## 多灾害功能 ### 功能特性 - **并发灾害**:一个作物可以同时遭受多种不同类型的灾害(干旱、虫害、杂草) - **独立判定**:每种灾害类型都有独立的概率计算和判定过程 - **完整记录**:所有灾害信息存储在作物的 `disasters` JSON字段中 ### 实现原理 ```php // 对每种灾害类型都进行独立判定 foreach ($disasterTypes as $disasterType => $baseProb) { // 计算该类型灾害的最终概率 $finalProb = $baseProb - $seedResistance - $landResistance; // 独立的随机数判定 if (mt_rand(1, 100) <= $finalProb * 100) { $generatedDisasters[] = $disasterInfo; } } ``` ### 数据结构 ```json { "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. **灾害处理**:优化多灾害的处理和恢复机制