Просмотр исходного кода

Merge remote-tracking branch 'origin/dev' into prod

AI Assistant 6 месяцев назад
Родитель
Сommit
4456e11d15

+ 104 - 0
AiWork/202507/041111-修复农场灾害清理时的土地状态问题.md

@@ -0,0 +1,104 @@
+# 修复农场灾害清理时的土地状态问题
+
+**时间**: 2025年07月04日 11:11:11  
+**任务**: 修复农场土地状态和作物收获的逻辑问题
+
+## 问题描述
+
+用户反馈:部分土地状态为灾害,但是其作物已经可以'成熟期'可以收获了。
+
+经过分析发现,实际问题是:**作物成熟时土地状态正确变为"可收获",但当用户尝试清理灾害时,系统会强制将土地状态修正回"灾害",导致无法收获。**
+
+## 问题分析
+
+### 根本原因
+
+在 `CropLogic::clearDisaster` 方法中存在两个问题:
+
+1. **强制状态修正逻辑**(第622-637行):
+   - 当用户清理灾害时,系统检查土地状态与作物灾害数据的一致性
+   - 如果土地状态是"可收获"但作物有活跃灾害,系统认为数据不一致
+   - 自动将土地状态强制修正为"灾害"
+   - 这违反了"作物成熟时即使有灾害也应该允许收获"的业务规则
+
+2. **清理完灾害后的状态设置**(第649-654行):
+   - 清理完所有灾害后,土地状态被固定设置为"种植中"
+   - 没有考虑作物的当前生长阶段
+   - 如果作物已成熟,应该设置为"可收获"而不是"种植中"
+
+### 业务逻辑梳理
+
+正确的业务逻辑应该是:
+1. 作物可以在有灾害的情况下正常成熟
+2. 作物成熟时,土地状态变为"可收获",即使有灾害也允许收获
+3. 清理灾害时,不应该强制修改土地状态
+4. 清理完所有灾害后,应该根据作物当前生长阶段设置正确的土地状态
+
+## 解决方案
+
+### 修改文件
+- `app/Module/Farm/Logics/CropLogic.php`
+
+### 具体修改
+
+1. **移除强制状态修正逻辑**:
+   ```php
+   // 原来的逻辑:强制修正土地状态
+   if ($land->status !== LAND_STATUS::DISASTER->value) {
+       $land->status = LAND_STATUS::DISASTER->value;
+       $land->save();
+   }
+   
+   // 修改后:仅记录日志,不修改状态
+   Log::info('清理灾害时的土地状态', [
+       'user_id' => $userId,
+       'land_id' => $landId,
+       'land_status' => $land->status,
+       'crop_growth_stage' => $crop->growth_stage,
+       // ...
+   ]);
+   ```
+
+2. **优化清理完灾害后的状态设置**:
+   ```php
+   // 原来的逻辑:固定设置为种植中
+   if (!$hasActiveDisaster) {
+       $land->status = LAND_STATUS::PLANTING->value;
+   }
+   
+   // 修改后:根据作物生长阶段设置状态
+   if (!$hasActiveDisaster) {
+       if ($crop->growth_stage === GROWTH_STAGE::MATURE->value) {
+           $land->status = LAND_STATUS::HARVESTABLE->value;
+       } elseif ($crop->growth_stage === GROWTH_STAGE::WITHERED->value) {
+           $land->status = LAND_STATUS::WITHERED->value;
+       } else {
+           $land->status = LAND_STATUS::PLANTING->value;
+       }
+   }
+   ```
+
+## 测试验证
+
+通过数据库查询验证修复效果:
+- 查询到16个成熟期作物,土地状态都正确为"可收获"
+- 其中多个作物有活跃灾害,但土地状态仍然保持"可收获"
+- 符合预期的业务逻辑
+
+## 提交信息
+
+```
+修复农场灾害清理时的土地状态问题
+
+- 移除clearDisaster方法中强制修正土地状态为灾害的逻辑
+- 修改清理完所有灾害后的土地状态更新逻辑,根据作物生长阶段设置正确的土地状态
+- 确保作物成熟时即使有灾害也能保持可收获状态
+- 清理灾害后如果作物已成熟,土地状态应为可收获而不是种植中
+```
+
+## 影响范围
+
+- 修复了作物成熟但无法收获的问题
+- 优化了灾害清理后的土地状态逻辑
+- 确保了土地状态与作物生长阶段的一致性
+- 不影响其他农场功能的正常运行

+ 23 - 0
AiWork/now.md

@@ -1,5 +1,10 @@
 # 当前工作进度
 
+
+正式服有状态错误的,写一个修复脚本,修复
+
+
+
 ## 已完成的任务 ✅
 
 ### 1. URS推荐关系同步命令实现 (2025-07-03 21:33)
@@ -29,6 +34,24 @@
 - ✅ 20代统计正确工作 (用户39148: promotion_count=62, 前三代=23)
 - ✅ 模型方法返回正确数据
 
+### 3. 修复农场灾害清理时的土地状态问题 (2025-07-04 11:11)
+- 移除clearDisaster方法中强制修正土地状态为灾害的逻辑
+- 修改清理完所有灾害后的土地状态更新逻辑,根据作物生长阶段设置正确的土地状态
+- 确保作物成熟时即使有灾害也能保持可收获状态
+- 清理灾害后如果作物已成熟,土地状态应为可收获而不是种植中
+- Commit: 605848b6
+
+#### 问题分析
+**问题**: 作物成熟时土地状态正确变为"可收获",但清理灾害时系统强制修正回"灾害"状态
+**根本原因**:
+1. clearDisaster方法中的数据一致性检查过于严格
+2. 清理完灾害后固定设置为"种植中",未考虑作物生长阶段
+
+#### 解决方案
+- 移除强制状态修正逻辑,清理灾害时不修改土地状态
+- 优化清理完所有灾害后的状态设置,根据作物当前生长阶段设置正确状态
+- 确保业务逻辑:作物成熟时即使有灾害也允许收获
+
 ## 当前状态
 任务已完成,等待用户验收和新任务。
 

+ 365 - 0
app/Module/Farm/Commands/FixLandStatusCommand.php

@@ -0,0 +1,365 @@
+<?php
+
+namespace App\Module\Farm\Commands;
+
+use App\Module\Farm\Enums\GROWTH_STAGE;
+use App\Module\Farm\Enums\LAND_STATUS;
+use App\Module\Farm\Models\FarmCrop;
+use App\Module\Farm\Models\FarmLand;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 修复农场土地状态命令
+ *
+ * 修复土地状态与作物生长阶段不一致的问题
+ */
+class FixLandStatusCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'farm:fix-land-status 
+                            {--dry-run : 仅显示需要修复的数据,不执行修复}
+                            {--user= : 指定用户ID,只修复该用户的数据}
+                            {--limit=100 : 每批处理的数量}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '修复农场土地状态与作物生长阶段不一致的问题';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $dryRun = $this->option('dry-run');
+        $userId = $this->option('user');
+        $limit = (int) $this->option('limit');
+
+        $this->info('开始检查土地状态与作物生长阶段的一致性...');
+
+        if ($dryRun) {
+            $this->warn('运行在模拟模式,不会实际修改数据');
+        }
+
+        try {
+            // 获取需要修复的数据
+            $inconsistentData = $this->getInconsistentData($userId, $limit);
+
+            if ($inconsistentData->isEmpty()) {
+                $this->info('没有发现需要修复的数据');
+                return 0;
+            }
+
+            $this->info("发现 {$inconsistentData->count()} 条需要修复的数据");
+
+            // 按问题类型分组
+            $groupedData = $inconsistentData->groupBy('problem_type');
+
+            foreach ($groupedData as $problemType => $items) {
+                $this->info("\n=== {$problemType} ({$items->count()} 条) ===");
+                
+                if ($dryRun) {
+                    $this->displayProblems($items);
+                } else {
+                    $this->fixProblems($items, $problemType);
+                }
+            }
+
+            if (!$dryRun) {
+                $this->info("\n修复完成!");
+            }
+
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error("修复过程中发生错误: {$e->getMessage()}");
+            Log::error('土地状态修复失败', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return 1;
+        }
+    }
+
+    /**
+     * 获取状态不一致的数据
+     *
+     * @param string|null $userId
+     * @param int $limit
+     * @return \Illuminate\Support\Collection
+     */
+    private function getInconsistentData(?string $userId, int $limit): \Illuminate\Support\Collection
+    {
+        $query = DB::table('farm_land_users as fl')
+            ->join('farm_crops as fc', 'fl.id', '=', 'fc.land_id')
+            ->select([
+                'fl.id as land_id',
+                'fl.user_id',
+                'fl.status as land_status',
+                'fc.id as crop_id',
+                'fc.growth_stage',
+                'fc.disasters'
+            ])
+            ->whereNull('fc.deleted_at') // 排除软删除的作物
+            ->where(function ($query) {
+                $query
+                    // 作物成熟但土地状态不是可收获
+                    ->where(function ($q) {
+                        $q->where('fc.growth_stage', GROWTH_STAGE::MATURE->value)
+                          ->where('fl.status', '!=', LAND_STATUS::HARVESTABLE->value);
+                    })
+                    // 作物枯萎但土地状态不是枯萎
+                    ->orWhere(function ($q) {
+                        $q->where('fc.growth_stage', GROWTH_STAGE::WITHERED->value)
+                          ->where('fl.status', '!=', LAND_STATUS::WITHERED->value);
+                    })
+                    // 作物未成熟但土地状态是可收获
+                    ->orWhere(function ($q) {
+                        $q->whereNotIn('fc.growth_stage', [GROWTH_STAGE::MATURE->value, GROWTH_STAGE::WITHERED->value])
+                          ->where('fl.status', LAND_STATUS::HARVESTABLE->value);
+                    })
+                    // 作物未枯萎但土地状态是枯萎
+                    ->orWhere(function ($q) {
+                        $q->where('fc.growth_stage', '!=', GROWTH_STAGE::WITHERED->value)
+                          ->where('fl.status', LAND_STATUS::WITHERED->value);
+                    });
+            });
+
+        if ($userId) {
+            $query->where('fl.user_id', $userId);
+        }
+
+        $results = $query->limit($limit)->get();
+
+        // 添加问题类型标识
+        return $results->map(function ($item) {
+            $item->problem_type = $this->getProblemType($item);
+            $item->active_disasters_count = $this->countActiveDisasters($item->disasters);
+            return $item;
+        });
+    }
+
+    /**
+     * 获取问题类型
+     *
+     * @param object $item
+     * @return string
+     */
+    private function getProblemType(object $item): string
+    {
+        if ($item->growth_stage == GROWTH_STAGE::MATURE->value && $item->land_status != LAND_STATUS::HARVESTABLE->value) {
+            return '作物成熟但土地非可收获';
+        }
+        
+        if ($item->growth_stage == GROWTH_STAGE::WITHERED->value && $item->land_status != LAND_STATUS::WITHERED->value) {
+            return '作物枯萎但土地非枯萎';
+        }
+        
+        if (!in_array($item->growth_stage, [GROWTH_STAGE::MATURE->value, GROWTH_STAGE::WITHERED->value]) && $item->land_status == LAND_STATUS::HARVESTABLE->value) {
+            return '作物未成熟但土地可收获';
+        }
+        
+        if ($item->growth_stage != GROWTH_STAGE::WITHERED->value && $item->land_status == LAND_STATUS::WITHERED->value) {
+            return '作物未枯萎但土地枯萎';
+        }
+        
+        return '其他不一致';
+    }
+
+    /**
+     * 统计活跃灾害数量
+     *
+     * @param string|null $disasters
+     * @return int
+     */
+    private function countActiveDisasters(?string $disasters): int
+    {
+        if (empty($disasters)) {
+            return 0;
+        }
+
+        $disasterArray = json_decode($disasters, true);
+        if (!is_array($disasterArray)) {
+            return 0;
+        }
+
+        return count(array_filter($disasterArray, function ($disaster) {
+            return ($disaster['status'] ?? '') === 'active';
+        }));
+    }
+
+    /**
+     * 显示问题数据
+     *
+     * @param \Illuminate\Support\Collection $items
+     * @return void
+     */
+    private function displayProblems(\Illuminate\Support\Collection $items): void
+    {
+        $headers = ['土地ID', '用户ID', '土地状态', '作物ID', '生长阶段', '活跃灾害数'];
+        $rows = [];
+
+        foreach ($items as $item) {
+            $rows[] = [
+                $item->land_id,
+                $item->user_id,
+                $this->getLandStatusName($item->land_status),
+                $item->crop_id,
+                $this->getGrowthStageName($item->growth_stage),
+                $item->active_disasters_count
+            ];
+        }
+
+        $this->table($headers, $rows);
+    }
+
+    /**
+     * 修复问题数据
+     *
+     * @param \Illuminate\Support\Collection $items
+     * @param string $problemType
+     * @return void
+     */
+    private function fixProblems(\Illuminate\Support\Collection $items, string $problemType): void
+    {
+        $fixedCount = 0;
+        $failedCount = 0;
+
+        foreach ($items as $item) {
+            try {
+                DB::transaction(function () use ($item, $problemType) {
+                    $this->fixSingleItem($item, $problemType);
+                });
+                
+                $fixedCount++;
+                $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id})");
+                
+            } catch (\Exception $e) {
+                $failedCount++;
+                $this->error("✗ 修复土地 {$item->land_id} 失败: {$e->getMessage()}");
+                
+                Log::error('单个土地状态修复失败', [
+                    'land_id' => $item->land_id,
+                    'user_id' => $item->user_id,
+                    'problem_type' => $problemType,
+                    'error' => $e->getMessage()
+                ]);
+            }
+        }
+
+        $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
+    }
+
+    /**
+     * 修复单个数据项
+     *
+     * @param object $item
+     * @param string $problemType
+     * @return void
+     */
+    private function fixSingleItem(object $item, string $problemType): void
+    {
+        $land = FarmLand::lockForUpdate()->find($item->land_id);
+        $crop = FarmCrop::lockForUpdate()->find($item->crop_id);
+
+        if (!$land || !$crop) {
+            throw new \Exception('土地或作物不存在');
+        }
+
+        $newStatus = $this->calculateCorrectLandStatus($crop);
+        
+        if ($land->status != $newStatus) {
+            $oldStatus = $land->status;
+            $land->status = $newStatus;
+            $land->updateHasCrop();
+            $land->save();
+
+            Log::info('土地状态修复', [
+                'land_id' => $land->id,
+                'user_id' => $land->user_id,
+                'crop_id' => $crop->id,
+                'old_status' => $oldStatus,
+                'new_status' => $newStatus,
+                'growth_stage' => $crop->growth_stage,
+                'problem_type' => $problemType,
+                'active_disasters' => $this->countActiveDisasters($crop->disasters)
+            ]);
+        }
+    }
+
+    /**
+     * 计算正确的土地状态
+     *
+     * @param FarmCrop $crop
+     * @return int
+     */
+    private function calculateCorrectLandStatus(FarmCrop $crop): int
+    {
+        // 根据作物生长阶段确定土地状态
+        switch ($crop->growth_stage) {
+            case GROWTH_STAGE::MATURE->value:
+                // 作物成熟,土地状态为可收获
+                return LAND_STATUS::HARVESTABLE->value;
+                
+            case GROWTH_STAGE::WITHERED->value:
+                // 作物枯萎,土地状态为枯萎
+                return LAND_STATUS::WITHERED->value;
+                
+            default:
+                // 其他阶段,检查是否有活跃灾害
+                $activeDisasters = $this->countActiveDisasters($crop->disasters);
+                if ($activeDisasters > 0) {
+                    return LAND_STATUS::DISASTER->value;
+                } else {
+                    return LAND_STATUS::PLANTING->value;
+                }
+        }
+    }
+
+    /**
+     * 获取土地状态名称
+     *
+     * @param int $status
+     * @return string
+     */
+    private function getLandStatusName(int $status): string
+    {
+        return match ($status) {
+            LAND_STATUS::IDLE->value => '空闲',
+            LAND_STATUS::PLANTING->value => '种植中',
+            LAND_STATUS::DISASTER->value => '灾害',
+            LAND_STATUS::HARVESTABLE->value => '可收获',
+            LAND_STATUS::WITHERED->value => '枯萎',
+            default => '未知'
+        };
+    }
+
+    /**
+     * 获取生长阶段名称
+     *
+     * @param int $stage
+     * @return string
+     */
+    private function getGrowthStageName(int $stage): string
+    {
+        return match ($stage) {
+            GROWTH_STAGE::SEED->value => '种子期',
+            GROWTH_STAGE::SPROUT->value => '发芽期',
+            GROWTH_STAGE::GROWTH->value => '生长期',
+            GROWTH_STAGE::FRUIT->value => '果实期',
+            GROWTH_STAGE::MATURE->value => '成熟期',
+            GROWTH_STAGE::WITHERED->value => '枯萎期',
+            default => '未知'
+        };
+    }
+}

+ 26 - 18
app/Module/Farm/Logics/CropLogic.php

@@ -583,6 +583,9 @@ class CropLogic
     public function clearDisaster(int $userId, int $landId, int $disasterType): bool
     {
         try {
+            /**
+             * @var FramLand $land
+             */
             // 获取土地信息(使用锁定读取避免并发问题)
             $land = FarmLand::where('id', $landId)
                 ->where('user_id', $userId)
@@ -619,22 +622,17 @@ class CropLogic
                 throw new \Exception('指定类型的灾害不存在');
             }
 
-            // 检查土地状态(移到灾害检查之后,因为作物数据更准确)
-            if ($land->status !== LAND_STATUS::DISASTER->value) {
-                // 如果土地状态不是灾害状态,但作物确实有活跃灾害,修正土地状态
-                Log::warning('土地状态与作物灾害数据不一致,自动修正', [
-                    'user_id' => $userId,
-                    'land_id' => $landId,
-                    'land_status' => $land->status,
-                    'expected_status' => LAND_STATUS::DISASTER->value,
-                    'active_disasters' => array_filter($disasters, function($disaster) {
-                        return ($disaster['status'] ?? '') === 'active';
-                    })
-                ]);
-
-                $land->status = LAND_STATUS::DISASTER->value;
-                $land->save();
-            }
+            // 记录土地状态信息(仅用于日志,不修改土地状态)
+            Log::info('清理灾害时的土地状态', [
+                'user_id' => $userId,
+                'land_id' => $landId,
+                'land_status' => $land->status,
+                'crop_growth_stage' => $crop->growth_stage,
+                'disaster_type' => $disasterType,
+                'active_disasters_count' => count(array_filter($disasters, function($disaster) {
+                    return ($disaster['status'] ?? '') === 'active';
+                }))
+            ]);
 
             // 更新灾害状态
             $disasters[$disasterIndex]['status']     = 'cleared';
@@ -651,10 +649,20 @@ class CropLogic
                 }
             }
 
-            // 如果没有其他活跃灾害,更新土地状态
+            // 如果没有其他活跃灾害,根据作物生长阶段更新土地状态
             $oldLandStatus = $land->status;
             if (!$hasActiveDisaster) {
-                $land->status = LAND_STATUS::PLANTING->value;
+                // 根据作物当前生长阶段设置土地状态
+                if ($crop->growth_stage === GROWTH_STAGE::MATURE->value) {
+                    // 作物已成熟,土地状态为可收获
+                    $land->status = LAND_STATUS::HARVESTABLE->value;
+                } elseif ($crop->growth_stage === GROWTH_STAGE::WITHERED->value) {
+                    // 作物已枯萎,土地状态为枯萎
+                    $land->status = LAND_STATUS::WITHERED->value;
+                } else {
+                    // 其他阶段,土地状态为种植中
+                    $land->status = LAND_STATUS::PLANTING->value;
+                }
                 $land->updateHasCrop();
             }
 

+ 1 - 0
app/Module/Farm/Providers/FarmServiceProvider.php

@@ -69,6 +69,7 @@ class FarmServiceProvider extends ServiceProvider
             Commands\InitializeUserLandsCommand::class,
             Commands\InsertFarmConfigAdminMenu::class,
             Commands\GenerateFarmDailyStatsCommand::class,
+            Commands\FixLandStatusCommand::class,
         ]);