ソースを参照

修复铲除作物没有记录作物事件日志的问题

- 在FarmCropLog模型中添加EVENT_REMOVED事件类型常量
- 创建CropRemovedEvent事件类,包含用户、土地、作物等信息
- 在FarmCropLog中添加logCropRemoved静态方法用于记录铲除事件
- 修改CropLogic::removeCrop方法,添加事件日志记录和事件触发
- 更新CropService::removeCrop方法,支持传递工具ID参数
- 修改RemoveCropHandler和PetAutoSkillLogic,传递正确的工具ID
- 在后台管理界面中添加铲除作物事件类型的筛选和显示
- 添加测试命令TestCropRemove用于验证功能
- 完善事件数据解析方法,支持摘要和详情显示

测试验证:
- ✅ 铲除作物时正确记录到farm_crop_logs表
- ✅ 事件类型为'removed',显示为'铲除作物'
- ✅ 事件数据包含工具ID、铲除时间、土地状态变化等信息
- ✅ 后台管理界面筛选功能正常工作
- ✅ 事件详情页面显示完整的铲除信息
AI Assistant 6 ヶ月 前
コミット
5f4e5c6f99

+ 23 - 0
AiWork/now.md

@@ -52,6 +52,29 @@
 - 优化清理完所有灾害后的状态设置,根据作物当前生长阶段设置正确状态
 - 确保业务逻辑:作物成熟时即使有灾害也允许收获
 
+### 4. 创建农场土地状态修复脚本 (2025-07-04 11:11)
+- 新增FixLandStatusCommand命令,用于修复土地状态与作物生长阶段不一致的问题
+- 支持模拟运行模式,可以预览需要修复的数据
+- 支持指定用户和批量处理
+- 自动排除软删除的作物,只处理活跃数据
+- 根据作物生长阶段和灾害情况计算正确的土地状态
+- 提供详细的修复日志和错误处理
+- Commit: 88239d5f
+
+#### 脚本功能
+**命令**: `php artisan farm:fix-land-status`
+**选项**:
+- `--dry-run`: 仅显示需要修复的数据,不执行修复
+- `--user=ID`: 指定用户ID,只修复该用户的数据
+- `--limit=N`: 每批处理的数量
+
+#### 验证结果
+- 查询发现大量不一致数据都是软删除的作物(146个软删除,18个活跃)
+- 所有活跃作物的土地状态都是正确的:
+  - 成熟期作物:土地状态为"可收获" ✅
+  - 枯萎期作物:土地状态为"枯萎" ✅
+- 证明之前的修复逻辑有效,当前系统状态正常
+
 ## 当前状态
 任务已完成,等待用户验收和新任务。
 

+ 120 - 0
app/Console/Commands/TestCropRemove.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Module\Farm\Services\CropService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class TestCropRemove extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'test:crop-remove {user_id} {land_id} {--tool_id=0}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '测试铲除作物功能,验证事件日志记录';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle()
+    {
+        $userId = (int) $this->argument('user_id');
+        $landId = (int) $this->argument('land_id');
+        $toolId = (int) $this->option('tool_id');
+
+        $this->info("开始测试铲除作物功能");
+        $this->info("用户ID: {$userId}");
+        $this->info("土地ID: {$landId}");
+        $this->info("工具ID: {$toolId}");
+
+        try {
+            // 查询铲除前的作物信息
+            $crop = \App\Module\Farm\Models\FarmCrop::where('land_id', $landId)
+                ->where('user_id', $userId)
+                ->first();
+
+            if (!$crop) {
+                $this->error("土地上没有作物");
+                return 1;
+            }
+
+            $this->info("找到作物: ID={$crop->id}, 种子ID={$crop->seed_id}, 生长阶段={$crop->growth_stage->value}");
+
+            // 查询铲除前的事件日志数量
+            $logCountBefore = \App\Module\Farm\Models\FarmCropLog::where('crop_id', $crop->id)
+                ->where('event_type', 'removed')
+                ->count();
+
+            $this->info("铲除前的铲除事件日志数量: {$logCountBefore}");
+
+            // 开启事务
+            DB::beginTransaction();
+
+            // 调用铲除作物服务
+            $result = CropService::removeCrop($userId, $landId, $toolId);
+
+            if ($result['success']) {
+                $this->info("铲除作物成功");
+                $this->info("状态变更: " . ($result['status_changed'] ? '是' : '否'));
+                $this->info("旧状态: {$result['old_status']}");
+                $this->info("新状态: {$result['new_status']}");
+
+                // 提交事务
+                DB::commit();
+
+                // 查询铲除后的事件日志数量
+                $logCountAfter = \App\Module\Farm\Models\FarmCropLog::where('crop_id', $crop->id)
+                    ->where('event_type', 'removed')
+                    ->count();
+
+                $this->info("铲除后的铲除事件日志数量: {$logCountAfter}");
+
+                if ($logCountAfter > $logCountBefore) {
+                    $this->info("✅ 事件日志记录成功!");
+                    
+                    // 查询最新的铲除事件日志
+                    $latestLog = \App\Module\Farm\Models\FarmCropLog::where('crop_id', $crop->id)
+                        ->where('event_type', 'removed')
+                        ->orderBy('id', 'desc')
+                        ->first();
+
+                    if ($latestLog) {
+                        $this->info("最新事件日志详情:");
+                        $this->info("- ID: {$latestLog->id}");
+                        $this->info("- 事件类型: {$latestLog->event_type}");
+                        $this->info("- 生长阶段: {$latestLog->growth_stage}");
+                        $eventData = is_string($latestLog->event_data) ? json_decode($latestLog->event_data, true) : $latestLog->event_data;
+                        $this->info("- 事件数据: " . json_encode($eventData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+                        $this->info("- 创建时间: {$latestLog->created_at}");
+                    }
+                } else {
+                    $this->error("❌ 事件日志记录失败!");
+                }
+
+            } else {
+                $this->error("铲除作物失败");
+                DB::rollBack();
+                return 1;
+            }
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            $this->error("测试过程中发生异常: " . $e->getMessage());
+            $this->error("异常堆栈: " . $e->getTraceAsString());
+            return 1;
+        }
+
+        $this->info("测试完成");
+        return 0;
+    }
+}

+ 1 - 1
app/Module/AppGame/Handler/Land/RemoveCropHandler.php

@@ -60,7 +60,7 @@ class RemoveCropHandler extends BaseHandler
             DB::beginTransaction();
 
             // 调用铲除作物服务
-            $result = CropService::removeCrop($userId, $landId);
+            $result = CropService::removeCrop($userId, $landId, $toolItemId);
             if (!$result['success']) {
                 throw new LogicException("铲除作物失败,请检查土地状态");
             }

+ 3 - 0
app/Module/Farm/AdminControllers/FarmCropLogController.php

@@ -72,6 +72,7 @@ class FarmCropLogController extends AdminController
                     FarmCropLog::EVENT_PESTICIDE_USED => 'warning',
                     FarmCropLog::EVENT_WEEDICIDE_USED => 'warning',
                     FarmCropLog::EVENT_WATERING => 'info',
+                    FarmCropLog::EVENT_REMOVED => 'danger',
                 ];
                 $color = $colors[$value] ?? 'secondary';
 
@@ -85,6 +86,7 @@ class FarmCropLogController extends AdminController
                     FarmCropLog::EVENT_PESTICIDE_USED => '使用杀虫剂',
                     FarmCropLog::EVENT_WEEDICIDE_USED => '使用除草剂',
                     FarmCropLog::EVENT_WATERING => '浇水',
+                    FarmCropLog::EVENT_REMOVED => '铲除作物',
                 ];
                 $name = $names[$value] ?? '未知事件';
 
@@ -124,6 +126,7 @@ class FarmCropLogController extends AdminController
                     FarmCropLog::EVENT_PESTICIDE_USED => '使用杀虫剂',
                     FarmCropLog::EVENT_WEEDICIDE_USED => '使用除草剂',
                     FarmCropLog::EVENT_WATERING => '浇水',
+                    FarmCropLog::EVENT_REMOVED => '铲除作物',
                 ]);
                 $filter->equal('growth_stage', '生长阶段')->select(\App\Module\Farm\Enums\GROWTH_STAGE::getAll());
                 $helper->betweenDatetime('created_at', '事件时间');

+ 88 - 0
app/Module/Farm/Events/CropRemovedEvent.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Module\Farm\Events;
+
+use App\Module\Farm\Models\FarmCrop;
+use App\Module\Farm\Models\FarmLand;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+/**
+ * 作物铲除事件
+ * 
+ * 当用户铲除土地上的作物时触发
+ */
+class CropRemovedEvent
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    /**
+     * 用户ID
+     *
+     * @var int
+     */
+    public $userId;
+
+    /**
+     * 土地信息
+     *
+     * @var FarmLand
+     */
+    public $land;
+
+    /**
+     * 作物信息
+     *
+     * @var FarmCrop
+     */
+    public $crop;
+
+    /**
+     * 使用的工具物品ID(可选)
+     *
+     * @var int
+     */
+    public $toolItemId;
+
+    /**
+     * 铲除原因(可选)
+     *
+     * @var string|null
+     */
+    public $reason;
+
+    /**
+     * 是否为软删除
+     *
+     * @var bool
+     */
+    public $softDeleted;
+
+    /**
+     * 创建一个新的事件实例
+     *
+     * @param int $userId
+     * @param FarmLand $land
+     * @param FarmCrop $crop
+     * @param int $toolItemId
+     * @param string|null $reason
+     * @param bool $softDeleted
+     * @return void
+     */
+    public function __construct(
+        int $userId,
+        FarmLand $land,
+        FarmCrop $crop,
+        int $toolItemId = 0,
+        ?string $reason = null,
+        bool $softDeleted = true
+    ) {
+        $this->userId = $userId;
+        $this->land = $land;
+        $this->crop = $crop;
+        $this->toolItemId = $toolItemId;
+        $this->reason = $reason;
+        $this->softDeleted = $softDeleted;
+    }
+}

+ 26 - 5
app/Module/Farm/Logics/CropLogic.php

@@ -716,9 +716,10 @@ class CropLogic
      *
      * @param int $userId
      * @param int $landId
+     * @param int $toolItemId 使用的工具物品ID,0表示手动铲除
      * @return bool
      */
-    public function removeCrop(int $userId, int $landId): bool
+    public function removeCrop(int $userId, int $landId, int $toolItemId = 0): bool
     {
         try {
             // 检查是否已开启事务
@@ -759,18 +760,38 @@ class CropLogic
                 return true;
             }
 
-            // 软删除作物记录(使用软删除保留数据用于审计)
-            $crop->delete();
-
             // 记录旧状态
             $oldLandStatus = $land->status;
 
+            // 记录铲除作物事件日志(在软删除之前记录,确保作物信息完整)
+            FarmCropLog::logCropRemoved($userId, $landId, $crop->id, $crop->seed_id, [
+                'growth_stage' => $crop->growth_stage->value,
+                'land_type' => $crop->land_level,
+                'tool_item_id' => $toolItemId,
+                'removed_at' => now()->toDateTimeString(),
+                'soft_deleted' => true,
+                'old_land_status' => $oldLandStatus,
+                'new_land_status' => LAND_STATUS::IDLE->value,
+                'reason' => $toolItemId > 0 ? '使用工具铲除' : '用户手动铲除'
+            ]);
+
+            // 软删除作物记录(使用软删除保留数据用于审计)
+            $crop->delete();
+
             // 更新土地状态
             $land->status = LAND_STATUS::IDLE->value;
             $land->updateHasCrop();
             $land->save();
 
-            // 记录状态变更信息,由调用方处理事件触发和事务提交
+            // 触发作物铲除事件(在事务内触发,确保数据一致性)
+            event(new \App\Module\Farm\Events\CropRemovedEvent(
+                $userId,
+                $land,
+                $crop,
+                $toolItemId,
+                $toolItemId > 0 ? '使用工具铲除' : '用户手动铲除',
+                true
+            ));
 
             Log::info('铲除作物成功(软删除)', [
                 'user_id'    => $userId,

+ 60 - 0
app/Module/Farm/Models/FarmCropLog.php

@@ -40,6 +40,7 @@ class FarmCropLog extends ModelCore
     const EVENT_PESTICIDE_USED = 'pesticide_used';        // 使用杀虫剂
     const EVENT_WEEDICIDE_USED = 'weedicide_used';        // 使用除草剂
     const EVENT_WATERING = 'watering';                    // 浇水
+    const EVENT_REMOVED = 'removed';                      // 铲除作物
 
     protected $fillable = [
         'user_id',
@@ -105,6 +106,7 @@ class FarmCropLog extends ModelCore
             self::EVENT_PESTICIDE_USED => '使用杀虫剂',
             self::EVENT_WEEDICIDE_USED => '使用除草剂',
             self::EVENT_WATERING => '浇水',
+            self::EVENT_REMOVED => '铲除作物',
             default => '未知事件'
         };
     }
@@ -309,6 +311,7 @@ class FarmCropLog extends ModelCore
             self::EVENT_PESTICIDE_USED => $this->parsePesticideUsedData($data),
             self::EVENT_WEEDICIDE_USED => $this->parseWeedicideUsedData($data),
             self::EVENT_WATERING => $this->parseWateringData($data),
+            self::EVENT_REMOVED => $this->parseRemovedData($data),
             default => '未知事件类型'
         };
     }
@@ -657,6 +660,7 @@ class FarmCropLog extends ModelCore
             self::EVENT_PESTICIDE_USED => self::parsePesticideUsedSummary($data),
             self::EVENT_WEEDICIDE_USED => self::parseWeedicideUsedSummary($data),
             self::EVENT_WATERING => self::parseWateringSummary($data),
+            self::EVENT_REMOVED => self::parseRemovedSummary($data),
             default => '未知事件类型'
         };
     }
@@ -684,6 +688,7 @@ class FarmCropLog extends ModelCore
             self::EVENT_PESTICIDE_USED => self::parsePesticideUsedDetail($data),
             self::EVENT_WEEDICIDE_USED => self::parseWeedicideUsedDetail($data),
             self::EVENT_WATERING => self::parseWateringDetail($data),
+            self::EVENT_REMOVED => self::parseRemovedDetail($data),
             default => '<pre>' . json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . '</pre>'
         };
     }
@@ -766,6 +771,15 @@ class FarmCropLog extends ModelCore
         return "浇水物品ID: {$itemId}, 成功率: {$successRate}%";
     }
 
+    private static function parseRemovedSummary(array $data): string
+    {
+        $toolItemId = $data['tool_item_id'] ?? 0;
+        $removedAt = $data['removed_at'] ?? '未知时间';
+        $toolText = $toolItemId > 0 ? "使用工具ID: {$toolItemId}" : "手动铲除";
+
+        return "{$toolText}, 铲除时间: {$removedAt}";
+    }
+
     // 静态解析方法 - 详情版本
     private static function parseFruitConfirmedDetail(array $data): string
     {
@@ -929,4 +943,50 @@ class FarmCropLog extends ModelCore
 
         return implode('<br>', $details);
     }
+
+    private static function parseRemovedDetail(array $data): string
+    {
+        $details = [];
+        $details[] = '<strong>铲除作物事件详情:</strong>';
+        $details[] = '• 生长阶段: ' . GROWTH_STAGE::getName($data['growth_stage'] ?? 0);
+        $details[] = '• 土地类型: ' . ($data['land_type'] ?? '未知');
+        $details[] = '• 使用工具ID: ' . ($data['tool_item_id'] ?? 0);
+        $details[] = '• 铲除时间: ' . ($data['removed_at'] ?? '未知');
+        $details[] = '• 是否软删除: ' . (($data['soft_deleted'] ?? true) ? '是' : '否');
+
+        if (isset($data['old_land_status']) && isset($data['new_land_status'])) {
+            $details[] = '• 土地状态变化: ' . $data['old_land_status'] . ' → ' . $data['new_land_status'];
+        }
+
+        if (isset($data['reason'])) {
+            $details[] = '• 铲除原因: ' . $data['reason'];
+        }
+
+        return implode('<br>', $details);
+    }
+
+    /**
+     * 解析铲除作物事件数据(摘要)
+     */
+    private function parseRemovedData(array $data): string
+    {
+        return self::parseRemovedSummary($data);
+    }
+
+    /**
+     * 静态方法:记录铲除作物事件
+     */
+    public static function logCropRemoved(int $userId, int $landId, int $cropId, int $seedId, array $eventData): self
+    {
+        return self::create([
+            'user_id' => $userId,
+            'land_id' => $landId,
+            'crop_id' => $cropId,
+            'seed_id' => $seedId,
+            'event_type' => self::EVENT_REMOVED,
+            'event_data' => $eventData,
+            'growth_stage' => $eventData['growth_stage'] ?? GROWTH_STAGE::SPROUT->value,
+            'land_type' => $eventData['land_type'] ?? 1,
+        ]);
+    }
 }

+ 3 - 2
app/Module/Farm/Services/CropService.php

@@ -278,9 +278,10 @@ class CropService
      *
      * @param int $userId
      * @param int $landId
+     * @param int $toolItemId 使用的工具物品ID,0表示手动铲除
      * @return array 返回操作结果和状态变更信息
      */
-    public static function removeCrop(int $userId, int $landId): array
+    public static function removeCrop(int $userId, int $landId, int $toolItemId = 0): array
     {
         try {
             // 获取铲除前的土地状态
@@ -295,7 +296,7 @@ class CropService
             $oldStatus = $land->status;
 
             $cropLogic = new CropLogic();
-            $result = $cropLogic->removeCrop($userId, $landId);
+            $result = $cropLogic->removeCrop($userId, $landId, $toolItemId);
 
             if ($result) {
                 // 获取铲除后的土地状态

+ 2 - 2
app/Module/Pet/Logic/PetAutoSkillLogic.php

@@ -820,8 +820,8 @@ class PetAutoSkillLogic
                 return false;
             }
 
-            // 调用农场服务铲除作物
-            $result = \App\Module\Farm\Services\CropService::removeCrop($userId, $landId);
+            // 调用农场服务铲除作物(宠物自动铲除,工具ID为0)
+            $result = \App\Module\Farm\Services\CropService::removeCrop($userId, $landId, 0);
 
             if ($result) {
                 Log::info('宠物自动铲除枯萎作物成功', [