RewardLogic.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. namespace App\Module\Game\Logics;
  3. use App\Module\Game\Dtos\RewardGroupDto;
  4. use App\Module\Game\Dtos\RewardItemDto;
  5. use App\Module\Game\Dtos\RewardResultDto;
  6. use App\Module\Game\Enums\REWARD_TYPE;
  7. use App\Module\Game\Events\RewardGrantedEvent;
  8. use App\Module\Game\Models\GameRewardGroup;
  9. use App\Module\Game\Models\GameRewardItem;
  10. use App\Module\Game\Models\GameRewardLog;
  11. use App\Module\GameItems\Services\ItemService;
  12. use Exception;
  13. use Illuminate\Support\Facades\DB;
  14. use Illuminate\Support\Facades\Log;
  15. /**
  16. * 奖励处理逻辑类
  17. *
  18. * 负责处理奖励的发放、记录等内部逻辑
  19. */
  20. class RewardLogic
  21. {
  22. /**
  23. * 获取奖励组
  24. *
  25. * @param int|string $groupIdOrCode 奖励组ID或编码
  26. * @return RewardGroupDto|null
  27. */
  28. public function getRewardGroup($groupIdOrCode): ?RewardGroupDto
  29. {
  30. $query = GameRewardGroup::with('rewardItems');
  31. if (is_numeric($groupIdOrCode)) {
  32. $group = $query->find($groupIdOrCode);
  33. } else {
  34. $group = $query->where('code', $groupIdOrCode)->first();
  35. }
  36. if (!$group) {
  37. return null;
  38. }
  39. return RewardGroupDto::fromModel($group, true);
  40. }
  41. /**
  42. * 发放奖励
  43. *
  44. * @param int $userId 用户ID
  45. * @param int|string $groupIdOrCode 奖励组ID或编码
  46. * @param string $sourceType 来源类型
  47. * @param int $sourceId 来源ID
  48. * @return RewardResultDto 奖励结果
  49. */
  50. public function grantReward(int $userId, $groupIdOrCode, string $sourceType, int $sourceId): RewardResultDto
  51. {
  52. try {
  53. // 获取奖励组
  54. $groupDto = $this->getRewardGroup($groupIdOrCode);
  55. if (!$groupDto) {
  56. return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}");
  57. }
  58. // 确定要发放的奖励项
  59. $rewardItems = $this->determineRewardItems($groupDto);
  60. if (empty($rewardItems)) {
  61. return RewardResultDto::fail("奖励组中没有可发放的奖励项");
  62. }
  63. // 开始事务
  64. DB::beginTransaction();
  65. // 发放各类奖励
  66. foreach ($rewardItems as $item) {
  67. $this->processRewardItem($userId, $item);
  68. }
  69. // 记录奖励日志
  70. $this->logReward($userId, $groupDto->id, $sourceType, $sourceId, $rewardItems);
  71. // 提交事务
  72. DB::commit();
  73. // 触发奖励发放事件
  74. event(new RewardGrantedEvent($userId, $groupDto->id, $groupDto->code, $sourceType, $sourceId, $rewardItems));
  75. // 返回成功结果
  76. return RewardResultDto::success(
  77. $userId,
  78. $groupDto->id,
  79. $groupDto->code,
  80. $groupDto->name,
  81. $sourceType,
  82. $sourceId,
  83. $rewardItems
  84. );
  85. } catch (Exception $e) {
  86. // 回滚事务
  87. DB::rollBack();
  88. Log::error("发放奖励失败", [
  89. 'userId' => $userId,
  90. 'groupIdOrCode' => $groupIdOrCode,
  91. 'sourceType' => $sourceType,
  92. 'sourceId' => $sourceId,
  93. 'error' => $e->getMessage(),
  94. 'trace' => $e->getTraceAsString()
  95. ]);
  96. return RewardResultDto::fail("发放奖励失败: " . $e->getMessage());
  97. }
  98. }
  99. /**
  100. * 确定要发放的奖励项
  101. *
  102. * @param RewardGroupDto $groupDto 奖励组DTO
  103. * @return RewardItemDto[] 要发放的奖励项
  104. */
  105. private function determineRewardItems(RewardGroupDto $groupDto): array
  106. {
  107. $items = $groupDto->items;
  108. // 如果不是随机发放,返回所有奖励项
  109. if (!$groupDto->isRandom) {
  110. return $items;
  111. }
  112. // 如果是随机发放,按权重随机选择指定数量的奖励项
  113. $selectedItems = [];
  114. $guaranteedItems = [];
  115. $normalItems = [];
  116. // 先分离必中项和普通项
  117. foreach ($items as $item) {
  118. if ($item->isGuaranteed) {
  119. $guaranteedItems[] = $item;
  120. } else {
  121. $normalItems[] = $item;
  122. }
  123. }
  124. // 先选择必中项
  125. $selectedItems = $guaranteedItems;
  126. // 如果必中项数量已经达到或超过随机数量,直接返回必中项
  127. if (count($selectedItems) >= $groupDto->randomCount) {
  128. return array_slice($selectedItems, 0, $groupDto->randomCount);
  129. }
  130. // 计算剩余需要选择的数量
  131. $remainingCount = $groupDto->randomCount - count($selectedItems);
  132. // 如果没有普通项,直接返回必中项
  133. if (empty($normalItems)) {
  134. return $selectedItems;
  135. }
  136. // 按权重随机选择普通项
  137. $totalWeight = array_sum(array_map(function ($item) {
  138. return $item->weight;
  139. }, $normalItems));
  140. // 如果总权重为0,随机选择
  141. if ($totalWeight <= 0) {
  142. shuffle($normalItems);
  143. $selectedNormalItems = array_slice($normalItems, 0, $remainingCount);
  144. } else {
  145. // 按权重随机选择
  146. $selectedNormalItems = [];
  147. for ($i = 0; $i < $remainingCount; $i++) {
  148. if (empty($normalItems)) {
  149. break;
  150. }
  151. $randomWeight = mt_rand(1, $totalWeight * 100) / 100;
  152. $currentWeight = 0;
  153. foreach ($normalItems as $key => $item) {
  154. $currentWeight += $item->weight;
  155. if ($randomWeight <= $currentWeight) {
  156. $selectedNormalItems[] = $item;
  157. $totalWeight -= $item->weight;
  158. unset($normalItems[$key]);
  159. $normalItems = array_values($normalItems);
  160. break;
  161. }
  162. }
  163. }
  164. }
  165. // 合并必中项和选中的普通项
  166. return array_merge($selectedItems, $selectedNormalItems);
  167. }
  168. /**
  169. * 处理单个奖励项
  170. *
  171. * @param int $userId 用户ID
  172. * @param RewardItemDto $item 奖励项
  173. * @return void
  174. */
  175. private function processRewardItem(int $userId, RewardItemDto $item): void
  176. {
  177. switch ($item->rewardType) {
  178. case REWARD_TYPE::ITEM:
  179. // 发放物品奖励
  180. ItemService::addItem($userId, $item->targetId, $item->quantity, [
  181. 'param1' => $item->param1,
  182. 'param2' => $item->param2,
  183. 'source' => 'reward',
  184. 'extra_data' => $item->extraData
  185. ]);
  186. break;
  187. case REWARD_TYPE::CURRENCY:
  188. // 发放货币奖励
  189. // 这里需要调用货币服务,根据实际情况实现
  190. // CurrencyService::addCurrency($userId, $item->targetId, $item->quantity, [
  191. // 'source' => 'reward',
  192. // 'param1' => $item->param1,
  193. // 'param2' => $item->param2
  194. // ]);
  195. break;
  196. case REWARD_TYPE::PET_EXP:
  197. // 发放宠物经验奖励
  198. // 这里需要调用宠物服务,根据实际情况实现
  199. // PetService::addExp($userId, $item->targetId, $item->quantity);
  200. break;
  201. case REWARD_TYPE::PET_ENERGY:
  202. // 发放宠物体力奖励
  203. // 这里需要调用宠物服务,根据实际情况实现
  204. // PetService::addEnergy($userId, $item->targetId, $item->quantity);
  205. break;
  206. default:
  207. // 其他类型奖励,记录日志
  208. Log::warning("未处理的奖励类型", [
  209. 'userId' => $userId,
  210. 'rewardType' => $item->rewardType,
  211. 'targetId' => $item->targetId,
  212. 'quantity' => $item->quantity
  213. ]);
  214. break;
  215. }
  216. }
  217. /**
  218. * 记录奖励日志
  219. *
  220. * @param int $userId 用户ID
  221. * @param int $groupId 奖励组ID
  222. * @param string $sourceType 来源类型
  223. * @param int $sourceId 来源ID
  224. * @param RewardItemDto[] $items 发放的奖励项
  225. * @return GameRewardLog
  226. */
  227. private function logReward(int $userId, int $groupId, string $sourceType, int $sourceId, array $items): GameRewardLog
  228. {
  229. // 将DTO转换为可存储的数组
  230. $itemsData = array_map(function (RewardItemDto $item) {
  231. return [
  232. 'id' => $item->id,
  233. 'reward_type' => $item->rewardType,
  234. 'target_id' => $item->targetId,
  235. 'param1' => $item->param1,
  236. 'param2' => $item->param2,
  237. 'quantity' => $item->quantity,
  238. 'extra_data' => $item->extraData
  239. ];
  240. }, $items);
  241. // 创建日志记录
  242. return GameRewardLog::create([
  243. 'user_id' => $userId,
  244. 'group_id' => $groupId,
  245. 'source_type' => $sourceType,
  246. 'source_id' => $sourceId,
  247. 'reward_items' => $itemsData
  248. ]);
  249. }
  250. }