소스 검색

refactor(pet): 重构宠物技能处理任务

- 新增 ProcessActiveSkillsJob 类,继承自 QueueJob
- 重构 handle 方法,使用 run 方法处理技能效果
- 优化技能效果处理逻辑,提高代码可读性和可维护性- 更新日志记录方式,使用 laravel 日志系统
-修复部分技能处理中的潜在问题
notfff 7 달 전
부모
커밋
e935eb59bd

+ 216 - 0
AiWork/202506/062206-优化自动收获附带铲除枯萎植物.md

@@ -0,0 +1,216 @@
+# 优化自动收获功能,附带铲除枯萎植物
+
+**时间**: 2025-06-06 22:06  
+**任务类型**: 功能优化  
+**紧急程度**: 急需  
+
+## 任务背景
+
+在修复自动杀虫技能问题后,用户要求优化自动收获功能,让它能够自动铲除枯萎的植物(不使用道具)。
+
+## 问题分析
+
+通过测试发现:
+1. **枯萎作物无法自动清理**:用户有2个枯萎状态的土地(63、67),但自动收获技能没有处理
+2. **作物状态比较问题**:作物的`growth_stage`字段存储的是枚举对象,而不是整数值
+3. **功能缺失**:自动收获技能只处理可收获的作物,没有主动清理枯萎作物
+
+## 解决方案
+
+### 1. 新增批量清理枯萎作物功能
+
+添加 `clearAllWitheredCrops` 方法,在自动收获开始时主动清理所有枯萎作物:
+
+```php
+protected function clearAllWitheredCrops(int $userId): int
+{
+    try {
+        // 获取所有枯萎状态的土地
+        $witheredLands = \App\Module\Farm\Models\FarmLand::where('user_id', $userId)
+            ->where('status', \App\Module\Farm\Enums\LAND_STATUS::WITHERED->value)
+            ->get();
+
+        $clearedCount = 0;
+
+        foreach ($witheredLands as $land) {
+            try {
+                // 开启事务处理单个土地的清理
+                DB::beginTransaction();
+
+                $cleared = $this->autoClearWitheredCrop($userId, $land->id);
+                if ($cleared) {
+                    $clearedCount++;
+                    Log::info('自动清理枯萎作物成功', [
+                        'user_id' => $userId,
+                        'land_id' => $land->id
+                    ]);
+                }
+
+                DB::commit();
+
+            } catch (\Exception $e) {
+                DB::rollBack();
+                Log::warning('自动清理枯萎作物失败', [
+                    'user_id' => $userId,
+                    'land_id' => $land->id,
+                    'error' => $e->getMessage()
+                ]);
+            }
+        }
+
+        if ($clearedCount > 0) {
+            Log::info('批量清理枯萎作物完成', [
+                'user_id' => $userId,
+                'cleared_count' => $clearedCount,
+                'total_withered_lands' => $witheredLands->count()
+            ]);
+        }
+
+        return $clearedCount;
+
+    } catch (\Exception $e) {
+        Log::error('批量清理枯萎作物失败', [
+            'user_id' => $userId,
+            'error' => $e->getMessage(),
+            'trace' => $e->getTraceAsString()
+        ]);
+
+        return 0;
+    }
+}
+```
+
+### 2. 修复作物状态比较问题
+
+修复 `autoClearWitheredCrop` 方法中的枚举对象比较问题:
+
+```php
+// 检查作物是否为枯萎状态
+$cropStageValue = is_object($crop->growth_stage) ? $crop->growth_stage->value : $crop->growth_stage;
+if ($cropStageValue !== \App\Module\Farm\Enums\GROWTH_STAGE::WITHERED->value) {
+    return false;
+}
+```
+
+### 3. 优化自动收获流程
+
+修改 `processAutoHarvest` 方法,在开始时先清理枯萎作物:
+
+```php
+public function processAutoHarvest(PetActiveSkill $activeSkill): void
+{
+    try {
+        $pet = $activeSkill->pet;
+        $userId = $pet->user_id;
+
+        Log::info('开始处理自动收菜技能', [
+            'active_skill_id' => $activeSkill->id,
+            'pet_id' => $pet->id,
+            'user_id' => $userId
+        ]);
+
+        // 首先清理所有枯萎的作物(不使用道具)
+        $witheredClearCount = $this->clearAllWitheredCrops($userId);
+
+        // 获取用户所有可收获的土地
+        $harvestableLands = LandService::getHarvestableLands($userId);
+
+        // ... 正常收获逻辑 ...
+
+        // 记录统计信息
+        $this->recordSkillStatistics($activeSkill, 'auto_harvest', [
+            'harvest_count' => $harvestCount,
+            'auto_cleared_count' => $autoClearedCount,
+            'withered_cleared_count' => $witheredClearCount,
+            'total_lands_checked' => $harvestableLands->count(),
+            'harvest_results' => $harvestResults
+        ]);
+    }
+}
+```
+
+## 测试结果
+
+修复后测试结果:
+
+1. **枯萎作物清理成功**:
+   - 土地63:枯萎状态 → 空闲状态
+   - 土地67:枯萎状态 → 空闲状态
+
+2. **统计信息正确**:`"withered_cleared_count":2`
+
+3. **网页显示更新**:
+   - 土地状态统计:种植中 10,空闲 2
+   - 灾害统计:虫害 0,干旱 0,杂草 0
+   - 杀虫剂数量:18个(消耗了1个)
+
+4. **日志记录完整**:
+   ```
+   [2025-06-06T22:02:54.654324+08:00] laravel.INFO: 宠物自动铲除枯萎作物成功 {"user_id":10002,"land_id":67,"crop_id":180}
+   [2025-06-06T22:02:54.731524+08:00] laravel.INFO: 批量清理枯萎作物完成 {"user_id":10002,"cleared_count":2,"total_withered_lands":2}
+   ```
+
+## 功能特点
+
+### 优化后的自动收获功能包括:
+
+1. **主动清理枯萎作物**:
+   - 在收获开始时扫描所有枯萎状态的土地
+   - 自动铲除枯萎作物,不消耗道具
+   - 将土地状态恢复为空闲
+
+2. **正常收获流程**:
+   - 收获成熟的作物
+   - 收获后自动铲除新产生的枯萎作物
+
+3. **完整统计记录**:
+   - `harvest_count`:收获的作物数量
+   - `auto_cleared_count`:收获后清理的枯萎作物数量
+   - `withered_cleared_count`:主动清理的枯萎作物数量
+
+## 技术细节
+
+### 关键修复点
+
+1. **枚举对象处理**:
+   - 问题:`crop->growth_stage` 存储的是枚举对象 `{"App\\Module\\Farm\\Enums\\GROWTH_STAGE":50}`
+   - 解决:使用 `is_object()` 检查并获取 `->value` 属性
+
+2. **事务管理**:
+   - 每个土地的清理操作独立事务
+   - 确保单个失败不影响其他土地的处理
+
+3. **不使用道具**:
+   - 直接调用 `CropService::removeCrop()` 方法
+   - 不消耗铲子等道具
+
+## 文件修改
+
+- `app/Module/Pet/Logic/PetAutoSkillLogic.php`
+  - 新增 `clearAllWitheredCrops` 方法(第718-757行)
+  - 修改 `processAutoHarvest` 方法(第27-117行)
+  - 修复 `autoClearWitheredCrop` 方法(第786-811行)
+
+## 验证方法
+
+1. 运行命令: `php artisan pet:process-active-skills --sync`
+2. 查看用户农场信息: `http://kku_laravel.local.gd/admin/farm-user-summary/10002`
+3. 确认枯萎土地变为空闲状态,统计信息正确
+
+## 提交信息
+
+```
+优化自动收获功能,附带铲除枯萎植物
+
+- 新增clearAllWitheredCrops方法,在自动收获开始时主动清理所有枯萎作物
+- 修复作物growth_stage枚举对象与整数值比较问题
+- 优化自动收获流程:先清理枯萎作物,再进行正常收获
+- 增加withered_cleared_count统计,记录清理的枯萎作物数量
+- 移除调试日志,保持代码整洁
+- 自动铲除枯萎作物不使用道具,直接调用CropService::removeCrop
+
+现在自动收获技能可以:
+1. 主动清理所有枯萎的作物(不消耗道具)
+2. 正常收获成熟的作物
+3. 收获后自动铲除新产生的枯萎作物
+```

+ 76 - 0
AiWork/202506/071042-修复宠物技能处理错误.md

@@ -0,0 +1,76 @@
+# 修复宠物技能处理错误
+
+## 任务时间
+- 开始时间:2025年06月07日 10:42:08 CST
+- 完成时间:2025年06月07日 10:45:00 CST
+
+## 问题描述
+执行宠物技能处理命令 `php artisan pet:process-active-skills --sync` 时出现错误:
+```
+TypeError: Cannot access offset of type string on string at /data/wwwroot/nusuus/kknongchang/kku_laravel/app/Module/Pet/Logic/PetAutoSkillLogic.php:690
+```
+
+## 问题分析
+错误发生在 `PetAutoSkillLogic.php` 的 `recordSkillStatistics` 方法中。问题原因:
+1. `PetActiveSkill` 模型的 `config` 字段虽然定义了 `'config' => 'array'` 的cast
+2. 但在创建记录时使用了 `json_encode()` 存储配置,导致数据库中存储的是JSON字符串
+3. 在某些情况下,Laravel的cast转换可能失败,导致 `$activeSkill->config` 返回字符串而不是数组
+4. 当代码尝试使用数组语法访问字符串时,就会出现 `Cannot access offset of type string on string` 错误
+
+## 解决方案
+在 `recordSkillStatistics` 方法中添加类型检查和转换逻辑,确保 `config` 字段始终为数组类型:
+
+```php
+protected function recordSkillStatistics(PetActiveSkill $activeSkill, string $actionType, array $statistics): void
+{
+    $config = $activeSkill->config;
+
+    // 确保config是数组类型
+    if (!is_array($config)) {
+        // 如果是字符串,尝试解析JSON
+        if (is_string($config)) {
+            $config = json_decode($config, true);
+            if (json_last_error() !== JSON_ERROR_NONE) {
+                $config = [];
+            }
+        } else {
+            $config = [];
+        }
+    }
+
+    // 其余逻辑保持不变...
+}
+```
+
+## 修改文件
+- `app/Module/Pet/Logic/PetAutoSkillLogic.php` - 修复 `recordSkillStatistics` 方法的类型安全问题
+
+## 测试结果
+修复后重新执行命令:
+```bash
+php artisan pet:process-active-skills --sync
+```
+
+执行成功,输出:
+```
+开始处理宠物激活技能...
+使用同步模式处理...
+宠物激活技能处理完成(同步模式)
+```
+
+日志显示正常处理了1个激活技能,没有任何错误。
+
+## 技术要点
+1. **类型安全**:在处理可能为不同类型的数据时,应该添加类型检查
+2. **一致性**:保持与模型中其他方法(如 `updateLastCheckTime`、`getLastCheckTime` 等)一致的类型安全处理逻辑
+3. **容错性**:当JSON解析失败时,提供合理的默认值(空数组)
+
+## 提交信息
+```
+修复宠物技能处理中config字段类型错误问题
+
+- 在recordSkillStatistics方法中添加config字段类型检查
+- 确保config字段始终为数组类型,避免字符串类型导致的TypeError
+- 修复Cannot access offset of type string on string错误
+- 保持与模型中其他方法一致的类型安全处理逻辑
+```

+ 7 - 20
AiWork/WORK.md

@@ -21,10 +21,15 @@ shop_items 的 $max_buy 确认被替代后移除,使用mcp执行sql
 
 ## 待处理任务
 
-
+
 
 ## 已完成任务(保留最新的10条,多余的删除)
 
+**2025-06-07 10:45** - 修复宠物技能处理中config字段类型错误问题
+- 问题:执行 `php artisan pet:process-active-skills --sync` 时出现 "Cannot access offset of type string on string" 错误
+- 修复:在recordSkillStatistics方法中添加config字段类型检查,确保config字段始终为数组类型
+- 结果:命令正常运行,宠物技能处理功能恢复正常,统计信息正确记录
+
 **2025-06-06 21:03** - 完善FarmLand模型has_crop字段功能:添加数据库字段、数值维护和后台展示
 - 需求:为FarmLand模型增加has_crop字段,用于判断土地是否有作物(有就算,枯萎也算)
 - 实现:创建数据库字段、模型定义、自动维护逻辑、后台管理展示和筛选功能
@@ -90,25 +95,7 @@ shop_items 的 $max_buy 确认被替代后移除,使用mcp执行sql
   - 完成时间: 2025-06-04 19:47
   - 描述: 修复宝箱配置列表页面消耗组/奖励组/条件组名称无法正常显示的问题,增加详细内容展示列和可点击跳转功能,修复关联关系命名问题,优化用户体验,提供完整的配置信息展示和关联访问
 
-- [x] 2025-06-04 15:30 - 为奖励项管理添加复制功能
-  - 任务记录: `AiWork/2025年06月/04日1530-为奖励项管理添加复制功能.md`
-  - 完成时间: 2025-06-04 15:30
-  - 描述: 为奖励项管理页面添加复制功能的行操作,创建DuplicateRewardItemAction类,实现完整的奖励项复制功能,包含事务处理、确认对话框和成功提示,通过MCP验证功能正常工作
-
-- [x] 2025-06-04 14:00 - 适配奖励组后台管理页面新功能
-  - 任务记录: `AiWork/2025年06月/04日1400-适配奖励组后台管理页面新功能.md`
-  - 完成时间: 2025-06-04 14:00
-  - 描述: 适配奖励组后台管理页面,支持独立概率模式等新功能,添加奖励模式选择、最小/最大数量、概率字段,完善用户界面和帮助文本,实现完整的新功能支持
-
-- [x] 2025-06-04 13:46 - 奖励组文档维护工作
-  - 任务记录: `AiWork/2025年06月/04日1346-奖励组文档维护工作.md`
-  - 完成时间: 2025-06-04 13:46
-  - 描述: 维护奖励组系统文档,确保文档与代码实现完全一致,添加独立概率模式、保底机制、宠物奖励等新功能说明,更新数据库表结构、使用方法和最佳实践,文档质量达到5星标准
-
-- [x] 2025-06-04 13:24 - 修复宝箱配置仓库类继承错误
-  - 任务记录: `AiWork/2025年06月/041324-修复宝箱配置仓库类继承错误.md`
-  - 完成时间: 2025-06-04 13:24
-  - 描述: 修复ItemChestConfigRepository继承UCore\DcatAdmin\Repository不存在的问题,改为继承Dcat\Admin\Repositories\EloquentRepository,统一Repository类的继承方式,修复后台宝箱配置管理页面无法访问的问题
+
 
 
 

+ 23 - 8
UCore/Queue/QueueJob.php

@@ -18,8 +18,23 @@ use Illuminate\Support\Facades\Log;
  */
 abstract class QueueJob implements ShouldQueue, QueueJobInterface
 {
+
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
+
+    public $args = [];
+
+    /**
+     * 创建新的任务实例
+     *
+     * @return void
+     */
+    public function __construct($args = [])
+    {
+        //
+        $this->args = $args;
+    }
+
     /**
      * 任务可尝试的次数。
      *
@@ -31,25 +46,25 @@ abstract class QueueJob implements ShouldQueue, QueueJobInterface
     {
         $start = microtime(true);
         Helper::add_log('handle', $this->job->getQueue(), static::class, $this->payload());
-        $res= null;
+        $res  = null;
         $diff = 0;
         try {
-            $res = $this->run();
+            $res  = $this->run();
             $diff = microtime(true) - $start;
-            Helper::add_log('runend-' . $res, $this->job->getQueue(), static::class, $this->payload(),'',$diff);
+            Helper::add_log('runend-' . $res, $this->job->getQueue(), static::class, $this->payload(), '', $diff);
             if ($res) {
                 $this->delete();
             }
         } catch (\Throwable $exception) {
-            Logger::exception('job',$exception);
+            Logger::exception('job', $exception);
             $desc = '';
             $desc .= $exception->getMessage();
             $desc .= $exception->getTraceAsString();
             Helper::add_log('Throwable-' . get_class($exception), $this->job->getQueue(), static::class, $this->payload(), $desc);
             // 10 * n 秒后重试
-            $this->release( $this->attempts()* $this->attempts()*$this->attempts() * 2);
+            $this->release($this->attempts() * $this->attempts() * $this->attempts() * 2);
         }
-        Helper::add_log('runend-' . $res, $this->job->getQueue(), static::class, $this->payload(),'',$diff);
+        Helper::add_log('runend-' . $res, $this->job->getQueue(), static::class, $this->payload(), '', $diff);
 
 
     }
@@ -63,9 +78,9 @@ abstract class QueueJob implements ShouldQueue, QueueJobInterface
 
     }
 
-    public function logInfo($name,$data = [])
+    public function logInfo($name, $data = [])
     {
-        Log::info(static::class.'-'.$name . ' ' . json_encode($data));
+        Log::info(static::class . '-' . $name . ' ' . json_encode($data));
     }
 
 }

+ 61 - 78
app/Module/Pet/Jobs/ProcessActiveSkillsJob.php

@@ -10,15 +10,16 @@ use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Support\Facades\Log;
+use UCore\Queue\QueueJob;
 
 /**
  * 处理宠物激活技能的定时任务
  *
  * 每分钟执行一次,检查所有激活中的宠物技能并执行相应操作
  */
-class ProcessActiveSkillsJob implements ShouldQueue
+class ProcessActiveSkillsJob extends QueueJob
 {
-    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
 
     /**
      * 任务超时时间(秒)
@@ -34,33 +35,65 @@ class ProcessActiveSkillsJob implements ShouldQueue
      */
     public $tries = 3;
 
+
+
     /**
-     * 创建新的任务实例
+     * 处理技能效果
      *
+     * @param PetActiveSkill $activeSkill 激活的技能
      * @return void
      */
-    public function __construct()
+    protected function processSkillEffect(PetActiveSkill $activeSkill): void
     {
-        //
+        $autoSkillLogic = new PetAutoSkillLogic();
+
+        switch ($activeSkill->skill_name) {
+            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_HARVESTING->value:
+                $autoSkillLogic->processAutoHarvest($activeSkill);
+                break;
+
+            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PLANTING->value:
+                $autoSkillLogic->processAutoPlant($activeSkill);
+                break;
+
+            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WEEDING->value:
+                $autoSkillLogic->processAutoWeeding($activeSkill);
+                break;
+
+            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WATERING->value:
+                $autoSkillLogic->processAutoWatering($activeSkill);
+                break;
+
+            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PEST_CONTROL->value:
+                $autoSkillLogic->processAutoPestControl($activeSkill);
+                break;
+
+            default:
+                Log::warning('未知的技能类型', [
+                    'active_skill_id' => $activeSkill->id,
+                    'skill_name'      => $activeSkill->skill_name
+                ]);
+                break;
+        }
+
+        // 更新最后检查时间
+        $activeSkill->updateLastCheckTime();
     }
 
-    /**
-     * 执行任务
-     *
-     * @return void
-     */
-    public function handle($check = false)
+
+    public function run(): bool
     {
+
         Log::info('开始处理宠物激活技能定时任务');
 
         try {
             // 获取所有生效中的技能
             $activeSkills = PetActiveSkill::active()->get();
 
-            Log::info('找到激活技能数量', ['count' => $activeSkills->count()]);
+            Log::info('找到激活技能数量', [ 'count' => $activeSkills->count() ]);
 
             $processedCount = 0;
-            $expiredCount = 0;
+            $expiredCount   = 0;
 
             /**
              * @var  PetActiveSkill $activeSkill
@@ -73,8 +106,8 @@ class ProcessActiveSkillsJob implements ShouldQueue
                         $expiredCount++;
                         Log::info('技能已过期', [
                             'active_skill_id' => $activeSkill->id,
-                            'pet_id' => $activeSkill->pet_id,
-                            'skill_name' => $activeSkill->skill_name
+                            'pet_id'          => $activeSkill->pet_id,
+                            'skill_name'      => $activeSkill->skill_name
                         ]);
                         continue;
                     }
@@ -84,16 +117,16 @@ class ProcessActiveSkillsJob implements ShouldQueue
                         Log::debug('shouldCheck false', [
 
                             'active_skill_id' => $activeSkill->id,
-                            'pet_id' => $activeSkill->pet_id,
-                            'skill_name' => $activeSkill->skill_name
+                            'pet_id'          => $activeSkill->pet_id,
+                            'skill_name'      => $activeSkill->skill_name
                         ]);
                         continue;
                     }
                     Log::debug('shouldCheck false', [
 
                         'active_skill_id' => $activeSkill->id,
-                        'pet_id' => $activeSkill->pet_id,
-                        'skill_name' => $activeSkill->skill_name
+                        'pet_id'          => $activeSkill->pet_id,
+                        'skill_name'      => $activeSkill->skill_name
                     ]);
 
                     // 处理技能效果
@@ -103,18 +136,18 @@ class ProcessActiveSkillsJob implements ShouldQueue
                 } catch (\Exception $e) {
                     Log::error('处理单个激活技能失败', [
                         'active_skill_id' => $activeSkill->id,
-                        'pet_id' => $activeSkill->pet_id,
-                        'skill_name' => $activeSkill->skill_name,
-                        'error' => $e->getMessage(),
-                        'trace' => $e->getTraceAsString()
+                        'pet_id'          => $activeSkill->pet_id,
+                        'skill_name'      => $activeSkill->skill_name,
+                        'error'           => $e->getMessage(),
+                        'trace'           => $e->getTraceAsString()
                     ]);
                 }
             }
 
             Log::info('宠物激活技能定时任务完成', [
-                'total_skills' => $activeSkills->count(),
+                'total_skills'    => $activeSkills->count(),
                 'processed_count' => $processedCount,
-                'expired_count' => $expiredCount
+                'expired_count'   => $expiredCount
             ]);
 
         } catch (\Exception $e) {
@@ -125,62 +158,12 @@ class ProcessActiveSkillsJob implements ShouldQueue
 
             throw $e;
         }
+        return true;
     }
 
-    /**
-     * 处理技能效果
-     *
-     * @param PetActiveSkill $activeSkill 激活的技能
-     * @return void
-     */
-    protected function processSkillEffect(PetActiveSkill $activeSkill): void
+    public function payload()
     {
-        $autoSkillLogic = new PetAutoSkillLogic();
-
-        switch ($activeSkill->skill_name) {
-            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_HARVESTING->value:
-                $autoSkillLogic->processAutoHarvest($activeSkill);
-                break;
-
-            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PLANTING->value:
-                $autoSkillLogic->processAutoPlant($activeSkill);
-                break;
-
-            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WEEDING->value:
-                $autoSkillLogic->processAutoWeeding($activeSkill);
-                break;
-
-            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WATERING->value:
-                $autoSkillLogic->processAutoWatering($activeSkill);
-                break;
-
-            case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PEST_CONTROL->value:
-                $autoSkillLogic->processAutoPestControl($activeSkill);
-                break;
-
-            default:
-                Log::warning('未知的技能类型', [
-                    'active_skill_id' => $activeSkill->id,
-                    'skill_name' => $activeSkill->skill_name
-                ]);
-                break;
-        }
-
-        // 更新最后检查时间
-        $activeSkill->updateLastCheckTime();
+        return [];
     }
 
-    /**
-     * 任务失败时的处理
-     *
-     * @param \Throwable $exception
-     * @return void
-     */
-    public function failed(\Throwable $exception)
-    {
-        Log::error('宠物激活技能定时任务失败', [
-            'error' => $exception->getMessage(),
-            'trace' => $exception->getTraceAsString()
-        ]);
-    }
 }