瀏覽代碼

新增修复已铲除作物土地状态的命令

- 创建FixRemovedCropLandStatusCommand命令
- 修复作物已软删除但土地状态不是空闲的问题
- 支持dry-run模式预览需要修复的数据
- 支持按用户ID和批次大小限制处理
- 按土地去重,避免重复修复同一土地
- 提供详细的修复日志和统计信息
- 已在ServiceProvider中注册命令
AI Assistant 6 月之前
父節點
當前提交
95c0d3f57b

+ 297 - 0
app/Module/Farm/Commands/FixRemovedCropLandStatusCommand.php

@@ -0,0 +1,297 @@
+<?php
+
+namespace App\Module\Farm\Commands;
+
+use App\Module\Farm\Enums\LAND_STATUS;
+use App\Module\Farm\Models\FarmLand;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 修复已铲除作物的土地状态命令
+ *
+ * 修复作物已经铲除(软删除)但土地状态不是空闲的问题
+ * php artisan farm:fix-removed-crop-land-status --dry-run
+ */
+class FixRemovedCropLandStatusCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'farm:fix-removed-crop-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 {
+            // 获取需要修复的数据
+            $problematicLands = $this->getProblematicLands($userId, $limit);
+
+            if ($problematicLands->isEmpty()) {
+                $this->info('没有发现需要修复的数据');
+                return 0;
+            }
+
+            $this->info("发现 {$problematicLands->count()} 条需要修复的土地");
+
+            if ($dryRun) {
+                $this->displayProblems($problematicLands);
+            } else {
+                $this->fixProblems($problematicLands);
+            }
+
+            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 getProblematicLands(?string $userId, int $limit): \Illuminate\Support\Collection
+    {
+        // 先获取有软删除作物的土地ID列表
+        $landsWithDeletedCrops = DB::table('farm_land_users')
+            ->join('farm_crops', 'farm_land_users.id', '=', 'farm_crops.land_id')
+            ->select('farm_land_users.id')
+            ->whereNotNull('farm_crops.deleted_at')
+            ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
+            ->when($userId, function ($query, $userId) {
+                return $query->where('farm_land_users.user_id', $userId);
+            })
+            ->distinct()
+            ->pluck('farm_land_users.id');
+
+        // 获取没有未删除作物的土地ID列表
+        $landsWithoutActiveCrops = DB::table('farm_land_users')
+            ->leftJoin('farm_crops', function ($join) {
+                $join->on('farm_land_users.id', '=', 'farm_crops.land_id')
+                     ->whereNull('farm_crops.deleted_at');
+            })
+            ->select('farm_land_users.id')
+            ->whereNull('farm_crops.id')
+            ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
+            ->when($userId, function ($query, $userId) {
+                return $query->where('farm_land_users.user_id', $userId);
+            })
+            ->pluck('farm_land_users.id');
+
+        // 合并所有有问题的土地ID
+        $allProblematicLandIds = $landsWithDeletedCrops->concat($landsWithoutActiveCrops)->unique()->take($limit);
+
+        // 获取详细信息
+        $results = collect();
+        foreach ($allProblematicLandIds as $landId) {
+            $land = DB::table('farm_land_users')->where('id', $landId)->first();
+            if (!$land) continue;
+
+            // 获取该土地的软删除作物信息
+            $deletedCrops = DB::table('farm_crops')
+                ->where('land_id', $landId)
+                ->whereNotNull('deleted_at')
+                ->get();
+
+            $item = (object) [
+                'land_id' => $land->id,
+                'user_id' => $land->user_id,
+                'land_status' => $land->status,
+                'has_crop' => $land->has_crop,
+                'deleted_crops_count' => $deletedCrops->count(),
+                'crop_ids' => $deletedCrops->pluck('id')->implode(','),
+                'first_deleted_at' => $deletedCrops->min('deleted_at'),
+                'last_deleted_at' => $deletedCrops->max('deleted_at'),
+                'seed_ids' => $deletedCrops->pluck('seed_id')->unique()->implode(',')
+            ];
+
+            $item->problem_type = $this->getProblemType($item);
+            $results->push($item);
+        }
+
+        return $results;
+    }
+
+    /**
+     * 获取问题类型
+     *
+     * @param object $item
+     * @return string
+     */
+    private function getProblemType(object $item): string
+    {
+        if ($item->deleted_crops_count > 0) {
+            return '作物已软删除但土地非空闲';
+        } elseif ($item->deleted_crops_count == 0) {
+            return '土地无作物但状态非空闲';
+        }
+
+        return '其他问题';
+    }
+
+    /**
+     * 显示问题数据
+     *
+     * @param \Illuminate\Support\Collection $items
+     * @return void
+     */
+    private function displayProblems(\Illuminate\Support\Collection $items): void
+    {
+        // 按问题类型分组显示
+        $groupedData = $items->groupBy('problem_type');
+
+        foreach ($groupedData as $problemType => $problemItems) {
+            $this->info("\n=== {$problemType} ({$problemItems->count()} 条) ===");
+
+            $headers = ['土地ID', '用户ID', '当前土地状态', 'has_crop', '软删除作物数', '作物IDs', '最后删除时间', '种子IDs'];
+            $rows = [];
+
+            foreach ($problemItems as $item) {
+                $rows[] = [
+                    $item->land_id,
+                    $item->user_id,
+                    $this->getLandStatusName($item->land_status),
+                    $item->has_crop ? '是' : '否',
+                    $item->deleted_crops_count,
+                    $item->crop_ids ? (strlen($item->crop_ids) > 50 ? substr($item->crop_ids, 0, 47) . '...' : $item->crop_ids) : 'N/A',
+                    $item->last_deleted_at ?? 'N/A',
+                    $item->seed_ids ?? 'N/A'
+                ];
+            }
+
+            $this->table($headers, $rows);
+        }
+    }
+
+    /**
+     * 修复问题数据
+     *
+     * @param \Illuminate\Support\Collection $items
+     * @return void
+     */
+    private function fixProblems(\Illuminate\Support\Collection $items): void
+    {
+        $fixedCount = 0;
+        $failedCount = 0;
+
+        foreach ($items as $item) {
+            try {
+                DB::transaction(function () use ($item) {
+                    $this->fixSingleLand($item);
+                });
+                
+                $fixedCount++;
+                $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id}) - {$item->problem_type}");
+                
+            } 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' => $item->problem_type,
+                    'error' => $e->getMessage()
+                ]);
+            }
+        }
+
+        $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
+    }
+
+    /**
+     * 修复单个土地
+     *
+     * @param object $item
+     * @return void
+     */
+    private function fixSingleLand(object $item): void
+    {
+        $land = FarmLand::lockForUpdate()->find($item->land_id);
+
+        if (!$land) {
+            throw new \Exception('土地不存在');
+        }
+
+        $oldStatus = $land->status;
+        $oldHasCrop = $land->has_crop;
+
+        // 设置土地状态为空闲
+        $land->status = LAND_STATUS::IDLE->value;
+        $land->updateHasCrop(); // 这会将has_crop设置为false
+        $land->save();
+
+        Log::info('已铲除作物土地状态修复', [
+            'land_id' => $land->id,
+            'user_id' => $land->user_id,
+            'deleted_crops_count' => $item->deleted_crops_count,
+            'crop_ids' => $item->crop_ids,
+            'old_status' => $oldStatus,
+            'new_status' => $land->status,
+            'old_has_crop' => $oldHasCrop,
+            'new_has_crop' => $land->has_crop,
+            'problem_type' => $item->problem_type,
+            'first_deleted_at' => $item->first_deleted_at,
+            'last_deleted_at' => $item->last_deleted_at
+        ]);
+    }
+
+    /**
+     * 获取土地状态名称
+     *
+     * @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 => '未知'
+        };
+    }
+}

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

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