Explorar el Código

feat(admin): 添加复制功能扩展- 为 Admin 页面的字段添加复制按钮功能
- 包含 Form 和 Show 页面的复制功能实现
- 提供便捷的方法为多个字段添加复制功能
- 支持自定义格式化函数
- 兼容新旧浏览器的复制 API

notfff hace 7 meses
padre
commit
90333018d0
Se han modificado 59 ficheros con 2900 adiciones y 1857 borrados
  1. 116 0
      app/Admin/Extensions/CopyableHelper.php
  2. 97 0
      app/Admin/Extensions/Form/Copyable.php
  3. 104 0
      app/Admin/Extensions/README.md
  4. 89 0
      app/Admin/Extensions/Show/Copyable.php
  5. 0 122
      app/Console/Commands/CheckAdminMenus.php
  6. 0 125
      app/Console/Commands/CheckAllAdminMenus.php
  7. 0 210
      app/Console/Commands/CheckControllerRoutes.php
  8. 0 117
      app/Console/Commands/CheckDuplicateMenus.php
  9. 0 197
      app/Console/Commands/CheckInvalidMenus.php
  10. 0 144
      app/Console/Commands/CheckMenuRoutes.php
  11. 67 0
      app/Console/Commands/DebugSeedMapping.php
  12. 0 403
      app/Console/Commands/FixAdminMenus.php
  13. 0 143
      app/Console/Commands/FixMenuUris.php
  14. 0 191
      app/Console/Commands/FixTaskMenus.php
  15. 169 0
      app/Console/Commands/TestSizeRotatingLog.php
  16. 148 0
      app/Logging/README.md
  17. 175 0
      app/Logging/SizeRotatingDailyHandler.php
  18. 96 0
      app/Logging/SizeRotatingDailyLogger.php
  19. 36 13
      app/Module/AppGame/Handler/Item/CraftHandler.php
  20. 37 13
      app/Module/AppGame/Handler/Item/DismantleHandler.php
  21. 41 16
      app/Module/AppGame/Handler/Item/OpenBoxHandler.php
  22. 68 24
      app/Module/AppGame/Handler/Land/HarvestHandler.php
  23. 50 34
      app/Module/AppGame/Handler/Land/RemoveCropHandler.php
  24. 62 35
      app/Module/AppGame/Handler/Land/SowHandler.php
  25. 35 35
      app/Module/AppGame/Handler/Shop/BuyHandler.php
  26. 14 2
      app/Module/AppGame/Handler/User/DataHandler.php
  27. 1 0
      app/Module/AppGame/HttpControllers/ProtobufController.php
  28. 29 3
      app/Module/AppGame/Proto/CropInfoDto.php
  29. 1 1
      app/Module/AppGame/Proto/LandInfoDto.php
  30. 1 1
      app/Module/AppGame/Tools/Protobuf.php
  31. 7 5
      app/Module/Farm/Logics/CropLogic.php
  32. 6 6
      app/Module/Farm/Models/FarmGodBuff.php
  33. 4 4
      app/Module/Farm/Services/CropService.php
  34. 54 0
      app/Module/Farm/Validations/CropHarvestValidation.php
  35. 72 0
      app/Module/Farm/Validations/CropPlantValidation.php
  36. 66 0
      app/Module/Farm/Validations/CropRemoveValidation.php
  37. 64 0
      app/Module/Farm/Validators/HarvestableStatusValidator.php
  38. 64 0
      app/Module/Farm/Validators/LandStatusValidator.php
  39. 71 0
      app/Module/Farm/Validators/RemovableStatusValidator.php
  40. 61 0
      app/Module/Farm/Validators/RemoveToolValidator.php
  41. 58 0
      app/Module/Farm/Validators/SeedItemValidator.php
  42. 74 0
      app/Module/Farm/Validators/SeedOwnershipValidator.php
  43. 67 0
      app/Module/GameItems/Validations/ChestOpenValidation.php
  44. 56 0
      app/Module/GameItems/Validations/ItemCraftValidation.php
  45. 61 0
      app/Module/GameItems/Validations/ItemDismantleValidation.php
  46. 62 0
      app/Module/GameItems/Validators/ChestItemValidator.php
  47. 61 0
      app/Module/GameItems/Validators/ChestOpenCostValidator.php
  48. 58 0
      app/Module/GameItems/Validators/ChestOwnershipValidator.php
  49. 60 0
      app/Module/GameItems/Validators/CraftMaterialsValidator.php
  50. 52 0
      app/Module/GameItems/Validators/CraftRecipeValidator.php
  51. 58 0
      app/Module/GameItems/Validators/DismantleItemValidator.php
  52. 75 0
      app/Module/GameItems/Validators/ItemOwnershipValidator.php
  53. 7 11
      app/Module/Shop/AdminControllers/ShopItemController.php
  54. 60 0
      app/Module/Shop/Validations/ShopBuyValidation.php
  55. 56 0
      app/Module/Shop/Validators/ShopBuyLimitValidator.php
  56. 61 0
      app/Module/Shop/Validators/ShopFundValidator.php
  57. 52 0
      app/Module/Shop/Validators/ShopItemValidator.php
  58. 6 2
      app/Providers/AppServiceProvider.php
  59. 11 0
      config/logging.php

+ 116 - 0
app/Admin/Extensions/CopyableHelper.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace App\Admin\Extensions;
+
+use App\Admin\Extensions\Form\Copyable as FormCopyable;
+use App\Admin\Extensions\Show\Copyable as ShowCopyable;
+use Dcat\Admin\Form;
+use Dcat\Admin\Show;
+
+/**
+ * 复制功能助手类
+ * 
+ * 提供便捷的方法为Show和Form添加复制功能
+ */
+class CopyableHelper
+{
+    /**
+     * 为Show字段添加复制功能
+     *
+     * @param Show $show Show实例
+     * @param string $field 字段名
+     * @param string|null $label 字段标签
+     * @return ShowCopyable
+     */
+    public static function addToShow(Show $show, string $field, ?string $label = null): ShowCopyable
+    {
+        return $show->field($field, $label)->as(function ($value) {
+            return (new ShowCopyable())->setValue($value)->render();
+        });
+    }
+
+    /**
+     * 为Form字段添加复制功能(只读显示)
+     *
+     * @param Form $form Form实例
+     * @param string $field 字段名
+     * @param string|null $label 字段标签
+     * @return FormCopyable
+     */
+    public static function addToForm(Form $form, string $field, ?string $label = null): FormCopyable
+    {
+        $copyable = new FormCopyable($field, [$label]);
+        $copyable->setForm($form);
+        return $copyable;
+    }
+
+    /**
+     * 批量为Show添加复制功能
+     *
+     * @param Show $show Show实例
+     * @param array $fields 字段配置数组,格式:['field_name' => 'Field Label']
+     * @return void
+     */
+    public static function addMultipleToShow(Show $show, array $fields): void
+    {
+        foreach ($fields as $field => $label) {
+            self::addToShow($show, $field, $label);
+        }
+    }
+
+    /**
+     * 批量为Form添加复制功能
+     *
+     * @param Form $form Form实例
+     * @param array $fields 字段配置数组,格式:['field_name' => 'Field Label']
+     * @return void
+     */
+    public static function addMultipleToForm(Form $form, array $fields): void
+    {
+        foreach ($fields as $field => $label) {
+            self::addToForm($form, $field, $label);
+        }
+    }
+
+    /**
+     * 为Show字段添加带格式化的复制功能
+     *
+     * @param Show $show Show实例
+     * @param string $field 字段名
+     * @param string|null $label 字段标签
+     * @param callable|null $formatter 格式化函数
+     * @return ShowCopyable
+     */
+    public static function addFormattedToShow(Show $show, string $field, ?string $label = null, ?callable $formatter = null): ShowCopyable
+    {
+        return $show->field($field, $label)->as(function ($value) use ($formatter) {
+            if ($formatter) {
+                $value = $formatter($value);
+            }
+            return (new ShowCopyable())->setValue($value)->render();
+        });
+    }
+
+    /**
+     * 为Form字段添加带格式化的复制功能
+     *
+     * @param Form $form Form实例
+     * @param string $field 字段名
+     * @param string|null $label 字段标签
+     * @param callable|null $formatter 格式化函数
+     * @return FormCopyable
+     */
+    public static function addFormattedToForm(Form $form, string $field, ?string $label = null, ?callable $formatter = null): FormCopyable
+    {
+        $copyable = new FormCopyable($field, [$label]);
+        $copyable->setForm($form);
+        
+        if ($formatter) {
+            $copyable->customFormat(function ($value) use ($formatter) {
+                return $formatter($value);
+            });
+        }
+        
+        return $copyable;
+    }
+}

+ 97 - 0
app/Admin/Extensions/Form/Copyable.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace App\Admin\Extensions\Form;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Form\Field;
+use Dcat\Admin\Support\Helper;
+
+/**
+ * Form页面复制功能
+ * 
+ * 为Form页面的字段添加复制按钮功能
+ */
+class Copyable extends Field
+{
+    /**
+     * 添加复制功能的JavaScript脚本
+     */
+    protected function addScript()
+    {
+        $script = <<<'JS'
+$('.form-field-copyable').off('click').on('click', function (e) {
+    e.preventDefault();
+    
+    var content = $(this).data('content');
+    
+    // 创建临时输入框
+    var $temp = $('<input>');
+    $("body").append($temp);
+    $temp.val(content).select();
+    
+    // 执行复制
+    try {
+        var successful = document.execCommand('copy');
+        if (successful) {
+            // 显示成功提示
+            Dcat.success('复制成功');
+        } else {
+            Dcat.error('复制失败');
+        }
+    } catch (err) {
+        // 使用现代API作为备选方案
+        if (navigator.clipboard) {
+            navigator.clipboard.writeText(content).then(function() {
+                Dcat.success('复制成功');
+            }).catch(function() {
+                Dcat.error('复制失败');
+            });
+        } else {
+            Dcat.error('浏览器不支持复制功能');
+        }
+    }
+    
+    $temp.remove();
+});
+JS;
+        Admin::script($script);
+    }
+
+    /**
+     * 渲染复制按钮
+     *
+     * @return string
+     */
+    public function render()
+    {
+        $this->addScript();
+
+        $value = Helper::htmlEntityEncode($this->value());
+        
+        if ($this->value() === '' || $this->value() === null) {
+            return '<div class="form-group"></div>';
+        }
+
+        $label = $this->label;
+        $id = $this->id();
+
+        $html = <<<HTML
+<div class="form-group">
+    <label for="{$id}" class="col-sm-2 control-label">{$label}</label>
+    <div class="col-sm-8">
+        <div class="form-field-copyable-wrapper">
+            <p class="form-control-static">{$value}</p>
+            <a href="javascript:void(0);" 
+               class="form-field-copyable btn btn-sm btn-outline-secondary" 
+               data-content="{$value}" 
+               title="点击复制">
+                <i class="fa fa-copy"></i> 复制
+            </a>
+        </div>
+    </div>
+</div>
+HTML;
+
+        return $html;
+    }
+}

+ 104 - 0
app/Admin/Extensions/README.md

@@ -0,0 +1,104 @@
+# Admin 扩展功能
+
+## 复制功能 (Copyable)
+
+为 Dcat Admin 的 Show 和 Form 页面添加复制按钮功能,方便用户复制字段内容。
+
+### 功能特点
+
+1. **Show 页面复制**:在详情页面为字段添加复制按钮
+2. **Form 页面复制**:在表单页面为只读字段添加复制按钮
+3. **批量添加**:支持批量为多个字段添加复制功能
+4. **格式化支持**:支持自定义格式化函数
+5. **现代浏览器兼容**:支持新旧浏览器的复制API
+
+### 使用方法
+
+#### 1. Show 页面使用
+
+```php
+use App\Admin\Extensions\CopyableHelper;
+
+// 在 Controller 的 detail 方法中
+protected function detail($id)
+{
+    $show = Show::make($id, new YourModel(), function (Show $show) {
+        // 单个字段添加复制功能
+        CopyableHelper::addToShow($show, 'id', 'ID');
+        CopyableHelper::addToShow($show, 'name', '名称');
+        
+        // 批量添加复制功能
+        CopyableHelper::addMultipleToShow($show, [
+            'email' => '邮箱',
+            'phone' => '电话',
+            'address' => '地址'
+        ]);
+        
+        // 带格式化的复制功能
+        CopyableHelper::addFormattedToShow($show, 'created_at', '创建时间', function($value) {
+            return $value ? $value->format('Y-m-d H:i:s') : '';
+        });
+    });
+
+    return $show;
+}
+```
+
+#### 2. Form 页面使用
+
+```php
+use App\Admin\Extensions\CopyableHelper;
+
+// 在 Controller 的 form 方法中
+protected function form()
+{
+    return Form::make(new YourModel(), function (Form $form) {
+        // 普通表单字段
+        $form->text('name', '名称');
+        
+        // 只读字段添加复制功能
+        CopyableHelper::addToForm($form, 'id', 'ID');
+        CopyableHelper::addToForm($form, 'created_at', '创建时间');
+        
+        // 批量添加复制功能
+        CopyableHelper::addMultipleToForm($form, [
+            'uuid' => 'UUID',
+            'token' => 'Token'
+        ]);
+    });
+}
+```
+
+#### 3. 直接使用扩展类
+
+```php
+use App\Admin\Extensions\Show\Copyable as ShowCopyable;
+use App\Admin\Extensions\Form\Copyable as FormCopyable;
+
+// Show 页面
+$show->field('field_name', '字段标签')->as(function ($value) {
+    return (new ShowCopyable())->setValue($value)->render();
+});
+
+// Form 页面
+$copyableField = new FormCopyable('field_name', ['字段标签']);
+$copyableField->setForm($form);
+```
+
+### 样式说明
+
+- **Show 页面**:复制按钮显示在字段值的右侧,点击后显示成功提示
+- **Form 页面**:复制按钮显示为独立的按钮,样式与表单保持一致
+- **Grid 页面**:继续使用原有的 `copyable()` 方法
+
+### 浏览器兼容性
+
+- 支持现代浏览器的 `navigator.clipboard` API
+- 兼容旧版浏览器的 `document.execCommand` 方法
+- 自动降级处理,确保在各种环境下都能正常工作
+
+### 注意事项
+
+1. 复制功能需要在 HTTPS 环境下才能完全发挥作用
+2. 某些浏览器可能需要用户交互才能触发复制功能
+3. 建议在生产环境中测试复制功能的兼容性

+ 89 - 0
app/Admin/Extensions/Show/Copyable.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace App\Admin\Extensions\Show;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Show\AbstractField;
+use Dcat\Admin\Support\Helper;
+
+/**
+ * Show页面复制功能
+ * 
+ * 为Show页面的字段添加复制按钮功能
+ */
+class Copyable extends AbstractField
+{
+    /**
+     * 添加复制功能的JavaScript脚本
+     */
+    protected function addScript()
+    {
+        $script = <<<'JS'
+$('.show-field-copyable').off('click').on('click', function (e) {
+    e.preventDefault();
+    
+    var content = $(this).data('content');
+    
+    // 创建临时输入框
+    var $temp = $('<input>');
+    $("body").append($temp);
+    $temp.val(content).select();
+    
+    // 执行复制
+    try {
+        var successful = document.execCommand('copy');
+        if (successful) {
+            // 显示成功提示
+            Dcat.success('复制成功');
+        } else {
+            Dcat.error('复制失败');
+        }
+    } catch (err) {
+        // 使用现代API作为备选方案
+        if (navigator.clipboard) {
+            navigator.clipboard.writeText(content).then(function() {
+                Dcat.success('复制成功');
+            }).catch(function() {
+                Dcat.error('复制失败');
+            });
+        } else {
+            Dcat.error('浏览器不支持复制功能');
+        }
+    }
+    
+    $temp.remove();
+});
+JS;
+        Admin::script($script);
+    }
+
+    /**
+     * 渲染复制按钮
+     *
+     * @return string
+     */
+    public function render()
+    {
+        $this->addScript();
+
+        $value = Helper::htmlEntityEncode($this->value);
+        
+        if ($this->value === '' || $this->value === null) {
+            return $this->value;
+        }
+
+        $html = <<<HTML
+<div class="show-field-copyable-wrapper">
+    <span class="show-field-value">{$value}</span>
+    <a href="javascript:void(0);" 
+       class="show-field-copyable text-muted ml-2" 
+       data-content="{$value}" 
+       title="点击复制">
+        <i class="fa fa-copy"></i>
+    </a>
+</div>
+HTML;
+
+        return $html;
+    }
+}

+ 0 - 122
app/Console/Commands/CheckAdminMenus.php

@@ -1,122 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-
-class CheckAdminMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-menus';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查AdminMenu表中的菜单项';
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        // 获取游戏系统设置和游戏运营管理菜单
-        $systemMenu = AdminMenu::where('title', 'like', '%游戏系统设置%')->first();
-        $operationMenu = AdminMenu::where('title', 'like', '%游戏运营管理%')->first();
-        
-        if (!$systemMenu || !$operationMenu) {
-            $this->error('未找到游戏系统设置或游戏运营管理菜单');
-            return 1;
-        }
-        
-        $this->info("游戏系统设置菜单ID: {$systemMenu->id}, 标题: {$systemMenu->title}");
-        $this->info("游戏运营管理菜单ID: {$operationMenu->id}, 标题: {$operationMenu->title}");
-        
-        // 获取游戏系统设置下的子菜单
-        $systemSubMenus = AdminMenu::where('parent_id', $systemMenu->id)->get();
-        $this->info("\n游戏系统设置下的子菜单:");
-        foreach ($systemSubMenus as $menu) {
-            $this->info("ID: {$menu->id}, 标题: {$menu->title}, URI: {$menu->uri}");
-            
-            // 获取二级子菜单
-            $subSubMenus = AdminMenu::where('parent_id', $menu->id)->get();
-            foreach ($subSubMenus as $subMenu) {
-                $this->info("  - ID: {$subMenu->id}, 标题: {$subMenu->title}, URI: {$subMenu->uri}");
-            }
-        }
-        
-        // 获取游戏运营管理下的子菜单
-        $operationSubMenus = AdminMenu::where('parent_id', $operationMenu->id)->get();
-        $this->info("\n游戏运营管理下的子菜单:");
-        foreach ($operationSubMenus as $menu) {
-            $this->info("ID: {$menu->id}, 标题: {$menu->title}, URI: {$menu->uri}");
-            
-            // 获取二级子菜单
-            $subSubMenus = AdminMenu::where('parent_id', $menu->id)->get();
-            foreach ($subSubMenus as $subMenu) {
-                $this->info("  - ID: {$subMenu->id}, 标题: {$subMenu->title}, URI: {$subMenu->uri}");
-            }
-        }
-        
-        // 检查各模块的后台控制器
-        $this->info("\n检查各模块的后台控制器:");
-        $modules = [
-            'Activity', 'Article', 'Farm', 'File', 'Fund', 'Game', 'GameItems', 
-            'OAuth', 'Pet', 'Sms', 'System', 'Task', 'Team', 'User'
-        ];
-        
-        foreach ($modules as $module) {
-            $this->info("\n{$module}模块:");
-            $controllerDir = app_path("Module/{$module}/AdminControllers");
-            
-            if (!is_dir($controllerDir)) {
-                $this->warn("  - 未找到AdminControllers目录");
-                continue;
-            }
-            
-            $controllers = array_filter(scandir($controllerDir), function($file) {
-                return !in_array($file, ['.', '..', 'Helper']) && pathinfo($file, PATHINFO_EXTENSION) === 'php';
-            });
-            
-            foreach ($controllers as $controller) {
-                $controllerName = pathinfo($controller, PATHINFO_FILENAME);
-                $this->info("  - {$controllerName}");
-                
-                // 尝试猜测URI
-                $uri = $this->guessUri($controllerName);
-                
-                // 检查菜单项是否存在
-                $menuExists = AdminMenu::where('uri', $uri)->exists();
-                $this->info("    URI: {$uri}, 菜单项" . ($menuExists ? '存在' : '不存在'));
-            }
-        }
-        
-        return 0;
-    }
-    
-    /**
-     * 根据控制器名称猜测URI
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    private function guessUri(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-        
-        // 转换为kebab-case
-        $uri = preg_replace('/([a-z])([A-Z])/', '$1-$2', $name);
-        $uri = strtolower($uri);
-        
-        return $uri;
-    }
-}

+ 0 - 125
app/Console/Commands/CheckAllAdminMenus.php

@@ -1,125 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Str;
-
-class CheckAllAdminMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-all-menus';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查所有模块的后台控制器是否都有对应的菜单项';
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始检查所有模块的后台控制器...");
-        
-        $modules = [
-            'Activity', 'Article', 'Farm', 'File', 'Fund', 'Game', 'GameItems', 
-            'OAuth', 'Pet', 'Sms', 'System', 'Task', 'Team', 'User'
-        ];
-        
-        $missingMenus = [];
-        $totalControllers = 0;
-        $totalMenus = 0;
-        
-        foreach ($modules as $module) {
-            $this->info("\n检查{$module}模块的后台控制器...");
-            
-            $controllerDir = app_path("Module/{$module}/AdminControllers");
-            
-            if (!is_dir($controllerDir)) {
-                $this->warn("  - 未找到AdminControllers目录");
-                continue;
-            }
-            
-            $controllers = array_filter(scandir($controllerDir), function($file) {
-                return !in_array($file, ['.', '..']) && 
-                       pathinfo($file, PATHINFO_EXTENSION) === 'php' &&
-                       !Str::contains($file, 'Helper');
-            });
-            
-            $moduleControllers = 0;
-            $moduleMenus = 0;
-            
-            foreach ($controllers as $controller) {
-                $controllerName = pathinfo($controller, PATHINFO_FILENAME);
-                $uri = $this->getControllerUri($controllerName);
-                
-                $totalControllers++;
-                $moduleControllers++;
-                
-                // 检查菜单项是否存在
-                $menuExists = AdminMenu::where('uri', $uri)->exists();
-                
-                if ($menuExists) {
-                    $totalMenus++;
-                    $moduleMenus++;
-                    $this->info("  - {$controllerName} => {$uri} [✓]");
-                } else {
-                    $missingMenus[] = [
-                        'module' => $module,
-                        'controller' => $controllerName,
-                        'uri' => $uri
-                    ];
-                    $this->error("  - {$controllerName} => {$uri} [✗]");
-                }
-            }
-            
-            $this->info("  {$module}模块: {$moduleMenus}/{$moduleControllers} 个控制器有对应的菜单项");
-        }
-        
-        $this->info("\n总计: {$totalMenus}/{$totalControllers} 个控制器有对应的菜单项");
-        
-        if (count($missingMenus) > 0) {
-            $this->info("\n缺失的菜单项:");
-            foreach ($missingMenus as $menu) {
-                $this->warn("  - {$menu['module']}模块: {$menu['controller']} => {$menu['uri']}");
-            }
-            
-            if ($this->confirm('是否要修复这些缺失的菜单项?')) {
-                foreach ($modules as $module) {
-                    $this->call('admin:fix-menus', ['module' => $module]);
-                }
-            }
-        } else {
-            $this->info("\n所有控制器都有对应的菜单项!");
-        }
-        
-        return 0;
-    }
-    
-    /**
-     * 获取控制器的URI
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    protected function getControllerUri(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-        
-        // 转换为kebab-case
-        $uri = Str::kebab($name);
-        
-        return $uri;
-    }
-}

+ 0 - 210
app/Console/Commands/CheckControllerRoutes.php

@@ -1,210 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Route;
-use Illuminate\Support\Str;
-use ReflectionClass;
-
-class CheckControllerRoutes extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-controller-routes {--fix : 是否修复问题} {--delete : 是否删除无效的菜单项}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查控制器的路由注解与菜单项的URI是否匹配';
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始检查控制器的路由注解与菜单项的URI是否匹配...");
-        
-        // 获取所有模块的控制器
-        $modules = [
-            'Activity', 'Article', 'Farm', 'File', 'Fund', 'Game', 'GameItems', 
-            'OAuth', 'Pet', 'Sms', 'System', 'Task', 'Team', 'User'
-        ];
-        
-        $controllers = [];
-        
-        foreach ($modules as $module) {
-            $controllerDir = app_path("Module/{$module}/AdminControllers");
-            
-            if (!is_dir($controllerDir)) {
-                continue;
-            }
-            
-            $controllerFiles = array_filter(scandir($controllerDir), function($file) {
-                return !in_array($file, ['.', '..']) && 
-                       pathinfo($file, PATHINFO_EXTENSION) === 'php' &&
-                       !Str::contains($file, 'Helper');
-            });
-            
-            foreach ($controllerFiles as $file) {
-                $controllerName = pathinfo($file, PATHINFO_FILENAME);
-                $controllerClass = "App\\Module\\{$module}\\AdminControllers\\{$controllerName}";
-                
-                if (class_exists($controllerClass)) {
-                    $controllers[$controllerClass] = [
-                        'module' => $module,
-                        'name' => $controllerName,
-                        'file' => $file,
-                        'class' => $controllerClass
-                    ];
-                }
-            }
-        }
-        
-        $this->info("找到 " . count($controllers) . " 个控制器");
-        
-        $routeAnnotations = [];
-        $mismatches = [];
-        
-        foreach ($controllers as $controllerClass => $controller) {
-            try {
-                $reflector = new ReflectionClass($controllerClass);
-                $docComment = $reflector->getDocComment();
-                
-                // 获取路由注解
-                $routeAnnotation = null;
-                
-                // 检查类上的注解
-                $classAttributes = $reflector->getAttributes();
-                
-                foreach ($classAttributes as $attribute) {
-                    $attributeName = $attribute->getName();
-                    
-                    if (Str::endsWith($attributeName, 'Resource')) {
-                        $arguments = $attribute->getArguments();
-                        
-                        if (!empty($arguments)) {
-                            $routeAnnotation = $arguments[0];
-                            break;
-                        }
-                    }
-                }
-                
-                if ($routeAnnotation) {
-                    $routeAnnotations[$controllerClass] = $routeAnnotation;
-                    
-                    // 获取控制器对应的菜单项
-                    $uri = $this->getControllerUri($controller['name']);
-                    $menus = AdminMenu::where('uri', $uri)->get();
-                    
-                    if ($menus->isEmpty()) {
-                        // 尝试查找复数形式
-                        $pluralUri = Str::plural($uri);
-                        $menus = AdminMenu::where('uri', $pluralUri)->get();
-                        
-                        if ($menus->isEmpty()) {
-                            // 尝试查找单数形式
-                            $singularUri = Str::singular($uri);
-                            $menus = AdminMenu::where('uri', $singularUri)->get();
-                        }
-                    }
-                    
-                    if ($menus->isNotEmpty()) {
-                        foreach ($menus as $menu) {
-                            if ($menu->uri !== $routeAnnotation) {
-                                $mismatches[] = [
-                                    'controller' => $controller,
-                                    'route_annotation' => $routeAnnotation,
-                                    'menu' => $menu
-                                ];
-                            }
-                        }
-                    }
-                }
-            } catch (\Exception $e) {
-                $this->error("解析控制器 {$controllerClass} 时出错: " . $e->getMessage());
-            }
-        }
-        
-        if (count($mismatches) > 0) {
-            $this->info("\n发现 " . count($mismatches) . " 个控制器的路由注解与菜单项的URI不匹配:");
-            
-            foreach ($mismatches as $index => $mismatch) {
-                $controller = $mismatch['controller'];
-                $routeAnnotation = $mismatch['route_annotation'];
-                $menu = $mismatch['menu'];
-                
-                $this->info("\n[{$index}] 控制器: {$controller['class']}");
-                $this->info("  - 路由注解: {$routeAnnotation}");
-                $this->info("  - 菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
-                
-                if ($this->option('fix')) {
-                    // 修复菜单项的URI
-                    $menu->uri = $routeAnnotation;
-                    $menu->save();
-                    $this->info("  - 已修复菜单项的URI: {$menu->uri} -> {$routeAnnotation}");
-                } elseif ($this->option('delete')) {
-                    // 删除菜单项
-                    $menu->delete();
-                    $this->info("  - 已删除菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
-                    
-                    // 创建新的菜单项
-                    $newMenu = AdminMenu::create([
-                        'parent_id' => $menu->parent_id,
-                        'order' => $menu->order,
-                        'title' => $menu->title,
-                        'icon' => $menu->icon,
-                        'uri' => $routeAnnotation,
-                        'show' => $menu->show
-                    ]);
-                    
-                    $this->info("  - 已创建新的菜单项: ID = {$newMenu->id}, 标题 = {$newMenu->title}, URI = {$newMenu->uri}");
-                }
-            }
-            
-            if (!$this->option('fix') && !$this->option('delete')) {
-                if ($this->confirm('是否要修复这些不匹配的菜单项?')) {
-                    foreach ($mismatches as $mismatch) {
-                        $controller = $mismatch['controller'];
-                        $routeAnnotation = $mismatch['route_annotation'];
-                        $menu = $mismatch['menu'];
-                        
-                        // 修复菜单项的URI
-                        $menu->uri = $routeAnnotation;
-                        $menu->save();
-                        $this->info("  - 已修复菜单项的URI: {$menu->uri} -> {$routeAnnotation}");
-                    }
-                }
-            }
-        } else {
-            $this->info("\n所有控制器的路由注解与菜单项的URI都匹配!");
-        }
-        
-        return 0;
-    }
-    
-    /**
-     * 根据控制器名称猜测URI
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    protected function getControllerUri(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-        
-        // 转换为kebab-case
-        $uri = Str::kebab($name);
-        
-        return $uri;
-    }
-}

+ 0 - 117
app/Console/Commands/CheckDuplicateMenus.php

@@ -1,117 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Str;
-
-class CheckDuplicateMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-duplicate-menus {--delete : 是否删除重复的菜单项}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查是否有重复的菜单项,特别是那些指向相同控制器但标题不同的菜单项';
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始检查是否有重复的菜单项...");
-        
-        // 获取所有菜单项
-        $menus = AdminMenu::all();
-        
-        // 按URI分组
-        $menusByUri = [];
-        
-        foreach ($menus as $menu) {
-            if (!empty($menu->uri)) {
-                $uri = ltrim($menu->uri, '/');
-                if (!isset($menusByUri[$uri])) {
-                    $menusByUri[$uri] = [];
-                }
-                $menusByUri[$uri][] = $menu;
-            }
-        }
-        
-        // 查找重复的菜单项
-        $duplicateMenus = [];
-        
-        foreach ($menusByUri as $uri => $menus) {
-            if (count($menus) > 1) {
-                $duplicateMenus[$uri] = $menus;
-            }
-        }
-        
-        if (count($duplicateMenus) > 0) {
-            $this->info("\n发现 " . count($duplicateMenus) . " 组重复的菜单项:");
-            
-            foreach ($duplicateMenus as $uri => $menus) {
-                $this->info("\nURI: {$uri}");
-                
-                $table = [];
-                foreach ($menus as $menu) {
-                    $table[] = [
-                        'id' => $menu->id,
-                        'title' => $menu->title,
-                        'parent_id' => $menu->parent_id,
-                        'order' => $menu->order,
-                        'show' => $menu->show ? '是' : '否'
-                    ];
-                }
-                
-                $this->table(['ID', '标题', '父ID', '排序', '显示'], $table);
-                
-                // 删除重复的菜单项
-                if ($this->option('delete') || $this->confirm("是否要删除这组重复的菜单项中的一些?")) {
-                    // 显示所有重复项
-                    foreach ($menus as $index => $menu) {
-                        $this->info("  [{$index}] ID = {$menu->id}, 标题 = {$menu->title}, 父ID = {$menu->parent_id}");
-                    }
-                    
-                    // 询问用户要保留哪个
-                    $keepIndex = $this->ask("请输入要保留的菜单项索引 (0-" . (count($menus) - 1) . ")");
-                    
-                    if (is_numeric($keepIndex) && $keepIndex >= 0 && $keepIndex < count($menus)) {
-                        for ($i = 0; $i < count($menus); $i++) {
-                            if ($i != $keepIndex) {
-                                // 获取子菜单
-                                $childMenus = AdminMenu::where('parent_id', $menus[$i]->id)->get();
-                                
-                                // 更新子菜单的父ID
-                                foreach ($childMenus as $childMenu) {
-                                    $childMenu->parent_id = $menus[$keepIndex]->id;
-                                    $childMenu->save();
-                                    $this->info("  - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menus[$keepIndex]->id}");
-                                }
-                                
-                                // 删除菜单项
-                                $menus[$i]->delete();
-                                $this->info("  - 删除菜单项: ID = {$menus[$i]->id}, 标题 = {$menus[$i]->title}");
-                            }
-                        }
-                    } else {
-                        $this->error("无效的索引,跳过此重复项");
-                    }
-                }
-            }
-        } else {
-            $this->info("\n未发现重复的菜单项!");
-        }
-        
-        return 0;
-    }
-}

+ 0 - 197
app/Console/Commands/CheckInvalidMenus.php

@@ -1,197 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Route;
-use Illuminate\Support\Str;
-
-class CheckInvalidMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-invalid-menus {--delete : 是否删除无效的菜单项}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查菜单项是否指向有效的控制器,并可选择删除无效的菜单项';
-
-    /**
-     * 已知的有效路由前缀
-     *
-     * @var array
-     */
-    protected $validPrefixes = [
-        'auth', 'dashboard', 'helpers', 'media', 'settings', 'logs', 'config'
-    ];
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始检查菜单项是否指向有效的控制器...");
-
-        // 获取所有菜单项
-        $menus = AdminMenu::all();
-
-        // 获取所有路由
-        $routes = Route::getRoutes();
-        $routeUris = [];
-
-        foreach ($routes as $route) {
-            if (Str::startsWith($route->uri(), 'admin/')) {
-                $routeUris[] = str_replace('admin/', '', $route->uri());
-            }
-        }
-
-        // 获取所有模块的控制器
-        $modules = [
-            'Activity', 'Article', 'Farm', 'File', 'Fund', 'Game', 'GameItems',
-            'OAuth', 'Pet', 'Sms', 'System', 'Task', 'Team', 'User'
-        ];
-
-        $controllerUris = [];
-
-        foreach ($modules as $module) {
-            $controllerDir = app_path("Module/{$module}/AdminControllers");
-
-            if (!is_dir($controllerDir)) {
-                continue;
-            }
-
-            $controllers = array_filter(scandir($controllerDir), function($file) {
-                return !in_array($file, ['.', '..']) &&
-                       pathinfo($file, PATHINFO_EXTENSION) === 'php' &&
-                       !Str::contains($file, 'Helper');
-            });
-
-            foreach ($controllers as $controller) {
-                $controllerName = pathinfo($controller, PATHINFO_FILENAME);
-                $uri = $this->getControllerUri($controllerName);
-                $controllerUris[] = $uri;
-            }
-        }
-
-        $invalidMenus = [];
-        $validMenus = [];
-
-        foreach ($menus as $menu) {
-            // 跳过空URI的菜单项(父菜单)
-            if (empty($menu->uri)) {
-                $validMenus[] = $menu;
-                continue;
-            }
-
-            // 跳过已知有效的路由前缀
-            $isValidPrefix = false;
-            foreach ($this->validPrefixes as $prefix) {
-                if (Str::startsWith($menu->uri, $prefix)) {
-                    $isValidPrefix = true;
-                    break;
-                }
-            }
-
-            if ($isValidPrefix) {
-                $validMenus[] = $menu;
-                continue;
-            }
-
-            // 检查URI是否存在于路由中
-            $uri = ltrim($menu->uri, '/');
-            $isValid = false;
-
-            // 检查路由
-            foreach ($routeUris as $routeUri) {
-                if (Str::startsWith($routeUri, $uri) || $routeUri === $uri) {
-                    $isValid = true;
-                    break;
-                }
-            }
-
-            // 检查控制器
-            if (!$isValid) {
-                foreach ($controllerUris as $controllerUri) {
-                    if ($controllerUri === $uri) {
-                        $isValid = true;
-                        break;
-                    }
-                }
-            }
-
-            if ($isValid) {
-                $validMenus[] = $menu;
-            } else {
-                $invalidMenus[] = $menu;
-            }
-        }
-
-        // 显示无效的菜单项
-        if (count($invalidMenus) > 0) {
-            $this->info("\n发现 " . count($invalidMenus) . " 个无效的菜单项:");
-
-            $table = [];
-            foreach ($invalidMenus as $menu) {
-                $table[] = [
-                    'id' => $menu->id,
-                    'title' => $menu->title,
-                    'uri' => $menu->uri,
-                    'parent_id' => $menu->parent_id
-                ];
-            }
-
-            $this->table(['ID', '标题', 'URI', '父ID'], $table);
-
-            // 删除无效的菜单项
-            if ($this->option('delete') || $this->confirm('是否要删除这些无效的菜单项?')) {
-                foreach ($invalidMenus as $menu) {
-                    // 获取子菜单
-                    $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
-
-                    // 更新子菜单的父ID
-                    foreach ($childMenus as $childMenu) {
-                        $childMenu->parent_id = $menu->parent_id;
-                        $childMenu->save();
-                        $this->info("  - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menu->parent_id}");
-                    }
-
-                    // 删除菜单项
-                    $menu->delete();
-                    $this->info("  - 删除菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
-                }
-
-                $this->info("\n已删除 " . count($invalidMenus) . " 个无效的菜单项");
-            }
-        } else {
-            $this->info("\n所有菜单项都指向有效的控制器!");
-        }
-
-        return 0;
-    }
-
-    /**
-     * 根据控制器名称猜测URI
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    protected function getControllerUri(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-
-        // 转换为kebab-case
-        $uri = Str::kebab($name);
-
-        return $uri;
-    }
-}

+ 0 - 144
app/Console/Commands/CheckMenuRoutes.php

@@ -1,144 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Route;
-use Illuminate\Support\Str;
-
-class CheckMenuRoutes extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:check-menu-routes {--delete : 是否删除无效的菜单项}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '检查菜单项是否指向有效的路由,特别是那些可能会导致404错误的菜单项';
-
-    /**
-     * 已知的有效路由前缀
-     *
-     * @var array
-     */
-    protected $validPrefixes = [
-        'auth', 'dashboard', 'helpers', 'media', 'settings', 'logs', 'config'
-    ];
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始检查菜单项是否指向有效的路由...");
-        
-        // 获取所有菜单项
-        $menus = AdminMenu::all();
-        
-        // 获取所有路由
-        $routes = Route::getRoutes();
-        $routeUris = [];
-        
-        foreach ($routes as $route) {
-            if (Str::startsWith($route->uri(), 'admin/')) {
-                $routeUris[] = str_replace('admin/', '', $route->uri());
-            }
-        }
-        
-        $validMenus = [];
-        $invalidMenus = [];
-        
-        foreach ($menus as $menu) {
-            // 跳过空URI的菜单项(父菜单)
-            if (empty($menu->uri)) {
-                continue;
-            }
-            
-            // 跳过已知有效的路由前缀
-            $isValidPrefix = false;
-            foreach ($this->validPrefixes as $prefix) {
-                if (Str::startsWith($menu->uri, $prefix)) {
-                    $isValidPrefix = true;
-                    break;
-                }
-            }
-            
-            if ($isValidPrefix) {
-                $validMenus[] = $menu;
-                continue;
-            }
-            
-            // 检查URI是否存在于路由中
-            $uri = ltrim($menu->uri, '/');
-            $isValid = false;
-            
-            foreach ($routeUris as $routeUri) {
-                if (Str::startsWith($routeUri, $uri) || $routeUri === $uri) {
-                    $isValid = true;
-                    break;
-                }
-            }
-            
-            if ($isValid) {
-                $validMenus[] = $menu;
-            } else {
-                $invalidMenus[] = $menu;
-            }
-        }
-        
-        // 显示有效的菜单项
-        $this->info("\n有效的菜单项(有对应的路由): " . count($validMenus) . " 个");
-        
-        // 显示无效的菜单项
-        if (count($invalidMenus) > 0) {
-            $this->info("\n无效的菜单项(没有对应的路由): " . count($invalidMenus) . " 个");
-            
-            $table = [];
-            foreach ($invalidMenus as $menu) {
-                $table[] = [
-                    'id' => $menu->id,
-                    'title' => $menu->title,
-                    'uri' => $menu->uri,
-                    'parent_id' => $menu->parent_id,
-                    'order' => $menu->order
-                ];
-            }
-            
-            $this->table(['ID', '标题', 'URI', '父ID', '排序'], $table);
-            
-            // 删除无效的菜单项
-            if ($this->option('delete') || $this->confirm('是否要删除这些无效的菜单项?')) {
-                foreach ($invalidMenus as $menu) {
-                    // 获取子菜单
-                    $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
-                    
-                    // 更新子菜单的父ID
-                    foreach ($childMenus as $childMenu) {
-                        $childMenu->parent_id = $menu->parent_id;
-                        $childMenu->save();
-                        $this->info("  - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menu->parent_id}");
-                    }
-                    
-                    // 删除菜单项
-                    $menu->delete();
-                    $this->info("  - 删除菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
-                }
-                
-                $this->info("\n已删除 " . count($invalidMenus) . " 个无效的菜单项");
-            }
-        } else {
-            $this->info("\n所有菜单项都指向有效的路由!");
-        }
-        
-        return 0;
-    }
-}

+ 67 - 0
app/Console/Commands/DebugSeedMapping.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Module\Farm\Models\FarmSeed;
+use Illuminate\Console\Command;
+
+/**
+ * 调试种子映射关系的命令
+ */
+class DebugSeedMapping extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'debug:seed-mapping {item_id?}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '调试种子物品ID和种子配置ID的映射关系';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $itemId = $this->argument('item_id');
+
+        if ($itemId) {
+            // 查询特定物品ID的种子配置
+            $seed = FarmSeed::where('item_id', $itemId)->first();
+            
+            if ($seed) {
+                $this->info("物品ID {$itemId} 对应的种子配置:");
+                $this->table(
+                    ['种子配置ID', '种子名称', '物品ID', '种子类型'],
+                    [[$seed->id, $seed->name, $seed->item_id, $seed->type]]
+                );
+            } else {
+                $this->error("物品ID {$itemId} 没有对应的种子配置");
+            }
+        } else {
+            // 显示所有种子配置的映射关系
+            $seeds = FarmSeed::orderBy('item_id')->get();
+            
+            $this->info("所有种子配置的映射关系:");
+            $data = [];
+            foreach ($seeds as $seed) {
+                $data[] = [$seed->id, $seed->name, $seed->item_id, $seed->type];
+            }
+            
+            $this->table(
+                ['种子配置ID', '种子名称', '物品ID', '种子类型'],
+                $data
+            );
+        }
+
+        return 0;
+    }
+}

+ 0 - 403
app/Console/Commands/FixAdminMenus.php

@@ -1,403 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Str;
-
-class FixAdminMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:fix-menus {module? : 指定要修复的模块,不指定则修复所有模块}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '修复AdminMenu表中的菜单项';
-
-    /**
-     * 游戏系统设置菜单ID
-     *
-     * @var int
-     */
-    protected $systemMenuId = 259;
-
-    /**
-     * 游戏运营管理菜单ID
-     *
-     * @var int
-     */
-    protected $operationMenuId = 260;
-
-    /**
-     * 模块配置菜单映射
-     *
-     * @var array
-     */
-    protected $moduleConfigMenus = [];
-
-    /**
-     * 模块管理菜单映射
-     *
-     * @var array
-     */
-    protected $moduleManageMenus = [];
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $module = $this->argument('module');
-
-        // 初始化模块菜单映射
-        $this->initModuleMenus();
-
-        if ($module) {
-            $this->info("开始修复{$module}模块的菜单项...");
-            $this->fixModuleMenus($module);
-        } else {
-            $this->info("开始修复所有模块的菜单项...");
-            $modules = [
-                'Team', 'Pet', 'Farm', 'GameItems', 'Task', 'Activity', 'Article', 
-                'Fund', 'Game', 'OAuth', 'Sms', 'System', 'User'
-            ];
-
-            foreach ($modules as $mod) {
-                $this->fixModuleMenus($mod);
-            }
-        }
-
-        $this->info("菜单项修复完成!");
-        return 0;
-    }
-
-    /**
-     * 初始化模块菜单映射
-     *
-     * @return void
-     */
-    protected function initModuleMenus()
-    {
-        // 获取游戏系统设置下的子菜单
-        $systemSubMenus = AdminMenu::where('parent_id', $this->systemMenuId)->get();
-        foreach ($systemSubMenus as $menu) {
-            $moduleName = $this->guessModuleName($menu->title);
-            if ($moduleName) {
-                $this->moduleConfigMenus[$moduleName] = $menu->id;
-            }
-        }
-
-        // 获取游戏运营管理下的子菜单
-        $operationSubMenus = AdminMenu::where('parent_id', $this->operationMenuId)->get();
-        foreach ($operationSubMenus as $menu) {
-            $moduleName = $this->guessModuleName($menu->title);
-            if ($moduleName) {
-                $this->moduleManageMenus[$moduleName] = $menu->id;
-            }
-        }
-    }
-
-    /**
-     * 根据菜单标题猜测模块名称
-     *
-     * @param string $title
-     * @return string|null
-     */
-    protected function guessModuleName(string $title): ?string
-    {
-        $moduleMap = [
-            '团队' => 'Team',
-            '宠物' => 'Pet',
-            '农场' => 'Farm',
-            '游戏物品' => 'GameItems',
-            '任务' => 'Task',
-            '活动' => 'Activity',
-            '文章' => 'Article',
-            '资金' => 'Fund',
-            '游戏' => 'Game',
-            '认证' => 'OAuth',
-            '短信' => 'Sms',
-            '系统' => 'System',
-            '用户' => 'User'
-        ];
-
-        foreach ($moduleMap as $keyword => $module) {
-            if (Str::contains($title, $keyword)) {
-                return $module;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * 修复指定模块的菜单项
-     *
-     * @param string $module
-     * @return void
-     */
-    protected function fixModuleMenus(string $module)
-    {
-        $this->info("修复{$module}模块的菜单项...");
-
-        // 获取模块的后台控制器
-        $controllerDir = app_path("Module/{$module}/AdminControllers");
-        if (!is_dir($controllerDir)) {
-            $this->warn("  - 未找到AdminControllers目录");
-            return;
-        }
-
-        $controllers = array_filter(scandir($controllerDir), function($file) {
-            return !in_array($file, ['.', '..']) && 
-                   !is_dir(app_path("Module/{$this->argument('module')}/AdminControllers/{$file}")) && 
-                   pathinfo($file, PATHINFO_EXTENSION) === 'php';
-        });
-
-        // 获取最大的order值
-        $maxOrder = AdminMenu::max('order');
-        $nextOrder = $maxOrder + 1;
-
-        // 创建或获取模块配置菜单
-        $configMenuId = $this->getOrCreateModuleConfigMenu($module, $nextOrder);
-        $nextOrder++;
-
-        // 创建或获取模块管理菜单
-        $manageMenuId = $this->getOrCreateModuleManageMenu($module, $nextOrder);
-        $nextOrder++;
-
-        // 分类控制器
-        $configControllers = [];
-        $manageControllers = [];
-
-        foreach ($controllers as $controller) {
-            $controllerName = pathinfo($controller, PATHINFO_FILENAME);
-            
-            // 跳过Helper目录和文件
-            if (Str::contains($controllerName, 'Helper')) {
-                continue;
-            }
-            
-            // 根据控制器名称判断是配置还是管理
-            if (Str::contains($controllerName, ['Config', 'Type', 'Category', 'Rule'])) {
-                $configControllers[] = $controllerName;
-            } else {
-                $manageControllers[] = $controllerName;
-            }
-        }
-
-        // 添加配置菜单项
-        foreach ($configControllers as $controller) {
-            $this->addMenuItemForController($module, $controller, $configMenuId, $nextOrder);
-            $nextOrder++;
-        }
-
-        // 添加管理菜单项
-        foreach ($manageControllers as $controller) {
-            $this->addMenuItemForController($module, $controller, $manageMenuId, $nextOrder);
-            $nextOrder++;
-        }
-    }
-
-    /**
-     * 获取或创建模块配置菜单
-     *
-     * @param string $module
-     * @param int $order
-     * @return int
-     */
-    protected function getOrCreateModuleConfigMenu(string $module, int $order): int
-    {
-        if (isset($this->moduleConfigMenus[$module])) {
-            return $this->moduleConfigMenus[$module];
-        }
-
-        $title = $this->getModuleTitle($module) . '配置';
-        $icon = $this->getModuleIcon($module);
-
-        $menu = AdminMenu::firstOrCreate(
-            ['title' => $title, 'uri' => ''],
-            [
-                'parent_id' => $this->systemMenuId,
-                'order' => $order,
-                'icon' => $icon,
-                'uri' => '',
-                'show' => 1
-            ]
-        );
-
-        $this->info("  - 创建{$module}模块配置菜单: {$menu->title}, ID: {$menu->id}");
-        $this->moduleConfigMenus[$module] = $menu->id;
-
-        return $menu->id;
-    }
-
-    /**
-     * 获取或创建模块管理菜单
-     *
-     * @param string $module
-     * @param int $order
-     * @return int
-     */
-    protected function getOrCreateModuleManageMenu(string $module, int $order): int
-    {
-        if (isset($this->moduleManageMenus[$module])) {
-            return $this->moduleManageMenus[$module];
-        }
-
-        $title = $this->getModuleTitle($module) . '管理';
-        $icon = $this->getModuleIcon($module);
-
-        $menu = AdminMenu::firstOrCreate(
-            ['title' => $title, 'uri' => ''],
-            [
-                'parent_id' => $this->operationMenuId,
-                'order' => $order,
-                'icon' => $icon,
-                'uri' => '',
-                'show' => 1
-            ]
-        );
-
-        $this->info("  - 创建{$module}模块管理菜单: {$menu->title}, ID: {$menu->id}");
-        $this->moduleManageMenus[$module] = $menu->id;
-
-        return $menu->id;
-    }
-
-    /**
-     * 为控制器添加菜单项
-     *
-     * @param string $module
-     * @param string $controllerName
-     * @param int $parentId
-     * @param int $order
-     * @return void
-     */
-    protected function addMenuItemForController(string $module, string $controllerName, int $parentId, int $order)
-    {
-        // 获取控制器类
-        $controllerClass = "App\\Module\\{$module}\\AdminControllers\\{$controllerName}";
-        
-        // 获取URI
-        $uri = $this->getControllerUri($controllerName);
-        
-        // 获取标题
-        $title = $this->getControllerTitle($controllerName);
-        
-        // 检查菜单项是否存在
-        $menuExists = AdminMenu::where('uri', $uri)->exists();
-        
-        if (!$menuExists) {
-            $menu = AdminMenu::create([
-                'parent_id' => $parentId,
-                'order' => $order,
-                'title' => $title,
-                'icon' => '',
-                'uri' => $uri,
-                'show' => 1
-            ]);
-            
-            $this->info("  - 创建菜单项: {$menu->title}, URI: {$menu->uri}");
-        } else {
-            $this->info("  - 菜单项已存在: {$title}, URI: {$uri}");
-        }
-    }
-
-    /**
-     * 获取控制器的URI
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    protected function getControllerUri(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-        
-        // 转换为kebab-case
-        $uri = Str::kebab($name);
-        
-        return $uri;
-    }
-
-    /**
-     * 获取控制器的标题
-     *
-     * @param string $controllerName
-     * @return string
-     */
-    protected function getControllerTitle(string $controllerName): string
-    {
-        // 移除Controller后缀
-        $name = str_replace('Controller', '', $controllerName);
-        
-        // 转换为标题
-        $title = Str::title(Str::snake($name, ' '));
-        
-        return $title;
-    }
-
-    /**
-     * 获取模块标题
-     *
-     * @param string $module
-     * @return string
-     */
-    protected function getModuleTitle(string $module): string
-    {
-        $titleMap = [
-            'Team' => '👥 团队',
-            'Pet' => '🐾 宠物',
-            'Farm' => '🌱 农场',
-            'GameItems' => '🎁 游戏物品',
-            'Task' => '📋 任务',
-            'Activity' => '🎯 活动',
-            'Article' => '📝 文章',
-            'Fund' => '💰 资金',
-            'Game' => '🎮 游戏',
-            'OAuth' => '🔑 认证',
-            'Sms' => '📱 短信',
-            'System' => '⚙️ 系统',
-            'User' => '👤 用户'
-        ];
-
-        return $titleMap[$module] ?? $module;
-    }
-
-    /**
-     * 获取模块图标
-     *
-     * @param string $module
-     * @return string
-     */
-    protected function getModuleIcon(string $module): string
-    {
-        $iconMap = [
-            'Team' => 'fa-users',
-            'Pet' => 'fa-paw',
-            'Farm' => 'fa-leaf',
-            'GameItems' => 'fa-gift',
-            'Task' => 'fa-tasks',
-            'Activity' => 'fa-bullseye',
-            'Article' => 'fa-file-text',
-            'Fund' => 'fa-money',
-            'Game' => 'fa-gamepad',
-            'OAuth' => 'fa-key',
-            'Sms' => 'fa-mobile',
-            'System' => 'fa-cogs',
-            'User' => 'fa-user'
-        ];
-
-        return $iconMap[$module] ?? 'fa-circle';
-    }
-}

+ 0 - 143
app/Console/Commands/FixMenuUris.php

@@ -1,143 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Str;
-
-class FixMenuUris extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:fix-menu-uris {--remove-duplicates : 删除重复的菜单项,保留中文的}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '修复菜单项的URI,移除开头的斜杠,并可选择删除重复的菜单项';
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始修复菜单项的URI...");
-
-        // 获取所有菜单项
-        $menus = AdminMenu::all();
-
-        $fixedCount = 0;
-
-        foreach ($menus as $menu) {
-            // 跳过空URI的菜单项(父菜单)
-            if (empty($menu->uri)) {
-                continue;
-            }
-
-            // 移除URI开头的斜杠
-            $uri = ltrim($menu->uri, '/');
-
-            if ($uri !== $menu->uri) {
-                $menu->uri = $uri;
-                $menu->save();
-                $this->info("  - 修复菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
-                $fixedCount++;
-            }
-        }
-
-        $this->info("\n已修复 {$fixedCount} 个菜单项的URI");
-
-        // 删除重复的菜单项
-        if ($this->option('remove-duplicates')) {
-            $this->info("\n开始查找和删除重复的菜单项...");
-
-            // 按URI和父ID分组
-            $menusByUriAndParent = [];
-
-            foreach (AdminMenu::all() as $menu) {
-                if (!empty($menu->uri)) {
-                    $key = $menu->uri . '_' . $menu->parent_id;
-                    if (!isset($menusByUriAndParent[$key])) {
-                        $menusByUriAndParent[$key] = [];
-                    }
-                    $menusByUriAndParent[$key][] = $menu;
-                }
-            }
-
-            $duplicateCount = 0;
-
-            foreach ($menusByUriAndParent as $key => $menus) {
-                $parts = explode('_', $key);
-                $uri = $parts[0];
-                if (count($menus) > 1) {
-                    $this->info("发现重复的菜单项: URI = {$uri}");
-
-                    // 查找中文菜单项
-                    $chineseMenu = null;
-                    $englishMenus = [];
-
-                    foreach ($menus as $menu) {
-                        // 检查标题是否包含中文字符
-                        if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $menu->title)) {
-                            $chineseMenu = $menu;
-                        } else {
-                            $englishMenus[] = $menu;
-                        }
-                    }
-
-                    if ($chineseMenu) {
-                        $this->info("  - 保留中文菜单项: ID = {$chineseMenu->id}, 标题 = {$chineseMenu->title}");
-
-                        foreach ($englishMenus as $menu) {
-                            // 获取子菜单
-                            $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
-
-                            // 更新子菜单的父ID
-                            foreach ($childMenus as $childMenu) {
-                                $childMenu->parent_id = $chineseMenu->id;
-                                $childMenu->save();
-                                $this->info("    - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$chineseMenu->id}");
-                            }
-
-                            // 删除菜单项
-                            $menu->delete();
-                            $this->info("    - 删除英文菜单项: ID = {$menu->id}, 标题 = {$menu->title}");
-                            $duplicateCount++;
-                        }
-                    } else {
-                        $this->info("  - 未找到中文菜单项,保留第一个菜单项: ID = {$menus[0]->id}, 标题 = {$menus[0]->title}");
-
-                        for ($i = 1; $i < count($menus); $i++) {
-                            // 获取子菜单
-                            $childMenus = AdminMenu::where('parent_id', $menus[$i]->id)->get();
-
-                            // 更新子菜单的父ID
-                            foreach ($childMenus as $childMenu) {
-                                $childMenu->parent_id = $menus[0]->id;
-                                $childMenu->save();
-                                $this->info("    - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menus[0]->id}");
-                            }
-
-                            // 删除菜单项
-                            $menus[$i]->delete();
-                            $this->info("    - 删除重复菜单项: ID = {$menus[$i]->id}, 标题 = {$menus[$i]->title}");
-                            $duplicateCount++;
-                        }
-                    }
-                }
-            }
-
-            $this->info("\n已删除 {$duplicateCount} 个重复的菜单项");
-        }
-
-        return 0;
-    }
-}

+ 0 - 191
app/Console/Commands/FixTaskMenus.php

@@ -1,191 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Module\System\Models\AdminMenu;
-use Illuminate\Console\Command;
-use Illuminate\Support\Str;
-
-class FixTaskMenus extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:fix-task-menus';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '修复任务相关的菜单项,删除英文的菜单项,保留中文的菜单项';
-
-    /**
-     * 任务相关的URI映射
-     *
-     * @var array
-     */
-    protected $taskUriMap = [
-        'task-achievement-condition' => 'task-achievement-conditions',
-        'task-category' => 'task-categories',
-        'task-completion-log' => 'task-completion-logs',
-        'task-condition' => 'task-conditions',
-        'task' => 'tasks',
-        'task-cost' => 'task-costs',
-        'task-cost-log' => 'task-cost-logs',
-        'task-reset-log' => 'task-reset-logs',
-        'task-reward' => 'task-rewards',
-        'task-reward-log' => 'task-reward-logs',
-        'task-user-progress' => 'task-user-progresses',
-        'task-user-task' => 'task-user-tasks'
-    ];
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $this->info("开始修复任务相关的菜单项...");
-        
-        $deletedCount = 0;
-        
-        // 获取所有任务相关的菜单项
-        $taskMenus = AdminMenu::where('uri', 'like', 'task%')
-                             ->orWhere('uri', 'like', 'tasks%')
-                             ->get();
-        
-        $this->info("找到 {$taskMenus->count()} 个任务相关的菜单项");
-        
-        // 按URI分组
-        $menusByUri = [];
-        
-        foreach ($taskMenus as $menu) {
-            $uri = $menu->uri;
-            
-            // 标准化URI
-            foreach ($this->taskUriMap as $singular => $plural) {
-                if ($uri === $singular) {
-                    $uri = $plural;
-                    break;
-                }
-            }
-            
-            if (!isset($menusByUri[$uri])) {
-                $menusByUri[$uri] = [];
-            }
-            
-            $menusByUri[$uri][] = $menu;
-        }
-        
-        // 处理每组菜单项
-        foreach ($menusByUri as $uri => $menus) {
-            if (count($menus) > 1) {
-                $this->info("发现重复的菜单项: URI = {$uri}");
-                
-                // 显示所有重复项
-                foreach ($menus as $index => $menu) {
-                    $this->info("  [{$index}] ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}, 父ID = {$menu->parent_id}");
-                }
-                
-                // 查找中文菜单项
-                $chineseMenu = null;
-                $englishMenus = [];
-                
-                foreach ($menus as $menu) {
-                    // 检查标题是否包含中文字符
-                    if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $menu->title)) {
-                        $chineseMenu = $menu;
-                    } else {
-                        $englishMenus[] = $menu;
-                    }
-                }
-                
-                if ($chineseMenu) {
-                    $this->info("  - 保留中文菜单项: ID = {$chineseMenu->id}, 标题 = {$chineseMenu->title}");
-                    
-                    // 标准化URI
-                    $standardUri = $uri;
-                    foreach ($this->taskUriMap as $singular => $plural) {
-                        if ($chineseMenu->uri === $singular) {
-                            $standardUri = $plural;
-                            $chineseMenu->uri = $standardUri;
-                            $chineseMenu->save();
-                            $this->info("    - 更新URI: {$singular} -> {$standardUri}");
-                            break;
-                        }
-                    }
-                    
-                    foreach ($englishMenus as $menu) {
-                        // 获取子菜单
-                        $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
-                        
-                        // 更新子菜单的父ID
-                        foreach ($childMenus as $childMenu) {
-                            $childMenu->parent_id = $chineseMenu->id;
-                            $childMenu->save();
-                            $this->info("    - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$chineseMenu->id}");
-                        }
-                        
-                        // 删除菜单项
-                        $menu->delete();
-                        $this->info("    - 删除英文菜单项: ID = {$menu->id}, 标题 = {$menu->title}");
-                        $deletedCount++;
-                    }
-                } else {
-                    $this->info("  - 未找到中文菜单项,保留第一个菜单项: ID = {$menus[0]->id}, 标题 = {$menus[0]->title}");
-                    
-                    // 标准化URI
-                    $standardUri = $uri;
-                    foreach ($this->taskUriMap as $singular => $plural) {
-                        if ($menus[0]->uri === $singular) {
-                            $standardUri = $plural;
-                            $menus[0]->uri = $standardUri;
-                            $menus[0]->save();
-                            $this->info("    - 更新URI: {$singular} -> {$standardUri}");
-                            break;
-                        }
-                    }
-                    
-                    for ($i = 1; $i < count($menus); $i++) {
-                        // 获取子菜单
-                        $childMenus = AdminMenu::where('parent_id', $menus[$i]->id)->get();
-                        
-                        // 更新子菜单的父ID
-                        foreach ($childMenus as $childMenu) {
-                            $childMenu->parent_id = $menus[0]->id;
-                            $childMenu->save();
-                            $this->info("    - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menus[0]->id}");
-                        }
-                        
-                        // 删除菜单项
-                        $menus[$i]->delete();
-                        $this->info("    - 删除重复菜单项: ID = {$menus[$i]->id}, 标题 = {$menus[$i]->title}");
-                        $deletedCount++;
-                    }
-                }
-            } else {
-                // 标准化URI
-                $menu = $menus[0];
-                $standardUri = $uri;
-                
-                foreach ($this->taskUriMap as $singular => $plural) {
-                    if ($menu->uri === $singular) {
-                        $standardUri = $plural;
-                        $menu->uri = $standardUri;
-                        $menu->save();
-                        $this->info("  - 更新URI: {$singular} -> {$standardUri}");
-                        break;
-                    }
-                }
-            }
-        }
-        
-        $this->info("\n已删除 {$deletedCount} 个重复的菜单项");
-        
-        return 0;
-    }
-}

+ 169 - 0
app/Console/Commands/TestSizeRotatingLog.php

@@ -0,0 +1,169 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Log\Logger;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 测试文件大小限制的日志驱动
+ */
+class TestSizeRotatingLog extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'log:test-size-rotating {--count=100 : 写入日志的数量} {--size=1K : 每条日志的大小} {--max-file-size=10K : 最大文件大小}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '测试支持文件大小限制的日志驱动功能';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $count = (int) $this->option('count');
+        $sizeStr = $this->option('size');
+        $maxFileSizeStr = $this->option('max-file-size');
+        $size = $this->parseSize($sizeStr);
+        $maxFileSize = $this->parseSize($maxFileSizeStr);
+
+        $this->info("开始测试日志驱动...");
+        $this->info("将写入 {$count} 条日志,每条大小约 {$sizeStr}");
+        $this->info("最大文件大小: {$maxFileSizeStr}");
+
+        /**
+         * 获取测试日志实例
+         * @var Logger $testLogger
+         */
+        $testLogger= Log::channel('size_rotating_daily');
+
+        // 生成指定大小的日志内容
+        $content = str_repeat('A', $size - 100); // 减去100字节用于时间戳等信息
+        $progressBar = $this->output->createProgressBar($count);
+        $progressBar->start();
+
+        for ($i = 1; $i <= $count; $i++) {
+            $message = "测试日志 #{$i} - " . $content;
+
+            // 随机使用不同的日志级别
+            $level = $this->getRandomLogLevel();
+
+            // 使用测试日志实例
+            $testLogger->$level($message);
+
+            $progressBar->advance();
+
+            // 每10条日志暂停一下,避免过快写入
+            if ($i % 10 === 0) {
+                usleep(1000); // 1ms
+            }
+        }
+
+        $progressBar->finish();
+        $this->newLine();
+
+        // 显示生成的日志文件
+        $this->showTestLogFiles();
+
+        $this->info("测试完成!");
+        return 0;
+    }
+
+    /**
+     * 解析大小字符串
+     *
+     * @param string $sizeStr
+     * @return int
+     */
+    private function parseSize(string $sizeStr): int
+    {
+        $sizeStr = trim($sizeStr);
+        $unit = strtoupper(substr($sizeStr, -1));
+        $value = (int) substr($sizeStr, 0, -1);
+
+        return match ($unit) {
+            'K' => $value * 1024,
+            'M' => $value * 1024 * 1024,
+            'G' => $value * 1024 * 1024 * 1024,
+            default => (int) $sizeStr,
+        };
+    }
+
+    /**
+     * 获取随机日志级别
+     *
+     * @return string
+     */
+    private function getRandomLogLevel(): string
+    {
+        $levels = ['debug', 'info', 'notice', 'warning', 'error'];
+        return $levels[array_rand($levels)];
+    }
+
+    /**
+     * 显示生成的测试日志文件
+     *
+     * @return void
+     */
+    private function showTestLogFiles(): void
+    {
+        $logPath = storage_path('logs');
+        $pattern = $logPath . '/laravel-' . date('Y-m-d') . '*.log';
+        $files = glob($pattern);
+
+        if (empty($files)) {
+            $this->warn("没有找到今天的测试日志文件");
+            return;
+        }
+
+        $this->newLine();
+        $this->info("生成的测试日志文件:");
+
+        $tableData = [];
+        foreach ($files as $file) {
+            $filename = basename($file);
+            $size = filesize($file);
+            $sizeFormatted = $this->formatFileSize($size);
+            $tableData[] = [$filename, $sizeFormatted, $size];
+        }
+
+        // 按文件名排序
+        usort($tableData, function ($a, $b) {
+            return strcmp($a[0], $b[0]);
+        });
+
+        $this->table(['文件名', '大小', '字节数'], $tableData);
+    }
+
+
+
+    /**
+     * 格式化文件大小
+     *
+     * @param int $bytes
+     * @return string
+     */
+    private function formatFileSize(int $bytes): string
+    {
+        if ($bytes >= 1024 * 1024 * 1024) {
+            return round($bytes / (1024 * 1024 * 1024), 2) . ' GB';
+        } elseif ($bytes >= 1024 * 1024) {
+            return round($bytes / (1024 * 1024), 2) . ' MB';
+        } elseif ($bytes >= 1024) {
+            return round($bytes / 1024, 2) . ' KB';
+        } else {
+            return $bytes . ' B';
+        }
+    }
+}

+ 148 - 0
app/Logging/README.md

@@ -0,0 +1,148 @@
+# 自定义日志驱动 - 支持文件大小限制的每日轮转
+
+## 功能特点
+
+1. **按日期创建日志文件**:如 `laravel-2025-05-26.log`
+2. **文件大小限制**:当文件超过指定大小时自动分割
+3. **自动备份**:分割后的文件命名为 `laravel-2025-05-26-1.log`、`laravel-2025-05-26-2.log` 等
+4. **自动清理**:支持设置保留天数,自动删除过期文件
+5. **灵活配置**:支持多种文件大小单位(K、M、G)
+
+## 使用方法
+
+### 1. 环境变量配置
+
+在 `.env` 文件中设置:
+
+```env
+# 使用自定义日志驱动
+LOG_CHANNEL=size_rotating_daily
+
+# 最大文件大小(支持 K、M、G 单位)
+LOG_MAX_FILE_SIZE=100M
+
+# 保留天数
+LOG_DAILY_DAYS=14
+
+# 日志级别
+LOG_LEVEL=debug
+```
+
+### 2. 配置选项说明
+
+| 配置项 | 说明 | 默认值 | 示例 |
+|--------|------|--------|------|
+| `max_file_size` | 单个文件最大大小 | 100M | 50M, 1G, 512K |
+| `days` | 保留天数(0表示不限制) | 14 | 7, 30, 0 |
+| `path` | 日志文件路径 | storage/logs/laravel.log | - |
+| `level` | 日志级别 | debug | info, warning, error |
+| `permission` | 文件权限 | 0777 | 0644, 0755 |
+| `locking` | 是否使用文件锁 | false | true, false |
+
+### 3. 文件大小单位
+
+- `K` 或 `KB`:千字节
+- `M` 或 `MB`:兆字节  
+- `G` 或 `GB`:千兆字节
+- 无单位:字节
+
+示例:
+- `1024` = 1024 字节
+- `10K` = 10 千字节
+- `50M` = 50 兆字节
+- `2G` = 2 千兆字节
+
+## 文件命名规则
+
+### 基础文件
+- `laravel-2025-05-26.log` - 当天的主日志文件
+
+### 分割文件
+- `laravel-2025-05-26-1.log` - 第一个分割文件
+- `laravel-2025-05-26-2.log` - 第二个分割文件
+- `laravel-2025-05-26-3.log` - 第三个分割文件
+
+### 分割逻辑
+1. 当主文件大小超过限制时,创建 `-1` 后缀的文件
+2. 当 `-1` 文件也超过限制时,创建 `-2` 后缀的文件
+3. 以此类推...
+
+## 使用示例
+
+### 基本使用
+
+```php
+use Illuminate\Support\Facades\Log;
+
+// 记录日志
+Log::info('这是一条信息日志');
+Log::error('这是一条错误日志');
+Log::debug('这是一条调试日志');
+```
+
+### 在特定通道中使用
+
+```php
+// 在配置文件中定义多个通道
+'channels' => [
+    'api' => [
+        'driver' => 'size_rotating_daily',
+        'path' => storage_path('logs/api.log'),
+        'max_file_size' => '50M',
+        'days' => 7,
+    ],
+    'error' => [
+        'driver' => 'size_rotating_daily',
+        'path' => storage_path('logs/error.log'),
+        'max_file_size' => '200M',
+        'days' => 30,
+        'level' => 'error',
+    ],
+],
+
+// 使用特定通道
+Log::channel('api')->info('API 请求日志');
+Log::channel('error')->error('系统错误日志');
+```
+
+## 性能考虑
+
+1. **文件锁定**:在高并发环境下,建议启用 `locking` 选项
+2. **文件大小**:建议设置合理的文件大小限制,避免单个文件过大
+3. **保留天数**:定期清理旧日志文件,避免磁盘空间不足
+
+## 故障排除
+
+### 权限问题
+确保 Laravel 有权限写入日志目录:
+```bash
+chmod -R 755 storage/logs
+chown -R www-data:www-data storage/logs
+```
+
+### 磁盘空间
+定期检查磁盘空间,确保有足够空间存储日志文件。
+
+### 配置验证
+可以通过以下命令测试日志配置:
+```bash
+php artisan tinker
+Log::info('测试日志消息');
+```
+
+## 与标准 daily 驱动的区别
+
+| 特性 | 标准 daily | size_rotating_daily |
+|------|------------|---------------------|
+| 按日期分割 | ✅ | ✅ |
+| 按大小分割 | ❌ | ✅ |
+| 自动备份 | ❌ | ✅ |
+| 文件大小限制 | ❌ | ✅ |
+| 保留天数 | ✅ | ✅ |
+| 性能 | 高 | 中等 |
+
+## 注意事项
+
+1. 该驱动会在每次写入日志时检查文件大小,可能会有轻微的性能影响
+2. 分割后的文件不会再次分割,如果需要更细粒度的控制,请调整 `max_file_size` 参数
+3. 文件清理只在日志处理器关闭时执行,建议定期重启应用或使用定时任务清理

+ 175 - 0
app/Logging/SizeRotatingDailyHandler.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace App\Logging;
+
+use Monolog\Handler\StreamHandler;
+use Monolog\LogRecord;
+use Monolog\Level;
+
+/**
+ * 支持文件大小限制的每日轮转日志处理器
+ *
+ * 功能特点:
+ * 1. 按日期创建日志文件(如:laravel-2025-05-26.log)
+ * 2. 当文件大小超过限制时,自动分割为备份文件(如:laravel-2025-05-26-1.log)
+ * 3. 支持设置最大文件数量,自动清理旧文件
+ */
+class SizeRotatingDailyHandler extends StreamHandler
+{
+    /**
+     * 最大文件大小(字节)
+     *
+     * @var int
+     */
+    protected int $maxFileSize;
+
+    /**
+     * 基础文件名
+     *
+     * @var string
+     */
+    protected string $baseFilename;
+
+    /**
+     * 最大文件数量
+     *
+     * @var int
+     */
+    protected int $maxFiles;
+
+    /**
+     * 构造函数
+     *
+     * @param string $filename 日志文件路径
+     * @param int $maxFiles 最大文件数量
+     * @param int|string|Level $level 日志级别
+     * @param bool $bubble 是否冒泡
+     * @param int|null $filePermission 文件权限
+     * @param bool $useLocking 是否使用文件锁
+     * @param int $maxFileSize 最大文件大小(字节),默认100MB
+     */
+    public function __construct(
+        string $filename,
+        int $maxFiles = 0,
+        int|string|Level $level = Level::Debug,
+        bool $bubble = true,
+        ?int $filePermission = null,
+        bool $useLocking = false,
+        int $maxFileSize = 104857600 // 100MB
+    ) {
+        $this->maxFileSize = $maxFileSize;
+        $this->baseFilename = $filename;
+        $this->maxFiles = $maxFiles;
+
+        // 获取当前应该使用的文件名
+        $currentFilename = $this->getCurrentFilename();
+
+        parent::__construct($currentFilename, $level, $bubble, $filePermission, $useLocking);
+    }
+
+    /**
+     * 获取当前应该使用的文件名
+     *
+     * @return string
+     */
+    protected function getCurrentFilename(): string
+    {
+        // 始终返回基础的时间文件名
+        // 文件大小检查和轮转在写入时处理
+        return $this->getTimedFilename();
+    }
+
+    /**
+     * 获取带时间戳的文件名
+     *
+     * @return string
+     */
+    protected function getTimedFilename(): string
+    {
+        $fileInfo = pathinfo($this->baseFilename);
+        $timedFilename = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '-' . date('Y-m-d');
+
+        if (isset($fileInfo['extension'])) {
+            $timedFilename .= '.' . $fileInfo['extension'];
+        }
+
+        return $timedFilename;
+    }
+
+    /**
+     * 获取下一个可用的备份文件名
+     *
+     * @param string $currentFilename 当前文件名
+     * @return string
+     */
+    protected function getNextBackupFilename(string $currentFilename): string
+    {
+        $fileInfo = pathinfo($currentFilename);
+        $baseName = $fileInfo['filename'];
+        $extension = isset($fileInfo['extension']) ? '.' . $fileInfo['extension'] : '';
+        $directory = $fileInfo['dirname'];
+
+        // 检查是否已经是分割文件(包含日期和计数器)
+        // 格式:filename-YYYY-MM-DD-N 或 filename-YYYY-MM-DD
+        if (preg_match('/^(.+)-(\d{4}-\d{2}-\d{2})$/', $baseName, $matches)) {
+            // 是日期文件但还没有计数器,从1开始
+            $baseNameWithDate = $baseName;
+            $startCounter = 1;
+        } else {
+            // 不是预期的格式,直接添加计数器
+            $baseNameWithDate = $baseName;
+            $startCounter = 1;
+        }
+
+        $counter = $startCounter;
+        do {
+            $backupFilename = $directory . '/' . $baseNameWithDate . '-' . $counter . $extension;
+            $counter++;
+        } while (file_exists($backupFilename));
+
+        return $backupFilename;
+    }
+
+    /**
+     * 重写写入方法,添加文件大小检查
+     *
+     * @param LogRecord $record
+     * @return void
+     */
+    protected function write(LogRecord $record): void
+    {
+        // 先调用父类写入方法
+        parent::write($record);
+
+        // 在写入后检查文件大小,如果超过限制则进行分割
+        if ($this->url && file_exists($this->url) && filesize($this->url) >= $this->maxFileSize) {
+            // 进行文件分割
+            $this->rotateDueToSize();
+        }
+    }
+
+    /**
+     * 由于文件大小超限而进行轮转
+     *
+     * @return void
+     */
+    protected function rotateDueToSize(): void
+    {
+        // 关闭当前文件句柄
+        if ($this->stream) {
+            fclose($this->stream);
+            $this->stream = null;
+        }
+
+        // 获取备份文件名
+        $backupFilename = $this->getNextBackupFilename($this->url);
+
+        // 将当前文件重命名为备份文件
+        if (file_exists($this->url)) {
+            rename($this->url, $backupFilename);
+        }
+
+        // 继续使用原始文件名,下次写入时会创建新的空文件
+        // 由于 $this->stream 已经设置为 null,父类的 write 方法会重新打开文件
+    }
+}

+ 96 - 0
app/Logging/SizeRotatingDailyLogger.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Logging;
+
+use Monolog\Logger;
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * 支持文件大小限制的每日轮转日志驱动
+ */
+class SizeRotatingDailyLogger
+{
+    /**
+     * 创建自定义 Monolog 实例
+     *
+     * @param array $config
+     * @return Logger
+     */
+    public function __invoke(array $config): Logger
+    {
+        $logger = new Logger($config['name'] ?? 'laravel');
+
+        // 获取配置参数
+        $path = $config['path'] ?? storage_path('logs/laravel.log');
+        $level = $config['level'] ?? 'debug';
+        $maxFiles = $config['days'] ?? 7;
+        $maxFileSize = $this->parseFileSize($config['max_file_size'] ?? '100M');
+        $permission = $config['permission'] ?? null;
+        $locking = $config['locking'] ?? false;
+
+        // 创建处理器
+        $handler = new SizeRotatingDailyHandler(
+            $path,
+            $maxFiles,
+            $this->parseLevel($level),
+            true,
+            $permission,
+            $locking,
+            $maxFileSize
+        );
+
+        // 设置格式化器
+        $formatter = new LineFormatter(
+            $config['format'] ?? null,
+            $config['date_format'] ?? null,
+            $config['allow_inline_line_breaks'] ?? false,
+            $config['ignore_empty_context_and_extra'] ?? false
+        );
+        
+        $handler->setFormatter($formatter);
+        $logger->pushHandler($handler);
+
+        return $logger;
+    }
+
+    /**
+     * 解析日志级别
+     *
+     * @param string $level
+     * @return int
+     */
+    protected function parseLevel(string $level): int
+    {
+        return match (strtolower($level)) {
+            'debug' => Logger::DEBUG,
+            'info' => Logger::INFO,
+            'notice' => Logger::NOTICE,
+            'warning' => Logger::WARNING,
+            'error' => Logger::ERROR,
+            'critical' => Logger::CRITICAL,
+            'alert' => Logger::ALERT,
+            'emergency' => Logger::EMERGENCY,
+            default => Logger::DEBUG,
+        };
+    }
+
+    /**
+     * 解析文件大小配置
+     *
+     * @param string $size
+     * @return int
+     */
+    protected function parseFileSize(string $size): int
+    {
+        $size = trim($size);
+        $unit = strtoupper(substr($size, -1));
+        $value = (int) substr($size, 0, -1);
+
+        return match ($unit) {
+            'K' => $value * 1024,
+            'M' => $value * 1024 * 1024,
+            'G' => $value * 1024 * 1024 * 1024,
+            default => (int) $size, // 如果没有单位,假设是字节
+        };
+    }
+}

+ 36 - 13
app/Module/AppGame/Handler/Item/CraftHandler.php

@@ -4,6 +4,7 @@ namespace App\Module\AppGame\Handler\Item;
 
 use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\GameItems\Services\CraftService;
+use App\Module\GameItems\Validations\ItemCraftValidation;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\Log;
 use Uraus\Kku\Request\RequestItemCraft;
@@ -38,16 +39,22 @@ class CraftHandler extends BaseHandler
             $quantity = $data->getNumber();
             $userId = $this->user_id;
 
-            // 验证参数
-            if ($recipeId <= 0) {
-                throw new LogicException("配方ID无效");
-            }
-
+            // 设置默认数量
             if ($quantity <= 0) {
-                $quantity = 1; // 默认合成1个
+                $quantity = 1;
             }
 
-            // 调用合成服务
+            // 先进行验证,避免不必要的事务开销
+            $validation = new ItemCraftValidation([
+                'user_id' => $userId,
+                'recipe_id' => $recipeId,
+                'quantity' => $quantity
+            ]);
+
+            // 验证数据
+            $validation->validated();
+
+            // 调用合成服务(服务内部会处理事务)
             $result = CraftService::craftItem($userId, $recipeId, [
                 'quantity' => $quantity,
                 'source_type' => 'app_craft',
@@ -70,23 +77,39 @@ class CraftHandler extends BaseHandler
                 'result' => $result
             ]);
 
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('物品合成验证失败', [
+                'user_id' => $userId ?? null,
+                'recipe_id' => $recipeId ?? null,
+                'quantity' => $quantity ?? null,
+                'error' => $e->getMessage()
+            ]);
+
         } catch (LogicException $e) {
-            // 设置错误响应
+            // 业务逻辑异常
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户物品合成失败', [
-                'user_id' => $this->user_id,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
+                'user_id' => $userId ?? null,
+                'recipe_id' => $recipeId ?? null,
+                'quantity' => $quantity ?? null,
+                'error' => $e->getMessage()
             ]);
+
         } catch (\Exception $e) {
-            // 设置错误响应
+            // 系统异常
             $this->response->setCode(500);
             $this->response->setMsg('系统错误,请稍后再试');
 
             Log::error('物品合成操作异常', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'recipe_id' => $recipeId ?? null,
+                'quantity' => $quantity ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 37 - 13
app/Module/AppGame/Handler/Item/DismantleHandler.php

@@ -4,6 +4,7 @@ namespace App\Module\AppGame\Handler\Item;
 
 use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\GameItems\Services\DismantleService;
+use App\Module\GameItems\Validations\ItemDismantleValidation;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\Log;
 use Uraus\Kku\Request\RequestItemDismantle;
@@ -38,16 +39,23 @@ class DismantleHandler extends BaseHandler
             $quantity = $data->getNumber();
             $userId = $this->user_id;
 
-            // 验证参数
-            if ($itemId <= 0) {
-                throw new LogicException("物品ID无效");
-            }
-
+            // 设置默认数量
             if ($quantity <= 0) {
-                $quantity = 1; // 默认分解1个
+                $quantity = 1;
             }
 
-            // 调用分解服务
+            // 先进行验证,避免不必要的事务开销
+            $validation = new ItemDismantleValidation([
+                'user_id' => $userId,
+                'item_id' => $itemId,
+                'quantity' => $quantity,
+                'instance_id' => 0 // 默认不指定实例
+            ]);
+
+            // 验证数据
+            $validation->validated();
+
+            // 调用分解服务(服务内部会处理事务)
             $result = DismantleService::dismantleItem($userId, $itemId, null, $quantity, [
                 'source_type' => 'app_dismantle',
                 'device_info' => request()->userAgent(),
@@ -69,23 +77,39 @@ class DismantleHandler extends BaseHandler
                 'result' => $result
             ]);
 
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('物品分解验证失败', [
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'quantity' => $quantity ?? null,
+                'error' => $e->getMessage()
+            ]);
+
         } catch (LogicException $e) {
-            // 设置错误响应
+            // 业务逻辑异常
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户物品分解失败', [
-                'user_id' => $this->user_id,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'quantity' => $quantity ?? null,
+                'error' => $e->getMessage()
             ]);
+
         } catch (\Exception $e) {
-            // 设置错误响应
+            // 系统异常
             $this->response->setCode(500);
             $this->response->setMsg('系统错误,请稍后再试');
 
             Log::error('物品分解操作异常', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'quantity' => $quantity ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 41 - 16
app/Module/AppGame/Handler/Item/OpenBoxHandler.php

@@ -4,7 +4,9 @@ namespace App\Module\AppGame\Handler\Item;
 
 use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\GameItems\Services\ChestService;
+use App\Module\GameItems\Validations\ChestOpenValidation;
 use Google\Protobuf\Internal\Message;
+
 use Illuminate\Support\Facades\Log;
 use Uraus\Kku\Request\RequestItemOpenBox;
 use Uraus\Kku\Response\ResponseItemOpenBox;
@@ -36,23 +38,30 @@ class OpenBoxHandler extends BaseHandler
             // 获取请求参数
             $itemId = $data->getItemId();
             $instanceId = $data->getItemInstanceId();
-            $selectIds = $data->getSelectIds()->getIterator()->getArrayCopy();
+            $selectIds = iterator_to_array($data->getSelectIds()->getIterator());
             $userId = $this->user_id;
 
-            // 验证参数
-            if ($itemId <= 0) {
-                throw new LogicException("宝箱ID无效");
-            }
+            // 先进行验证,避免不必要的事务开销
+            $validation = new ChestOpenValidation([
+                'user_id' => $userId,
+                'item_id' => $itemId,
+                'instance_id' => $instanceId,
+                'quantity' => 1
+            ]);
+
+            // 验证数据
+            $validation->validated();
 
-            // 调用宝箱开启服务
-            $result = ChestService::openChest($userId, $itemId, $instanceId, [
+            // 创建服务实例并调用宝箱开启服务
+            $chestService = new ChestService(app(\App\Module\GameItems\Services\ItemService::class));
+            $result = $chestService->openChest($userId, $itemId, 1, [
                 'select_ids' => $selectIds,
                 'source_type' => 'app_open_box',
                 'device_info' => request()->userAgent(),
                 'ip_address' => request()->ip(),
             ]);
 
-            if (!$result) {
+            if (!$result || !$result['success']) {
                 throw new LogicException("开启宝箱失败,请检查宝箱是否存在或是否可开启");
             }
 
@@ -68,23 +77,39 @@ class OpenBoxHandler extends BaseHandler
                 'result' => $result
             ]);
 
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('宝箱开启验证失败', [
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'instance_id' => $instanceId ?? null,
+                'error' => $e->getMessage()
+            ]);
+
         } catch (LogicException $e) {
-            // 设置错误响应
+            // 业务逻辑异常
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
-            
+
             Log::warning('用户开启宝箱失败', [
-                'user_id' => $this->user_id,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'instance_id' => $instanceId ?? null,
+                'error' => $e->getMessage()
             ]);
+
         } catch (\Exception $e) {
-            // 设置错误响应
+            // 系统异常
             $this->response->setCode(500);
             $this->response->setMsg('系统错误,请稍后再试');
-            
+
             Log::error('开启宝箱操作异常', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'item_id' => $itemId ?? null,
+                'instance_id' => $instanceId ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 68 - 24
app/Module/AppGame/Handler/Land/HarvestHandler.php

@@ -3,11 +3,14 @@
 namespace App\Module\AppGame\Handler\Land;
 
 use App\Module\AppGame\Handler\BaseHandler;
+use App\Module\AppGame\Proto\LandInfoDto;
 use App\Module\Farm\Services\CropService;
 use App\Module\Farm\Services\LandService;
+use App\Module\Farm\Validations\CropHarvestValidation;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
+use Uraus\Kku\Common\LastData;
 use Uraus\Kku\Request\RequestLandHarvest;
 use Uraus\Kku\Response\ResponseLandHarvest;
 use UCore\Exception\LogicException;
@@ -39,46 +42,87 @@ class HarvestHandler extends BaseHandler
             $landId = $data->getLandId();
             $userId = $this->user_id;
 
-            // 验证土地是否存在且属于当前用户
-            // 获取用户所有土地
-            $userLands = LandService::getUserLands($userId);
-            $landInfo = null;
-
-            // 查找指定ID的土地
-            foreach ($userLands as $land) {
-                if ($land->id == $landId) {
-                    $landInfo = $land;
-                    break;
-                }
-            }
+            // 先进行验证,避免不必要的事务开销
+            $validation = new CropHarvestValidation([
+                'user_id' => $userId,
+                'land_id' => $landId
+            ]);
 
-            if (!$landInfo) {
-                throw new LogicException("土地不存在或不属于当前用户");
-            }
+            // 验证数据
+            $validation->validated();
+
+            // 验证通过后,开启事务
             DB::beginTransaction();
+
             // 调用收获服务
-            $res = $harvestResult = CropService::harvestCrop($userId, $landId);
-            if ($res->error) {
-                throw new LogicException("收获失败,请检查土地状态或作物生长阶段");
+            $harvestResult = CropService::harvestCrop($userId, $landId);
+            if ($harvestResult->error) {
+                throw new LogicException($harvestResult->message ?: "收获失败,请检查土地状态或作物生长阶段");
             }
 
+            // 提交事务
+            DB::commit();
 
+            // 获取更新后的土地信息并返回
+            $updatedLand = LandService::getUserLand($userId, $landId);
+            if ($updatedLand) {
+                // 创建LastData对象
+                $lastData = new LastData();
+                $landData = LandInfoDto::toDataLand($updatedLand);
+                $lastData->setLands([$landData]);
+
+                // 设置LastData到响应
+                $this->response->setLastData($lastData);
+            }
+
+            // 设置响应状态
+            $this->response->setCode(0);
+            $this->response->setMsg('收获成功');
 
             Log::info('用户收获成功', [
                 'user_id' => $userId,
                 'land_id' => $landId,
-                'harvest_result' => $harvestResult
+                'harvest_result' => $harvestResult->data ?? []
             ]);
-            DB::commit();
 
-        } catch (\Exception $e) {
-            DB::rollBack();
-            // 设置错误响应
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败,此时可能还没有开启事务
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('收获验证失败', [
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'error' => $e->getMessage()
+            ]);
+
+        } catch (LogicException $e) {
+            // 业务逻辑异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户收获失败', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'error' => $e->getMessage()
+            ]);
+
+        } catch (\Exception $e) {
+            // 系统异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
+            $this->response->setCode(500);
+            $this->response->setMsg('系统错误,请稍后再试');
+
+            Log::error('收获操作异常', [
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 50 - 34
app/Module/AppGame/Handler/Land/RemoveCropHandler.php

@@ -4,9 +4,10 @@ namespace App\Module\AppGame\Handler\Land;
 
 use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\Farm\Services\CropService;
-use App\Module\Farm\Services\LandService;
+use App\Module\Farm\Validations\CropRemoveValidation;
 use App\Module\GameItems\Services\ItemService;
 use Google\Protobuf\Internal\Message;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Uraus\Kku\Request\RequestLandRemoveCrop;
 use Uraus\Kku\Response\ResponseLandRemoveCrop;
@@ -37,33 +38,21 @@ class RemoveCropHandler extends BaseHandler
         try {
             // 获取请求参数
             $landId = $data->getLandId();
-            $userItemId = $data->getUserItemId();
+            $toolItemId = $data->getUserItemId() ?? 0; // 铲除工具ID,可选
             $userId = $this->user_id;
 
-            // 验证土地是否存在且属于当前用户
-            // 获取用户所有土地
-            $userLands = LandService::getUserLands($userId);
-            $landInfo = null;
-
-            // 查找指定ID的土地
-            foreach ($userLands as $land) {
-                if ($land->id == $landId) {
-                    $landInfo = $land;
-                    break;
-                }
-            }
+            // 先进行验证,避免不必要的事务开销
+            $validation = new CropRemoveValidation([
+                'user_id' => $userId,
+                'land_id' => $landId,
+                'tool_item_id' => $toolItemId
+            ]);
 
-            if (!$landInfo) {
-                throw new LogicException("土地不存在或不属于当前用户");
-            }
+            // 验证数据
+            $validation->validated();
 
-            // 如果提供了物品ID,验证用户是否拥有该物品
-            if ($userItemId > 0) {
-                $hasItem = ItemService::getUserItems($userId, ['item_id' => $userItemId]);
-                if ($hasItem->isEmpty()) {
-                    throw new LogicException("您没有该铲除工具");
-                }
-            }
+            // 验证通过后,开启事务
+            DB::beginTransaction();
 
             // 调用铲除作物服务
             $result = CropService::removeCrop($userId, $landId);
@@ -71,15 +60,18 @@ class RemoveCropHandler extends BaseHandler
                 throw new LogicException("铲除作物失败,请检查土地状态");
             }
 
-            // 如果使用了物品,消耗物品
-            if ($userItemId > 0) {
-                ItemService::consumeItem($userId, $userItemId, null, 1, [
+            // 如果使用了工具,消耗工具
+            if ($toolItemId > 0) {
+                ItemService::consumeItem($userId, $toolItemId, null, 1, [
                     'source_type' => 'land_remove_crop',
                     'source_id' => $landId,
                     'details' => ['land_id' => $landId]
                 ]);
             }
 
+            // 提交事务
+            DB::commit();
+
             // 设置响应状态
             $this->response->setCode(0);
             $this->response->setMsg('铲除作物成功');
@@ -87,26 +79,50 @@ class RemoveCropHandler extends BaseHandler
             Log::info('用户铲除作物成功', [
                 'user_id' => $userId,
                 'land_id' => $landId,
-                'item_id' => $userItemId
+                'tool_item_id' => $toolItemId
+            ]);
+
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败,此时可能还没有开启事务
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('铲除作物验证失败', [
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'tool_item_id' => $toolItemId ?? null,
+                'error' => $e->getMessage()
             ]);
 
         } catch (LogicException $e) {
-            // 设置错误响应
+            // 业务逻辑异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户铲除作物失败', [
-                'user_id' => $this->user_id,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'tool_item_id' => $toolItemId ?? null,
+                'error' => $e->getMessage()
             ]);
+
         } catch (\Exception $e) {
-            // 设置错误响应
+            // 系统异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
             $this->response->setCode(500);
             $this->response->setMsg('系统错误,请稍后再试');
 
             Log::error('铲除作物操作异常', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'tool_item_id' => $toolItemId ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 62 - 35
app/Module/AppGame/Handler/Land/SowHandler.php

@@ -3,12 +3,15 @@
 namespace App\Module\AppGame\Handler\Land;
 
 use App\Module\AppGame\Handler\BaseHandler;
+use App\Module\AppGame\Proto\LandInfoDto;
 use App\Module\Farm\Services\CropService;
 use App\Module\Farm\Services\LandService;
+use App\Module\Farm\Validations\CropPlantValidation;
 use App\Module\GameItems\Services\ItemService;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
+use Uraus\Kku\Common\LastData;
 use Uraus\Kku\Request\RequestLandSow;
 use Uraus\Kku\Response\ResponseLandSow;
 use UCore\Exception\LogicException;
@@ -42,35 +45,18 @@ class SowHandler extends BaseHandler
             $itemInstanceId = $data->getItemInstanceId();
             $userId = $this->user_id;
 
-            // 验证土地是否存在且属于当前用户
-            // 获取用户所有土地
-            $userLands = LandService::getUserLands($userId);
-            $landInfo = null;
-
-            // 查找指定ID的土地
-            foreach ($userLands as $land) {
-                if ($land->id == $landId) {
-                    $landInfo = $land;
-                    break;
-                }
-            }
-
-            if (!$landInfo) {
-                throw new LogicException("土地不存在或不属于当前用户");
-            }
-
-            // 在事务开始前检查土地状态
-            if ($landInfo->status !== \App\Module\Farm\Enums\LAND_STATUS::IDLE->value) {
-                throw new LogicException("土地状态不允许种植");
-            }
+            // 先进行验证,避免不必要的事务开销
+            $validation = new CropPlantValidation([
+                'user_id' => $userId,
+                'land_id' => $landId,
+                'item_id' => $itemId,
+                'item_instance_id' => $itemInstanceId
+            ]);
 
-            // 验证用户是否拥有该种子物品
-            $hasItem = ItemService::getUserItems($userId, ['item_id' => $itemId]);
-            if ($hasItem->isEmpty()) {
-                throw new LogicException("您没有该种子");
-            }
+            // 验证数据
+            $validation->validated();
 
-            // 开启数据库事务
+            // 验证通过后,开启事务
             DB::beginTransaction();
 
             // 调用Farm模块的CropService种植作物
@@ -92,11 +78,24 @@ class SowHandler extends BaseHandler
                 ]
             ]);
 
+            // 提交事务
+            DB::commit();
 
+            // 获取更新后的土地信息并返回
+            $updatedLand = LandService::getUserLand($userId, $landId);
+            if ($updatedLand) {
+                // 创建LastData对象
+                $lastData = new LastData();
+                $landData = LandInfoDto::toDataLand($updatedLand);
+                $lastData->setLands([$landData]);
 
+                // 设置LastData到响应
+                $this->response->setLastData($lastData);
+            }
 
-            // 提交事务
-            DB::commit();
+            // 设置响应状态
+            $this->response->setCode(0);
+            $this->response->setMsg('种植成功');
 
             Log::info('用户种植成功', [
                 'user_id' => $userId,
@@ -107,22 +106,50 @@ class SowHandler extends BaseHandler
                 'sow_log_id' => $sowLogId
             ]);
 
-        } catch (LogicException $e) {
-            // 回滚事务
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败,此时可能还没有开启事务
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
 
-            DB::rollBack();
+            Log::warning('种植验证失败', [
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'item_id' => $itemId ?? null,
+                'error' => $e->getMessage()
+            ]);
 
+        } catch (LogicException $e) {
+            // 业务逻辑异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
 
-            // 设置错误响应
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户种植失败', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'item_id' => $itemId ?? null,
+                'error' => $e->getMessage()
+            ]);
+
+        } catch (\Exception $e) {
+            // 系统异常,需要回滚事务
+            if (DB::transactionLevel() > 0) {
+                DB::rollBack();
+            }
+
+            $this->response->setCode(500);
+            $this->response->setMsg('系统错误,请稍后再试');
+
+            Log::error('种植操作异常', [
+                'user_id' => $userId ?? null,
+                'land_id' => $landId ?? null,
+                'item_id' => $itemId ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);
-
         }
 
         return $response;

+ 35 - 35
app/Module/AppGame/Handler/Shop/BuyHandler.php

@@ -6,7 +6,8 @@ use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\Fund\Enums\LOG_TYPE;
 use App\Module\Fund\Logic\User as FundUser;
 use App\Module\GameItems\Services\ItemService;
-use App\Module\Shop\Models\ShopItem;
+
+use App\Module\Shop\Validations\ShopBuyValidation;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
@@ -44,36 +45,21 @@ class BuyHandler extends BaseHandler
             $number = $data->getNumber();
             $userId = $this->user_id;
 
-            // 参数验证
-            if ($goodId <= 0) {
-                throw new LogicException("商品ID无效");
-            }
-
-            if ($number <= 0) {
-                throw new LogicException("购买数量必须大于0");
-            }
-
-            // 获取商品信息
-            $shopItem = ShopItem::findOrFail($goodId);
-
-            if (!$shopItem->is_active) {
-                throw new LogicException("该商品已下架");
-            }
-
-            // 检查购买限制
-            if ($shopItem->max_buy > 0) {
-                // 获取用户已购买数量
-                $boughtCount = $shopItem->getUserBoughtCount($userId);
+            // 先进行验证,避免不必要的事务开销
+            $validation = new ShopBuyValidation([
+                'user_id' => $userId,
+                'good_id' => $goodId,
+                'number' => $number
+            ]);
 
-                if ($boughtCount + $number > $shopItem->max_buy) {
-                    throw new LogicException("超出购买限制,最多还能购买" . ($shopItem->max_buy - $boughtCount) . "个");
-                }
-            }
+            // 验证数据
+            $validation->validated();
 
-            // 计算总价
+            // 从验证结果中获取商品信息
+            $shopItem = $validation->shop_item;
             $totalPrice = $shopItem->price * $number;
 
-            // 开始事务
+            // 验证通过后,开启事务
             DB::beginTransaction();
 
             // 扣除用户货币
@@ -143,33 +129,47 @@ class BuyHandler extends BaseHandler
                 'item_quantity' => $shopItem->item_quantity
             ]);
 
+        } catch (\UCore\Exception\ValidateException $e) {
+            // 验证失败,此时可能还没有开启事务
+            $this->response->setCode(400);
+            $this->response->setMsg($e->getMessage());
+
+            Log::warning('商品购买验证失败', [
+                'user_id' => $userId ?? null,
+                'good_id' => $goodId ?? null,
+                'number' => $number ?? null,
+                'error' => $e->getMessage()
+            ]);
+
         } catch (LogicException $e) {
-            // 回滚事务
+            // 业务逻辑异常,需要回滚事务
             if (DB::transactionLevel() > 0) {
                 DB::rollBack();
             }
 
-            // 设置错误响应
             $this->response->setCode(400);
             $this->response->setMsg($e->getMessage());
 
             Log::warning('用户购买商品失败', [
-                'user_id' => $this->user_id,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
+                'user_id' => $userId ?? null,
+                'good_id' => $goodId ?? null,
+                'number' => $number ?? null,
+                'error' => $e->getMessage()
             ]);
+
         } catch (\Exception $e) {
-            // 回滚事务
+            // 系统异常,需要回滚事务
             if (DB::transactionLevel() > 0) {
                 DB::rollBack();
             }
 
-            // 设置错误响应
             $this->response->setCode(500);
             $this->response->setMsg('系统错误,请稍后再试');
 
             Log::error('购买商品操作异常', [
-                'user_id' => $this->user_id,
+                'user_id' => $userId ?? null,
+                'good_id' => $goodId ?? null,
+                'number' => $number ?? null,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 14 - 2
app/Module/AppGame/Handler/User/DataHandler.php

@@ -132,7 +132,7 @@ class DataHandler extends BaseHandler
         foreach ($items as $item) {
             $li = new DataItem();
             if ($item->instanceId) {
-                $li->setInstanceId($item->instance_id);
+                $li->setInstanceId($item->instanceId);
             }
             $li->setItemId($item->itemId);
             $li->setQuantity($item->quantity);
@@ -182,7 +182,19 @@ class DataHandler extends BaseHandler
                 $godBuffDto->id = $buff->id;
                 $godBuffDto->userId = $buff->user_id;
                 $godBuffDto->buffType = $buff->buff_type;
-                $godBuffDto->expireTime = $buff->expire_time ? $buff->expire_time->toDateTimeString() : null;
+
+                // 安全地处理过期时间,确保正确的类型转换
+                if ($buff->expire_time) {
+                    if (is_string($buff->expire_time)) {
+                        $godBuffDto->expireTime = $buff->expire_time;
+                    } elseif ($buff->expire_time instanceof \Carbon\Carbon) {
+                        $godBuffDto->expireTime = $buff->expire_time->toDateTimeString();
+                    } else {
+                        $godBuffDto->expireTime = (string)$buff->expire_time;
+                    }
+                } else {
+                    $godBuffDto->expireTime = null;
+                }
 
                 // 转换为Protobuf的DataGod对象
                 $dataGod = new DataGod();

+ 1 - 0
app/Module/AppGame/HttpControllers/ProtobufController.php

@@ -67,6 +67,7 @@ class ProtobufController extends Controller
                 Logger::debug('base64: '.base64_encode($rawData));
                 $request->mergeFromString($rawData);
             }
+            \Illuminate\Support\Facades\Log::debug('Proto请求信息-json', json_decode($request->serializeToJsonString(),true));
 
             $requestLogger->setProtobufJson($request->serializeToJsonString());
             // 获取路由配置

+ 29 - 3
app/Module/AppGame/Proto/CropInfoDto.php

@@ -20,9 +20,17 @@ class CropInfoDto
      */
     public static function fillDataLand(\App\Module\Farm\Dtos\CropInfoDto $cropInfoDto, DataLand $dataLand): DataLand
     {
-        // 设置作物ID和种子ID
+        // 设置作物ID
         $dataLand->setPlantId($cropInfoDto->id);
-        $dataLand->setSeedId($cropInfoDto->seedId);
+
+        // 设置种子物品ID(需要从种子配置中获取对应的物品ID)
+        $seedItemId = self::getSeedItemId($cropInfoDto->seedId);
+        $dataLand->setSeedId($seedItemId);
+
+        // 设置果实ID(发芽期确定的最终产出果实ID)
+        if ($cropInfoDto->finalOutputItemId) {
+            $dataLand->setFruitId($cropInfoDto->finalOutputItemId);
+        }
 
         // 设置种植时间
         if (!empty($cropInfoDto->plantTime)) {
@@ -77,5 +85,23 @@ class CropInfoDto
         return $dataLand;
     }
 
-
+    /**
+     * 根据种子配置ID获取对应的物品ID
+     *
+     * @param int $seedId 种子配置ID
+     * @return int 物品ID
+     */
+    private static function getSeedItemId(int $seedId): int
+    {
+        try {
+            $seed = \App\Module\Farm\Models\FarmSeed::find($seedId);
+            return $seed ? $seed->item_id : 0;
+        } catch (\Exception $e) {
+            \Illuminate\Support\Facades\Log::error('获取种子物品ID失败', [
+                'seed_id' => $seedId,
+                'error' => $e->getMessage()
+            ]);
+            return 0;
+        }
+    }
 }

+ 1 - 1
app/Module/AppGame/Proto/LandInfoDto.php

@@ -31,6 +31,7 @@ class LandInfoDto
 
         // 如果有作物信息,设置作物相关字段
         if ($landInfoDto->crop) {
+            // 设置种子配置ID(这是种子表的主键ID)
             $dataLand->setSeedId($landInfoDto->crop->seedId);
             $dataLand->setPlantId($landInfoDto->crop->id);
 
@@ -67,5 +68,4 @@ class LandInfoDto
     }
 
 
-
 }

+ 1 - 1
app/Module/AppGame/Tools/Protobuf.php

@@ -43,7 +43,7 @@ class Protobuf
             'response_size' => strlen($response->serializeToString()),
             'response_json_size' => strlen($response->serializeToJsonString())
         ]);
-
+        \Illuminate\Support\Facades\Log::debug('Proto响应信息-json', json_decode($response->serializeToJsonString(),true));
         // 强制返回JSON格式(用于调试或客户端不支持二进制解析时)
         if ($forceJson) {
             return response()->json(

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

@@ -88,11 +88,11 @@ class CropLogic
      *
      * @param int $userId
      * @param int $landId
-     * @param int $seedId
+     * @param int $itemId 种子物品ID
      * @return array|null 返回包含CropInfoDto和日志ID的数组,格式为['crop' => CropInfoDto, 'log_id' => int]
      * @throws \Exception
      */
-    public function plantCrop(int $userId, int $landId, int $seedId): ?array
+    public function plantCrop(int $userId, int $landId, int $itemId): ?array
     {
         try {
             // 检查是否已开启事务
@@ -112,13 +112,15 @@ class CropLogic
                 throw new \Exception('土地状态不允许种植');
             }
 
-            // 获取种子信息
-            $seed = FarmSeed::find($seedId);
+            // 根据物品ID获取种子配置信息
+            $seed = FarmSeed::where('item_id', $itemId)->first();
 
             if (!$seed) {
-                throw new \Exception('种子不存在');
+                throw new \Exception('种子配置不存在');
             }
 
+            $seedId = $seed->id;
+
             // 创建作物记录
             $crop = new FarmCrop();
             $crop->land_id = $landId;

+ 6 - 6
app/Module/Farm/Models/FarmGodBuff.php

@@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
 /**
  * 神灵加持模型
- * field start 
+ * field start
  * @property  int  $id  主键ID
  * @property  int  $user_id  用户ID
  * @property  int  $buff_type  buff类型:1丰收之神,2雨露之神,3屠草之神,4拭虫之神
@@ -37,14 +37,14 @@ class FarmGodBuff extends Model
     ];
 
     /**
-     * 应该被转换为日期的属性
+     * 应该被转换为原生类型的属性
      *
      * @var array
      */
-    protected $dates = [
-        'expire_time',
-        'created_at',
-        'updated_at',
+    protected $casts = [
+        'expire_time' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
     ];
 
     /**

+ 4 - 4
app/Module/Farm/Services/CropService.php

@@ -62,19 +62,19 @@ class CropService
      *
      * @param int $userId
      * @param int $landId
-     * @param int $seedId
+     * @param int $itemId 种子物品ID
      * @return array|null 返回包含CropInfoDto和日志ID的数组,格式为['crop' => CropInfoDto, 'log_id' => int]
      */
-    public static function plantCrop(int $userId, int $landId, int $seedId): ?array
+    public static function plantCrop(int $userId, int $landId, int $itemId): ?array
     {
         try {
             $cropLogic = new CropLogic();
-            return $cropLogic->plantCrop($userId, $landId, $seedId);
+            return $cropLogic->plantCrop($userId, $landId, $itemId);
         } catch (\Exception $e) {
             Log::error('种植作物失败', [
                 'user_id' => $userId,
                 'land_id' => $landId,
-                'seed_id' => $seedId,
+                'item_id' => $itemId,
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString()
             ]);

+ 54 - 0
app/Module/Farm/Validations/CropHarvestValidation.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Module\Farm\Validations;
+
+use App\Module\Farm\Validators\LandOwnershipValidator;
+use App\Module\Farm\Validators\HarvestableStatusValidator;
+use UCore\ValidationCore;
+
+/**
+ * 作物收获验证类
+ * 
+ * 用于验证作物收获操作的输入数据,包括用户ID、土地ID
+ */
+class CropHarvestValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,land_id', 'required'
+            ],
+            [
+                'user_id,land_id', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            // 验证土地是否属于用户
+            [
+                'land_id', new LandOwnershipValidator($this, ['user_id', 'land']),
+                'msg' => '土地不存在或不属于当前用户'
+            ],
+            // 验证土地状态是否可收获
+            [
+                'land_id', new HarvestableStatusValidator($this, ['land']),
+                'msg' => '土地状态不允许收获'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [];
+    }
+}

+ 72 - 0
app/Module/Farm/Validations/CropPlantValidation.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Module\Farm\Validations;
+
+use App\Module\Farm\Validators\LandOwnershipValidator;
+use App\Module\Farm\Validators\LandStatusValidator;
+use App\Module\Farm\Validators\SeedItemValidator;
+use App\Module\Farm\Validators\SeedOwnershipValidator;
+use UCore\ValidationCore;
+
+/**
+ * 作物种植验证类
+ * 
+ * 用于验证作物种植操作的输入数据,包括用户ID、土地ID、种子ID等
+ */
+class CropPlantValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,land_id,item_id', 'required'
+            ],
+            [
+                'user_id,land_id,item_id', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            [
+                'item_instance_id', 'integer', 'min' => 0,
+                'msg' => '{attr}必须是大于等于0的整数'
+            ],
+            // 验证土地是否属于用户
+            [
+                'land_id', new LandOwnershipValidator($this, ['user_id', 'land']),
+                'msg' => '土地不存在或不属于当前用户'
+            ],
+            // 验证土地状态是否允许种植
+            [
+                'land_id', new LandStatusValidator($this, ['land']),
+                'msg' => '土地状态不允许种植'
+            ],
+            // 验证物品是否为种子类型
+            [
+                'item_id', new SeedItemValidator($this, ['seed_item']),
+                'msg' => '物品不是种子类型'
+            ],
+            // 验证用户是否拥有该种子
+            [
+                'item_id', new SeedOwnershipValidator($this, ['user_id', 'item_instance_id']),
+                'msg' => '您没有该种子'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [
+            'item_instance_id' => 0
+        ];
+    }
+}

+ 66 - 0
app/Module/Farm/Validations/CropRemoveValidation.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Module\Farm\Validations;
+
+use App\Module\Farm\Validators\LandOwnershipValidator;
+use App\Module\Farm\Validators\RemovableStatusValidator;
+use App\Module\Farm\Validators\RemoveToolValidator;
+use UCore\ValidationCore;
+
+/**
+ * 作物铲除验证类
+ * 
+ * 用于验证作物铲除操作的输入数据,包括用户ID、土地ID和工具ID
+ */
+class CropRemoveValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,land_id', 'required'
+            ],
+            [
+                'user_id,land_id', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            [
+                'tool_item_id', 'integer', 'min' => 0,
+                'msg' => '{attr}必须是大于等于0的整数'
+            ],
+            // 验证土地是否属于用户
+            [
+                'land_id', new LandOwnershipValidator($this, ['user_id', 'land']),
+                'msg' => '土地不存在或不属于当前用户'
+            ],
+            // 验证土地状态是否可铲除
+            [
+                'land_id', new RemovableStatusValidator($this, ['land']),
+                'msg' => '土地状态不允许铲除作物'
+            ],
+            // 验证铲除工具(如果提供)
+            [
+                'tool_item_id', new RemoveToolValidator($this, ['user_id']),
+                'msg' => '铲除工具验证失败'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [
+            'tool_item_id' => 0
+        ];
+    }
+}

+ 64 - 0
app/Module/Farm/Validators/HarvestableStatusValidator.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\Farm\Enums\LAND_STATUS;
+use UCore\Validator;
+
+/**
+ * 可收获状态验证器
+ * 
+ * 验证土地状态是否允许收获
+ */
+class HarvestableStatusValidator extends Validator
+{
+    /**
+     * 验证可收获状态
+     *
+     * @param mixed $value 土地ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取土地对象的键名
+        $landKey = $this->args[0] ?? 'land';
+        $land = $this->validation->$landKey ?? null;
+
+        if (!$land) {
+            $this->addError('土地信息不存在,请先验证土地归属');
+            return false;
+        }
+
+        try {
+            // 检查土地状态是否为可收获状态
+            if ($land->status !== LAND_STATUS::HARVESTABLE->value) {
+                $statusName = $this->getStatusName($land->status);
+                $this->addError("土地当前状态为{$statusName},不允许收获");
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证收获状态时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 获取状态名称
+     *
+     * @param int $status 状态值
+     * @return string 状态名称
+     */
+    private function getStatusName(int $status): string
+    {
+        return match ($status) {
+            LAND_STATUS::IDLE->value => '空闲',
+            LAND_STATUS::PLANTING->value => '种植中',
+            LAND_STATUS::HARVESTABLE->value => '可收获',
+            LAND_STATUS::WITHERED->value => '枯萎',
+            default => '未知状态'
+        };
+    }
+}

+ 64 - 0
app/Module/Farm/Validators/LandStatusValidator.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\Farm\Enums\LAND_STATUS;
+use UCore\Validator;
+
+/**
+ * 土地状态验证器
+ * 
+ * 验证土地状态是否允许进行特定操作
+ */
+class LandStatusValidator extends Validator
+{
+    /**
+     * 验证土地状态
+     *
+     * @param mixed $value 土地ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取土地对象的键名
+        $landKey = $this->args[0] ?? 'land';
+        $land = $this->validation->$landKey ?? null;
+
+        if (!$land) {
+            $this->addError('土地信息不存在,请先验证土地归属');
+            return false;
+        }
+
+        try {
+            // 检查土地状态是否为空闲状态(允许种植)
+            if ($land->status !== LAND_STATUS::IDLE->value) {
+                $statusName = $this->getStatusName($land->status);
+                $this->addError("土地当前状态为{$statusName},不允许种植");
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证土地状态时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 获取状态名称
+     *
+     * @param int $status 状态值
+     * @return string 状态名称
+     */
+    private function getStatusName(int $status): string
+    {
+        return match ($status) {
+            LAND_STATUS::IDLE->value => '空闲',
+            LAND_STATUS::PLANTING->value => '种植中',
+            LAND_STATUS::HARVESTABLE->value => '可收获',
+            LAND_STATUS::WITHERED->value => '枯萎',
+            default => '未知状态'
+        };
+    }
+}

+ 71 - 0
app/Module/Farm/Validators/RemovableStatusValidator.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\Farm\Enums\LAND_STATUS;
+use UCore\Validator;
+
+/**
+ * 可铲除状态验证器
+ * 
+ * 验证土地状态是否允许铲除作物
+ */
+class RemovableStatusValidator extends Validator
+{
+    /**
+     * 验证可铲除状态
+     *
+     * @param mixed $value 土地ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取土地对象的键名
+        $landKey = $this->args[0] ?? 'land';
+        $land = $this->validation->$landKey ?? null;
+
+        if (!$land) {
+            $this->addError('土地信息不存在,请先验证土地归属');
+            return false;
+        }
+
+        try {
+            // 检查土地状态是否允许铲除作物
+            // 只有种植中、可收获、枯萎状态才能铲除
+            $allowedStatuses = [
+                LAND_STATUS::PLANTING->value,
+                LAND_STATUS::HARVESTABLE->value,
+                LAND_STATUS::WITHERED->value
+            ];
+
+            if (!in_array($land->status, $allowedStatuses)) {
+                $statusName = $this->getStatusName($land->status);
+                $this->addError("土地当前状态为{$statusName},不允许铲除作物");
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证铲除状态时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 获取状态名称
+     *
+     * @param int $status 状态值
+     * @return string 状态名称
+     */
+    private function getStatusName(int $status): string
+    {
+        return match ($status) {
+            LAND_STATUS::IDLE->value => '空闲',
+            LAND_STATUS::PLANTING->value => '种植中',
+            LAND_STATUS::HARVESTABLE->value => '可收获',
+            LAND_STATUS::WITHERED->value => '枯萎',
+            default => '未知状态'
+        };
+    }
+}

+ 61 - 0
app/Module/Farm/Validators/RemoveToolValidator.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\GameItems\Services\ItemService;
+use UCore\Validator;
+
+/**
+ * 铲除工具验证器
+ * 
+ * 验证用户是否拥有铲除工具(如果需要)
+ */
+class RemoveToolValidator extends Validator
+{
+    /**
+     * 验证铲除工具
+     *
+     * @param mixed $value 工具物品ID
+     * @param array $data 包含用户ID的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $toolItemId = (int)$value;
+        
+        // 如果没有提供工具ID,则跳过验证(允许不使用工具)
+        if ($toolItemId <= 0) {
+            return true;
+        }
+
+        // 从 args 获取用户ID的键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $userId = $data[$userIdKey] ?? null;
+
+        if (!$userId) {
+            $this->addError('用户ID不能为空');
+            return false;
+        }
+
+        try {
+            // 检查用户是否拥有该工具
+            $userItems = ItemService::getUserItems($userId, ['item_id' => $toolItemId]);
+            
+            if ($userItems->isEmpty()) {
+                $this->addError('您没有该铲除工具');
+                return false;
+            }
+
+            $totalQuantity = $userItems->sum('quantity');
+            if ($totalQuantity <= 0) {
+                $this->addError('铲除工具数量不足');
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证铲除工具时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 58 - 0
app/Module/Farm/Validators/SeedItemValidator.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\GameItems\Enums\ITEM_TYPE;
+use App\Module\GameItems\Models\Item;
+use App\Module\Farm\Models\FarmSeed;
+use UCore\Validator;
+
+/**
+ * 种子物品验证器
+ *
+ * 验证物品是否为种子类型且配置正确
+ */
+class SeedItemValidator extends Validator
+{
+    /**
+     * 验证种子物品
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+
+        try {
+            // 获取物品信息
+            $item = Item::find($itemId);
+
+            if (!$item) {
+                $this->addError("物品不存在");
+                return false;
+            }
+
+
+
+            // 检查种子是否有配置信息
+            $seedConfig = FarmSeed::where('item_id', $itemId)->first();
+            if (!$seedConfig) {
+                $this->addError("种子没有配置信息");
+                return false;
+            }
+
+            // 将种子信息保存到验证对象中,供后续使用
+            $seedItemKey = $this->args[0] ?? null;
+            if ($seedItemKey) {
+                $this->validation->$seedItemKey = $item;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证种子物品时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 74 - 0
app/Module/Farm/Validators/SeedOwnershipValidator.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Module\Farm\Validators;
+
+use App\Module\GameItems\Services\ItemService;
+use UCore\Validator;
+
+/**
+ * 种子归属验证器
+ * 
+ * 验证用户是否拥有指定的种子
+ */
+class SeedOwnershipValidator extends Validator
+{
+    /**
+     * 验证种子归属
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含用户ID和实例ID的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+        
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $instanceIdKey = $this->args[1] ?? 'item_instance_id';
+
+        $userId = $data[$userIdKey] ?? null;
+        $instanceId = $data[$instanceIdKey] ?? null;
+
+        if (!$userId) {
+            $this->addError('用户ID不能为空');
+            return false;
+        }
+
+        try {
+            // 检查用户是否拥有该种子
+            $userItems = ItemService::getUserItems($userId, ['item_id' => $itemId]);
+            
+            if ($userItems->isEmpty()) {
+                $this->addError('您没有该种子');
+                return false;
+            }
+
+            // 如果指定了实例ID,检查特定实例
+            if ($instanceId > 0) {
+                $userItem = ItemService::getUserItem($userId, $itemId, $instanceId);
+                if (!$userItem) {
+                    $this->addError('指定的种子实例不存在');
+                    return false;
+                }
+
+                if ($userItem->quantity <= 0) {
+                    $this->addError('种子数量不足');
+                    return false;
+                }
+            } else {
+                // 检查总数量
+                $totalQuantity = $userItems->sum('quantity');
+                if ($totalQuantity <= 0) {
+                    $this->addError('种子数量不足');
+                    return false;
+                }
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证种子归属时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 67 - 0
app/Module/GameItems/Validations/ChestOpenValidation.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Module\GameItems\Validations;
+
+use App\Module\GameItems\Validators\ChestItemValidator;
+use App\Module\GameItems\Validators\ChestOwnershipValidator;
+use App\Module\GameItems\Validators\ChestOpenCostValidator;
+use UCore\ValidationCore;
+
+/**
+ * 宝箱开启验证类
+ * 
+ * 用于验证宝箱开启操作的输入数据,包括用户ID、宝箱ID和实例ID
+ */
+class ChestOpenValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,item_id', 'required'
+            ],
+            [
+                'user_id,item_id', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            [
+                'instance_id', 'integer', 'min' => 0,
+                'msg' => '{attr}必须是大于等于0的整数'
+            ],
+            // 验证物品是否为宝箱类型
+            [
+                'item_id', new ChestItemValidator($this, ['chest_item']),
+                'msg' => '物品验证失败'
+            ],
+            // 验证用户是否拥有该宝箱
+            [
+                'item_id', new ChestOwnershipValidator($this, ['user_id', 'instance_id']),
+                'msg' => '宝箱不存在或不属于当前用户'
+            ],
+            // 验证开启消耗是否充足
+            [
+                'item_id', new ChestOpenCostValidator($this, ['user_id', 'quantity']),
+                'msg' => '开启消耗不足'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [
+            'instance_id' => 0,
+            'quantity' => 1
+        ];
+    }
+}

+ 56 - 0
app/Module/GameItems/Validations/ItemCraftValidation.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Module\GameItems\Validations;
+
+use App\Module\GameItems\Validators\CraftRecipeValidator;
+use App\Module\GameItems\Validators\CraftMaterialsValidator;
+use UCore\ValidationCore;
+
+/**
+ * 物品合成验证类
+ * 
+ * 用于验证物品合成操作的输入数据,包括用户ID、配方ID和数量
+ */
+class ItemCraftValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,recipe_id', 'required'
+            ],
+            [
+                'user_id,recipe_id,quantity', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            // 验证配方是否存在且可用
+            [
+                'recipe_id', new CraftRecipeValidator($this, ['recipe']),
+                'msg' => '配方验证失败'
+            ],
+            // 验证合成材料是否充足
+            [
+                'recipe_id', new CraftMaterialsValidator($this, ['user_id', 'quantity', 'recipe']),
+                'msg' => '合成材料不足'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [
+            'quantity' => 1
+        ];
+    }
+}

+ 61 - 0
app/Module/GameItems/Validations/ItemDismantleValidation.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Module\GameItems\Validations;
+
+use App\Module\GameItems\Validators\DismantleItemValidator;
+use App\Module\GameItems\Validators\ItemOwnershipValidator;
+use UCore\ValidationCore;
+
+/**
+ * 物品分解验证类
+ * 
+ * 用于验证物品分解操作的输入数据,包括用户ID、物品ID和数量
+ */
+class ItemDismantleValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,item_id', 'required'
+            ],
+            [
+                'user_id,item_id,quantity', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            [
+                'instance_id', 'integer', 'min' => 0,
+                'msg' => '{attr}必须是大于等于0的整数'
+            ],
+            // 验证物品是否可分解
+            [
+                'item_id', new DismantleItemValidator($this, ['dismantle_item']),
+                'msg' => '物品验证失败'
+            ],
+            // 验证用户是否拥有该物品
+            [
+                'item_id', new ItemOwnershipValidator($this, ['user_id', 'instance_id', 'quantity']),
+                'msg' => '物品不存在或数量不足'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [
+            'instance_id' => 0,
+            'quantity' => 1
+        ];
+    }
+}

+ 62 - 0
app/Module/GameItems/Validators/ChestItemValidator.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Enums\ITEM_TYPE;
+use App\Module\GameItems\Models\Item;
+use App\Module\GameItems\Models\ItemChestContent;
+use UCore\Validator;
+
+/**
+ * 宝箱物品验证器
+ * 
+ * 验证物品是否为宝箱类型且配置正确
+ */
+class ChestItemValidator extends Validator
+{
+    /**
+     * 验证宝箱物品
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+
+        try {
+            // 获取物品信息
+            $item = Item::find($itemId);
+            
+            if (!$item) {
+                $this->addError("物品不存在");
+                return false;
+            }
+
+            // 检查是否为宝箱类型
+            if ($item->type !== ITEM_TYPE::OPENABLE->value) {
+                $this->addError("该物品不是宝箱类型");
+                return false;
+            }
+
+            // 检查宝箱是否有内容配置
+            $chestContents = ItemChestContent::where('chest_id', $itemId)->exists();
+            if (!$chestContents) {
+                $this->addError("宝箱没有配置内容");
+                return false;
+            }
+
+            // 将宝箱信息保存到验证对象中,供后续使用
+            $chestItemKey = $this->args[0] ?? null;
+            if ($chestItemKey) {
+                $this->validation->$chestItemKey = $item;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证宝箱物品时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 61 - 0
app/Module/GameItems/Validators/ChestOpenCostValidator.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Logics\ChestOpenCostLogic;
+use App\Module\GameItems\Repositorys\ItemChestOpenCostRepository;
+use App\Module\GameItems\Logics\Item as ItemLogic;
+use UCore\Validator;
+
+/**
+ * 宝箱开启消耗验证器
+ * 
+ * 验证用户是否有足够的资源开启宝箱
+ */
+class ChestOpenCostValidator extends Validator
+{
+    /**
+     * 验证宝箱开启消耗
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含用户ID和数量的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+        
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $quantityKey = $this->args[1] ?? 'quantity';
+
+        $userId = $data[$userIdKey] ?? null;
+        $quantity = $data[$quantityKey] ?? 1;
+
+        if (!$userId) {
+            $this->addError('用户ID不能为空');
+            return false;
+        }
+
+        try {
+            // 创建消耗逻辑实例
+            $chestOpenCostLogic = new ChestOpenCostLogic(
+                app(ItemChestOpenCostRepository::class),
+                new ItemLogic()
+            );
+
+            // 验证宝箱开启消耗
+            list($isValid, $message, $costDetails) = $chestOpenCostLogic->validateChestOpenCosts($userId, $itemId, $quantity);
+            
+            if (!$isValid) {
+                $this->addError($message);
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证宝箱开启消耗时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 58 - 0
app/Module/GameItems/Validators/ChestOwnershipValidator.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Services\ItemService;
+use UCore\Validator;
+
+/**
+ * 宝箱归属验证器
+ * 
+ * 验证用户是否拥有指定的宝箱
+ */
+class ChestOwnershipValidator extends Validator
+{
+    /**
+     * 验证宝箱归属
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含用户ID和实例ID的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+        
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $instanceIdKey = $this->args[1] ?? 'instance_id';
+
+        $userId = $data[$userIdKey] ?? null;
+        $instanceId = $data[$instanceIdKey] ?? null;
+
+        if (!$userId) {
+            $this->addError('用户ID不能为空');
+            return false;
+        }
+
+        try {
+            // 检查用户是否拥有该宝箱
+            $userItem = ItemService::getUserItem($userId, $itemId, $instanceId);
+            
+            if (!$userItem) {
+                $this->addError('您没有该宝箱或宝箱不存在');
+                return false;
+            }
+
+            if ($userItem->quantity <= 0) {
+                $this->addError('宝箱数量不足');
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证宝箱归属时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 60 - 0
app/Module/GameItems/Validators/CraftMaterialsValidator.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Services\ItemService;
+use UCore\Validator;
+
+/**
+ * 合成材料验证器
+ * 
+ * 验证用户是否有足够的材料进行合成
+ */
+class CraftMaterialsValidator extends Validator
+{
+    /**
+     * 验证合成材料
+     *
+     * @param mixed $value 配方ID
+     * @param array $data 包含用户ID和数量的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $quantityKey = $this->args[1] ?? 'quantity';
+        $recipeKey = $this->args[2] ?? 'recipe';
+
+        $userId = $data[$userIdKey] ?? null;
+        $quantity = $data[$quantityKey] ?? 1;
+        $recipe = $this->validation->$recipeKey ?? null;
+
+        if (!$userId || !$recipe) {
+            $this->addError('验证合成材料时缺少必要参数');
+            return false;
+        }
+
+        try {
+            // 检查每个材料是否充足
+            foreach ($recipe->materials as $material) {
+                $requiredQuantity = $material->quantity * $quantity;
+                
+                // 获取用户拥有的该物品数量
+                $userItems = ItemService::getUserItems($userId, ['item_id' => $material->item_id]);
+                $totalQuantity = $userItems->sum('quantity');
+
+                if ($totalQuantity < $requiredQuantity) {
+                    $itemName = $material->item->name ?? "物品ID:{$material->item_id}";
+                    $this->addError("材料不足:{$itemName},需要{$requiredQuantity}个,当前拥有{$totalQuantity}个");
+                    return false;
+                }
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证合成材料时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 52 - 0
app/Module/GameItems/Validators/CraftRecipeValidator.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Models\ItemCraftRecipe;
+use UCore\Validator;
+
+/**
+ * 合成配方验证器
+ * 
+ * 验证合成配方是否存在且可用
+ */
+class CraftRecipeValidator extends Validator
+{
+    /**
+     * 验证合成配方
+     *
+     * @param mixed $value 配方ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $recipeId = (int)$value;
+
+        try {
+            // 获取配方信息
+            $recipe = ItemCraftRecipe::find($recipeId);
+            
+            if (!$recipe) {
+                $this->addError("合成配方不存在");
+                return false;
+            }
+
+            if (!$recipe->is_active) {
+                $this->addError("该合成配方已禁用");
+                return false;
+            }
+
+            // 将配方信息保存到验证对象中,供后续使用
+            $recipeKey = $this->args[0] ?? null;
+            if ($recipeKey) {
+                $this->validation->$recipeKey = $recipe;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证合成配方时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 58 - 0
app/Module/GameItems/Validators/DismantleItemValidator.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Models\Item;
+use App\Module\GameItems\Models\ItemDismantleConfig;
+use UCore\Validator;
+
+/**
+ * 分解物品验证器
+ * 
+ * 验证物品是否可以分解且配置正确
+ */
+class DismantleItemValidator extends Validator
+{
+    /**
+     * 验证分解物品
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+
+        try {
+            // 获取物品信息
+            $item = Item::find($itemId);
+            
+            if (!$item) {
+                $this->addError("物品不存在");
+                return false;
+            }
+
+            // 检查物品是否可分解
+            $dismantleConfig = ItemDismantleConfig::where('item_id', $itemId)
+                ->where('is_active', true)
+                ->first();
+                
+            if (!$dismantleConfig) {
+                $this->addError("该物品不可分解或分解配置已禁用");
+                return false;
+            }
+
+            // 将物品信息保存到验证对象中,供后续使用
+            $dismantleItemKey = $this->args[0] ?? null;
+            if ($dismantleItemKey) {
+                $this->validation->$dismantleItemKey = $item;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证分解物品时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 75 - 0
app/Module/GameItems/Validators/ItemOwnershipValidator.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace App\Module\GameItems\Validators;
+
+use App\Module\GameItems\Services\ItemService;
+use UCore\Validator;
+
+/**
+ * 物品归属验证器
+ * 
+ * 验证用户是否拥有指定的物品和数量
+ */
+class ItemOwnershipValidator extends Validator
+{
+    /**
+     * 验证物品归属
+     *
+     * @param mixed $value 物品ID
+     * @param array $data 包含用户ID、实例ID和数量的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $itemId = (int)$value;
+        
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $instanceIdKey = $this->args[1] ?? 'instance_id';
+        $quantityKey = $this->args[2] ?? 'quantity';
+
+        $userId = $data[$userIdKey] ?? null;
+        $instanceId = $data[$instanceIdKey] ?? 0;
+        $requiredQuantity = $data[$quantityKey] ?? 1;
+
+        if (!$userId) {
+            $this->addError('用户ID不能为空');
+            return false;
+        }
+
+        try {
+            // 如果指定了实例ID,检查特定实例
+            if ($instanceId > 0) {
+                $userItem = ItemService::getUserItem($userId, $itemId, $instanceId);
+                if (!$userItem) {
+                    $this->addError('指定的物品实例不存在');
+                    return false;
+                }
+
+                if ($userItem->quantity < $requiredQuantity) {
+                    $this->addError("物品数量不足,需要{$requiredQuantity}个,当前拥有{$userItem->quantity}个");
+                    return false;
+                }
+            } else {
+                // 检查用户是否拥有足够数量的该物品
+                $userItems = ItemService::getUserItems($userId, ['item_id' => $itemId]);
+                
+                if ($userItems->isEmpty()) {
+                    $this->addError('您没有该物品');
+                    return false;
+                }
+
+                $totalQuantity = $userItems->sum('quantity');
+                if ($totalQuantity < $requiredQuantity) {
+                    $this->addError("物品数量不足,需要{$requiredQuantity}个,当前拥有{$totalQuantity}个");
+                    return false;
+                }
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证物品归属时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 7 - 11
app/Module/Shop/AdminControllers/ShopItemController.php

@@ -2,12 +2,8 @@
 
 namespace App\Module\Shop\AdminControllers;
 
-use App\Module\Fund\Models\Currency;
+use App\Module\Fund\Models\FundCurrencyModel;
 use App\Module\GameItems\Models\Item;
-use App\Module\Shop\AdminControllers\Helper\FilterHelper;
-use App\Module\Shop\AdminControllers\Helper\FormHelper;
-use App\Module\Shop\AdminControllers\Helper\GridHelper;
-use App\Module\Shop\AdminControllers\Helper\ShowHelper;
 use App\Module\Shop\Models\ShopCategory;
 use App\Module\Shop\Repositorys\ShopItemRepository;
 use Dcat\Admin\Form;
@@ -42,10 +38,10 @@ class ShopItemController extends AdminController
             $grid->column('image', '商品图片')->image('', 50, 50);
             $grid->column('category.name', '所属分类');
             $grid->column('item.name', '关联物品');
-            $grid->column('item_quantity', '物品数量');
+            $grid->column('item_quantity', '物品数量')->copyable();
             $grid->column('price', '价格');
             $grid->column('currency_id', '货币类型')->display(function ($currencyId) {
-                $currency = Currency::find($currencyId);
+                $currency = FundCurrencyModel::find($currencyId);
                 return $currency ? $currency->name : '未知';
             });
             $grid->column('max_buy', '购买限制')->display(function ($maxBuy) {
@@ -67,7 +63,7 @@ class ShopItemController extends AdminController
                     Item::pluck('name', 'id')
                 );
                 $filter->equal('currency_id', '货币类型')->select(
-                    Currency::pluck('name', 'id')
+                    FundCurrencyModel::pluck('name', 'id')
                 );
                 $filter->equal('is_active', '状态')->select([
                     0 => '禁用',
@@ -105,7 +101,7 @@ class ShopItemController extends AdminController
             $show->field('item_quantity', '物品数量');
             $show->field('price', '价格');
             $show->field('currency_id', '货币类型')->as(function ($currencyId) {
-                $currency = Currency::find($currencyId);
+                $currency = FundCurrencyModel::find($currencyId);
                 return $currency ? $currency->name : '未知';
             });
             $show->field('max_buy', '购买限制')->as(function ($maxBuy) {
@@ -143,14 +139,14 @@ class ShopItemController extends AdminController
             $form->number('item_quantity', '物品数量')->min(1)->default(1)->required();
             $form->number('price', '价格')->min(0)->required();
             $form->select('currency_id', '货币类型')
-                ->options(Currency::pluck('name', 'id'))
+                ->options(FundCurrencyModel::pluck('name', 'id'))
                 ->required();
             $form->number('max_buy', '购买限制')->min(0)->default(0)->help('0表示无限制');
             $form->number('sort_order', '排序权重')->default(0)->help('数字越小越靠前');
             $form->switch('is_active', '状态')->default(true);
             $form->datetime('start_time', '上架时间')->help('留空表示不限制上架时间');
             $form->datetime('end_time', '下架时间')->help('留空表示不限制下架时间');
-            
+
             $form->display('created_at', '创建时间');
             $form->display('updated_at', '更新时间');
         });

+ 60 - 0
app/Module/Shop/Validations/ShopBuyValidation.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Module\Shop\Validations;
+
+use App\Module\Shop\Validators\ShopItemValidator;
+use App\Module\Shop\Validators\ShopBuyLimitValidator;
+use App\Module\Shop\Validators\ShopFundValidator;
+use UCore\ValidationCore;
+
+/**
+ * 商店购买验证类
+ * 
+ * 用于验证商店购买操作的输入数据,包括用户ID、商品ID和购买数量
+ */
+class ShopBuyValidation extends ValidationCore
+{
+    /**
+     * 验证规则
+     *
+     * @param array $rules 自定义规则
+     * @return array
+     */
+    public function rules($rules = []): array
+    {
+        return [
+            [
+                'user_id,good_id,number', 'required'
+            ],
+            [
+                'user_id,good_id,number', 'integer', 'min' => 1,
+                'msg' => '{attr}必须是大于0的整数'
+            ],
+            // 验证商品是否存在且可购买
+            [
+                'good_id', new ShopItemValidator($this, ['shop_item']),
+                'msg' => '商品验证失败'
+            ],
+            // 验证购买限制
+            [
+                'good_id', new ShopBuyLimitValidator($this, ['user_id', 'number', 'shop_item']),
+                'msg' => '购买限制验证失败'
+            ],
+            // 验证用户资金是否充足
+            [
+                'good_id', new ShopFundValidator($this, ['user_id', 'number', 'shop_item']),
+                'msg' => '资金不足'
+            ]
+        ];
+    }
+
+    /**
+     * 设置默认值
+     *
+     * @return array
+     */
+    public function default(): array
+    {
+        return [];
+    }
+}

+ 56 - 0
app/Module/Shop/Validators/ShopBuyLimitValidator.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Module\Shop\Validators;
+
+use UCore\Validator;
+
+/**
+ * 商店购买限制验证器
+ * 
+ * 验证用户购买数量是否超出限制
+ */
+class ShopBuyLimitValidator extends Validator
+{
+    /**
+     * 验证购买限制
+     *
+     * @param mixed $value 商品ID
+     * @param array $data 包含用户ID和购买数量的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $numberKey = $this->args[1] ?? 'number';
+        $shopItemKey = $this->args[2] ?? 'shop_item';
+
+        $userId = $data[$userIdKey] ?? null;
+        $number = $data[$numberKey] ?? null;
+        $shopItem = $this->validation->$shopItemKey ?? null;
+
+        if (!$userId || !$number || !$shopItem) {
+            $this->addError('验证购买限制时缺少必要参数');
+            return false;
+        }
+
+        try {
+            // 检查购买限制
+            if ($shopItem->max_buy > 0) {
+                // 获取用户已购买数量
+                $boughtCount = $shopItem->getUserBoughtCount($userId);
+
+                if ($boughtCount + $number > $shopItem->max_buy) {
+                    $remainingCount = $shopItem->max_buy - $boughtCount;
+                    $this->addError("超出购买限制,最多还能购买{$remainingCount}个");
+                    return false;
+                }
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证购买限制时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 61 - 0
app/Module/Shop/Validators/ShopFundValidator.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Module\Shop\Validators;
+
+use App\Module\Fund\Services\AccountService;
+use UCore\Validator;
+
+/**
+ * 商店资金验证器
+ * 
+ * 验证用户资金是否充足
+ */
+class ShopFundValidator extends Validator
+{
+    /**
+     * 验证用户资金
+     *
+     * @param mixed $value 商品ID
+     * @param array $data 包含用户ID和购买数量的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        // 从 args 获取参数键名
+        $userIdKey = $this->args[0] ?? 'user_id';
+        $numberKey = $this->args[1] ?? 'number';
+        $shopItemKey = $this->args[2] ?? 'shop_item';
+
+        $userId = $data[$userIdKey] ?? null;
+        $number = $data[$numberKey] ?? null;
+        $shopItem = $this->validation->$shopItemKey ?? null;
+
+        if (!$userId || !$number || !$shopItem) {
+            $this->addError('验证资金时缺少必要参数');
+            return false;
+        }
+
+        try {
+            // 计算总价
+            $totalPrice = $shopItem->price * $number;
+
+            // 获取用户账户余额
+            $userAccount = AccountService::getUserAccount($userId, $shopItem->currency_id);
+            
+            if (!$userAccount) {
+                $this->addError('用户账户不存在');
+                return false;
+            }
+
+            if ($userAccount->balance < $totalPrice) {
+                $this->addError('余额不足,需要' . $totalPrice . ',当前余额' . $userAccount->balance);
+                return false;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证资金时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 52 - 0
app/Module/Shop/Validators/ShopItemValidator.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Module\Shop\Validators;
+
+use App\Module\Shop\Models\ShopItem;
+use UCore\Validator;
+
+/**
+ * 商品验证器
+ * 
+ * 验证商品是否存在且可购买
+ */
+class ShopItemValidator extends Validator
+{
+    /**
+     * 验证商品
+     *
+     * @param mixed $value 商品ID
+     * @param array $data 包含其他数据的数组
+     * @return bool 验证是否通过
+     */
+    public function validate(mixed $value, array $data): bool
+    {
+        $goodId = (int)$value;
+
+        try {
+            // 获取商品信息
+            $shopItem = ShopItem::find($goodId);
+            
+            if (!$shopItem) {
+                $this->addError("商品不存在");
+                return false;
+            }
+
+            if (!$shopItem->is_active) {
+                $this->addError("该商品已下架");
+                return false;
+            }
+
+            // 将商品信息保存到验证对象中,供后续使用
+            $shopItemKey = $this->args[0] ?? null;
+            if ($shopItemKey) {
+                $this->validation->$shopItemKey = $shopItem;
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            $this->addError('验证商品时发生错误: ' . $e->getMessage());
+            return false;
+        }
+    }
+}

+ 6 - 2
app/Providers/AppServiceProvider.php

@@ -20,8 +20,12 @@ class AppServiceProvider extends ServiceProvider
      */
     public function boot(): void
     {
-        //
-//        Carbon::setTi('Asia/Shanghai');
+        // 注册自定义日志驱动
+        $this->app->make('log')->extend('size_rotating_daily', function ($app, $config) {
+            $logger = new \App\Logging\SizeRotatingDailyLogger();
+            return $logger($config);
+        });
 
+//        Carbon::setTi('Asia/Shanghai');
     }
 }

+ 11 - 0
config/logging.php

@@ -74,6 +74,17 @@ return [
             'permission' => 0777,
         ],
 
+        'size_rotating_daily' => [
+            'driver' => 'size_rotating_daily',
+            'path' => storage_path('logs/laravel.log'),
+            'level' => env('LOG_LEVEL', 'debug'),
+            'days' => env('LOG_DAILY_DAYS', 14),
+            'max_file_size' => env('LOG_MAX_FILE_SIZE', '100K'),
+            'permission' => 0777,
+            'locking' => false,
+            'replace_placeholders' => true,
+        ],
+
         'slack' => [
             'driver' => 'slack',
             'url' => env('LOG_SLACK_WEBHOOK_URL'),