Просмотр исходного кода

修复Mex模块订单撮合物品转移失败问题

问题描述:
- 订单123等卖出订单撮合失败,错误信息为'物品转移失败'
- 用户有足够的物品总数,但撮合时只能使用未冻结的物品

根本原因:
- ItemLogic::consumeNormalItem方法硬编码了is_frozen=false条件
- MexMatchLogic::transferFrozenItemsToWarehouse传递的include_frozen参数被忽略
- 导致撮合时无法消耗已冻结的物品

修复方案:
1. 修改ItemLogic::consumeNormalItem支持include_frozen参数
2. 修改ItemLogic::consumeUniqueItem支持include_frozen参数
3. 根据include_frozen参数动态构建查询条件

测试结果:
- 订单123(100个物品3)撮合成功,状态变为COMPLETED
- 订单112(1000个物品2)撮合成功,状态变为COMPLETED
- 所有待撮合订单的last_match_failure_reason已清空

影响范围:
- 仅影响物品消耗逻辑,向后兼容
- 默认行为不变(不包含冻结物品)
- 只有明确传递include_frozen=true时才包含冻结物品
AI Assistant 6 месяцев назад
Родитель
Сommit
0f47a5f1ed

+ 119 - 0
AiWork/2025年06月/21日2132-修复Mex撮合命令日志记录bug.md

@@ -0,0 +1,119 @@
+# 修复Mex撮合命令日志记录bug
+
+**时间**: 2025年06月21日 21:32  
+**任务**: 修复php artisan mex:user-buy-item-match没有正常产生日志的问题
+
+## 问题描述
+
+用户报告执行`php artisan mex:user-buy-item-match`命令时没有正常产生日志记录。
+
+## 问题分析
+
+通过代码分析和测试发现问题根源:
+
+### 1. 问题定位
+- 在`MexMatchLogic::executeUserBuyItemMatch`方法中,当没有待撮合的订单时
+- 代码查询`MexOrder`表获取待撮合订单的商品ID列表(第69-73行)
+- 如果查询结果为空数组,foreach循环不会执行
+- 因此不会调用`executeUserBuyItemMatchForItem`方法
+- 导致没有记录任何撮合日志
+
+### 2. 相同问题
+- `executeUserSellItemMatch`方法也存在相同问题
+- 当没有待撮合的卖出订单时,同样不会记录日志
+
+## 解决方案
+
+### 修复逻辑
+根据用户偏好,撮合日志应该记录所有撮合尝试(包括成功和失败的),目的是记录撮合过程本身。
+
+### 代码修改
+
+#### 1. 修复用户买入物品撮合
+在`executeUserBuyItemMatch`方法中添加:
+```php
+// 如果没有待撮合的订单,记录一条总体日志表示没有可处理的商品
+if (empty($itemIds)) {
+    $endTime = microtime(true);
+    $executionTimeMs = round(($endTime - $startTime) * 1000);
+    
+    // 记录没有待撮合订单的日志(使用商品ID 0 表示全局撮合任务)
+    MexMatchLogLogic::logMatch(
+        MatchType::USER_BUY, 
+        0, // 使用0表示全局撮合任务
+        $batchSize, 
+        [
+            'success' => true,
+            'message' => '没有待撮合的用户买入物品订单',
+            'matched_orders' => 0,
+            'total_amount' => '0.00000',
+        ], 
+        $executionTimeMs
+    );
+}
+```
+
+#### 2. 修复用户卖出物品撮合
+在`executeUserSellItemMatch`方法中添加相同逻辑,消息改为"没有待撮合的用户卖出物品订单"。
+
+### 设计说明
+- 使用`item_id = 0`表示全局撮合任务,区别于具体商品的撮合
+- 保持日志记录的一致性和完整性
+- 确保所有撮合尝试都有日志记录
+
+## 测试验证
+
+### 1. 测试没有待撮合订单的情况
+```bash
+php artisan mex:user-buy-item-match
+```
+**结果**: 成功记录日志,item_id=0,message="没有待撮合的用户买入物品订单"
+
+### 2. 测试有待撮合订单的情况
+```bash
+php artisan mex:user-sell-item-match
+```
+**结果**: 正常记录每个商品的撮合日志
+
+### 3. 数据库验证
+```sql
+SELECT * FROM kku_mex_match_logs WHERE created_at >= '2025-06-21 21:32:00' ORDER BY created_at DESC;
+```
+**结果**: 
+- ID 526: USER_BUY, item_id=0, "没有待撮合的用户买入物品订单"
+- ID 531: USER_SELL, item_id=0, "没有待撮合的用户卖出物品订单"
+
+## 文件修改
+
+### 修改的文件
+1. `app/Module/Mex/Logic/MexMatchLogic.php`
+   - 修复`executeUserBuyItemMatch`方法
+   - 修复`executeUserSellItemMatch`方法
+
+### 新增的文件
+1. `app/Module/Mex/Tests/match_logging_test.php`
+   - 撮合日志测试脚本
+   - 验证修复效果
+
+## 提交信息
+
+```
+修复Mex模块撮合命令没有正常产生日志的bug
+
+- 修复executeUserBuyItemMatch方法:当没有待撮合订单时也记录日志
+- 修复executeUserSellItemMatch方法:当没有待撮合订单时也记录日志  
+- 使用item_id=0表示全局撮合任务,区别于具体商品的撮合
+- 确保撮合日志记录所有撮合尝试,包括成功和失败的情况
+- 添加撮合日志测试脚本验证修复效果
+```
+
+## 总结
+
+成功修复了Mex模块撮合命令的日志记录问题:
+1. ✅ 解决了没有待撮合订单时不记录日志的bug
+2. ✅ 保持了日志记录的完整性和一致性
+3. ✅ 使用合理的设计(item_id=0)表示全局撮合任务
+4. ✅ 通过测试验证了修复效果
+5. ✅ 代码已提交并推送到远程仓库
+
+现在撮合命令无论是否有待撮合订单,都会正常记录日志,满足用户的监控和调试需求。

+ 29 - 12
app/Module/GameItems/Logics/Item.php

@@ -327,14 +327,22 @@ class Item
     {
         Helper::check_tr();
 
-        // 获取用户物品(排除冻结的物品)
-        $userItems = ItemUser::where('user_id', $userId)
+        // 检查是否包含冻结物品
+        $includeFrozen = $options['include_frozen'] ?? false;
+
+        // 构建查询条件
+        $query = ItemUser::where('user_id', $userId)
             ->where('item_id', $itemId)
             ->whereNull('instance_id')
-            ->where('is_frozen', false) // 只获取未冻结的物品
-            ->where('quantity', '>', 0) // 确保数量大于0
-            ->orderBy('expire_at')      // 优先消耗即将过期的物品
-            ->get();
+            ->where('quantity', '>', 0); // 确保数量大于0
+
+        // 根据include_frozen参数决定是否包含冻结物品
+        if (!$includeFrozen) {
+            $query->where('is_frozen', false); // 只获取未冻结的物品
+        }
+
+        // 获取用户物品(优先消耗即将过期的物品)
+        $userItems = $query->orderBy('expire_at')->get();
 
         // 检查物品数量是否足够
         $totalQuantity = $userItems->sum('quantity');
@@ -450,15 +458,24 @@ class Item
     {
         Helper::check_tr();
 
-        // 获取用户物品(确保未冻结)
-        $userItem = ItemUser::where('user_id', $userId)
+        // 检查是否包含冻结物品
+        $includeFrozen = $options['include_frozen'] ?? false;
+
+        // 构建查询条件
+        $query = ItemUser::where('user_id', $userId)
             ->where('item_id', $itemId)
-            ->where('instance_id', $instanceId)
-            ->where('is_frozen', false) // 只获取未冻结的物品
-            ->first();
+            ->where('instance_id', $instanceId);
+
+        // 根据include_frozen参数决定是否包含冻结物品
+        if (!$includeFrozen) {
+            $query->where('is_frozen', false); // 只获取未冻结的物品
+        }
+
+        $userItem = $query->first();
 
         if (!$userItem) {
-            throw new Exception("用户 {$userId} 没有物品实例 {$instanceId}");
+            $frozenText = $includeFrozen ? '' : '(未冻结)';
+            throw new Exception("用户 {$userId} 没有物品实例 {$instanceId}{$frozenText}");
         }
 
         // 获取来源信息

+ 165 - 0
tests/Unit/GameItems/ItemConsumeFrozenTest.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace Tests\Unit\GameItems;
+
+use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
+use App\Module\GameItems\Services\ItemService;
+use App\Module\GameItems\Models\ItemUser;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Facades\DB;
+use Tests\TestCase;
+
+/**
+ * 物品消耗冻结功能测试
+ * 
+ * 测试物品消耗时是否正确处理include_frozen参数
+ */
+class ItemConsumeFrozenTest extends TestCase
+{
+    use RefreshDatabase;
+
+    private int $testUserId = 99999;
+    private int $testItemId = 1001;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        
+        // 创建测试用户物品
+        DB::transaction(function () {
+            // 添加100个未冻结物品
+            ItemUser::create([
+                'user_id' => $this->testUserId,
+                'item_id' => $this->testItemId,
+                'instance_id' => null,
+                'quantity' => 100,
+                'is_frozen' => false,
+                'frozen_log_id' => null,
+            ]);
+
+            // 添加50个冻结物品
+            ItemUser::create([
+                'user_id' => $this->testUserId,
+                'item_id' => $this->testItemId,
+                'instance_id' => null,
+                'quantity' => 50,
+                'is_frozen' => true,
+                'frozen_log_id' => 1, // 假设的冻结日志ID
+            ]);
+        });
+    }
+
+    /**
+     * 测试不包含冻结物品的消耗(默认行为)
+     */
+    public function testConsumeItemWithoutFrozen()
+    {
+        DB::transaction(function () {
+            // 消耗80个物品,不包含冻结物品
+            $result = ItemService::consumeItem(
+                $this->testUserId,
+                $this->testItemId,
+                null,
+                80,
+                ['source_type' => 'test', 'source_id' => 1]
+            );
+
+            $this->assertTrue($result['success']);
+            $this->assertEquals($this->testItemId, $result['item_id']);
+            $this->assertEquals(80, $result['quantity']);
+
+            // 验证剩余数量:应该还有20个未冻结物品,50个冻结物品不变
+            $remainingItems = ItemUser::where('user_id', $this->testUserId)
+                ->where('item_id', $this->testItemId)
+                ->get();
+
+            $unfrozenQuantity = $remainingItems->where('is_frozen', false)->sum('quantity');
+            $frozenQuantity = $remainingItems->where('is_frozen', true)->sum('quantity');
+
+            $this->assertEquals(20, $unfrozenQuantity); // 100 - 80 = 20
+            $this->assertEquals(50, $frozenQuantity);   // 冻结物品不变
+        });
+    }
+
+    /**
+     * 测试包含冻结物品的消耗
+     */
+    public function testConsumeItemWithFrozen()
+    {
+        DB::transaction(function () {
+            // 消耗120个物品,包含冻结物品
+            $result = ItemService::consumeItem(
+                $this->testUserId,
+                $this->testItemId,
+                null,
+                120,
+                [
+                    'source_type' => 'test',
+                    'source_id' => 1,
+                    'include_frozen' => true // 包含冻结物品
+                ]
+            );
+
+            $this->assertTrue($result['success']);
+            $this->assertEquals($this->testItemId, $result['item_id']);
+            $this->assertEquals(120, $result['quantity']);
+
+            // 验证剩余数量:应该还有30个物品(100+50-120=30)
+            $remainingQuantity = ItemUser::where('user_id', $this->testUserId)
+                ->where('item_id', $this->testItemId)
+                ->sum('quantity');
+
+            $this->assertEquals(30, $remainingQuantity);
+        });
+    }
+
+    /**
+     * 测试不包含冻结物品时数量不足的情况
+     */
+    public function testConsumeItemInsufficientWithoutFrozen()
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('数量不足');
+
+        DB::transaction(function () {
+            // 尝试消耗120个物品,但只有100个未冻结物品
+            ItemService::consumeItem(
+                $this->testUserId,
+                $this->testItemId,
+                null,
+                120,
+                ['source_type' => 'test', 'source_id' => 1]
+            );
+        });
+    }
+
+    /**
+     * 测试包含冻结物品时数量足够的情况
+     */
+    public function testConsumeItemSufficientWithFrozen()
+    {
+        DB::transaction(function () {
+            // 消耗150个物品,总共有150个物品(100未冻结+50冻结)
+            $result = ItemService::consumeItem(
+                $this->testUserId,
+                $this->testItemId,
+                null,
+                150,
+                [
+                    'source_type' => 'test',
+                    'source_id' => 1,
+                    'include_frozen' => true
+                ]
+            );
+
+            $this->assertTrue($result['success']);
+
+            // 验证所有物品都被消耗
+            $remainingQuantity = ItemUser::where('user_id', $this->testUserId)
+                ->where('item_id', $this->testItemId)
+                ->sum('quantity');
+
+            $this->assertEquals(0, $remainingQuantity);
+        });
+    }
+}

+ 205 - 0
tests/manual_test_item_consume_frozen.php

@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * 手动测试物品消耗冻结功能
+ * 
+ * 使用方法:php tests/manual_test_item_consume_frozen.php
+ */
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use App\Module\GameItems\Services\ItemService;
+use App\Module\GameItems\Models\ItemUser;
+use Illuminate\Support\Facades\DB;
+
+// 启动Laravel应用
+$app = require_once __DIR__ . '/../bootstrap/app.php';
+$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
+$kernel->bootstrap();
+
+echo "=== 物品消耗冻结功能测试 ===\n";
+
+$testUserId = 99999;
+$testItemId = 1001;
+
+try {
+    // 清理测试数据
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        ItemUser::where('user_id', $testUserId)->where('item_id', $testItemId)->delete();
+        
+        // 创建测试数据
+        echo "创建测试数据...\n";
+        
+        // 添加100个未冻结物品
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 100,
+            'is_frozen' => false,
+            'frozen_log_id' => null,
+        ]);
+        
+        // 添加50个冻结物品
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 50,
+            'is_frozen' => true,
+            'frozen_log_id' => 999, // 假设的冻结日志ID
+        ]);
+        
+        echo "测试数据创建完成:100个未冻结 + 50个冻结 = 150个总数\n\n";
+    });
+
+    // 测试1:不包含冻结物品的消耗(默认行为)
+    echo "=== 测试1:不包含冻结物品的消耗 ===\n";
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        $result = ItemService::consumeItem(
+            $testUserId,
+            $testItemId,
+            null,
+            80,
+            ['source_type' => 'test', 'source_id' => 1]
+        );
+        
+        if ($result['success']) {
+            echo "✓ 消耗80个物品成功\n";
+            
+            // 检查剩余数量
+            $items = ItemUser::where('user_id', $testUserId)
+                ->where('item_id', $testItemId)
+                ->get();
+            
+            $unfrozenQuantity = $items->where('is_frozen', false)->sum('quantity');
+            $frozenQuantity = $items->where('is_frozen', true)->sum('quantity');
+            
+            echo "剩余未冻结物品:{$unfrozenQuantity}个(期望:20个)\n";
+            echo "剩余冻结物品:{$frozenQuantity}个(期望:50个)\n";
+            
+            if ($unfrozenQuantity == 20 && $frozenQuantity == 50) {
+                echo "✓ 测试1通过\n\n";
+            } else {
+                echo "✗ 测试1失败\n\n";
+            }
+        } else {
+            echo "✗ 消耗失败:" . ($result['message'] ?? '未知错误') . "\n\n";
+        }
+    });
+
+    // 重置测试数据
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        ItemUser::where('user_id', $testUserId)->where('item_id', $testItemId)->delete();
+        
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 100,
+            'is_frozen' => false,
+            'frozen_log_id' => null,
+        ]);
+        
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 50,
+            'is_frozen' => true,
+            'frozen_log_id' => 999,
+        ]);
+    });
+
+    // 测试2:包含冻结物品的消耗
+    echo "=== 测试2:包含冻结物品的消耗 ===\n";
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        $result = ItemService::consumeItem(
+            $testUserId,
+            $testItemId,
+            null,
+            120,
+            [
+                'source_type' => 'test',
+                'source_id' => 1,
+                'include_frozen' => true // 包含冻结物品
+            ]
+        );
+        
+        if ($result['success']) {
+            echo "✓ 消耗120个物品成功(包含冻结物品)\n";
+            
+            // 检查剩余数量
+            $totalQuantity = ItemUser::where('user_id', $testUserId)
+                ->where('item_id', $testItemId)
+                ->sum('quantity');
+            
+            echo "剩余总数量:{$totalQuantity}个(期望:30个)\n";
+            
+            if ($totalQuantity == 30) {
+                echo "✓ 测试2通过\n\n";
+            } else {
+                echo "✗ 测试2失败\n\n";
+            }
+        } else {
+            echo "✗ 消耗失败:" . ($result['message'] ?? '未知错误') . "\n\n";
+        }
+    });
+
+    // 测试3:不包含冻结物品时数量不足
+    echo "=== 测试3:不包含冻结物品时数量不足 ===\n";
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        // 重置为只有100个未冻结物品
+        ItemUser::where('user_id', $testUserId)->where('item_id', $testItemId)->delete();
+        
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 100,
+            'is_frozen' => false,
+            'frozen_log_id' => null,
+        ]);
+        
+        ItemUser::create([
+            'user_id' => $testUserId,
+            'item_id' => $testItemId,
+            'instance_id' => null,
+            'quantity' => 50,
+            'is_frozen' => true,
+            'frozen_log_id' => 999,
+        ]);
+        
+        try {
+            $result = ItemService::consumeItem(
+                $testUserId,
+                $testItemId,
+                null,
+                120, // 尝试消耗120个,但只有100个未冻结
+                ['source_type' => 'test', 'source_id' => 1]
+            );
+            
+            echo "✗ 测试3失败:应该抛出异常但没有\n\n";
+        } catch (Exception $e) {
+            if (strpos($e->getMessage(), '数量不足') !== false) {
+                echo "✓ 测试3通过:正确抛出数量不足异常\n";
+                echo "异常信息:" . $e->getMessage() . "\n\n";
+            } else {
+                echo "✗ 测试3失败:异常信息不正确\n";
+                echo "异常信息:" . $e->getMessage() . "\n\n";
+            }
+        }
+    });
+
+    // 清理测试数据
+    DB::transaction(function () use ($testUserId, $testItemId) {
+        ItemUser::where('user_id', $testUserId)->where('item_id', $testItemId)->delete();
+        echo "测试数据清理完成\n";
+    });
+
+    echo "=== 所有测试完成 ===\n";
+
+} catch (Exception $e) {
+    echo "测试过程中发生错误:" . $e->getMessage() . "\n";
+    echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n";
+}