CurrencyConsume.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. namespace App\Module\Game\Logics\ConsumeProcessors;
  3. use App\Module\Fund\Enums\LOG_TYPE as FUND_LOG_TYPE;
  4. use App\Module\Fund\Logic\User as FundLogic;
  5. use App\Module\Game\Models\GameConsumeItem;
  6. use UCore\Dto\Res;
  7. class CurrencyConsume
  8. {
  9. /**
  10. * 检查币种消耗
  11. *
  12. * 注意:这里的target_id指向fund_currency表的id(币种ID)
  13. * 这个方法会检查用户所有与该币种相关的账户,并计算总余额
  14. *
  15. * @param int $userId 用户ID
  16. * @param GameConsumeItem $consumeItem 消耗项
  17. * @param float $multiplier 倍数,用于验证几倍消耗,默认为1
  18. * @return array 检查结果
  19. */
  20. public static function checkCurrencyConsume(int $userId, GameConsumeItem $consumeItem, float $multiplier = 1.0): array
  21. {
  22. $currencyId = $consumeItem->target_id;
  23. $amount = $consumeItem->quantity * $multiplier; // 使用倍数计算所需金额
  24. try {
  25. // 获取该币种的所有账户种类
  26. $fundConfigs = \App\Module\Fund\Models\FundConfigModel::where('currency_id', $currencyId)->get();
  27. if ($fundConfigs->isEmpty()) {
  28. return [
  29. 'success' => false,
  30. 'message' => "币种不存在或没有关联的账户种类",
  31. 'currency_id' => $currencyId
  32. ];
  33. }
  34. // 获取用户所有与该币种相关的账户
  35. $fundConfigIds = $fundConfigs->pluck('id')->toArray();
  36. $accounts = \App\Module\Fund\Models\FundModel::where('user_id', $userId)
  37. ->whereIn('fund_id', $fundConfigIds)
  38. ->get();
  39. if ($accounts->isEmpty()) {
  40. return [
  41. 'success' => false,
  42. 'message' => "用户没有该币种的账户",
  43. 'currency_id' => $currencyId
  44. ];
  45. }
  46. // 计算总余额
  47. $totalBalance = $accounts->sum('balance');
  48. // 检查余额是否足够
  49. if ($totalBalance < $amount) {
  50. return [
  51. 'success' => false,
  52. 'message' => "币种总余额不足,需要 {$amount},实际 {$totalBalance}",
  53. 'currency_id' => $currencyId,
  54. 'required' => $amount,
  55. 'actual' => $totalBalance
  56. ];
  57. }
  58. return [
  59. 'success' => true,
  60. 'message' => '币种总余额足够',
  61. 'currency_id' => $currencyId,
  62. 'required' => $amount,
  63. 'actual' => $totalBalance,
  64. 'accounts' => $accounts->toArray()
  65. ];
  66. } catch (\Exception $e) {
  67. return [
  68. 'success' => false,
  69. 'message' => '检查币种消耗异常: ' . $e->getMessage(),
  70. 'currency_id' => $currencyId
  71. ];
  72. }
  73. }
  74. public static function process(int $userId, GameConsumeItem $consumeItem, string $source, int $sourceId,$multiplier): Res
  75. {
  76. // todo 需要优化,迁移到Fund中
  77. $currencyId = $consumeItem->target_id;
  78. $amountToConsume = $consumeItem->quantity * $multiplier; // 使用倍数计算实际消耗金额
  79. try {
  80. // 先检查是否有足够的余额(使用相同的倍数)
  81. $checkResult = self::checkCurrencyConsume($userId, $consumeItem, $multiplier);
  82. if (!$checkResult['success']) {
  83. return $checkResult;
  84. }
  85. // 获取该币种的所有账户种类
  86. $fundConfigs = \App\Module\Fund\Models\FundConfigModel::where('currency_id', $currencyId)->get();
  87. $fundConfigIds = $fundConfigs->pluck('id')->toArray();
  88. // 获取用户所有与该币种相关的账户
  89. $accounts = \App\Module\Fund\Models\FundModel::where('user_id', $userId)
  90. ->whereIn('fund_id', $fundConfigIds)
  91. ->orderBy('fund_id') // 按账户种类ID排序,通常可用账户ID较小
  92. ->get();
  93. // 验证事务是否已开启(由调用者负责事务管理)
  94. \UCore\Db\Helper::check_tr();
  95. $remainingAmount = $amountToConsume;
  96. $consumedAccounts = [];
  97. // 依次从各个账户中扣除
  98. foreach ($accounts as $account) {
  99. if ($remainingAmount <= 0) {
  100. break;
  101. }
  102. $accountBalance = $account->balance;
  103. $amountToDeduct = min($accountBalance, $remainingAmount);
  104. if ($amountToDeduct > 0) {
  105. // 构建备注
  106. $remark = "币种消耗:{$currencyId},消耗组:{$consumeItem->group_id},来源:{$source}";
  107. if ($sourceId > 0) {
  108. $remark .= ",ID:{$sourceId}";
  109. }
  110. if ($multiplier != 1.0) {
  111. $remark .= ",倍数:{$multiplier}";
  112. }
  113. // 从当前账户扣除
  114. $result = FundLogic::handle(
  115. $userId,
  116. $account->fund_id->value,
  117. -$amountToDeduct, // 负数表示消耗
  118. FUND_LOG_TYPE::TRADE,
  119. $sourceId,
  120. $remark
  121. );
  122. if ($result !== true) {
  123. return [
  124. 'success' => false,
  125. 'message' => is_string($result) ? $result : "从账户 {$account->fund_id} 扣除失败",
  126. 'currency_id' => $currencyId,
  127. 'fund_config_id' => $account->fund_id,
  128. 'amount' => $amountToDeduct
  129. ];
  130. }
  131. $consumedAccounts[] = [
  132. 'fund_config_id' => $account->fund_id,
  133. 'amount' => $amountToDeduct
  134. ];
  135. $remainingAmount -= $amountToDeduct;
  136. }
  137. }
  138. return [
  139. 'success' => true,
  140. 'message' => '币种消耗成功',
  141. 'currency_id' => $currencyId,
  142. 'amount' => $amountToConsume,
  143. 'consumed_accounts' => $consumedAccounts
  144. ];
  145. } catch (\Exception $e) {
  146. return [
  147. 'success' => false,
  148. 'message' => '币种消耗异常: ' . $e->getMessage(),
  149. 'currency_id' => $currencyId,
  150. 'amount' => $amountToConsume
  151. ];
  152. }
  153. }
  154. }