FixRemovedCropLandStatusCommand.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. namespace App\Module\Farm\Commands;
  3. use App\Module\Farm\Enums\LAND_STATUS;
  4. use App\Module\Farm\Models\FarmLand;
  5. use Illuminate\Console\Command;
  6. use Illuminate\Support\Facades\DB;
  7. use Illuminate\Support\Facades\Log;
  8. /**
  9. * 修复已铲除作物的土地状态命令
  10. *
  11. * 修复作物已经铲除(软删除)但土地状态不是空闲的问题
  12. * php artisan farm:fix-removed-crop-land-status --dry-run
  13. */
  14. class FixRemovedCropLandStatusCommand extends Command
  15. {
  16. /**
  17. * 命令签名
  18. *
  19. * @var string
  20. */
  21. protected $signature = 'farm:fix-removed-crop-land-status
  22. {--dry-run : 仅显示需要修复的数据,不执行修复}
  23. {--user= : 指定用户ID,只修复该用户的数据}
  24. {--limit=100 : 每批处理的数量}';
  25. /**
  26. * 命令描述
  27. *
  28. * @var string
  29. */
  30. protected $description = '修复作物已经铲除(软删除)但土地状态不是空闲的问题';
  31. /**
  32. * 执行命令
  33. *
  34. * @return int
  35. */
  36. public function handle(): int
  37. {
  38. $dryRun = $this->option('dry-run');
  39. $userId = $this->option('user');
  40. $limit = (int) $this->option('limit');
  41. $this->info('开始检查已铲除作物的土地状态...');
  42. if ($dryRun) {
  43. $this->warn('运行在模拟模式,不会实际修改数据');
  44. }
  45. try {
  46. // 获取需要修复的数据
  47. $problematicLands = $this->getProblematicLands($userId, $limit);
  48. if ($problematicLands->isEmpty()) {
  49. $this->info('没有发现需要修复的数据');
  50. return 0;
  51. }
  52. $this->info("发现 {$problematicLands->count()} 条需要修复的土地");
  53. if ($dryRun) {
  54. $this->displayProblems($problematicLands);
  55. } else {
  56. $this->fixProblems($problematicLands);
  57. }
  58. if (!$dryRun) {
  59. $this->info("\n修复完成!");
  60. }
  61. return 0;
  62. } catch (\Exception $e) {
  63. $this->error("修复过程中发生错误: {$e->getMessage()}");
  64. Log::error('已铲除作物土地状态修复失败', [
  65. 'error' => $e->getMessage(),
  66. 'trace' => $e->getTraceAsString()
  67. ]);
  68. return 1;
  69. }
  70. }
  71. /**
  72. * 获取有问题的土地数据
  73. * 查找作物已软删除但土地状态不是空闲的情况
  74. *
  75. * @param string|null $userId
  76. * @param int $limit
  77. * @return \Illuminate\Support\Collection
  78. */
  79. private function getProblematicLands(?string $userId, int $limit): \Illuminate\Support\Collection
  80. {
  81. // 先获取有软删除作物的土地ID列表
  82. $landsWithDeletedCrops = DB::table('farm_land_users')
  83. ->join('farm_crops', 'farm_land_users.id', '=', 'farm_crops.land_id')
  84. ->select('farm_land_users.id')
  85. ->whereNotNull('farm_crops.deleted_at')
  86. ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
  87. ->when($userId, function ($query, $userId) {
  88. return $query->where('farm_land_users.user_id', $userId);
  89. })
  90. ->distinct()
  91. ->pluck('farm_land_users.id');
  92. // 获取没有未删除作物的土地ID列表
  93. $landsWithoutActiveCrops = DB::table('farm_land_users')
  94. ->leftJoin('farm_crops', function ($join) {
  95. $join->on('farm_land_users.id', '=', 'farm_crops.land_id')
  96. ->whereNull('farm_crops.deleted_at');
  97. })
  98. ->select('farm_land_users.id')
  99. ->whereNull('farm_crops.id')
  100. ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
  101. ->when($userId, function ($query, $userId) {
  102. return $query->where('farm_land_users.user_id', $userId);
  103. })
  104. ->pluck('farm_land_users.id');
  105. // 合并所有有问题的土地ID
  106. $allProblematicLandIds = $landsWithDeletedCrops->concat($landsWithoutActiveCrops)->unique()->take($limit);
  107. // 获取详细信息
  108. $results = collect();
  109. foreach ($allProblematicLandIds as $landId) {
  110. $land = DB::table('farm_land_users')->where('id', $landId)->first();
  111. if (!$land) continue;
  112. // 获取该土地的软删除作物信息
  113. $deletedCrops = DB::table('farm_crops')
  114. ->where('land_id', $landId)
  115. ->whereNotNull('deleted_at')
  116. ->get();
  117. $item = (object) [
  118. 'land_id' => $land->id,
  119. 'user_id' => $land->user_id,
  120. 'land_status' => $land->status,
  121. 'has_crop' => $land->has_crop,
  122. 'deleted_crops_count' => $deletedCrops->count(),
  123. 'crop_ids' => $deletedCrops->pluck('id')->implode(','),
  124. 'first_deleted_at' => $deletedCrops->min('deleted_at'),
  125. 'last_deleted_at' => $deletedCrops->max('deleted_at'),
  126. 'seed_ids' => $deletedCrops->pluck('seed_id')->unique()->implode(',')
  127. ];
  128. $item->problem_type = $this->getProblemType($item);
  129. $results->push($item);
  130. }
  131. return $results;
  132. }
  133. /**
  134. * 获取问题类型
  135. *
  136. * @param object $item
  137. * @return string
  138. */
  139. private function getProblemType(object $item): string
  140. {
  141. if ($item->deleted_crops_count > 0) {
  142. return '作物已软删除但土地非空闲';
  143. } elseif ($item->deleted_crops_count == 0) {
  144. return '土地无作物但状态非空闲';
  145. }
  146. return '其他问题';
  147. }
  148. /**
  149. * 显示问题数据
  150. *
  151. * @param \Illuminate\Support\Collection $items
  152. * @return void
  153. */
  154. private function displayProblems(\Illuminate\Support\Collection $items): void
  155. {
  156. // 按问题类型分组显示
  157. $groupedData = $items->groupBy('problem_type');
  158. foreach ($groupedData as $problemType => $problemItems) {
  159. $this->info("\n=== {$problemType} ({$problemItems->count()} 条) ===");
  160. $headers = ['土地ID', '用户ID', '当前土地状态', 'has_crop', '软删除作物数', '作物IDs', '最后删除时间', '种子IDs'];
  161. $rows = [];
  162. foreach ($problemItems as $item) {
  163. $rows[] = [
  164. $item->land_id,
  165. $item->user_id,
  166. $this->getLandStatusName($item->land_status),
  167. $item->has_crop ? '是' : '否',
  168. $item->deleted_crops_count,
  169. $item->crop_ids ? (strlen($item->crop_ids) > 50 ? substr($item->crop_ids, 0, 47) . '...' : $item->crop_ids) : 'N/A',
  170. $item->last_deleted_at ?? 'N/A',
  171. $item->seed_ids ?? 'N/A'
  172. ];
  173. }
  174. $this->table($headers, $rows);
  175. }
  176. }
  177. /**
  178. * 修复问题数据
  179. *
  180. * @param \Illuminate\Support\Collection $items
  181. * @return void
  182. */
  183. private function fixProblems(\Illuminate\Support\Collection $items): void
  184. {
  185. $fixedCount = 0;
  186. $failedCount = 0;
  187. foreach ($items as $item) {
  188. try {
  189. DB::transaction(function () use ($item) {
  190. $this->fixSingleLand($item);
  191. });
  192. $fixedCount++;
  193. $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id}) - {$item->problem_type}");
  194. } catch (\Exception $e) {
  195. $failedCount++;
  196. $this->error("✗ 修复土地 {$item->land_id} 失败: {$e->getMessage()}");
  197. Log::error('单个土地状态修复失败', [
  198. 'land_id' => $item->land_id,
  199. 'user_id' => $item->user_id,
  200. 'problem_type' => $item->problem_type,
  201. 'error' => $e->getMessage()
  202. ]);
  203. }
  204. }
  205. $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
  206. }
  207. /**
  208. * 修复单个土地
  209. *
  210. * @param object $item
  211. * @return void
  212. */
  213. private function fixSingleLand(object $item): void
  214. {
  215. $land = FarmLand::lockForUpdate()->find($item->land_id);
  216. if (!$land) {
  217. throw new \Exception('土地不存在');
  218. }
  219. $oldStatus = $land->status;
  220. $oldHasCrop = $land->has_crop;
  221. // 设置土地状态为空闲
  222. $land->status = LAND_STATUS::IDLE->value;
  223. $land->updateHasCrop(); // 这会将has_crop设置为false
  224. $land->save();
  225. Log::info('已铲除作物土地状态修复', [
  226. 'land_id' => $land->id,
  227. 'user_id' => $land->user_id,
  228. 'deleted_crops_count' => $item->deleted_crops_count,
  229. 'crop_ids' => $item->crop_ids,
  230. 'old_status' => $oldStatus,
  231. 'new_status' => $land->status,
  232. 'old_has_crop' => $oldHasCrop,
  233. 'new_has_crop' => $land->has_crop,
  234. 'problem_type' => $item->problem_type,
  235. 'first_deleted_at' => $item->first_deleted_at,
  236. 'last_deleted_at' => $item->last_deleted_at
  237. ]);
  238. }
  239. /**
  240. * 获取土地状态名称
  241. *
  242. * @param int $status
  243. * @return string
  244. */
  245. private function getLandStatusName(int $status): string
  246. {
  247. return match ($status) {
  248. LAND_STATUS::IDLE->value => '空闲',
  249. LAND_STATUS::PLANTING->value => '种植中',
  250. LAND_STATUS::DISASTER->value => '灾害',
  251. LAND_STATUS::HARVESTABLE->value => '可收获',
  252. LAND_STATUS::WITHERED->value => '枯萎',
  253. default => '未知'
  254. };
  255. }
  256. }