FixLandStatusCommand.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. namespace App\Module\Farm\Commands;
  3. use App\Module\Farm\Enums\GROWTH_STAGE;
  4. use App\Module\Farm\Enums\LAND_STATUS;
  5. use App\Module\Farm\Models\FarmCrop;
  6. use App\Module\Farm\Models\FarmLand;
  7. use Illuminate\Console\Command;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. /**
  11. * 修复农场土地状态命令
  12. *
  13. * 修复土地状态与作物生长阶段不一致的问题
  14. */
  15. class FixLandStatusCommand extends Command
  16. {
  17. /**
  18. * 命令签名
  19. *
  20. * @var string
  21. */
  22. protected $signature = 'farm:fix-land-status
  23. {--dry-run : 仅显示需要修复的数据,不执行修复}
  24. {--user= : 指定用户ID,只修复该用户的数据}
  25. {--limit=100 : 每批处理的数量}';
  26. /**
  27. * 命令描述
  28. *
  29. * @var string
  30. */
  31. protected $description = '修复农场土地状态与作物生长阶段不一致的问题';
  32. /**
  33. * 执行命令
  34. *
  35. * @return int
  36. */
  37. public function handle(): int
  38. {
  39. $dryRun = $this->option('dry-run');
  40. $userId = $this->option('user');
  41. $limit = (int) $this->option('limit');
  42. $this->info('开始检查土地状态与作物生长阶段的一致性...');
  43. if ($dryRun) {
  44. $this->warn('运行在模拟模式,不会实际修改数据');
  45. }
  46. try {
  47. // 获取需要修复的数据
  48. $inconsistentData = $this->getInconsistentData($userId, $limit);
  49. if ($inconsistentData->isEmpty()) {
  50. $this->info('没有发现需要修复的数据');
  51. return 0;
  52. }
  53. $this->info("发现 {$inconsistentData->count()} 条需要修复的数据");
  54. // 按问题类型分组
  55. $groupedData = $inconsistentData->groupBy('problem_type');
  56. foreach ($groupedData as $problemType => $items) {
  57. $this->info("\n=== {$problemType} ({$items->count()} 条) ===");
  58. if ($dryRun) {
  59. $this->displayProblems($items);
  60. } else {
  61. $this->fixProblems($items, $problemType);
  62. }
  63. }
  64. if (!$dryRun) {
  65. $this->info("\n修复完成!");
  66. }
  67. return 0;
  68. } catch (\Exception $e) {
  69. $this->error("修复过程中发生错误: {$e->getMessage()}");
  70. Log::error('土地状态修复失败', [
  71. 'error' => $e->getMessage(),
  72. 'trace' => $e->getTraceAsString()
  73. ]);
  74. return 1;
  75. }
  76. }
  77. /**
  78. * 获取状态不一致的数据
  79. *
  80. * @param string|null $userId
  81. * @param int $limit
  82. * @return \Illuminate\Support\Collection
  83. */
  84. private function getInconsistentData(?string $userId, int $limit): \Illuminate\Support\Collection
  85. {
  86. $query = DB::table('farm_land_users as fl')
  87. ->join('farm_crops as fc', 'fl.id', '=', 'fc.land_id')
  88. ->select([
  89. 'fl.id as land_id',
  90. 'fl.user_id',
  91. 'fl.status as land_status',
  92. 'fc.id as crop_id',
  93. 'fc.growth_stage',
  94. 'fc.disasters'
  95. ])
  96. ->whereNull('fc.deleted_at') // 排除软删除的作物
  97. ->where(function ($query) {
  98. $query
  99. // 作物成熟但土地状态不是可收获
  100. ->where(function ($q) {
  101. $q->where('fc.growth_stage', GROWTH_STAGE::MATURE->value)
  102. ->where('fl.status', '!=', LAND_STATUS::HARVESTABLE->value);
  103. })
  104. // 作物枯萎但土地状态不是枯萎
  105. ->orWhere(function ($q) {
  106. $q->where('fc.growth_stage', GROWTH_STAGE::WITHERED->value)
  107. ->where('fl.status', '!=', LAND_STATUS::WITHERED->value);
  108. })
  109. // 作物未成熟但土地状态是可收获
  110. ->orWhere(function ($q) {
  111. $q->whereNotIn('fc.growth_stage', [GROWTH_STAGE::MATURE->value, GROWTH_STAGE::WITHERED->value])
  112. ->where('fl.status', LAND_STATUS::HARVESTABLE->value);
  113. })
  114. // 作物未枯萎但土地状态是枯萎
  115. ->orWhere(function ($q) {
  116. $q->where('fc.growth_stage', '!=', GROWTH_STAGE::WITHERED->value)
  117. ->where('fl.status', LAND_STATUS::WITHERED->value);
  118. });
  119. });
  120. if ($userId) {
  121. $query->where('fl.user_id', $userId);
  122. }
  123. $results = $query->limit($limit)->get();
  124. // 添加问题类型标识
  125. return $results->map(function ($item) {
  126. $item->problem_type = $this->getProblemType($item);
  127. $item->active_disasters_count = $this->countActiveDisasters($item->disasters);
  128. return $item;
  129. });
  130. }
  131. /**
  132. * 获取问题类型
  133. *
  134. * @param object $item
  135. * @return string
  136. */
  137. private function getProblemType(object $item): string
  138. {
  139. if ($item->growth_stage == GROWTH_STAGE::MATURE->value && $item->land_status != LAND_STATUS::HARVESTABLE->value) {
  140. return '作物成熟但土地非可收获';
  141. }
  142. if ($item->growth_stage == GROWTH_STAGE::WITHERED->value && $item->land_status != LAND_STATUS::WITHERED->value) {
  143. return '作物枯萎但土地非枯萎';
  144. }
  145. if (!in_array($item->growth_stage, [GROWTH_STAGE::MATURE->value, GROWTH_STAGE::WITHERED->value]) && $item->land_status == LAND_STATUS::HARVESTABLE->value) {
  146. return '作物未成熟但土地可收获';
  147. }
  148. if ($item->growth_stage != GROWTH_STAGE::WITHERED->value && $item->land_status == LAND_STATUS::WITHERED->value) {
  149. return '作物未枯萎但土地枯萎';
  150. }
  151. return '其他不一致';
  152. }
  153. /**
  154. * 统计活跃灾害数量
  155. *
  156. * @param string|null $disasters
  157. * @return int
  158. */
  159. private function countActiveDisasters(?string $disasters): int
  160. {
  161. if (empty($disasters)) {
  162. return 0;
  163. }
  164. $disasterArray = json_decode($disasters, true);
  165. if (!is_array($disasterArray)) {
  166. return 0;
  167. }
  168. return count(array_filter($disasterArray, function ($disaster) {
  169. return ($disaster['status'] ?? '') === 'active';
  170. }));
  171. }
  172. /**
  173. * 显示问题数据
  174. *
  175. * @param \Illuminate\Support\Collection $items
  176. * @return void
  177. */
  178. private function displayProblems(\Illuminate\Support\Collection $items): void
  179. {
  180. $headers = ['土地ID', '用户ID', '土地状态', '作物ID', '生长阶段', '活跃灾害数'];
  181. $rows = [];
  182. foreach ($items as $item) {
  183. $rows[] = [
  184. $item->land_id,
  185. $item->user_id,
  186. $this->getLandStatusName($item->land_status),
  187. $item->crop_id,
  188. $this->getGrowthStageName($item->growth_stage),
  189. $item->active_disasters_count
  190. ];
  191. }
  192. $this->table($headers, $rows);
  193. }
  194. /**
  195. * 修复问题数据
  196. *
  197. * @param \Illuminate\Support\Collection $items
  198. * @param string $problemType
  199. * @return void
  200. */
  201. private function fixProblems(\Illuminate\Support\Collection $items, string $problemType): void
  202. {
  203. $fixedCount = 0;
  204. $failedCount = 0;
  205. foreach ($items as $item) {
  206. try {
  207. DB::transaction(function () use ($item, $problemType) {
  208. $this->fixSingleItem($item, $problemType);
  209. });
  210. $fixedCount++;
  211. $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id})");
  212. } catch (\Exception $e) {
  213. $failedCount++;
  214. $this->error("✗ 修复土地 {$item->land_id} 失败: {$e->getMessage()}");
  215. Log::error('单个土地状态修复失败', [
  216. 'land_id' => $item->land_id,
  217. 'user_id' => $item->user_id,
  218. 'problem_type' => $problemType,
  219. 'error' => $e->getMessage()
  220. ]);
  221. }
  222. }
  223. $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
  224. }
  225. /**
  226. * 修复单个数据项
  227. *
  228. * @param object $item
  229. * @param string $problemType
  230. * @return void
  231. */
  232. private function fixSingleItem(object $item, string $problemType): void
  233. {
  234. $land = FarmLand::lockForUpdate()->find($item->land_id);
  235. $crop = FarmCrop::lockForUpdate()->find($item->crop_id);
  236. if (!$land || !$crop) {
  237. throw new \Exception('土地或作物不存在');
  238. }
  239. $newStatus = $this->calculateCorrectLandStatus($crop);
  240. if ($land->status != $newStatus) {
  241. $oldStatus = $land->status;
  242. $land->status = $newStatus;
  243. $land->updateHasCrop();
  244. $land->save();
  245. Log::info('土地状态修复', [
  246. 'land_id' => $land->id,
  247. 'user_id' => $land->user_id,
  248. 'crop_id' => $crop->id,
  249. 'old_status' => $oldStatus,
  250. 'new_status' => $newStatus,
  251. 'growth_stage' => $crop->growth_stage,
  252. 'problem_type' => $problemType,
  253. 'active_disasters' => $this->countActiveDisasters($crop->disasters)
  254. ]);
  255. }
  256. }
  257. /**
  258. * 计算正确的土地状态
  259. *
  260. * @param FarmCrop $crop
  261. * @return int
  262. */
  263. private function calculateCorrectLandStatus(FarmCrop $crop): int
  264. {
  265. // 根据作物生长阶段确定土地状态
  266. switch ($crop->growth_stage) {
  267. case GROWTH_STAGE::MATURE->value:
  268. // 作物成熟,土地状态为可收获
  269. return LAND_STATUS::HARVESTABLE->value;
  270. case GROWTH_STAGE::WITHERED->value:
  271. // 作物枯萎,土地状态为枯萎
  272. return LAND_STATUS::WITHERED->value;
  273. default:
  274. // 其他阶段,检查是否有活跃灾害
  275. $activeDisasters = $this->countActiveDisasters($crop->disasters);
  276. if ($activeDisasters > 0) {
  277. return LAND_STATUS::DISASTER->value;
  278. } else {
  279. return LAND_STATUS::PLANTING->value;
  280. }
  281. }
  282. }
  283. /**
  284. * 获取土地状态名称
  285. *
  286. * @param int $status
  287. * @return string
  288. */
  289. private function getLandStatusName(int $status): string
  290. {
  291. return match ($status) {
  292. LAND_STATUS::IDLE->value => '空闲',
  293. LAND_STATUS::PLANTING->value => '种植中',
  294. LAND_STATUS::DISASTER->value => '灾害',
  295. LAND_STATUS::HARVESTABLE->value => '可收获',
  296. LAND_STATUS::WITHERED->value => '枯萎',
  297. default => '未知'
  298. };
  299. }
  300. /**
  301. * 获取生长阶段名称
  302. *
  303. * @param int $stage
  304. * @return string
  305. */
  306. private function getGrowthStageName(int $stage): string
  307. {
  308. return match ($stage) {
  309. GROWTH_STAGE::SEED->value => '种子期',
  310. GROWTH_STAGE::SPROUT->value => '发芽期',
  311. GROWTH_STAGE::GROWTH->value => '生长期',
  312. GROWTH_STAGE::FRUIT->value => '果实期',
  313. GROWTH_STAGE::MATURE->value => '成熟期',
  314. GROWTH_STAGE::WITHERED->value => '枯萎期',
  315. default => '未知'
  316. };
  317. }
  318. }