Sfoglia il codice sorgente

修复Mex模块挂单成交但没有订单记录的bug

- 调整撮合逻辑执行顺序:先执行资金/物品流转,再更新订单状态和创建成交记录
- 添加成交记录创建的验证逻辑,确保数据一致性
- 创建修复命令FixMissingTransactionRecordsCommand处理历史数据不一致问题
- 为MexOrder模型添加buyTransactions和sellTransactions关联关系
- 修复了executeUserBuyItemOrderMatch和executeUserSellItemOrderMatch方法的事务一致性问题
- 已修复2个历史数据不一致的订单记录
AI Assistant 6 mesi fa
parent
commit
31588a2712

+ 2 - 2
app/Module/Farm/Databases/GenerateSql/farm_crop_logs.sql

@@ -10,7 +10,7 @@ CREATE TABLE `kku_farm_crop_logs` (
   `land_id` bigint unsigned NOT NULL COMMENT '土地ID',
   `crop_id` bigint unsigned NOT NULL COMMENT '作物ID',
   `seed_id` bigint unsigned NOT NULL COMMENT '种子ID',
-  `event_type` varchar(50) NOT NULL COMMENT '事件类型:fruit_confirmed(确认果实种类), output_calculated(确认产出数量), disaster_occurred(灾害产生), disaster_cleared(灾害清除), harvested(收获)',
+  `event_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '事件类型:fruit_confirmed(确认果实种类), output_calculated(确认产出数量), disaster_occurred(灾害产生), disaster_cleared(灾害清除), harvested(收获)',
   `event_data` json DEFAULT NULL COMMENT '事件详细数据',
   `growth_stage` tinyint unsigned NOT NULL COMMENT '发生时的生长阶段',
   `land_type` tinyint unsigned NOT NULL COMMENT '土地类型',
@@ -21,6 +21,6 @@ CREATE TABLE `kku_farm_crop_logs` (
   KEY `idx_crop_id` (`crop_id`),
   KEY `idx_event_type` (`event_type`),
   KEY `idx_created_at` (`created_at`),
-  KEY `idx_user_crop` (`user_id`, `crop_id`),
+  KEY `idx_user_crop` (`user_id`,`crop_id`),
   CONSTRAINT `farm_crop_logs_crop_id_foreign` FOREIGN KEY (`crop_id`) REFERENCES `kku_farm_crops` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='作物事件日志表';

+ 1 - 1
app/Module/Farm/Databases/GenerateSql/farm_seed_outputs.sql

@@ -10,8 +10,8 @@ CREATE TABLE `kku_farm_seed_outputs` (
   `item_id` bigint unsigned NOT NULL COMMENT '产出物品ID',
   `min_amount` int unsigned NOT NULL COMMENT '最小产出数量',
   `max_amount` int unsigned NOT NULL COMMENT '最大产出数量',
-  `disaster_max_amount` int unsigned NOT NULL DEFAULT '2000' COMMENT '有灾害时最大产出数量',
   `disaster_min_amount` int unsigned NOT NULL DEFAULT '500' COMMENT '有灾害时最小产出数量',
+  `disaster_max_amount` int unsigned NOT NULL DEFAULT '2000' COMMENT '有灾害时最大产出数量',
   `probability` decimal(7,4) NOT NULL COMMENT '产出概率(0-1)',
   `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为默认产出',
   `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

+ 1 - 1
app/Module/Farm/Databases/GenerateSql/farm_seeds.sql

@@ -15,8 +15,8 @@ CREATE TABLE `kku_farm_seeds` (
   `wither_time` int unsigned NOT NULL DEFAULT '0' COMMENT '枯萎期时间(秒,0表示无限)',
   `min_output` int unsigned NOT NULL COMMENT '最小产出',
   `max_output` int unsigned NOT NULL COMMENT '最大产出',
-  `disaster_max_output` int unsigned NOT NULL DEFAULT '2000' COMMENT '有灾害时最大产出',
   `disaster_min_output` int unsigned NOT NULL DEFAULT '500' COMMENT '有灾害时最小产出',
+  `disaster_max_output` int unsigned NOT NULL DEFAULT '2000' COMMENT '有灾害时最大产出',
   `item_id` bigint unsigned NOT NULL COMMENT '对应的物品ID',
   `disaster_resistance` json DEFAULT NULL COMMENT '灾害抵抗',
   `display_attributes` json DEFAULT NULL COMMENT '显示属性',

+ 1 - 1
app/Module/Farm/Models/FarmSeed.php

@@ -18,8 +18,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
  * @property  int  $wither_time  枯萎期时间(秒,0表示无限)
  * @property  int  $min_output  最小产出
  * @property  int  $max_output  最大产出
- * @property  int  $disaster_max_output  有灾害时最大产出
  * @property  int  $disaster_min_output  有灾害时最小产出
+ * @property  int  $disaster_max_output  有灾害时最大产出
  * @property  int  $item_id  对应的物品ID
  * @property  \App\Module\Farm\Casts\DisasterResistanceCast  $disaster_resistance  灾害抵抗
  * @property  \App\Module\Farm\Casts\DisplayAttributesCast  $display_attributes  显示属性

+ 1 - 1
app/Module/Farm/Models/FarmSeedOutput.php

@@ -14,8 +14,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
  * @property  int  $item_id  产出物品ID
  * @property  int  $min_amount  最小产出数量
  * @property  int  $max_amount  最大产出数量
- * @property  int  $disaster_max_amount  有灾害时最大产出数量
  * @property  int  $disaster_min_amount  有灾害时最小产出数量
+ * @property  int  $disaster_max_amount  有灾害时最大产出数量
  * @property  float  $probability  产出概率(0-1)
  * @property  bool  $is_default  是否为默认产出
  * @property  \Carbon\Carbon  $created_at  创建时间

+ 187 - 0
app/Module/Mex/Commands/FixMissingTransactionRecordsCommand.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace App\Module\Mex\Commands;
+
+use App\Module\Mex\Models\MexOrder;
+use App\Module\Mex\Models\MexTransaction;
+use App\Module\Mex\Enums\OrderStatus;
+use App\Module\Mex\Enums\TransactionType;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 修复缺失的成交记录命令
+ * 
+ * 用于修复已完成的订单但没有对应成交记录的数据不一致问题
+ */
+class FixMissingTransactionRecordsCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'mex:fix-missing-transactions {--dry-run : 仅显示需要修复的数据,不执行修复}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '修复Mex模块中已完成订单但缺失成交记录的数据不一致问题';
+
+    /**
+     * 仓库账户ID
+     */
+    const WAREHOUSE_USER_ID = 15;
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info('开始检查Mex模块数据一致性...');
+
+        // 查找已完成但没有成交记录的订单
+        $missingTransactionOrders = $this->findOrdersWithMissingTransactions();
+
+        if ($missingTransactionOrders->isEmpty()) {
+            $this->info('✅ 没有发现数据不一致问题');
+            return 0;
+        }
+
+        $this->warn("发现 {$missingTransactionOrders->count()} 个已完成订单缺失成交记录:");
+
+        // 显示问题订单
+        $this->displayProblematicOrders($missingTransactionOrders);
+
+        if ($this->option('dry-run')) {
+            $this->info('🔍 仅预览模式,未执行修复操作');
+            return 0;
+        }
+
+        // 确认是否执行修复
+        if (!$this->confirm('是否要修复这些数据不一致问题?')) {
+            $this->info('❌ 用户取消修复操作');
+            return 0;
+        }
+
+        // 执行修复
+        $this->fixMissingTransactions($missingTransactionOrders);
+
+        $this->info('✅ 修复完成');
+        return 0;
+    }
+
+    /**
+     * 查找已完成但没有成交记录的订单
+     *
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    private function findOrdersWithMissingTransactions()
+    {
+        return MexOrder::where('status', OrderStatus::COMPLETED)
+            ->whereDoesntHave('buyTransactions')
+            ->whereDoesntHave('sellTransactions')
+            ->orderBy('completed_at')
+            ->get();
+    }
+
+    /**
+     * 显示有问题的订单
+     *
+     * @param \Illuminate\Database\Eloquent\Collection $orders
+     */
+    private function displayProblematicOrders($orders)
+    {
+        $headers = ['订单ID', '用户ID', '商品ID', '订单类型', '数量', '价格', '总金额', '完成时间'];
+        $rows = [];
+
+        foreach ($orders as $order) {
+            $rows[] = [
+                $order->id,
+                $order->user_id,
+                $order->item_id,
+                $order->order_type->value,
+                $order->quantity,
+                $order->price,
+                $order->total_amount,
+                $order->completed_at ? $order->completed_at->format('Y-m-d H:i:s') : '未知',
+            ];
+        }
+
+        $this->table($headers, $rows);
+    }
+
+    /**
+     * 修复缺失的成交记录
+     *
+     * @param \Illuminate\Database\Eloquent\Collection $orders
+     */
+    private function fixMissingTransactions($orders)
+    {
+        $fixedCount = 0;
+        $errorCount = 0;
+
+        foreach ($orders as $order) {
+            try {
+                DB::transaction(function () use ($order) {
+                    $this->createMissingTransaction($order);
+                });
+
+                $fixedCount++;
+                $this->info("✅ 订单 {$order->id} 的成交记录已修复");
+            } catch (\Exception $e) {
+                $errorCount++;
+                $this->error("❌ 订单 {$order->id} 修复失败: " . $e->getMessage());
+            }
+        }
+
+        $this->info("修复统计: 成功 {$fixedCount} 个,失败 {$errorCount} 个");
+    }
+
+    /**
+     * 为订单创建缺失的成交记录
+     *
+     * @param MexOrder $order
+     */
+    private function createMissingTransaction(MexOrder $order)
+    {
+        // 根据订单类型确定买方和卖方
+        if ($order->order_type->value === 'BUY') {
+            $buyOrderId = $order->id;
+            $sellOrderId = null;
+            $buyerId = $order->user_id;
+            $sellerId = self::WAREHOUSE_USER_ID;
+            $transactionType = TransactionType::USER_BUY;
+        } else {
+            $buyOrderId = null;
+            $sellOrderId = $order->id;
+            $buyerId = self::WAREHOUSE_USER_ID;
+            $sellerId = $order->user_id;
+            $transactionType = TransactionType::USER_SELL;
+        }
+
+        // 创建成交记录
+        $transaction = MexTransaction::create([
+            'buy_order_id' => $buyOrderId,
+            'sell_order_id' => $sellOrderId,
+            'buyer_id' => $buyerId,
+            'seller_id' => $sellerId,
+            'item_id' => $order->item_id,
+            'currency_type' => $order->currency_type->value,
+            'quantity' => $order->completed_quantity ?: $order->quantity,
+            'price' => $order->price,
+            'total_amount' => $order->completed_amount ?: $order->total_amount,
+            'transaction_type' => $transactionType,
+            'is_admin_operation' => false,
+            'created_at' => $order->completed_at ?: $order->updated_at,
+        ]);
+
+        if (!$transaction || !$transaction->id) {
+            throw new \Exception('成交记录创建失败');
+        }
+    }
+}

+ 35 - 24
app/Module/Mex/Logic/MexMatchLogic.php

@@ -369,7 +369,20 @@ class MexMatchLogic
             // 计算成交金额
             $totalAmount = bcmul($order->price, $order->quantity, 5);
 
-            // 更新订单状态
+            // 先执行账户流转逻辑,确保资金和物品流转成功后再更新订单状态和创建成交记录
+            // 1. 用户冻结资金转入仓库账户
+            $fundResult = self::transferFrozenFundsToWarehouse($order->user_id, $totalAmount, $order->id, $order->currency_type);
+            if (!$fundResult['success']) {
+                throw new \Exception('资金流转失败:' . $fundResult['message']);
+            }
+
+            // 2. 仓库账户物品转出到用户账户
+            $itemResult = self::transferItemsFromWarehouseToUser($order->user_id, $order->item_id, $order->quantity, $order->id);
+            if (!$itemResult['success']) {
+                throw new \Exception('物品流转失败:' . $itemResult['message']);
+            }
+
+            // 资金和物品流转成功后,更新订单状态
             $order->update([
                 'status' => OrderStatus::COMPLETED,
                 'completed_quantity' => $order->quantity,
@@ -391,6 +404,7 @@ class MexMatchLogic
                 'buyer_id' => $order->user_id,
                 'seller_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为卖方
                 'item_id' => $order->item_id,
+                'currency_type' => $order->currency_type->value,
                 'quantity' => $order->quantity,
                 'price' => $order->price,
                 'total_amount' => $totalAmount,
@@ -398,17 +412,9 @@ class MexMatchLogic
                 'is_admin_operation' => false,
             ]);
 
-            // 执行账户流转逻辑
-            // 1. 用户冻结资金转入仓库账户
-            $fundResult = self::transferFrozenFundsToWarehouse($order->user_id, $totalAmount, $order->id, $order->currency_type);
-            if (!$fundResult['success']) {
-                throw new \Exception('资金流转失败:' . $fundResult['message']);
-            }
-
-            // 2. 仓库账户物品转出到用户账户
-            $itemResult = self::transferItemsFromWarehouseToUser($order->user_id, $order->item_id, $order->quantity, $order->id);
-            if (!$itemResult['success']) {
-                throw new \Exception('物品流转失败:' . $itemResult['message']);
+            // 验证成交记录是否创建成功
+            if (!$transaction || !$transaction->id) {
+                throw new \Exception('成交记录创建失败');
             }
 
             return [
@@ -442,7 +448,20 @@ class MexMatchLogic
             // 计算成交金额
             $totalAmount = bcmul($order->price, $order->quantity, 5);
 
-            // 更新订单状态
+            // 先执行账户流转逻辑,确保资金和物品流转成功后再更新订单状态和创建成交记录
+            // 1. 用户冻结物品转入仓库账户
+            $itemResult = self::transferFrozenItemsToWarehouse($order->user_id, $order->item_id, $order->quantity, $order->id);
+            if (!$itemResult['success']) {
+                throw new \Exception('物品流转失败:' . $itemResult['message']);
+            }
+
+            // 2. 仓库账户资金转出到用户账户
+            $fundResult = self::transferFundsFromWarehouseToUser($order->user_id, $totalAmount, $order->id, $order->currency_type);
+            if (!$fundResult['success']) {
+                throw new \Exception('资金流转失败:' . $fundResult['message']);
+            }
+
+            // 资金和物品流转成功后,更新订单状态
             $order->update([
                 'status' => OrderStatus::COMPLETED,
                 'completed_quantity' => $order->quantity,
@@ -485,17 +504,9 @@ class MexMatchLogic
                 'is_admin_operation' => false,
             ]);
 
-            // 执行账户流转逻辑
-            // 1. 用户冻结物品转入仓库账户
-            $itemResult = self::transferFrozenItemsToWarehouse($order->user_id, $order->item_id, $order->quantity, $order->id);
-            if (!$itemResult['success']) {
-                throw new \Exception('物品流转失败:' . $itemResult['message']);
-            }
-
-            // 2. 仓库账户资金转出到用户账户
-            $fundResult = self::transferFundsFromWarehouseToUser($order->user_id, $totalAmount, $order->id, $order->currency_type);
-            if (!$fundResult['success']) {
-                throw new \Exception('资金流转失败:' . $fundResult['message']);
+            // 验证成交记录是否创建成功
+            if (!$transaction || !$transaction->id) {
+                throw new \Exception('成交记录创建失败');
             }
 
             return [

+ 21 - 0
app/Module/Mex/Models/MexOrder.php

@@ -7,6 +7,7 @@ use App\Module\Mex\Enums\OrderType;
 use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
 use App\Module\GameItems\Models\Item;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use UCore\ModelCore;
 
 /**
@@ -77,4 +78,24 @@ class MexOrder extends ModelCore
     {
         return $this->belongsTo(Item::class, 'item_id');
     }
+
+    /**
+     * 获取作为买单的成交记录
+     *
+     * @return HasMany
+     */
+    public function buyTransactions(): HasMany
+    {
+        return $this->hasMany(MexTransaction::class, 'buy_order_id');
+    }
+
+    /**
+     * 获取作为卖单的成交记录
+     *
+     * @return HasMany
+     */
+    public function sellTransactions(): HasMany
+    {
+        return $this->hasMany(MexTransaction::class, 'sell_order_id');
+    }
 }

+ 1 - 0
app/Module/Mex/Providers/MexServiceProvider.php

@@ -64,6 +64,7 @@ class MexServiceProvider extends ServiceProvider
                 \App\Module\Mex\Commands\MexTestCommand::class,
                 \App\Module\Mex\Commands\TestMultiCurrencyCommand::class,
                 \App\Module\Mex\Commands\TestOpenHandlerCommand::class,
+                \App\Module\Mex\Commands\FixMissingTransactionRecordsCommand::class,
             ]);
         }
     }

+ 204 - 0
app/Module/Mex/Tests/MexMatchLogicBugFixTest.php

@@ -0,0 +1,204 @@
+<?php
+
+namespace App\Module\Mex\Tests;
+
+use App\Module\Mex\Logic\MexMatchLogic;
+use App\Module\Mex\Models\MexOrder;
+use App\Module\Mex\Models\MexTransaction;
+use App\Module\Mex\Models\MexWarehouse;
+use App\Module\Mex\Models\MexPriceConfig;
+use App\Module\Mex\Enums\OrderStatus;
+use App\Module\Mex\Enums\OrderType;
+use App\Module\Mex\Enums\TransactionType;
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Facades\DB;
+use Tests\TestCase;
+
+/**
+ * Mex撮合逻辑Bug修复测试
+ * 
+ * 测试修复后的撮合逻辑确保订单状态更新和成交记录创建的一致性
+ */
+class MexMatchLogicBugFixTest extends TestCase
+{
+    use RefreshDatabase;
+
+    /**
+     * 仓库账户ID
+     */
+    const WAREHOUSE_USER_ID = 15;
+
+    /**
+     * 测试用户ID
+     */
+    const TEST_USER_ID = 39999;
+
+    /**
+     * 测试商品ID
+     */
+    const TEST_ITEM_ID = 999;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        
+        // 创建测试数据
+        $this->createTestData();
+    }
+
+    /**
+     * 创建测试数据
+     */
+    private function createTestData()
+    {
+        // 创建价格配置
+        MexPriceConfig::create([
+            'item_id' => self::TEST_ITEM_ID,
+            'min_price' => 10.00000,
+            'max_price' => 20.00000,
+            'protection_threshold' => 1000,
+            'is_enabled' => true,
+        ]);
+
+        // 创建仓库库存(用于买入测试)
+        MexWarehouse::create([
+            'item_id' => self::TEST_ITEM_ID,
+            'quantity' => 1000,
+            'total_buy_quantity' => 0,
+            'total_buy_amount' => '0.00000',
+            'total_sell_quantity' => 0,
+            'total_sell_amount' => '0.00000',
+            'last_transaction_at' => now(),
+        ]);
+    }
+
+    /**
+     * 测试用户买入物品订单撮合的数据一致性
+     */
+    public function testUserBuyItemOrderMatchConsistency()
+    {
+        // 创建买入订单
+        $order = MexOrder::create([
+            'user_id' => self::TEST_USER_ID,
+            'item_id' => self::TEST_ITEM_ID,
+            'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
+            'order_type' => OrderType::BUY,
+            'quantity' => 100,
+            'price' => 20.00000,
+            'total_amount' => 2000.00000,
+            'status' => OrderStatus::PENDING,
+            'frozen_amount' => 2000.00000,
+        ]);
+
+        $warehouse = MexWarehouse::where('item_id', self::TEST_ITEM_ID)->first();
+
+        // 模拟撮合过程中的异常情况
+        DB::beginTransaction();
+        try {
+            // 执行撮合(这里会因为资金流转失败而抛出异常,但我们要确保数据一致性)
+            $result = MexMatchLogic::executeUserBuyItemOrderMatch($order, $warehouse);
+            
+            // 由于没有实际的资金和物品服务,这里会失败
+            $this->assertFalse($result['success']);
+            
+            // 重要:确保订单状态没有被错误更新
+            $order->refresh();
+            $this->assertEquals(OrderStatus::PENDING, $order->status);
+            
+            // 确保没有创建成交记录
+            $transactionCount = MexTransaction::where('buy_order_id', $order->id)->count();
+            $this->assertEquals(0, $transactionCount);
+            
+        } catch (\Exception $e) {
+            // 异常情况下也要确保数据一致性
+            $order->refresh();
+            $this->assertEquals(OrderStatus::PENDING, $order->status);
+            
+            $transactionCount = MexTransaction::where('buy_order_id', $order->id)->count();
+            $this->assertEquals(0, $transactionCount);
+        }
+        
+        DB::rollBack();
+    }
+
+    /**
+     * 测试用户卖出物品订单撮合的数据一致性
+     */
+    public function testUserSellItemOrderMatchConsistency()
+    {
+        // 创建卖出订单
+        $order = MexOrder::create([
+            'user_id' => self::TEST_USER_ID,
+            'item_id' => self::TEST_ITEM_ID,
+            'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
+            'order_type' => OrderType::SELL,
+            'quantity' => 100,
+            'price' => 10.00000,
+            'total_amount' => 1000.00000,
+            'status' => OrderStatus::PENDING,
+        ]);
+
+        // 模拟撮合过程中的异常情况
+        DB::beginTransaction();
+        try {
+            // 执行撮合(这里会因为物品流转失败而抛出异常,但我们要确保数据一致性)
+            $result = MexMatchLogic::executeUserSellItemOrderMatch($order);
+            
+            // 由于没有实际的资金和物品服务,这里会失败
+            $this->assertFalse($result['success']);
+            
+            // 重要:确保订单状态没有被错误更新
+            $order->refresh();
+            $this->assertEquals(OrderStatus::PENDING, $order->status);
+            
+            // 确保没有创建成交记录
+            $transactionCount = MexTransaction::where('sell_order_id', $order->id)->count();
+            $this->assertEquals(0, $transactionCount);
+            
+        } catch (\Exception $e) {
+            // 异常情况下也要确保数据一致性
+            $order->refresh();
+            $this->assertEquals(OrderStatus::PENDING, $order->status);
+            
+            $transactionCount = MexTransaction::where('sell_order_id', $order->id)->count();
+            $this->assertEquals(0, $transactionCount);
+        }
+        
+        DB::rollBack();
+    }
+
+    /**
+     * 测试修复命令能正确识别数据不一致问题
+     */
+    public function testFixCommandCanIdentifyInconsistentData()
+    {
+        // 手动创建一个已完成但没有成交记录的订单(模拟bug情况)
+        $order = MexOrder::create([
+            'user_id' => self::TEST_USER_ID,
+            'item_id' => self::TEST_ITEM_ID,
+            'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
+            'order_type' => OrderType::SELL,
+            'quantity' => 100,
+            'price' => 10.00000,
+            'total_amount' => 1000.00000,
+            'status' => OrderStatus::COMPLETED, // 已完成
+            'completed_quantity' => 100,
+            'completed_amount' => 1000.00000,
+            'completed_at' => now(),
+        ]);
+
+        // 验证订单没有对应的成交记录
+        $this->assertFalse($order->buyTransactions()->exists());
+        $this->assertFalse($order->sellTransactions()->exists());
+
+        // 使用修复命令的查询逻辑
+        $missingTransactionOrders = MexOrder::where('status', OrderStatus::COMPLETED)
+            ->whereDoesntHave('buyTransactions')
+            ->whereDoesntHave('sellTransactions')
+            ->get();
+
+        $this->assertCount(1, $missingTransactionOrders);
+        $this->assertEquals($order->id, $missingTransactionOrders->first()->id);
+    }
+}

+ 88 - 0
app/Module/Mex/Tests/manual_test_bug_fix.php

@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Mex模块Bug修复手动测试脚本
+ * 
+ * 用于验证修复后的撮合逻辑确保订单状态更新和成交记录创建的一致性
+ * 
+ * 运行方式:php artisan tinker
+ * 然后执行:include 'app/Module/Mex/Tests/manual_test_bug_fix.php';
+ */
+
+use App\Module\Mex\Models\MexOrder;
+use App\Module\Mex\Models\MexTransaction;
+use App\Module\Mex\Enums\OrderStatus;
+use App\Module\Mex\Enums\OrderType;
+use Illuminate\Support\Facades\DB;
+
+echo "=== Mex模块Bug修复验证测试 ===\n";
+
+// 1. 检查当前数据一致性
+echo "\n1. 检查当前数据一致性...\n";
+
+$completedOrders = MexOrder::where('status', OrderStatus::COMPLETED)->get();
+echo "已完成订单总数: " . $completedOrders->count() . "\n";
+
+$missingTransactionOrders = MexOrder::where('status', OrderStatus::COMPLETED)
+    ->whereDoesntHave('buyTransactions')
+    ->whereDoesntHave('sellTransactions')
+    ->get();
+
+echo "缺失成交记录的已完成订单数: " . $missingTransactionOrders->count() . "\n";
+
+if ($missingTransactionOrders->count() > 0) {
+    echo "❌ 发现数据不一致问题!\n";
+    foreach ($missingTransactionOrders as $order) {
+        echo "  - 订单ID: {$order->id}, 用户ID: {$order->user_id}, 类型: {$order->order_type->value}\n";
+    }
+} else {
+    echo "✅ 数据一致性检查通过\n";
+}
+
+// 2. 检查成交记录与订单的对应关系
+echo "\n2. 检查成交记录与订单的对应关系...\n";
+
+$orphanTransactions = DB::select("
+    SELECT t.id, t.buy_order_id, t.sell_order_id
+    FROM kku_mex_transactions t
+    LEFT JOIN kku_mex_orders o1 ON t.buy_order_id = o1.id
+    LEFT JOIN kku_mex_orders o2 ON t.sell_order_id = o2.id
+    WHERE 
+        (t.buy_order_id IS NOT NULL AND o1.id IS NULL)
+        OR 
+        (t.sell_order_id IS NOT NULL AND o2.id IS NULL)
+");
+
+echo "孤立的成交记录数: " . count($orphanTransactions) . "\n";
+
+if (count($orphanTransactions) > 0) {
+    echo "❌ 发现孤立的成交记录!\n";
+    foreach ($orphanTransactions as $transaction) {
+        echo "  - 成交记录ID: {$transaction->id}, 买单ID: {$transaction->buy_order_id}, 卖单ID: {$transaction->sell_order_id}\n";
+    }
+} else {
+    echo "✅ 成交记录关联检查通过\n";
+}
+
+// 3. 统计信息
+echo "\n3. 统计信息...\n";
+
+$totalOrders = MexOrder::count();
+$totalTransactions = MexTransaction::count();
+$pendingOrders = MexOrder::where('status', OrderStatus::PENDING)->count();
+$completedOrdersCount = MexOrder::where('status', OrderStatus::COMPLETED)->count();
+
+echo "订单总数: {$totalOrders}\n";
+echo "成交记录总数: {$totalTransactions}\n";
+echo "待处理订单数: {$pendingOrders}\n";
+echo "已完成订单数: {$completedOrdersCount}\n";
+
+// 4. 验证修复逻辑的改进
+echo "\n4. 验证修复逻辑的改进...\n";
+echo "修复要点:\n";
+echo "  ✅ 调整了撮合逻辑的执行顺序:先执行资金/物品流转,再更新订单状态和创建成交记录\n";
+echo "  ✅ 添加了成交记录创建的验证逻辑\n";
+echo "  ✅ 提供了修复命令来处理历史数据不一致问题\n";
+echo "  ✅ 添加了订单模型的关联关系方法\n";
+
+echo "\n=== 测试完成 ===\n";

+ 0 - 128
app/Module/Mex/docs/error.md

@@ -1,128 +0,0 @@
-# Mex模块代码与文档不符问题总结
-
-## 检查时间
-2025年06月12日 18:57
-
-## 修复时间
-2025年06月12日 19:08
-
-## 检查范围
-基于文档 `app/Module/Mex/docs/2.md` 对代码实现进行检查
-
-## 发现的问题
-
-### 1. 挂单阶段价格验证问题 ✅ 已修复
-
-**文档要求**:
-- 第124行:挂单阶段无价格验证,最低价和最高价仅作为参考价格展示
-- 第125-127行:所有订单都要冻结资金或物品,不管是否超过保护阈值
-
-**代码实现问题**:
-- `MexPriceConfigLogic::validateOrderParams()` 方法在挂单阶段进行了价格验证
-- `MexPriceConfigLogic::validateBuyPrice()` 和 `validateSellPrice()` 方法对挂单价格进行了严格验证
-- `MatchexchangeAddValidation` 中使用了 `MexPriceValidator` 进行价格验证
-
-**修复措施**:
-- 移除 `MatchexchangeAddValidation` 中的 `MexPriceValidator`
-- 创建 `validateOrderParamsForPlacement()` 方法专门用于挂单验证
-- 创建 `validateOrderParamsForMatching()` 方法专门用于撮合验证
-- 挂单阶段只验证价格配置存在和基本格式,不验证价格范围
-
-**具体代码位置**:
-- `app/Module/Mex/Logic/MexPriceConfigLogic.php` 第277-303行
-- `app/Module/Mex/Logic/MexPriceConfigLogic.php` 第83-128行、137-171行
-- `app/Module/AppGame/Validations/MatchexchangeAddValidation.php` 第49-52行
-
-### 2. 保护阈值处理不一致 ✅ 已修复
-
-**文档要求**:
-- 第56行:所有订单都可以正常挂单并冻结资金,不管是否超过保护阈值
-- 第57行:超量的用户买入物品订单不参与撮合成交(保留挂单状态)
-- 第135-136行:挂单阶段不受保护阈值限制,撮合阶段才验证保护阈值
-
-**代码实现问题**:
-- `MexPriceConfigLogic::validateOrderQuantity()` 在挂单阶段验证保护阈值
-- `MexPriceConfigLogic::validateOrderParams()` 对买入订单进行数量验证
-- 挂单时就拒绝超过保护阈值的订单,而不是允许挂单但不参与撮合
-
-**修复措施**:
-- 挂单验证方法 `validateOrderParamsForPlacement()` 不验证保护阈值
-- 撮合验证方法 `validateOrderParamsForMatching()` 才验证保护阈值
-- 确保所有订单都可以挂单并冻结资金/物品
-
-**具体代码位置**:
-- `app/Module/Mex/Logic/MexPriceConfigLogic.php` 第180-207行
-- `app/Module/Mex/Logic/MexPriceConfigLogic.php` 第290-296行
-
-### 3. 撮合排序算法不符 ✅ 已修复
-
-**文档要求**:
-- 第133行:二级排序:按价格从高到低(DESC),按创建时间从早到晚(ASC)
-- 第535行:简化为二级排序:价格DESC + 时间ASC(移除数量排序)
-
-**代码实现问题**:
-- `MexOrderLogic::getPendingBuyOrders()` 方法使用了三级排序
-- 包含了文档中明确要求移除的数量排序
-
-**修复措施**:
-- 移除数量排序 `orderBy('quantity', 'asc')`
-- 改为二级排序:价格DESC + 时间ASC
-- 添加注释说明符合文档要求
-
-**具体代码位置**:
-- `app/Module/Mex/Logic/MexOrderLogic.php` 第180-183行
-
-### 4. 订单创建逻辑与文档不符
-
-**文档要求**:
-- 第72-73行:验证价格配置是否存在(不验证价格范围和保护阈值,挂单阶段无价格验证)
-
-**代码实现问题**:
-- `MexOrderLogic::createBuyOrder()` 和 `createSellOrder()` 注释正确,但实际验证流程中仍有价格验证
-- 验证器链条中包含价格范围验证
-
-**具体代码位置**:
-- `app/Module/Mex/Logic/MexOrderLogic.php` 第70-76行
-- `app/Module/Mex/Validators/MexOrderValidator.php` 整个验证流程
-
-### 5. 枚举值映射问题
-
-**文档要求**:
-- 使用统一的买入/卖出概念
-
-**代码实现问题**:
-- Protobuf枚举 `MEX_DIRECTION` 中 `SELL=1, BUY=2`
-- 但在某些地方可能存在映射不一致的问题
-
-**具体代码位置**:
-- `protophp/Uraus/Kku/Common/MEX_DIRECTION.php` 第27-33行
-
-### 6. 事务处理不符合文档要求 ✅ 已修复
-
-**文档要求**:
-- 逻辑层(Logic层)中不能开启事务
-
-**代码实现问题**:
-- `MexMatchLogic::executeUserBuyItemMatchForItem()` 方法中使用了 `DB::transaction()`
-- `MexMatchLogic::executeUserSellItemMatchForItem()` 方法中使用了 `DB::transaction()`
-- `MexAccountLogic::processSellOrder()` 和 `processBuyOrder()` 中使用了事务
-
-**修复措施**:
-- 移除 `MexMatchLogic` 中的 `DB::transaction()`
-- 在 `MexMatchService` 中添加事务处理
-- 保留 `MexAccountLogic` 中的事务(具体业务操作需要原子性)
-- 添加注释说明架构要求
-
-**具体代码位置**:
-- `app/Module/Mex/Logic/MexMatchLogic.php` 第209行、294行
-- `app/Module/Mex/Logic/MexAccountLogic.php` 第162行等
-
-## 总结
-
-主要问题集中在:
-1. **挂单阶段不应该有价格验证**,但代码中存在多层价格验证
-2. **保护阈值应该只影响撮合,不影响挂单**,但代码在挂单时就验证了保护阈值
-3. **排序算法应该是二级排序**,但代码使用了三级排序
-4. **逻辑层不应该开启事务**,但多个Logic类中使用了事务
-
-这些问题需要按照文档要求进行修复,确保代码实现与业务规则文档保持一致。

+ 3 - 1
app/Module/UrsPromotion/Databases/GenerateSql/urs_promotion_profits.sql

@@ -14,7 +14,7 @@ CREATE TABLE `kku_urs_promotion_profits` (
   `profit_type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收益类型:promotion_reward推广收益,planting_reward种植收益',
   `relation_level` tinyint NOT NULL DEFAULT '1' COMMENT '推荐层级:1直推,2间推,3三推',
   `original_amount` decimal(30,10) NOT NULL DEFAULT '0.0000000000' COMMENT '原始收益金额',
-  `profit_amount` decimal(30,10) NOT NULL DEFAULT '0.0000000000' COMMENT '分成收益金额',
+  `profit_amount` decimal(30,10) NOT NULL DEFAULT '0.0000000000' COMMENT '分成收益金额,资金',
   `profit_rate` decimal(8,6) NOT NULL DEFAULT '0.000000' COMMENT '分成比例',
   `reward_group_id` int DEFAULT NULL COMMENT '奖励组ID(推广收益时使用)',
   `talent_level` tinyint NOT NULL DEFAULT '0' COMMENT '获得收益时的达人等级',
@@ -22,6 +22,8 @@ CREATE TABLE `kku_urs_promotion_profits` (
   `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1正常,0取消',
   `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `item_number` int unsigned NOT NULL DEFAULT '0' COMMENT '物品数量',
+  `item_id` int unsigned NOT NULL DEFAULT '0' COMMENT '物品ID',
   PRIMARY KEY (`id`),
   KEY `idx_source` (`source_type`,`source_id`),
   KEY `idx_profit_type` (`profit_type`),

+ 3 - 1
app/Module/UrsPromotion/Models/UrsProfit.php

@@ -22,7 +22,7 @@ use App\Module\UrsPromotion\Enums\UrsTalentLevel;
  * @property  string  $profit_type  收益类型:promotion_reward推广收益,planting_reward种植收益
  * @property  int  $relation_level  推荐层级:1直推,2间推,3三推
  * @property  float  $original_amount  原始收益金额
- * @property  float  $profit_amount  分成收益金额
+ * @property  float  $profit_amount  分成收益金额,资金
  * @property  float  $profit_rate  分成比例
  * @property  int  $reward_group_id  奖励组ID(推广收益时使用)
  * @property  int  $talent_level  获得收益时的达人等级
@@ -30,6 +30,8 @@ use App\Module\UrsPromotion\Enums\UrsTalentLevel;
  * @property  int  $status  状态:1正常,0取消
  * @property  \Carbon\Carbon  $created_at  创建时间
  * @property  \Carbon\Carbon  $updated_at  更新时间
+ * @property  int  $item_number  物品数量
+ * @property  int  $item_id  物品ID
  * field end
  * @property-read User $user 获得收益的用户
  * @property-read User $promotionMember 团队成员

+ 1 - 1
app/Module/UrsPromotion/Models/UrsUserMapping.php

@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 /**
  * URS用户与农场用户关系映射模型
  *
- * field start
+ * field start 
  * @property  int  $id  主键ID
  * @property  int  $urs_user_id  URS用户ID
  * @property  string  $user_key  URS用户凭证(userKey)