find($groupIdOrCode); } else { $group = $query->where('code', $groupIdOrCode)->first(); } if (!$group) { return null; } return RewardGroupDto::fromModel($group, true); } /** * 发放奖励 * * @param int $userId 用户ID * @param int|string $groupIdOrCode 奖励组ID或编码 * @param string $sourceType 来源类型 * @param int $sourceId 来源ID * @return RewardResultDto 奖励结果 */ public function grantReward(int $userId, $groupIdOrCode, string $sourceType, int $sourceId,int $multiplier = 1): RewardResultDto { try { // 获取奖励组 $groupDto = $this->getRewardGroup($groupIdOrCode); if (!$groupDto) { return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}"); } // 确定要发放的奖励项 $rewardItems = $this->determineRewardItems($groupDto, $userId,$multiplier); if (empty($rewardItems)) { return RewardResultDto::fail("奖励组中没有可发放的奖励项"); } // 检查事务是否已开启 Helper::check_tr(); // 发放各类奖励 foreach ($rewardItems as $item) { RewardProcessorDispatcher::process($userId, $item, $sourceType, $sourceId); } // 记录奖励日志 $this->logReward($userId, $groupDto->id, $sourceType, $sourceId, $rewardItems); // 触发奖励发放事件 event(new RewardGrantedEvent($userId, $groupDto->id, $groupDto->code, $sourceType, $sourceId, $rewardItems)); // 返回成功结果 return RewardResultDto::success( $userId, $groupDto->id, $groupDto->code, $groupDto->name, $sourceType, $sourceId, $rewardItems ); } catch (Exception $e) { Log::error("发放奖励失败", [ 'userId' => $userId, 'groupIdOrCode' => $groupIdOrCode, 'sourceType' => $sourceType, 'sourceId' => $sourceId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return RewardResultDto::fail("发放奖励失败: " . $e->getMessage()); } } /** * 确定要发放的奖励项 * * @param RewardGroupDto $groupDto 奖励组DTO * @param int|null $userId 用户ID(用于保底机制) * @return RewardItemDto[] 要发放的奖励项 */ private function determineRewardItems(RewardGroupDto $groupDto, ?int $userId = null,int $multiplier = 1): array { // 检查奖励模式 $rewardMode = $groupDto->rewardMode ?? REWARD_MODE::WEIGHT_SELECTION->value; if ($rewardMode == REWARD_MODE::INDEPENDENT_PROBABILITY->value) { if($multiplier > 1){ throw new LogicException('倍率,只支持权重模式'); } // 独立概率模式 return IndependentProbabilityReward::process($groupDto, $userId); } else { // 权重选择模式(默认) return WeightSelectionReward::process($groupDto, $userId,$multiplier); } } /** * 记录奖励日志 * * @param int $userId 用户ID * @param int $groupId 奖励组ID * @param string $sourceType 来源类型 * @param int $sourceId 来源ID * @param RewardItemDto[] $items 发放的奖励项 * @return GameRewardLog */ private function logReward(int $userId, int $groupId, string $sourceType, int $sourceId, array $items): GameRewardLog { // 将DTO转换为可存储的数组 $itemsData = array_map(function (RewardItemDto $item) { return [ 'id' => $item->id, 'reward_type' => $item->rewardType, 'target_id' => $item->targetId, 'param1' => $item->param1, 'param2' => $item->param2, 'quantity' => $item->quantity, 'extra_data' => $item->extraData ]; }, $items); // 创建日志记录 return GameRewardLog::create([ 'user_id' => $userId, 'group_id' => $groupId, 'source_type' => $sourceType, 'source_id' => $sourceId, 'reward_items' => $itemsData ]); } /** * 模拟奖励发放(不实际发放,仅返回奖励结果) * * @param int|string $groupIdOrCode 奖励组ID或编码 * @return RewardResultDto 奖励结果 */ public function simulateReward($groupIdOrCode): RewardResultDto { try { // 获取奖励组 $groupDto = $this->getRewardGroup($groupIdOrCode); if (!$groupDto) { return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}"); } // 确定要发放的奖励项(仅模拟,不实际发放) $rewardItems = $this->determineRewardItems($groupDto); if (empty($rewardItems)) { return RewardResultDto::fail("奖励组中没有可发放的奖励项"); } // 返回模拟结果(不实际发放奖励) return RewardResultDto::success( 0, // 模拟用户ID $groupDto->id, $groupDto->code, $groupDto->name, 'simulate', // 模拟来源类型 0, // 模拟来源ID $rewardItems ); } catch (\Exception $e) { return RewardResultDto::fail("模拟奖励失败: " . $e->getMessage()); } } /** * 发放奖励(支持保底机制) * * @param int $userId 用户ID * @param int|string $groupIdOrCode 奖励组ID或编码 * @param string $sourceType 来源类型 * @param int $sourceId 来源ID * @param bool $enablePity 是否启用保底机制 * @return RewardResultDto 奖励结果 */ public function grantRewardWithPity(int $userId, $groupIdOrCode, string $sourceType, int $sourceId, bool $enablePity = true): RewardResultDto { try { // 获取奖励组 $groupDto = $this->getRewardGroup($groupIdOrCode); if (!$groupDto) { return RewardResultDto::fail("奖励组不存在: {$groupIdOrCode}"); } // 确定要发放的奖励项(支持保底机制) $rewardItems = $this->determineRewardItemsWithPity($groupDto, $userId, $enablePity); if (empty($rewardItems)) { return RewardResultDto::fail("奖励组中没有可发放的奖励项"); } // 检查事务是否已开启 Helper::check_tr(); // 发放各类奖励 foreach ($rewardItems as $item) { RewardProcessorDispatcher::process($userId, $item, $sourceType, $sourceId); } // 更新保底计数(如果启用保底机制) if ($enablePity) { $obtainedItemIds = array_map(function ($item) { return $item->id; }, $rewardItems); PityService::updatePityCounts($userId, $groupDto->id, $obtainedItemIds); } // 记录奖励日志 $this->logReward($userId, $groupDto->id, $sourceType, $sourceId, $rewardItems); // 触发奖励发放事件 event(new RewardGrantedEvent($userId, $groupDto->id, $groupDto->code, $sourceType, $sourceId, $rewardItems)); // 返回成功结果 return RewardResultDto::success( $userId, $groupDto->id, $groupDto->code, $groupDto->name, $sourceType, $sourceId, $rewardItems ); } catch (Exception $e) { Log::error("发放奖励失败(保底机制)", [ 'userId' => $userId, 'groupIdOrCode' => $groupIdOrCode, 'sourceType' => $sourceType, 'sourceId' => $sourceId, 'enablePity' => $enablePity, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return RewardResultDto::fail("发放奖励失败: " . $e->getMessage()); } } /** * 确定要发放的奖励项(支持保底机制) * * @param RewardGroupDto $groupDto 奖励组DTO * @param int $userId 用户ID * @param bool $enablePity 是否启用保底机制 * @return RewardItemDto[] 要发放的奖励项 */ private function determineRewardItemsWithPity(RewardGroupDto $groupDto, int $userId, bool $enablePity): array { // 检查奖励模式 $rewardMode = $groupDto->rewardMode ?? REWARD_MODE::WEIGHT_SELECTION->value; // 如果启用保底机制,传递用户ID;否则传递null $userIdForPity = $enablePity ? $userId : null; if ($rewardMode == REWARD_MODE::INDEPENDENT_PROBABILITY->value) { // 独立概率模式 return IndependentProbabilityReward::process($groupDto, $userIdForPity); } else { // 权重选择模式(默认) return WeightSelectionReward::process($groupDto, $userIdForPity); } } }