FixRemovedCropLandStatusCommand.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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. * 修复没有作物(has_crop为0)但土地状态不是空闲的问题
  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 = '修复没有作物(has_crop为0)但土地状态不是空闲的问题';
  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. * 查找没有作物(has_crop为0)但土地状态不是空闲的情况
  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. // 查找has_crop为0但状态不是空闲的土地
  82. $query = DB::table('farm_land_users')
  83. ->select([
  84. 'id as land_id',
  85. 'user_id',
  86. 'status as land_status',
  87. 'has_crop'
  88. ])
  89. ->where('has_crop', 0) // has_crop为0(没有作物)
  90. ->where('status', '!=', LAND_STATUS::IDLE->value) // 状态不是空闲
  91. ->when($userId, function ($query, $userId) {
  92. return $query->where('user_id', $userId);
  93. })
  94. ->limit($limit);
  95. $results = $query->get();
  96. // 添加问题类型标识和额外信息
  97. return $results->map(function ($item) {
  98. // 检查是否有未删除的作物(用于验证has_crop字段的正确性)
  99. $activeCrops = DB::table('farm_crops')
  100. ->where('land_id', $item->land_id)
  101. ->whereNull('deleted_at')
  102. ->count();
  103. // 检查是否有软删除的作物
  104. $deletedCrops = DB::table('farm_crops')
  105. ->where('land_id', $item->land_id)
  106. ->whereNotNull('deleted_at')
  107. ->count();
  108. $item->active_crops_count = $activeCrops;
  109. $item->deleted_crops_count = $deletedCrops;
  110. $item->problem_type = $this->getProblemType($item);
  111. return $item;
  112. });
  113. }
  114. /**
  115. * 获取问题类型
  116. *
  117. * @param object $item
  118. * @return string
  119. */
  120. private function getProblemType(object $item): string
  121. {
  122. if ($item->active_crops_count > 0) {
  123. return 'has_crop为0但实际有活跃作物';
  124. } else {
  125. return 'has_crop为0且状态非空闲';
  126. }
  127. }
  128. /**
  129. * 显示问题数据
  130. *
  131. * @param \Illuminate\Support\Collection $items
  132. * @return void
  133. */
  134. private function displayProblems(\Illuminate\Support\Collection $items): void
  135. {
  136. // 按问题类型分组显示
  137. $groupedData = $items->groupBy('problem_type');
  138. foreach ($groupedData as $problemType => $problemItems) {
  139. $this->info("\n=== {$problemType} ({$problemItems->count()} 条) ===");
  140. $headers = ['土地ID', '用户ID', '当前土地状态', 'has_crop', '活跃作物数', '软删除作物数'];
  141. $rows = [];
  142. foreach ($problemItems as $item) {
  143. $rows[] = [
  144. $item->land_id,
  145. $item->user_id,
  146. $this->getLandStatusName($item->land_status),
  147. $item->has_crop ? '是' : '否',
  148. $item->active_crops_count,
  149. $item->deleted_crops_count
  150. ];
  151. }
  152. $this->table($headers, $rows);
  153. }
  154. }
  155. /**
  156. * 修复问题数据
  157. *
  158. * @param \Illuminate\Support\Collection $items
  159. * @return void
  160. */
  161. private function fixProblems(\Illuminate\Support\Collection $items): void
  162. {
  163. $fixedCount = 0;
  164. $failedCount = 0;
  165. foreach ($items as $item) {
  166. try {
  167. DB::transaction(function () use ($item) {
  168. $this->fixSingleLand($item);
  169. });
  170. $fixedCount++;
  171. $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id}) - {$item->problem_type}");
  172. } catch (\Exception $e) {
  173. $failedCount++;
  174. $this->error("✗ 修复土地 {$item->land_id} 失败: {$e->getMessage()}");
  175. Log::error('单个土地状态修复失败', [
  176. 'land_id' => $item->land_id,
  177. 'user_id' => $item->user_id,
  178. 'problem_type' => $item->problem_type,
  179. 'error' => $e->getMessage()
  180. ]);
  181. }
  182. }
  183. $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
  184. }
  185. /**
  186. * 修复单个土地
  187. *
  188. * @param object $item
  189. * @return void
  190. */
  191. private function fixSingleLand(object $item): void
  192. {
  193. $land = FarmLand::lockForUpdate()->find($item->land_id);
  194. if (!$land) {
  195. throw new \Exception('土地不存在');
  196. }
  197. $oldStatus = $land->status;
  198. $oldHasCrop = $land->has_crop;
  199. // 如果has_crop为0但状态不是空闲,将状态设置为空闲
  200. $land->status = LAND_STATUS::IDLE->value;
  201. $land->updateHasCrop(); // 这会根据状态更新has_crop字段
  202. $land->save();
  203. Log::info('修复has_crop为0但状态非空闲的土地', [
  204. 'land_id' => $land->id,
  205. 'user_id' => $land->user_id,
  206. 'active_crops_count' => $item->active_crops_count,
  207. 'deleted_crops_count' => $item->deleted_crops_count,
  208. 'old_status' => $oldStatus,
  209. 'new_status' => $land->status,
  210. 'old_has_crop' => $oldHasCrop,
  211. 'new_has_crop' => $land->has_crop,
  212. 'problem_type' => $item->problem_type
  213. ]);
  214. }
  215. /**
  216. * 获取土地状态名称
  217. *
  218. * @param int $status
  219. * @return string
  220. */
  221. private function getLandStatusName(int $status): string
  222. {
  223. return match ($status) {
  224. LAND_STATUS::IDLE->value => '空闲',
  225. LAND_STATUS::PLANTING->value => '种植中',
  226. LAND_STATUS::DISASTER->value => '灾害',
  227. LAND_STATUS::HARVESTABLE->value => '可收获',
  228. LAND_STATUS::WITHERED->value => '枯萎',
  229. default => '未知'
  230. };
  231. }
  232. }