manual_test_unfreeze_concurrency.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. /**
  3. * 手动测试脚本:验证解冻过程中的并发安全性
  4. *
  5. * 测试lock for update是否能正确处理并发场景
  6. */
  7. require_once __DIR__ . '/../vendor/autoload.php';
  8. use App\Module\GameItems\Services\ItemService;
  9. use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
  10. use Illuminate\Support\Facades\DB;
  11. // 启动Laravel应用
  12. $app = require_once __DIR__ . '/../bootstrap/app.php';
  13. $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
  14. echo "=== 物品解冻并发安全性测试 ===\n\n";
  15. try {
  16. DB::beginTransaction();
  17. $userId = 1001;
  18. $itemId = 1;
  19. $freezeQuantity = 100;
  20. $consumeQuantity = 60;
  21. echo "1. 准备测试数据\n";
  22. // 添加物品(确保有足够的物品用于测试)
  23. $addResult = ItemService::addItem($userId, $itemId, 200);
  24. echo " 添加物品: " . json_encode($addResult, JSON_UNESCAPED_UNICODE) . "\n";
  25. // 冻结物品
  26. echo "\n2. 冻结物品\n";
  27. $freezeResult = ItemService::freezeItem(
  28. $userId,
  29. $itemId,
  30. null,
  31. $freezeQuantity,
  32. FREEZE_REASON_TYPE::TRADE_ORDER->value,
  33. [
  34. 'source_id' => 12345,
  35. 'source_type' => 'test_order',
  36. 'operator_id' => 1
  37. ]
  38. );
  39. $freezeLogId = $freezeResult['frozen_items'][0]['freeze_log_id'];
  40. echo " 冻结成功,日志ID: {$freezeLogId}\n";
  41. // 消耗部分冻结物品
  42. echo "\n3. 消耗部分冻结物品\n";
  43. $consumeResult = ItemService::consumeItem(
  44. $userId,
  45. $itemId,
  46. null,
  47. $consumeQuantity,
  48. [
  49. 'include_frozen' => true,
  50. 'source_type' => 'test_consume',
  51. 'source_id' => 67890
  52. ]
  53. );
  54. echo " 消耗成功: {$consumeQuantity}个\n";
  55. // 检查当前状态
  56. echo "\n4. 检查当前物品状态\n";
  57. $userItems = DB::table('item_users')
  58. ->where('user_id', $userId)
  59. ->where('item_id', $itemId)
  60. ->get();
  61. foreach ($userItems as $item) {
  62. $frozenStatus = $item->is_frozen ? '冻结' : '可用';
  63. echo " 物品堆ID: {$item->id}, 数量: {$item->quantity}, 状态: {$frozenStatus}\n";
  64. }
  65. // 模拟并发场景:在解冻的同时消耗可用物品
  66. echo "\n5. 模拟并发场景测试\n";
  67. echo " 开始解冻操作(会锁定可用物品)...\n";
  68. // 这个操作会锁定可用物品记录
  69. $unfreezeResult = ItemService::unfreezeItem($freezeLogId);
  70. echo " 解冻成功: " . json_encode($unfreezeResult, JSON_UNESCAPED_UNICODE) . "\n";
  71. echo " 补足差额: " . ($unfreezeResult['shortage_compensated'] ?? 0) . "\n";
  72. // 检查解冻后的状态
  73. echo "\n6. 检查解冻后的物品状态\n";
  74. $userItemsAfter = DB::table('item_users')
  75. ->where('user_id', $userId)
  76. ->where('item_id', $itemId)
  77. ->get();
  78. foreach ($userItemsAfter as $item) {
  79. $frozenStatus = $item->is_frozen ? '冻结' : '可用';
  80. echo " 物品堆ID: {$item->id}, 数量: {$item->quantity}, 状态: {$frozenStatus}\n";
  81. }
  82. // 测试边界情况:可用数量刚好不足
  83. echo "\n7. 测试边界情况:可用数量不足\n";
  84. // 消耗大部分可用物品,使其不足以补足
  85. $availableQuantity = DB::table('item_users')
  86. ->where('user_id', $userId)
  87. ->where('item_id', $itemId)
  88. ->where('is_frozen', false)
  89. ->sum('quantity');
  90. echo " 当前可用数量: {$availableQuantity}\n";
  91. if ($availableQuantity > 10) {
  92. $consumeMore = $availableQuantity - 5; // 留5个,不足以补足
  93. echo " 消耗更多可用物品: {$consumeMore}个\n";
  94. $consumeResult2 = ItemService::consumeItem(
  95. $userId,
  96. $itemId,
  97. null,
  98. $consumeMore,
  99. [
  100. 'include_frozen' => false, // 只消耗可用物品
  101. 'source_type' => 'test_consume_more',
  102. 'source_id' => 67891
  103. ]
  104. );
  105. // 重新冻结更多数量,然后部分消耗,创建需要补足但数量不足的场景
  106. $freezeResult2 = ItemService::freezeItem(
  107. $userId,
  108. $itemId,
  109. null,
  110. 5, // 冻结5个
  111. FREEZE_REASON_TYPE::TRADE_ORDER->value,
  112. [
  113. 'source_id' => 12346,
  114. 'source_type' => 'test_order_2',
  115. 'operator_id' => 1
  116. ]
  117. );
  118. $freezeLogId2 = $freezeResult2['frozen_items'][0]['freeze_log_id'];
  119. // 部分消耗这个冻结堆(消耗3个,剩余2个)
  120. $consumeResult3 = ItemService::consumeItem(
  121. $userId,
  122. $itemId,
  123. null,
  124. 3,
  125. [
  126. 'include_frozen' => true,
  127. 'source_type' => 'test_consume_partial',
  128. 'source_id' => 67892
  129. ]
  130. );
  131. echo " 冻结5个,消耗3个,剩余2个,需要补足3个,但可用数量不足\n";
  132. // 尝试解冻(应该失败,因为可用数量不足)
  133. echo " 尝试解冻(预期失败)...\n";
  134. try {
  135. $unfreezeResult2 = ItemService::unfreezeItem($freezeLogId2);
  136. echo " 意外成功: " . json_encode($unfreezeResult2, JSON_UNESCAPED_UNICODE) . "\n";
  137. } catch (Exception $e) {
  138. echo " 预期失败: " . $e->getMessage() . "\n";
  139. }
  140. // 测试安全解冻
  141. echo " 尝试安全解冻...\n";
  142. try {
  143. $safeUnfreezeResult = ItemService::safeUnfreezeItem($freezeLogId2);
  144. echo " 安全解冻结果: " . json_encode($safeUnfreezeResult, JSON_UNESCAPED_UNICODE) . "\n";
  145. } catch (Exception $e) {
  146. echo " 安全解冻失败: " . $e->getMessage() . "\n";
  147. }
  148. }
  149. echo "\n=== 测试完成 ===\n";
  150. DB::rollback();
  151. echo "已回滚测试数据\n";
  152. } catch (Exception $e) {
  153. DB::rollback();
  154. echo "测试失败: " . $e->getMessage() . "\n";
  155. echo "堆栈跟踪: " . $e->getTraceAsString() . "\n";
  156. }