Browse Source

解冻逻辑修复

dongasai 6 months ago
parent
commit
7aa7292f56

+ 10 - 8
app/Module/GameItems/Logics/ItemFreeze.php

@@ -799,6 +799,7 @@ class ItemFreeze
         // 查找对应的冻结物品
         $frozenItem = ItemUser::where('frozen_log_id', $freezeLogId)
             ->where('is_frozen', true)
+            ->lockForUpdate()
             ->first();
 
         if (!$frozenItem) {
@@ -858,6 +859,7 @@ class ItemFreeze
 
                 // 从其他冻结堆中减少数量
                 $otherFrozenItem->quantity = $newQuantity;
+
                 $otherFrozenItem->save();
 
                 // 记录解冻详情
@@ -891,7 +893,7 @@ class ItemFreeze
                     $newQuantity,
                     $otherFrozenItem->id,
                     true, // 旧冻结状态:已冻结
-                    $newQuantity > 0, // 新冻结状态:如果数量>0仍冻结,否则解冻
+                    true, // 新冻结状态:如果数量>0仍冻结,否则解冻
                     [
                         'action' => 'safe_unfreeze_compensation_from_other_frozen',
                         'target_freeze_log_id' => $freezeLogId,
@@ -904,10 +906,10 @@ class ItemFreeze
 
             // 将补足的数量加到目标冻结堆
             $frozenItem->quantity = $originalFrozenQuantity;
-            $frozenItem->save();
+//            $frozenItem->save();
         }
 
-        // 创建解冻日志
+        // 创建解冻日志(记录原始冻结数量)
         $unfreezeLog = ItemFreezeLog::createLog(
             $frozenItem->user_id,
             $frozenItem->item_id,
@@ -921,18 +923,18 @@ class ItemFreeze
             $freezeLog->operator_id
         );
 
-        // 解冻物品
+        // 解冻目标冻结堆
         $frozenItem->is_frozen = false;
-        $frozenItem->frozen_log_id = null;
+//        $frozenItem->frozen_log_id = null;
         $frozenItem->save();
 
-        // 触发解冻完成事件
+        // 触发解冻完成事件(目标冻结堆解冻)
         event(new ItemQuantityChanged(
             $frozenItem->user_id,
             $frozenItem->item_id,
             $frozenItem->instance_id,
-            $originalFrozenQuantity,
-            $originalFrozenQuantity,
+            $originalFrozenQuantity, // 解冻的数量
+            $originalFrozenQuantity, // 数量不变,只是状态变更
             $frozenItem->id,
             true,  // 旧冻结状态:已冻结
             false, // 新冻结状态:未冻结

+ 101 - 111
app/Module/Mex/Logic/MexOrderLogic.php

@@ -21,6 +21,7 @@ use UCore\Helper\Logger;
  */
 class MexOrderLogic
 {
+
     /**
      * 创建卖出订单
      *
@@ -42,30 +43,30 @@ class MexOrderLogic
         // 验证价格配置是否存在(不验证价格范围,挂单阶段无价格验证)
         $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
         if (!$priceConfig) {
-            return ['success' => false, 'message' => '商品未配置价格信息'];
+            return [ 'success' => false, 'message' => '商品未配置价格信息' ];
         }
 
         // 直接计算总金额,信任Fund模块的精度处理
-        $totalAmount = bcmul($price , $quantity,9);
+        $totalAmount = bcmul($price, $quantity, 9);
 
         try {
             // 1. 验证用户是否有足够的物品
             $checkResult = ItemService::checkItemQuantity($userId, $itemId, $quantity);
             if (!$checkResult->success) {
-                return ['success' => false, 'message' => $checkResult->message];
+                return [ 'success' => false, 'message' => $checkResult->message ];
             }
 
             // 2. 创建订单记录
             $order = MexOrder::create([
-                'user_id' => $userId,
-                'item_id' => $itemId,
-                'currency_type' => $currencyType->value,
-                'order_type' => OrderType::SELL,
-                'quantity' => $quantity,
-                'price' => $price,
-                'total_amount' => $totalAmount,
-                'status' => OrderStatus::PENDING,
-            ]);
+                                          'user_id'       => $userId,
+                                          'item_id'       => $itemId,
+                                          'currency_type' => $currencyType->value,
+                                          'order_type'    => OrderType::SELL,
+                                          'quantity'      => $quantity,
+                                          'price'         => $price,
+                                          'total_amount'  => $totalAmount,
+                                          'status'        => OrderStatus::PENDING,
+                                      ]);
 
             // 3. 冻结用户物品
             $freezeResult = ItemService::freezeItem(
@@ -76,7 +77,7 @@ class MexOrderLogic
                 "农贸市场卖出订单冻结,订单ID:{$order->id}",
                 [
                     'reason_type' => FREEZE_REASON_TYPE::TRADE_ORDER->value,
-                    'source_id' => $order->id,
+                    'source_id'   => $order->id,
                     'source_type' => 'mex_sell_order',
                 ]
             );
@@ -89,13 +90,13 @@ class MexOrderLogic
             event(new OrderCreatedEvent($order));
 
             return [
-                'success' => true,
-                'order_id' => $order->id,
+                'success'       => true,
+                'order_id'      => $order->id,
                 'freeze_log_id' => $freezeResult['freeze_log_id'] ?? null,
-                'message' => '卖出订单创建成功,等待撮合'
+                'message'       => '卖出订单创建成功,等待撮合'
             ];
         } catch (\Exception $e) {
-            return ['success' => false, 'message' => '创建订单失败:' . $e->getMessage()];
+            return [ 'success' => false, 'message' => '创建订单失败:' . $e->getMessage() ];
         }
     }
 
@@ -120,47 +121,46 @@ class MexOrderLogic
         // 验证价格配置是否存在(不验证价格范围和保护阈值,挂单阶段无价格验证)
         $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
         if (!$priceConfig) {
-            return ['success' => false, 'message' => '商品未配置价格信息'];
+            return [ 'success' => false, 'message' => '商品未配置价格信息' ];
         }
 
         // 直接计算总金额,信任Fund模块的精度处理
-        $totalAmount = bcmul($price , $quantity,9) ;
+        $totalAmount = bcmul($price, $quantity, 9);
         Logger::debug(" create ");
 
         try {
             // 1. 创建订单记录
             $order = MexOrder::create([
-                'user_id' => $userId,
-                'item_id' => $itemId,
-                'currency_type' => $currencyType->value,
-                'order_type' => OrderType::BUY,
-                'quantity' => $quantity,
-                'price' => $price,
-                'total_amount' => $totalAmount,
-                'status' => OrderStatus::PENDING,
-                'frozen_amount' => $totalAmount,
-            ]);
+                                          'user_id'       => $userId,
+                                          'item_id'       => $itemId,
+                                          'currency_type' => $currencyType->value,
+                                          'order_type'    => OrderType::BUY,
+                                          'quantity'      => $quantity,
+                                          'price'         => $price,
+                                          'total_amount'  => $totalAmount,
+                                          'status'        => OrderStatus::PENDING,
+                                          'frozen_amount' => $totalAmount,
+                                      ]);
             // 2. 冻结用户资金
             $freezeResult = self::freezeOrderFunds($order);
             if (!$freezeResult['success']) {
-                return ['success' => false, 'message' => '冻结资金失败:' . $freezeResult['message']];
+                return [ 'success' => false, 'message' => '冻结资金失败:' . $freezeResult['message'] ];
             }
 
             // 触发订单创建事件
             event(new OrderCreatedEvent($order));
 
             return [
-                'success' => true,
+                'success'  => true,
                 'order_id' => $order->id,
-                'message' => '买入订单创建成功,等待撮合'
+                'message'  => '买入订单创建成功,等待撮合'
             ];
         } catch (\Exception $e) {
-            return ['success' => false, 'message' => '创建订单失败:' . $e->getMessage()];
+            return [ 'success' => false, 'message' => '创建订单失败:' . $e->getMessage() ];
         }
     }
 
 
-
     /**
      * 取消订单
      *
@@ -175,36 +175,35 @@ class MexOrderLogic
 
         $order = MexOrder::where('id', $orderId)->where('user_id', $userId)->first();
         if (!$order) {
-            return ['success' => false, 'message' => '订单不存在'];
+            return [ 'success' => false, 'message' => '订单不存在' ];
         }
 
         if ($order->status !== OrderStatus::PENDING) {
-            return ['success' => false, 'message' => '只能取消等待中的订单'];
+            return [ 'success' => false, 'message' => '只能取消等待中的订单' ];
         }
 
-        try {
-            // 1. 更新订单状态为取消
-            $order->update(['status' => OrderStatus::CANCELLED]);
-
-            // 2. 处理解冻逻辑
-            if ($order->order_type === OrderType::SELL) {
-                // 卖出订单:解冻物品
-                $unfreezeResult = self::unfreezeOrderItems($order);
-                if (!$unfreezeResult['success']) {
-                    return ['success' => false, 'message' => '解冻物品失败:' . $unfreezeResult['message']];
-                }
-            } elseif ($order->order_type === OrderType::BUY) {
-                // 买入订单:解冻资金
-                $unfreezeResult = self::unfreezeOrderFunds($order);
-                if (!$unfreezeResult['success']) {
-                    return ['success' => false, 'message' => '解冻资金失败:' . $unfreezeResult['message']];
-                }
-            }
 
-            return ['success' => true, 'message' => '订单已取消'];
-        } catch (\Exception $e) {
-            return ['success' => false, 'message' => '取消订单失败:' . $e->getMessage()];
+        // 1. 更新订单状态为取消
+        $order->update([ 'status' => OrderStatus::CANCELLED ]);
+
+        // 2. 处理解冻逻辑
+        if ($order->order_type === OrderType::SELL) {
+            // 卖出订单:解冻物品
+            $unfreezeResult = self::unfreezeOrderItems($order);
+            if (!$unfreezeResult['success']) {
+                return [ 'success' => false, 'message' => '解冻物品失败:' . $unfreezeResult['message'] ];
+            }
+        } elseif ($order->order_type === OrderType::BUY) {
+            // 买入订单:解冻资金
+            $unfreezeResult = self::unfreezeOrderFunds($order);
+            if (!$unfreezeResult['success']) {
+                return [ 'success' => false, 'message' => '解冻资金失败:' . $unfreezeResult['message'] ];
+            }
         }
+
+        return [ 'success' => true, 'message' => '订单已取消' ];
+
+
     }
 
     /**
@@ -215,7 +214,7 @@ class MexOrderLogic
      * @param int $pageSize 每页数量
      * @return array 订单列表
      */
-    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20, $itemId =  null): array
+    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20, $itemId = null): array
     {
         $where['user_id'] = $userId;
         if ($itemId) {
@@ -223,12 +222,12 @@ class MexOrderLogic
         }
         $orders = MexOrder::where($where)
             ->orderBy('created_at', 'desc')
-            ->paginate($pageSize, ['*'], 'page', $page);
+            ->paginate($pageSize, [ '*' ], 'page', $page);
 
         return [
-            'orders' => $orders->items(),
-            'total' => $orders->total(),
-            'page' => $page,
+            'orders'    => $orders->items(),
+            'total'     => $orders->total(),
+            'page'      => $page,
             'page_size' => $pageSize,
         ];
     }
@@ -243,6 +242,7 @@ class MexOrderLogic
     public static function getOrderDetail(int $userId, int $orderId): ?array
     {
         $order = MexOrder::where('id', $orderId)->where('user_id', $userId)->first();
+
         return $order ? $order->toArray() : null;
     }
 
@@ -254,41 +254,30 @@ class MexOrderLogic
      */
     private static function unfreezeOrderItems(MexOrder $order): array
     {
-        try {
-            // 查找该订单相关的冻结记录
-            $freezeLogs = \App\Module\GameItems\Models\ItemFreezeLog::where('source_id', $order->id)
-                ->where('source_type', 'mex_sell_order')
-                ->where('action_type', \App\Module\GameItems\Enums\FREEZE_ACTION_TYPE::FREEZE)
-                ->get();
-
-            if ($freezeLogs->isEmpty()) {
-                return ['success' => true, 'message' => '没有找到需要解冻的物品'];
-            }
+        // 查找该订单相关的冻结记录
+        $freezeLogs = \App\Module\GameItems\Models\ItemFreezeLog::where('source_id', $order->id)
+            ->where('source_type', 'mex_sell_order')
+            ->where('action_type', \App\Module\GameItems\Enums\FREEZE_ACTION_TYPE::FREEZE)
+            ->get();
 
-            $unfrozenCount = 0;
-            foreach ($freezeLogs as $freezeLog) {
-                try {
-                    // 使用ItemService解冻物品
-                    \App\Module\GameItems\Services\ItemService::safeUnfreezeItem($freezeLog->id);
-                    $unfrozenCount++;
-                } catch (\Exception $e) {
-                    // 记录错误但继续处理其他冻结记录
-                    Logger::error("解冻物品失败", [
-                        'order_id' => $order->id,
-                        'freeze_log_id' => $freezeLog->id,
-                        'error' => $e->getMessage()
-                    ]);
-                }
-            }
+        if ($freezeLogs->isEmpty()) {
+            return [ 'success' => true, 'message' => '没有找到需要解冻的物品' ];
+        }
 
-            return [
-                'success' => true,
-                'message' => "成功解冻 {$unfrozenCount} 个冻结记录",
-                'unfrozen_count' => $unfrozenCount
-            ];
-        } catch (\Exception $e) {
-            return ['success' => false, 'message' => $e->getMessage()];
+        $unfrozenCount = 0;
+        foreach ($freezeLogs as $freezeLog) {
+            // 使用ItemService解冻物品
+            \App\Module\GameItems\Services\ItemService::safeUnfreezeItem($freezeLog->id);
+            $unfrozenCount++;
         }
+
+        return [
+            'success'        => true,
+            'message'        => "成功解冻 {$unfrozenCount} 个冻结记录",
+            'unfrozen_count' => $unfrozenCount
+        ];
+
+
     }
 
     /**
@@ -302,10 +291,10 @@ class MexOrderLogic
         try {
             // 获取可用账户类型和冻结账户类型
             $availableAccountType = FundLogic::getAvailableAccountType($order->currency_type);
-            $frozenAccountType = FundLogic::getFrozenAccountType($order->currency_type);
+            $frozenAccountType    = FundLogic::getFrozenAccountType($order->currency_type);
 
             if (!$availableAccountType || !$frozenAccountType) {
-                return ['success' => false, 'message' => '不支持的币种类型'];
+                return [ 'success' => false, 'message' => '不支持的币种类型' ];
             }
 
             // 创建Fund服务实例(可用账户)
@@ -313,7 +302,7 @@ class MexOrderLogic
 
             // 验证用户可用余额是否充足
             if ($fundService->balance() < $order->frozen_amount) {
-                return ['success' => false, 'message' => '可用余额不足'];
+                return [ 'success' => false, 'message' => '可用余额不足' ];
             }
 
             // 使用circulation方法将资金从可用账户转移到冻结账户
@@ -326,17 +315,17 @@ class MexOrderLogic
             );
 
             if (is_string($circulationResult)) {
-                return ['success' => false, 'message' => '资金冻结失败:' . $circulationResult];
+                return [ 'success' => false, 'message' => '资金冻结失败:' . $circulationResult ];
             }
 
             return [
-                'success' => true,
-                'message' => '资金冻结成功',
-                'frozen_amount' => $order->frozen_amount,
+                'success'        => true,
+                'message'        => '资金冻结成功',
+                'frozen_amount'  => $order->frozen_amount,
                 'circulation_id' => $circulationResult->id ?? null
             ];
         } catch (\Exception $e) {
-            return ['success' => false, 'message' => '冻结资金异常:' . $e->getMessage()];
+            return [ 'success' => false, 'message' => '冻结资金异常:' . $e->getMessage() ];
         }
     }
 
@@ -351,10 +340,10 @@ class MexOrderLogic
         try {
             // 获取可用账户类型和冻结账户类型
             $availableAccountType = FundLogic::getAvailableAccountType($order->currency_type);
-            $frozenAccountType = FundLogic::getFrozenAccountType($order->currency_type);
+            $frozenAccountType    = FundLogic::getFrozenAccountType($order->currency_type);
 
             if (!$availableAccountType || !$frozenAccountType) {
-                return ['success' => false, 'message' => '不支持的币种类型'];
+                return [ 'success' => false, 'message' => '不支持的币种类型' ];
             }
 
             // 创建Fund服务实例(冻结账户)
@@ -362,30 +351,30 @@ class MexOrderLogic
 
             // 验证冻结账户余额是否充足
             if ($fundService->balance() < $order->frozen_amount) {
-                return ['success' => false, 'message' => '冻结账户余额不足,可能已被解冻'];
+                return [ 'success' => false, 'message' => '冻结账户余额不足,可能已被解冻' ];
             }
 
             // 使用circulation方法将资金从冻结账户转移回可用账户
             $circulationResult = $fundService->circulation(
                 $availableAccountType,
-                (float)  $order->frozen_amount,
+                (float)$order->frozen_amount,
                 $order->id,
                 'mex_buy_order_cancel',
                 "农贸市场买入订单取消解冻,订单ID:{$order->id}"
             );
 
             if (is_string($circulationResult)) {
-                return ['success' => false, 'message' => '资金解冻失败:' . $circulationResult];
+                return [ 'success' => false, 'message' => '资金解冻失败:' . $circulationResult ];
             }
 
             return [
-                'success' => true,
-                'message' => '资金解冻成功',
+                'success'         => true,
+                'message'         => '资金解冻成功',
                 'unfrozen_amount' => $order->frozen_amount,
-                'circulation_id' => $circulationResult->id ?? null
+                'circulation_id'  => $circulationResult->id ?? null
             ];
         } catch (\Exception $e) {
-            return ['success' => false, 'message' => '解冻资金异常:' . $e->getMessage()];
+            return [ 'success' => false, 'message' => '解冻资金异常:' . $e->getMessage() ];
         }
     }
 
@@ -409,4 +398,5 @@ class MexOrderLogic
 
         return $orders->toArray();
     }
+
 }

+ 199 - 0
tests/test_safe_unfreeze_correct_logic.php

@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * 手动测试脚本:验证修正后的安全解冻逻辑
+ * 
+ * 测试安全解冻不再"补足到目标堆",而是直接解冻
+ */
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use App\Module\GameItems\Services\ItemService;
+use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
+use Illuminate\Support\Facades\DB;
+
+// 启动Laravel应用
+$app = require_once __DIR__ . '/../bootstrap/app.php';
+$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
+
+echo "=== 安全解冻正确逻辑验证测试 ===\n\n";
+
+try {
+    DB::beginTransaction();
+    
+    $userId = 1001;
+    $itemId = 1;
+    
+    echo "1. 准备测试数据\n";
+    
+    // 添加物品
+    $addResult = ItemService::addItem($userId, $itemId, 200);
+    echo "   添加200个物品成功\n";
+    
+    // 冻结第一批物品(目标解冻堆)
+    $freezeResult1 = ItemService::freezeItem(
+        $userId, 
+        $itemId, 
+        null,
+        100, 
+        FREEZE_REASON_TYPE::TRADE_ORDER->value,
+        [
+            'source_id' => 12345,
+            'source_type' => 'test_order_1',
+            'operator_id' => 1
+        ]
+    );
+    $freezeLogId1 = $freezeResult1['frozen_items'][0]['freeze_log_id'];
+    echo "   冻结100个物品成功,日志ID: {$freezeLogId1}\n";
+    
+    // 冻结第二批物品(补足来源)
+    $freezeResult2 = ItemService::freezeItem(
+        $userId, 
+        $itemId, 
+        null,
+        60, 
+        FREEZE_REASON_TYPE::TRADE_ORDER->value,
+        [
+            'source_id' => 12346,
+            'source_type' => 'test_order_2',
+            'operator_id' => 1
+        ]
+    );
+    $freezeLogId2 = $freezeResult2['frozen_items'][0]['freeze_log_id'];
+    echo "   冻结60个物品成功,日志ID: {$freezeLogId2}\n";
+    
+    echo "\n2. 部分消耗目标冻结堆\n";
+    
+    // 消耗目标冻结堆的60个,剩余40个
+    $consumeResult = ItemService::consumeItem(
+        $userId, 
+        $itemId, 
+        null,
+        60, 
+        [
+            'include_frozen' => true,
+            'source_type' => 'test_consume',
+            'source_id' => 67890
+        ]
+    );
+    echo "   消耗60个冻结物品成功\n";
+    
+    // 检查消耗后状态
+    echo "\n3. 检查消耗后的物品状态\n";
+    $userItems = DB::table('item_users')
+        ->where('user_id', $userId)
+        ->where('item_id', $itemId)
+        ->get();
+    
+    foreach ($userItems as $item) {
+        $frozenStatus = $item->is_frozen ? '冻结' : '可用';
+        $freezeLogId = $item->frozen_log_id ?? 'N/A';
+        echo "   物品堆ID: {$item->id}, 数量: {$item->quantity}, 状态: {$frozenStatus}, 冻结日志: {$freezeLogId}\n";
+    }
+    
+    // 现在状态应该是:
+    // - 目标冻结堆:40个(100-60)
+    // - 补足来源堆:60个
+    // - 可用物品:40个
+    
+    echo "\n4. 执行安全解冻\n";
+    
+    try {
+        $safeUnfreezeResult = ItemService::safeUnfreezeItem($freezeLogId1);
+        echo "   安全解冻成功: " . json_encode($safeUnfreezeResult, JSON_UNESCAPED_UNICODE) . "\n";
+        
+        $totalUnfrozen = $safeUnfreezeResult['unfrozen_quantity'];
+        $targetUnfrozen = $safeUnfreezeResult['target_unfrozen_quantity'];
+        $compensated = $safeUnfreezeResult['shortage_compensated'];
+        
+        echo "   总解冻数量: {$totalUnfrozen}\n";
+        echo "   目标堆解冻: {$targetUnfrozen}\n";
+        echo "   补足数量: {$compensated}\n";
+        
+        // 验证逻辑
+        if ($totalUnfrozen == 100) {
+            echo "   ✅ 总解冻数量正确:100个\n";
+        } else {
+            echo "   ❌ 总解冻数量错误:应该100个,实际 {$totalUnfrozen}\n";
+        }
+        
+        if ($targetUnfrozen == 40) {
+            echo "   ✅ 目标堆解冻正确:40个\n";
+        } else {
+            echo "   ❌ 目标堆解冻错误:应该40个,实际 {$targetUnfrozen}\n";
+        }
+        
+        if ($compensated == 60) {
+            echo "   ✅ 补足数量正确:60个\n";
+        } else {
+            echo "   ❌ 补足数量错误:应该60个,实际 {$compensated}\n";
+        }
+        
+    } catch (Exception $e) {
+        echo "   ❌ 安全解冻失败: " . $e->getMessage() . "\n";
+    }
+    
+    echo "\n5. 检查解冻后的物品状态\n";
+    $userItemsFinal = DB::table('item_users')
+        ->where('user_id', $userId)
+        ->where('item_id', $itemId)
+        ->get();
+    
+    $totalAvailable = 0;
+    $totalFrozen = 0;
+    
+    foreach ($userItemsFinal as $item) {
+        $frozenStatus = $item->is_frozen ? '冻结' : '可用';
+        $freezeLogId = $item->frozen_log_id ?? 'N/A';
+        echo "   物品堆ID: {$item->id}, 数量: {$item->quantity}, 状态: {$frozenStatus}, 冻结日志: {$freezeLogId}\n";
+        
+        if ($item->is_frozen) {
+            $totalFrozen += $item->quantity;
+        } else {
+            $totalAvailable += $item->quantity;
+        }
+    }
+    
+    echo "   总计:可用 {$totalAvailable},冻结 {$totalFrozen}\n";
+    
+    // 验证最终状态
+    echo "\n6. 验证最终状态\n";
+    
+    // 总数量应该是140(200-60消耗)
+    $totalQuantity = $totalAvailable + $totalFrozen;
+    if ($totalQuantity == 140) {
+        echo "   ✅ 数量守恒正确:总数量140(200-60消耗)\n";
+    } else {
+        echo "   ❌ 数量守恒错误:总数量应该140,实际 {$totalQuantity}\n";
+    }
+    
+    // 可用物品应该是140(40原始可用+40目标堆解冻+60补足解冻)
+    if ($totalAvailable == 140) {
+        echo "   ✅ 可用数量正确:140个(40原始+40目标堆+60补足)\n";
+    } else {
+        echo "   ❌ 可用数量错误:应该140个,实际 {$totalAvailable}\n";
+    }
+    
+    // 冻结物品应该是0(第二个冻结堆被完全用于补足)
+    if ($totalFrozen == 0) {
+        echo "   ✅ 冻结数量正确:0个(第二个冻结堆被完全用于补足)\n";
+    } else {
+        echo "   ❌ 冻结数量错误:应该0个,实际 {$totalFrozen}\n";
+    }
+    
+    echo "\n7. 验证解冻逻辑的正确性\n";
+    echo "   原始逻辑(错误):目标堆40 + 补足60 → 目标堆100 → 解冻100\n";
+    echo "   修正逻辑(正确):目标堆40直接解冻 + 其他堆60直接解冻 = 总共100可用\n";
+    echo "   ✅ 新逻辑避免了不必要的'补足到目标堆'步骤\n";
+    
+    echo "\n=== 测试完成 ===\n";
+    echo "安全解冻逻辑修正验证完成,不再有多余的补足步骤\n";
+    
+    DB::rollback();
+    echo "已回滚测试数据\n";
+    
+} catch (Exception $e) {
+    DB::rollback();
+    echo "测试失败: " . $e->getMessage() . "\n";
+    echo "堆栈跟踪: " . $e->getTraceAsString() . "\n";
+}