Selaa lähdekoodia

修复后台系统配置页面报错

- 修复 ConfigController 中 AppConfig 类的导入路径错误(Repositories -> Repositorys)
- 修复重定向路由名称错误(config.index -> system-config.index)
- 修复 ToConfigAdmin 中的路由名称错误(config_admin.index -> system-configs-admin.index)
- 页面现在可以正常加载并显示系统配置列表
AI Assistant 6 kuukautta sitten
vanhempi
commit
7c15ee4767

+ 139 - 0
AiWork/202506/201823-Transfer模块手续费异常修复.md

@@ -0,0 +1,139 @@
+# Transfer模块手续费异常修复
+
+**时间**: 2025年06月20日 18:23  
+**任务**: 修复Transfer模块手续费计算异常问题
+
+## 问题描述
+
+用户反馈订单67的手续费异常:
+- 用户39075房屋等级为4,应该收取30%手续费
+- 实际只收了1%手续费(fee_rate: 0.0100, fee_amount: 10.0000)
+- 转出金额10000,应该收取3000手续费
+
+## 问题分析
+
+### 1. 数据验证
+```sql
+-- 用户房屋等级确认
+SELECT * FROM kku_farm_users WHERE user_id = 39075;
+-- 结果:house_level = 4
+
+-- 手续费配置确认  
+SELECT * FROM kku_urs_promotion_transfer_fee_configs WHERE house_level = 4 AND transfer_type = 'out';
+-- 结果:fee_rate = 0.3000 (30%)
+
+-- 订单记录确认
+SELECT * FROM kku_transfer_orders WHERE id = 67;
+-- 结果:fee_rate = 0.0100, fee_amount = 10.0000 (异常)
+```
+
+### 2. 根本原因分析
+
+通过代码审查和日志分析发现三个问题:
+
+1. **TransferApp模型问题**:
+   - `calculateOutFee`方法没有使用事件机制
+   - 直接计算手续费,不触发`FeeCalculatingEvent`事件
+
+2. **上下文传递问题**:
+   - TransferLogic调用`calculateOutFee`时没有传递用户ID
+   - URS监听器无法获取用户信息进行费率调整
+
+3. **监听器逻辑错误**:
+   - URS监听器只在费率更优惠时才应用
+   - 对于高等级用户(费率更高),监听器不会应用正确费率
+
+## 修复方案
+
+### 1. 修改TransferApp模型
+```php
+// 修改前
+public function calculateOutFee(string $amount): array
+{
+    return $this->calculateOutFeeWithExtraCharge($amount, $this->fee_out_rate, $this->fee_out_min, $this->fee_out_max);
+}
+
+// 修改后  
+public function calculateOutFee(string $amount, array $context = []): array
+{
+    // 使用FeeService来计算手续费,这样可以触发事件机制
+    return \App\Module\Transfer\Services\FeeService::calculateOutFee($this, $amount, $context);
+}
+```
+
+### 2. 修改TransferLogic传递用户ID
+```php
+// 修改前
+$feeInfo = $app->calculateOutFee($amount);
+
+// 修改后
+$feeInfo = $app->calculateOutFee($amount, ['user_id' => $data['user_id']]);
+```
+
+### 3. 修正URS监听器逻辑
+```php
+// 修改前:只在更优惠时应用
+if ($ursFeeRate < $event->feeRate) {
+    // 应用优惠费率
+}
+
+// 修改后:应用正确费率
+if ($ursFeeRate !== $event->feeRate) {
+    // 应用URS费率(无论是否更优惠)
+}
+```
+
+## 测试验证
+
+### 1. 功能测试
+创建测试脚本验证修复效果:
+```bash
+php test_transfer_fee.php
+```
+
+测试结果:
+- URS推广模块正确识别房屋等级4用户应收取30%费率
+- Transfer模块在有用户ID上下文时正确应用30%费率  
+- 事件机制正常工作,监听器成功修改费率
+
+### 2. 日志验证
+查看应用日志确认事件触发:
+```
+[2025-06-20T18:36:40] laravel.INFO: URS转出手续费已调整 {
+    "user_id":39075,
+    "old_fee_rate":0.01,
+    "new_fee_rate":0.3,
+    "adjustment_type":"increase"
+}
+```
+
+## 影响范围
+
+### 正面影响
+1. **修复手续费计算**:房屋等级用户现在收取正确费率
+2. **完善事件机制**:Transfer模块正确触发手续费计算事件
+3. **提升系统准确性**:手续费计算更加精确和可靠
+
+### 兼容性
+1. **向后兼容**:不影响现有转账功能
+2. **API兼容**:TransferApp::calculateOutFee方法保持兼容
+3. **数据兼容**:不影响历史订单数据
+
+## 提交信息
+
+**Commit**: 3751f0dd  
+**文件变更**:
+- `app/Module/Transfer/Models/TransferApp.php`
+- `app/Module/Transfer/Logics/TransferLogic.php`  
+- `app/Module/UrsPromotion/Listeners/UrsTransferFeeListener.php`
+
+## 后续建议
+
+1. **监控新订单**:观察新创建的转出订单手续费是否正确
+2. **性能监控**:关注事件机制对性能的影响
+3. **日志清理**:定期清理手续费调整日志避免日志过多
+4. **文档更新**:更新Transfer模块文档说明事件机制使用
+
+## 总结
+
+成功修复了Transfer模块手续费计算异常问题,确保用户根据房屋等级收取正确的转出手续费。修复涉及事件机制完善、上下文传递和监听器逻辑优化,提升了系统的准确性和可靠性。

+ 206 - 0
app/Console/Commands/GenerateConfigCommand.php

@@ -0,0 +1,206 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Str;
+
+class GenerateProtoRouteCommand extends Command
+{
+    protected $signature = 'proto:route {--force : 强制覆盖现有配置}';
+    protected $description = '生成 Protobuf 路由配置';
+
+    protected $protoDir = 'protophp/Uraus/Kku';
+    protected $configPath = 'config/proto_route.php';
+
+    public function handle()
+    {
+        $this->info('开始扫描 Protobuf 请求定义...');
+
+        // 获取所有模块
+        $modules = $this->scanModules();
+
+        // 生成配置数组
+        $config = $this->generateConfig($modules);
+
+        // 生成配置文件
+        $this->generateConfigFile($config);
+
+        $this->info('Protobuf 路由配置生成完成!');
+
+        // 输出提示信息
+        $this->showHandlerGuide($modules);
+
+        return Command::SUCCESS;
+    }
+
+    protected function scanModules(): array
+    {
+        $modules = [];
+        $baseDir = base_path($this->protoDir);
+
+        // 扫描 Request 目录
+        $requestDir = $baseDir . '/Request';
+        if (File::isDirectory($requestDir)) {
+            // 扫描所有 Request 开头的 PHP 文件
+            $files = File::glob($requestDir . '/Request*.php');
+            foreach ($files as $file) {
+                $className = basename($file, '.php');
+                if (strpos($className, 'Request') === 0 && $className !== 'Request') {
+                    // 解析模块和方法
+                    $this->parseRequestClass($className, $modules);
+                }
+            }
+        }
+
+        // 扫描根目录下的 Request 文件
+        $rootFiles = File::glob($baseDir . '/Request*.php');
+        foreach ($rootFiles as $file) {
+            $className = basename($file, '.php');
+            if (strpos($className, 'Request') === 0 && $className !== 'Request') {
+                // 解析模块和方法
+                $this->parseRequestClass($className, $modules);
+            }
+        }
+
+        return $modules;
+    }
+
+    /**
+     * 解析请求类名,提取模块和方法
+     *
+     * @param string $className 请求类名,如 RequestPublicLogin
+     * @param array &$modules 模块数组,按引用传递
+     * @return void
+     */
+    protected function parseRequestClass(string $className, array &$modules): void
+    {
+        // 如果是 RequestPublicLogin 格式,则直接处理
+        if ($className === 'RequestPublicLogin') {
+            if (!isset($modules['public'])) {
+                $modules['public'] = [];
+            }
+            if (!in_array('login', $modules['public'])) {
+                $modules['public'][] = 'login';
+            }
+            return;
+        }
+
+        // 如果是 Request_RequestPublicLogin 格式,则跳过
+        if (strpos($className, 'Request_Request') === 0) {
+            return;
+        }
+
+        // 移除 Request 前缀
+        $name = substr($className, strlen('Request'));
+
+        // 检查是否包含下划线,表示有方法部分
+        if (strpos($name, '_') !== false) {
+            // 格式如 Public_Login
+            list($module, $method) = explode('_', $name, 2);
+        } else {
+            // 如果是 RequestPublicLogin 这样的格式,需要特殊处理
+            if (preg_match('/^([A-Z][a-z]+)([A-Z].*)$/', $name, $matches)) {
+                $module = $matches[1]; // Public
+                $method = $matches[2]; // Login
+            } else {
+                // 其他格式,如果没有明显的分隔,则整个作为模块
+                $module = $name;
+                $method = '';
+            }
+        }
+
+        // 模块名首字母小写
+        $moduleKey = lcfirst($module);
+
+        // 方法名转为小写下划线格式
+        $methodKey = Str::snake(lcfirst($method));
+
+        // 如果方法为空,则不添加
+        if (!empty($methodKey)) {
+            if (!isset($modules[$moduleKey])) {
+                $modules[$moduleKey] = [];
+            }
+
+            if (!in_array($methodKey, $modules[$moduleKey])) {
+                $modules[$moduleKey][] = $methodKey;
+            }
+        }
+    }
+
+    protected function extractMethodsFromContent(string $content): array
+    {
+        // 不再从文件内容中提取方法,而是从类名中解析
+        return [];
+    }
+
+    protected function scanMethodFields(string $moduleDir): array
+    {
+        return []; // 不再需要扫描子目录
+    }
+
+    protected function generateConfig(array $modules): array
+    {
+        $routes = [];
+        foreach ($modules as $field => $methods) {
+            if (!empty($methods) && !empty($field)) {
+                $routes[$field] = array_values($methods);
+            }
+        }
+
+        return [
+            'routes' => $routes,
+            'generated_at' => date('P Y-m-d H:i:s'),
+            'conventions' => [
+                'handler_namespace' => 'App\\Module\\AppGame\\Handler',
+                'request_namespace' => 'Uraus\\Kku\\Request',
+                'response_namespace' => 'Uraus\\Kku\\Response'
+            ]
+        ];
+    }
+
+    protected function generateConfigFile(array $config): void
+    {
+        $configContent = "<?php\n\nreturn " . var_export($config, true) . ";\n";
+
+        $configPath = config_path('proto_route.php');
+
+        if (File::exists($configPath) && !$this->option('force')) {
+            if (!$this->confirm('配置文件已存在,是否覆盖?')) {
+                $this->warn('操作已取消');
+                return;
+            }
+        }
+
+        File::put($configPath, $configContent);
+        $this->info("配置文件已生成:{$configPath}");
+    }
+
+    protected function showHandlerGuide(array $modules): void
+    {
+        $this->info("\n按照以下约定创建处理器类:\n");
+
+        foreach ($modules as $field => $methods) {
+            if (empty($methods)) {
+                continue;
+            }
+
+            $moduleName = ucfirst($field);
+            $this->line("\n模块: {$moduleName} (字段: {$field})");
+            foreach ($methods as $method) {
+                $methodName = Str::studly($method);
+                $handlerClass = "App\\Module\\AppGame\\Handler\\{$moduleName}\\{$methodName}Handler";
+                $requestClass = "Uraus\\Kku\\Request\\Request{$moduleName}" . ($method ? "_" . Str::studly($method) : "");
+                $responseClass = "Uraus\\Kku\\Response\\Response{$moduleName}" . ($method ? "_" . Str::studly($method) : "");
+
+                $this->line("\n  方法: {$method}");
+                $this->line("  - Handler: {$handlerClass}");
+                $this->line("  - Request: {$requestClass}");
+                $this->line("  - Response: {$responseClass}");
+            }
+        }
+
+        $this->info("\n提示:处理器类会根据约定自动加载,无需手动配置。");
+    }
+}

+ 1 - 1
app/Module/System/Admin/Actions/ToConfigAdmin.php

@@ -13,7 +13,7 @@ class ToConfigAdmin extends HrefAbstractTool
     public function href(): string
     {
         // HrefAbstractTool
-        return admin_route('config_admin.index',['group'=>request('group')]);
+        return admin_route('system-configs-admin.index',['group'=>request('group')]);
     }
 
 }

+ 2 - 2
app/Module/System/AdminControllers/ConfigController.php

@@ -12,7 +12,7 @@ use App\Module\System\Admin\Actions\ConfigStringEditAction;
 use App\Module\System\Admin\Actions\ConfigSwitchEditAction;
 use App\Module\System\Admin\Actions\ConfigTimeEditAction;
 use App\Module\System\Admin\Actions\ToConfigAdmin;
-use App\Module\System\Repositories\AppConfig;
+use App\Module\System\Repositorys\AppConfig;
 use App\Module\System\Services\ConfigService;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Http\Controllers\AdminController;
@@ -65,7 +65,7 @@ class ConfigController extends AdminController
                 $helper->equalRadioVk('group2', ConfigService::getGroupKv2($group), '子分组');
 
                 if (count($filter->getConditions()) == 0) {
-                    return admin_exit(admin_redirect(admin_route('config.index', [ 'group' => '应用配置' ])));
+                    return admin_exit(admin_redirect(admin_route('system-config.index', [ 'group' => '应用配置' ])));
                 }
             });
             $grid->disableCreateButton();