|
|
@@ -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 => '未知'
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|