option('dry-run'); if ($isDryRun) { $this->info('=== 干运行模式:仅显示需要修复的数据 ==='); } else { $this->info('=== 开始修复作物异常灾害数据 ==='); } try { // 查找有异常灾害数量的作物(超过9个灾害为异常) // 只处理未成熟的作物:发芽期(20)、生长期(30)、果实期(35) $excessiveCrops = FarmCrop::whereRaw('JSON_LENGTH(disasters) > 9') ->where('deleted_at', null) ->whereIn('growth_stage', [20, 30, 35]) // 只处理未成熟的作物 ->get(); $totalCount = $excessiveCrops->count(); $this->info("发现 {$totalCount} 个作物有异常灾害数据"); if ($totalCount === 0) { $this->info('没有发现需要修复的数据'); return 0; } // 显示统计信息 $this->displayStatistics($excessiveCrops); if ($isDryRun) { $this->info('=== 干运行完成,未执行实际修复 ==='); return 0; } // 确认是否继续 if (!$this->confirm('是否继续执行修复?这将清理异常的灾害数据')) { $this->info('用户取消操作'); return 0; } // 执行修复 $fixedCount = $this->fixExcessiveDisasters($excessiveCrops); $this->info("=== 修复完成 ==="); $this->info("总共修复了 {$fixedCount} 个作物的异常灾害数据"); Log::info('作物异常灾害数据修复完成', [ 'total_crops' => $totalCount, 'fixed_crops' => $fixedCount ]); return 0; } catch (\Exception $e) { $this->error('修复失败: ' . $e->getMessage()); Log::error('作物异常灾害数据修复失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return 1; } } /** * 显示统计信息 * * @param \Illuminate\Support\Collection $crops */ private function displayStatistics($crops) { $this->info('=== 异常数据统计 ==='); // 按灾害数量分组统计 $disasterCounts = $crops->groupBy(function ($crop) { return count($crop->disasters ?? []); })->map(function ($group) { return $group->count(); })->sortKeys(); $this->table( ['灾害数量', '作物数量'], $disasterCounts->map(function ($count, $disasters) { return [$disasters, $count]; })->toArray() ); // 显示最严重的几个案例 $worstCases = $crops->sortByDesc(function ($crop) { return count($crop->disasters ?? []); })->take(5); $this->info('=== 最严重的5个案例 ==='); $this->table( ['作物ID', '用户ID', '生长阶段', '灾害数量', '最后检查时间'], $worstCases->map(function ($crop) { return [ $crop->id, $crop->user_id, is_object($crop->growth_stage) ? $crop->growth_stage->value : $crop->growth_stage, count($crop->disasters ?? []), $crop->last_disaster_check_time ?? 'NULL' ]; })->toArray() ); } /** * 修复异常灾害数据 * * @param \Illuminate\Support\Collection $crops * @return int 修复的作物数量 */ private function fixExcessiveDisasters($crops): int { $fixedCount = 0; $progressBar = $this->output->createProgressBar($crops->count()); $progressBar->start(); foreach ($crops as $crop) { try { // 为每个作物使用独立事务 DB::transaction(function () use ($crop, &$fixedCount) { // 重新获取作物数据,确保数据一致性 $lockedCrop = FarmCrop::where('id', $crop->id) ->lockForUpdate() ->first(); if (!$lockedCrop || $lockedCrop->trashed()) { return; // 作物已被删除,跳过 } $disasters = $lockedCrop->disasters ?? []; if (count($disasters) > 9) { // 策略:保留每个阶段每种类型的最新一个灾害,最多9个 $fixedDisasters = $this->getOptimalDisasters($disasters); // 更新作物数据 $lockedCrop->disasters = $fixedDisasters; $lockedCrop->can_disaster = false; // 设置为不能再产生灾害 $lockedCrop->save(); $fixedCount++; Log::info('修复作物异常灾害数据', [ 'crop_id' => $lockedCrop->id, 'user_id' => $lockedCrop->user_id, 'growth_stage' => $lockedCrop->growth_stage, 'original_disaster_count' => count($disasters), 'fixed_disaster_count' => count($fixedDisasters), 'original_disasters' => array_column($disasters, 'type'), 'fixed_disasters' => array_column($fixedDisasters, 'type') ]); } }); } catch (\Exception $e) { Log::error('修复单个作物失败', [ 'crop_id' => $crop->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); } $progressBar->advance(); } $progressBar->finish(); $this->newLine(); return $fixedCount; } /** * 获取最优的灾害组合 * * 策略:每种灾害类型保留最新的几个,最多保留9个 * 理论上:3个阶段(发芽期、生长期、果实期) × 3种灾害类型 = 9个灾害 * * @param array $disasters * @return array */ private function getOptimalDisasters(array $disasters): array { // 按灾害类型分组 $disastersByType = []; foreach ($disasters as $disaster) { $type = $disaster['type'] ?? 0; if (!isset($disastersByType[$type])) { $disastersByType[$type] = []; } $disastersByType[$type][] = $disaster; } $fixedDisasters = []; // 对每种类型,保留最新的几个(最多3个,对应3个生长阶段) foreach ($disastersByType as $type => $typeDisasters) { // 按生成时间排序,取最新的几个 usort($typeDisasters, function ($a, $b) { return strcmp($b['generated_ts'] ?? '', $a['generated_ts'] ?? ''); }); // 每种类型最多保留3个(对应3个可产生灾害的阶段) $keepCount = min(3, count($typeDisasters)); for ($i = 0; $i < $keepCount; $i++) { $fixedDisasters[] = $typeDisasters[$i]; } } // 如果还是超过9个,按生成时间保留最新的9个 if (count($fixedDisasters) > 9) { usort($fixedDisasters, function ($a, $b) { return strcmp($b['generated_ts'] ?? '', $a['generated_ts'] ?? ''); }); $fixedDisasters = array_slice($fixedDisasters, 0, 9); } return $fixedDisasters; } }