RewardLogic.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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_MODE;
  7. use App\Module\Game\Events\RewardGrantedEvent;
  8. use App\Module\Game\Models\GameRewardGroup;
  9. use App\Module\Game\Models\GameRewardLog;
  10. use App\Module\Game\Services\PityService;
  11. use App\Module\Game\Logics\WeightSelectionReward;
  12. use App\Module\Game\Logics\IndependentProbabilityReward;
  13. use App\Module\Game\Logics\RewardProcessors\RewardProcessorDispatcher;
  14. use Exception;
  15. use Illuminate\Support\Facades\Log;
  16. use UCore\Db\Helper;
  17. /**
  18. * 奖励处理逻辑类
  19. *
  20. * 负责处理奖励的发放、记录等内部逻辑
  21. */
  22. class RewardLogic
  23. {
  24. /**
  25. * 获取奖励组
  26. *
  27. * @param int|string $groupIdOrCode 奖励组ID或编码
  28. * @return RewardGroupDto|null
  29. */
  30. public function getRewardGroup($groupIdOrCode): ?RewardGroupDto
  31. {
  32. $query = GameRewardGroup::with('rewardItems');
  33. if (is_numeric($groupIdOrCode)) {
  34. $group = $query->find($groupIdOrCode);
  35. } else {
  36. $group = $query->where('code', $groupIdOrCode)->first();
  37. }
  38. if (!$group) {
  39. return null;
  40. }
  41. return RewardGroupDto::fromModel($group, true);
  42. }
  43. /**
  44. * 发放奖励
  45. *
  46. * @param int $userId 用户ID
  47. * @param int|string $groupIdOrCode 奖励组ID或编码
  48. * @param string $sourceType 来源类型
  49. * @param int $sourceId 来源ID
  50. * @return RewardResultDto 奖励结果
  51. */
  52. public function grantReward(int $userId, $groupIdOrCode, string $sourceType, int $sourceId): RewardResultDto
  53. {
  54. try {
  55. // 获取奖励组
  56. $groupDto = $this->getRewardGroup($groupIdOrCode);
  57. if (!$groupDto) {
  58. return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}");
  59. }
  60. // 确定要发放的奖励项
  61. $rewardItems = $this->determineRewardItems($groupDto, $userId);
  62. if (empty($rewardItems)) {
  63. return RewardResultDto::fail("奖励组中没有可发放的奖励项");
  64. }
  65. // 检查事务是否已开启
  66. Helper::check_tr();
  67. // 发放各类奖励
  68. foreach ($rewardItems as $item) {
  69. RewardProcessorDispatcher::process($userId, $item, $sourceType, $sourceId);
  70. }
  71. // 记录奖励日志
  72. $this->logReward($userId, $groupDto->id, $sourceType, $sourceId, $rewardItems);
  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. Log::error("发放奖励失败", [
  87. 'userId' => $userId,
  88. 'groupIdOrCode' => $groupIdOrCode,
  89. 'sourceType' => $sourceType,
  90. 'sourceId' => $sourceId,
  91. 'error' => $e->getMessage(),
  92. 'trace' => $e->getTraceAsString()
  93. ]);
  94. return RewardResultDto::fail("发放奖励失败: " . $e->getMessage());
  95. }
  96. }
  97. /**
  98. * 确定要发放的奖励项
  99. *
  100. * @param RewardGroupDto $groupDto 奖励组DTO
  101. * @param int|null $userId 用户ID(用于保底机制)
  102. * @return RewardItemDto[] 要发放的奖励项
  103. */
  104. private function determineRewardItems(RewardGroupDto $groupDto, ?int $userId = null): array
  105. {
  106. // 检查奖励模式
  107. $rewardMode = $groupDto->rewardMode ?? REWARD_MODE::WEIGHT_SELECTION->value;
  108. if ($rewardMode == REWARD_MODE::INDEPENDENT_PROBABILITY->value) {
  109. // 独立概率模式
  110. return IndependentProbabilityReward::process($groupDto, $userId);
  111. } else {
  112. // 权重选择模式(默认)
  113. return WeightSelectionReward::process($groupDto, $userId);
  114. }
  115. }
  116. /**
  117. * 记录奖励日志
  118. *
  119. * @param int $userId 用户ID
  120. * @param int $groupId 奖励组ID
  121. * @param string $sourceType 来源类型
  122. * @param int $sourceId 来源ID
  123. * @param RewardItemDto[] $items 发放的奖励项
  124. * @return GameRewardLog
  125. */
  126. private function logReward(int $userId, int $groupId, string $sourceType, int $sourceId, array $items): GameRewardLog
  127. {
  128. // 将DTO转换为可存储的数组
  129. $itemsData = array_map(function (RewardItemDto $item) {
  130. return [
  131. 'id' => $item->id,
  132. 'reward_type' => $item->rewardType,
  133. 'target_id' => $item->targetId,
  134. 'param1' => $item->param1,
  135. 'param2' => $item->param2,
  136. 'quantity' => $item->quantity,
  137. 'extra_data' => $item->extraData
  138. ];
  139. }, $items);
  140. // 创建日志记录
  141. return GameRewardLog::create([
  142. 'user_id' => $userId,
  143. 'group_id' => $groupId,
  144. 'source_type' => $sourceType,
  145. 'source_id' => $sourceId,
  146. 'reward_items' => $itemsData
  147. ]);
  148. }
  149. /**
  150. * 模拟奖励发放(不实际发放,仅返回奖励结果)
  151. *
  152. * @param int|string $groupIdOrCode 奖励组ID或编码
  153. * @return RewardResultDto 奖励结果
  154. */
  155. public function simulateReward($groupIdOrCode): RewardResultDto
  156. {
  157. try {
  158. // 获取奖励组
  159. $groupDto = $this->getRewardGroup($groupIdOrCode);
  160. if (!$groupDto) {
  161. return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}");
  162. }
  163. // 确定要发放的奖励项(仅模拟,不实际发放)
  164. $rewardItems = $this->determineRewardItems($groupDto);
  165. if (empty($rewardItems)) {
  166. return RewardResultDto::fail("奖励组中没有可发放的奖励项");
  167. }
  168. // 返回模拟结果(不实际发放奖励)
  169. return RewardResultDto::success(
  170. 0, // 模拟用户ID
  171. $groupDto->id,
  172. $groupDto->code,
  173. $groupDto->name,
  174. 'simulate', // 模拟来源类型
  175. 0, // 模拟来源ID
  176. $rewardItems
  177. );
  178. } catch (\Exception $e) {
  179. return RewardResultDto::fail("模拟奖励失败: " . $e->getMessage());
  180. }
  181. }
  182. /**
  183. * 发放奖励(支持保底机制)
  184. *
  185. * @param int $userId 用户ID
  186. * @param int|string $groupIdOrCode 奖励组ID或编码
  187. * @param string $sourceType 来源类型
  188. * @param int $sourceId 来源ID
  189. * @param bool $enablePity 是否启用保底机制
  190. * @return RewardResultDto 奖励结果
  191. */
  192. public function grantRewardWithPity(int $userId, $groupIdOrCode, string $sourceType, int $sourceId, bool $enablePity = true): RewardResultDto
  193. {
  194. try {
  195. // 获取奖励组
  196. $groupDto = $this->getRewardGroup($groupIdOrCode);
  197. if (!$groupDto) {
  198. return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}");
  199. }
  200. // 确定要发放的奖励项(支持保底机制)
  201. $rewardItems = $this->determineRewardItemsWithPity($groupDto, $userId, $enablePity);
  202. if (empty($rewardItems)) {
  203. return RewardResultDto::fail("奖励组中没有可发放的奖励项");
  204. }
  205. // 检查事务是否已开启
  206. Helper::check_tr();
  207. // 发放各类奖励
  208. foreach ($rewardItems as $item) {
  209. RewardProcessorDispatcher::process($userId, $item, $sourceType, $sourceId);
  210. }
  211. // 更新保底计数(如果启用保底机制)
  212. if ($enablePity) {
  213. $obtainedItemIds = array_map(function ($item) {
  214. return $item->id;
  215. }, $rewardItems);
  216. PityService::updatePityCounts($userId, $groupDto->id, $obtainedItemIds);
  217. }
  218. // 记录奖励日志
  219. $this->logReward($userId, $groupDto->id, $sourceType, $sourceId, $rewardItems);
  220. // 触发奖励发放事件
  221. event(new RewardGrantedEvent($userId, $groupDto->id, $groupDto->code, $sourceType, $sourceId, $rewardItems));
  222. // 返回成功结果
  223. return RewardResultDto::success(
  224. $userId,
  225. $groupDto->id,
  226. $groupDto->code,
  227. $groupDto->name,
  228. $sourceType,
  229. $sourceId,
  230. $rewardItems
  231. );
  232. } catch (Exception $e) {
  233. Log::error("发放奖励失败(保底机制)", [
  234. 'userId' => $userId,
  235. 'groupIdOrCode' => $groupIdOrCode,
  236. 'sourceType' => $sourceType,
  237. 'sourceId' => $sourceId,
  238. 'enablePity' => $enablePity,
  239. 'error' => $e->getMessage(),
  240. 'trace' => $e->getTraceAsString()
  241. ]);
  242. return RewardResultDto::fail("发放奖励失败: " . $e->getMessage());
  243. }
  244. }
  245. /**
  246. * 确定要发放的奖励项(支持保底机制)
  247. *
  248. * @param RewardGroupDto $groupDto 奖励组DTO
  249. * @param int $userId 用户ID
  250. * @param bool $enablePity 是否启用保底机制
  251. * @return RewardItemDto[] 要发放的奖励项
  252. */
  253. private function determineRewardItemsWithPity(RewardGroupDto $groupDto, int $userId, bool $enablePity): array
  254. {
  255. // 检查奖励模式
  256. $rewardMode = $groupDto->rewardMode ?? REWARD_MODE::WEIGHT_SELECTION->value;
  257. // 如果启用保底机制,传递用户ID;否则传递null
  258. $userIdForPity = $enablePity ? $userId : null;
  259. if ($rewardMode == REWARD_MODE::INDEPENDENT_PROBABILITY->value) {
  260. // 独立概率模式
  261. return IndependentProbabilityReward::process($groupDto, $userIdForPity);
  262. } else {
  263. // 权重选择模式(默认)
  264. return WeightSelectionReward::process($groupDto, $userIdForPity);
  265. }
  266. }
  267. }