RewardLogic.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  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\Enums\REWARD_MODE;
  8. use App\Module\Game\Events\RewardGrantedEvent;
  9. use App\Module\Game\Models\GameRewardGroup;
  10. use App\Module\Game\Models\GameRewardItem;
  11. use App\Module\Game\Models\GameRewardLog;
  12. use App\Module\GameItems\Services\ItemService;
  13. use Exception;
  14. use Illuminate\Support\Facades\Log;
  15. use ReflectionClass;
  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);
  62. if (empty($rewardItems)) {
  63. return RewardResultDto::fail("奖励组中没有可发放的奖励项");
  64. }
  65. // 检查事务是否已开启
  66. Helper::check_tr();
  67. // 发放各类奖励
  68. foreach ($rewardItems as $item) {
  69. $this->processRewardItem($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. * @return RewardItemDto[] 要发放的奖励项
  102. */
  103. private function determineRewardItems(RewardGroupDto $groupDto): array
  104. {
  105. $items = $groupDto->items;
  106. // 检查奖励模式
  107. $rewardMode = $groupDto->rewardMode ?? REWARD_MODE::WEIGHT_SELECTION->value;
  108. if ($rewardMode == REWARD_MODE::INDEPENDENT_PROBABILITY->value) {
  109. // 独立概率模式:每个奖励项独立判断是否获得
  110. return $this->determineRewardItemsByProbability($items);
  111. } else {
  112. // 权重选择模式:传统的权重选择机制
  113. return $this->determineRewardItemsByWeight($groupDto);
  114. }
  115. }
  116. /**
  117. * 独立概率模式:每个奖励项独立判断是否获得
  118. *
  119. * @param RewardItemDto[] $items 奖励项列表
  120. * @return RewardItemDto[] 要发放的奖励项
  121. */
  122. private function determineRewardItemsByProbability(array $items): array
  123. {
  124. $selectedItems = [];
  125. foreach ($items as $item) {
  126. // 必中项直接添加
  127. if ($item->isGuaranteed) {
  128. $selectedItems[] = $this->processRewardItemQuantity($item);
  129. continue;
  130. }
  131. // 检查是否有概率设置
  132. $probability = $item->probability ?? 0;
  133. if ($probability <= 0) {
  134. continue; // 概率为0或未设置,跳过
  135. }
  136. // 生成随机数判断是否命中
  137. $random = mt_rand(1, 10000) / 100; // 生成0.01-100.00的随机数
  138. if ($random <= $probability) {
  139. $selectedItems[] = $this->processRewardItemQuantity($item);
  140. }
  141. // 未命中则不添加到结果中(实现"未命中没有"的需求)
  142. }
  143. return $selectedItems;
  144. }
  145. /**
  146. * 权重选择模式:传统的权重选择机制
  147. *
  148. * @param RewardGroupDto $groupDto 奖励组DTO
  149. * @return RewardItemDto[] 要发放的奖励项
  150. */
  151. private function determineRewardItemsByWeight(RewardGroupDto $groupDto): array
  152. {
  153. $items = $groupDto->items;
  154. // 如果不是随机发放,返回所有奖励项
  155. if (!$groupDto->isRandom) {
  156. return array_map([$this, 'processRewardItemQuantity'], $items);
  157. }
  158. // 如果是随机发放,按权重随机选择指定数量的奖励项
  159. $selectedItems = [];
  160. $guaranteedItems = [];
  161. $normalItems = [];
  162. // 先分离必中项和普通项
  163. foreach ($items as $item) {
  164. if ($item->isGuaranteed) {
  165. $guaranteedItems[] = $item;
  166. } else {
  167. $normalItems[] = $item;
  168. }
  169. }
  170. // 先选择必中项
  171. $selectedItems = array_map([$this, 'processRewardItemQuantity'], $guaranteedItems);
  172. // 如果必中项数量已经达到或超过随机数量,直接返回必中项
  173. if (count($selectedItems) >= $groupDto->randomCount) {
  174. return array_slice($selectedItems, 0, $groupDto->randomCount);
  175. }
  176. // 计算剩余需要选择的数量
  177. $remainingCount = $groupDto->randomCount - count($selectedItems);
  178. // 如果没有普通项,直接返回必中项
  179. if (empty($normalItems)) {
  180. return $selectedItems;
  181. }
  182. // 按权重随机选择普通项
  183. $totalWeight = array_sum(array_map(function ($item) {
  184. return $item->weight;
  185. }, $normalItems));
  186. // 如果总权重为0,随机选择
  187. if ($totalWeight <= 0) {
  188. shuffle($normalItems);
  189. $selectedNormalItems = array_slice($normalItems, 0, $remainingCount);
  190. } else {
  191. // 按权重随机选择
  192. $selectedNormalItems = [];
  193. for ($i = 0; $i < $remainingCount; $i++) {
  194. if (empty($normalItems)) {
  195. break;
  196. }
  197. $randomWeight = mt_rand(1, $totalWeight * 100) / 100;
  198. $currentWeight = 0;
  199. foreach ($normalItems as $key => $item) {
  200. $currentWeight += $item->weight;
  201. if ($randomWeight <= $currentWeight) {
  202. $selectedNormalItems[] = $this->processRewardItemQuantity($item);
  203. $totalWeight -= $item->weight;
  204. unset($normalItems[$key]);
  205. $normalItems = array_values($normalItems);
  206. break;
  207. }
  208. }
  209. }
  210. }
  211. // 合并必中项和选中的普通项
  212. return array_merge($selectedItems, $selectedNormalItems);
  213. }
  214. /**
  215. * 处理奖励项的数量(支持数量范围)
  216. *
  217. * @param RewardItemDto $item 奖励项
  218. * @return RewardItemDto 处理后的奖励项
  219. */
  220. private function processRewardItemQuantity(RewardItemDto $item): RewardItemDto
  221. {
  222. // 克隆奖励项以避免修改原始数据
  223. $processedItem = clone $item;
  224. // 检查是否有数量范围设置
  225. if ($item->minQuantity !== null && $item->maxQuantity !== null) {
  226. // 使用数量范围
  227. $minQty = max(1, $item->minQuantity);
  228. $maxQty = max($minQty, $item->maxQuantity);
  229. $processedItem->quantity = mt_rand($minQty, $maxQty);
  230. } else {
  231. // 使用固定数量
  232. $processedItem->quantity = max(1, $item->quantity);
  233. }
  234. return $processedItem;
  235. }
  236. /**
  237. * 处理单个奖励项
  238. *
  239. * @param int $userId 用户ID
  240. * @param RewardItemDto $item 奖励项
  241. * @param string $sourceType 来源类型
  242. * @param int $sourceId 来源ID
  243. * @return void
  244. * @throws Exception
  245. */
  246. private function processRewardItem(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  247. {
  248. switch ($item->rewardType) {
  249. case REWARD_TYPE::ITEM->valueInt():
  250. $this->processItemReward($userId, $item, $sourceType, $sourceId);
  251. break;
  252. case REWARD_TYPE::FUND_CONFIG->valueInt():
  253. $this->processFundConfigReward($userId, $item, $sourceType, $sourceId);
  254. break;
  255. case REWARD_TYPE::CURRENCY->valueInt():
  256. $this->processCurrencyReward($userId, $item, $sourceType, $sourceId);
  257. break;
  258. case REWARD_TYPE::PET_EXP->valueInt():
  259. $this->processPetExpReward($userId, $item, $sourceType, $sourceId);
  260. break;
  261. case REWARD_TYPE::PET_ENERGY->valueInt():
  262. $this->processPetEnergyReward($userId, $item, $sourceType, $sourceId);
  263. break;
  264. case REWARD_TYPE::FARM_SHRINE->valueInt():
  265. $this->processFarmShrineReward($userId, $item, $sourceType, $sourceId);
  266. break;
  267. case REWARD_TYPE::OTHER->valueInt():
  268. $this->processOtherReward($userId, $item, $sourceType, $sourceId);
  269. break;
  270. default:
  271. // 未知奖励类型,记录日志
  272. Log::warning("未知的奖励类型", [
  273. 'userId' => $userId,
  274. 'rewardType' => $item->rewardType,
  275. 'targetId' => $item->targetId,
  276. 'quantity' => $item->quantity,
  277. 'sourceType' => $sourceType,
  278. 'sourceId' => $sourceId
  279. ]);
  280. break;
  281. }
  282. }
  283. /**
  284. * 记录奖励日志
  285. *
  286. * @param int $userId 用户ID
  287. * @param int $groupId 奖励组ID
  288. * @param string $sourceType 来源类型
  289. * @param int $sourceId 来源ID
  290. * @param RewardItemDto[] $items 发放的奖励项
  291. * @return GameRewardLog
  292. */
  293. private function logReward(int $userId, int $groupId, string $sourceType, int $sourceId, array $items): GameRewardLog
  294. {
  295. // 将DTO转换为可存储的数组
  296. $itemsData = array_map(function (RewardItemDto $item) {
  297. return [
  298. 'id' => $item->id,
  299. 'reward_type' => $item->rewardType,
  300. 'target_id' => $item->targetId,
  301. 'param1' => $item->param1,
  302. 'param2' => $item->param2,
  303. 'quantity' => $item->quantity,
  304. 'extra_data' => $item->extraData
  305. ];
  306. }, $items);
  307. // 创建日志记录
  308. return GameRewardLog::create([
  309. 'user_id' => $userId,
  310. 'group_id' => $groupId,
  311. 'source_type' => $sourceType,
  312. 'source_id' => $sourceId,
  313. 'reward_items' => $itemsData
  314. ]);
  315. }
  316. /**
  317. * 处理物品奖励
  318. *
  319. * @param int $userId 用户ID
  320. * @param RewardItemDto $item 奖励项
  321. * @param string $sourceType 来源类型
  322. * @param int $sourceId 来源ID
  323. * @return void
  324. * @throws Exception
  325. */
  326. private function processItemReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  327. {
  328. try {
  329. // 发放物品奖励,传递来源信息
  330. ItemService::addItem($userId, $item->targetId, $item->quantity, [
  331. 'param1' => $item->param1,
  332. 'param2' => $item->param2,
  333. 'source_type' => $sourceType,
  334. 'source_id' => $sourceId,
  335. 'source' => 'reward', // 保持向后兼容
  336. 'extra_data' => $item->extraData
  337. ]);
  338. Log::info("物品奖励发放成功", [
  339. 'userId' => $userId,
  340. 'itemId' => $item->targetId,
  341. 'quantity' => $item->quantity,
  342. 'sourceType' => $sourceType,
  343. 'sourceId' => $sourceId
  344. ]);
  345. } catch (Exception $e) {
  346. Log::error("物品奖励发放失败", [
  347. 'userId' => $userId,
  348. 'itemId' => $item->targetId,
  349. 'quantity' => $item->quantity,
  350. 'sourceType' => $sourceType,
  351. 'sourceId' => $sourceId,
  352. 'error' => $e->getMessage()
  353. ]);
  354. throw new Exception("物品奖励发放失败: " . $e->getMessage());
  355. }
  356. }
  357. /**
  358. * 处理账户种类奖励(FUND_CONFIG)
  359. *
  360. * @param int $userId 用户ID
  361. * @param RewardItemDto $item 奖励项
  362. * @param string $sourceType 来源类型
  363. * @param int $sourceId 来源ID
  364. * @return void
  365. * @throws Exception
  366. */
  367. private function processFundConfigReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  368. {
  369. try {
  370. // 使用Fund模块的User类来处理资金变更
  371. // target_id 是 fund_config 表的 id(账户种类ID)
  372. $result = \App\Module\Fund\Logic\User::handle(
  373. $userId,
  374. $item->targetId, // fund_config_id
  375. $item->quantity, // 金额
  376. \App\Module\Fund\Enums\LOG_TYPE::TRADE, // 日志类型为贸易
  377. $sourceId, // 关联ID,使用来源ID
  378. "奖励发放:账户种类奖励 - {$sourceType}#{$sourceId}"
  379. );
  380. if (is_string($result)) {
  381. throw new Exception("账户种类奖励发放失败: " . $result);
  382. }
  383. Log::info("账户种类奖励发放成功", [
  384. 'userId' => $userId,
  385. 'fundConfigId' => $item->targetId,
  386. 'amount' => $item->quantity,
  387. 'sourceType' => $sourceType,
  388. 'sourceId' => $sourceId
  389. ]);
  390. } catch (Exception $e) {
  391. Log::error("账户种类奖励发放失败", [
  392. 'userId' => $userId,
  393. 'fundConfigId' => $item->targetId,
  394. 'amount' => $item->quantity,
  395. 'sourceType' => $sourceType,
  396. 'sourceId' => $sourceId,
  397. 'error' => $e->getMessage()
  398. ]);
  399. throw new Exception("账户种类奖励发放失败: " . $e->getMessage());
  400. }
  401. }
  402. /**
  403. * 处理币种奖励(CURRENCY)
  404. *
  405. * @param int $userId 用户ID
  406. * @param RewardItemDto $item 奖励项
  407. * @param string $sourceType 来源类型
  408. * @param int $sourceId 来源ID
  409. * @return void
  410. * @throws Exception
  411. */
  412. private function processCurrencyReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  413. {
  414. try {
  415. // 对于币种奖励,需要先根据币种ID找到对应的账户种类ID
  416. // 这里需要查询 fund_config 表,找到对应币种的账户种类
  417. $fundConfig = \App\Module\Fund\Models\FundConfigModel::where('currency_id', $item->targetId)->first();
  418. if (!$fundConfig) {
  419. throw new Exception("找不到币种ID为 {$item->targetId} 的账户配置");
  420. }
  421. // 使用找到的账户种类ID来发放奖励
  422. $result = \App\Module\Fund\Logic\User::handle(
  423. $userId,
  424. $fundConfig->id, // fund_config_id
  425. $item->quantity, // 金额
  426. \App\Module\Fund\Enums\LOG_TYPE::TRADE, // 日志类型
  427. $sourceId, // 关联ID,使用来源ID
  428. "奖励发放:币种奖励 - {$sourceType}#{$sourceId}"
  429. );
  430. if (is_string($result)) {
  431. throw new Exception("币种奖励发放失败: " . $result);
  432. }
  433. Log::info("币种奖励发放成功", [
  434. 'userId' => $userId,
  435. 'currencyId' => $item->targetId,
  436. 'fundConfigId' => $fundConfig->id,
  437. 'amount' => $item->quantity,
  438. 'sourceType' => $sourceType,
  439. 'sourceId' => $sourceId
  440. ]);
  441. } catch (Exception $e) {
  442. Log::error("币种奖励发放失败", [
  443. 'userId' => $userId,
  444. 'currencyId' => $item->targetId,
  445. 'amount' => $item->quantity,
  446. 'sourceType' => $sourceType,
  447. 'sourceId' => $sourceId,
  448. 'error' => $e->getMessage()
  449. ]);
  450. throw new Exception("币种奖励发放失败: " . $e->getMessage());
  451. }
  452. }
  453. /**
  454. * 处理宠物经验奖励
  455. *
  456. * @param int $userId 用户ID
  457. * @param RewardItemDto $item 奖励项
  458. * @param string $sourceType 来源类型
  459. * @param int $sourceId 来源ID
  460. * @return void
  461. * @throws Exception
  462. */
  463. private function processPetExpReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  464. {
  465. try {
  466. // 获取用户的宠物
  467. $pet = \App\Module\Pet\Models\PetUser::where('user_id', $userId)
  468. ->where('id', $item->targetId)
  469. ->first();
  470. if (!$pet) {
  471. throw new Exception("找不到ID为 {$item->targetId} 的宠物");
  472. }
  473. // 使用宠物逻辑类来增加经验
  474. $petLogic = new \App\Module\Pet\Logic\PetLogic();
  475. $reflection = new \ReflectionClass($petLogic);
  476. $method = $reflection->getMethod('addExperience');
  477. $method->setAccessible(true);
  478. $levelUpOccurred = $method->invoke($petLogic, $item->targetId, $item->quantity);
  479. Log::info("宠物经验奖励发放成功", [
  480. 'userId' => $userId,
  481. 'petId' => $item->targetId,
  482. 'expAmount' => $item->quantity,
  483. 'levelUp' => $levelUpOccurred,
  484. 'sourceType' => $sourceType,
  485. 'sourceId' => $sourceId
  486. ]);
  487. } catch (Exception $e) {
  488. Log::error("宠物经验奖励发放失败", [
  489. 'userId' => $userId,
  490. 'petId' => $item->targetId,
  491. 'expAmount' => $item->quantity,
  492. 'sourceType' => $sourceType,
  493. 'sourceId' => $sourceId,
  494. 'error' => $e->getMessage()
  495. ]);
  496. throw new Exception("宠物经验奖励发放失败: " . $e->getMessage());
  497. }
  498. }
  499. /**
  500. * 处理宠物体力奖励
  501. *
  502. * @param int $userId 用户ID
  503. * @param RewardItemDto $item 奖励项
  504. * @param string $sourceType 来源类型
  505. * @param int $sourceId 来源ID
  506. * @return void
  507. * @throws Exception
  508. */
  509. private function processPetEnergyReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  510. {
  511. try {
  512. // 获取用户的宠物
  513. $pet = \App\Module\Pet\Models\PetUser::where('user_id', $userId)
  514. ->where('id', $item->targetId)
  515. ->first();
  516. if (!$pet) {
  517. throw new Exception("找不到ID为 {$item->targetId} 的宠物");
  518. }
  519. // 使用宠物逻辑类来增加体力
  520. $petLogic = new \App\Module\Pet\Logic\PetLogic();
  521. $reflection = new \ReflectionClass($petLogic);
  522. $method = $reflection->getMethod('addStamina');
  523. $method->setAccessible(true);
  524. $actualGained = $method->invoke($petLogic, $item->targetId, $item->quantity);
  525. Log::info("宠物体力奖励发放成功", [
  526. 'userId' => $userId,
  527. 'petId' => $item->targetId,
  528. 'staminaAmount' => $item->quantity,
  529. 'actualGained' => $actualGained,
  530. 'sourceType' => $sourceType,
  531. 'sourceId' => $sourceId
  532. ]);
  533. } catch (Exception $e) {
  534. Log::error("宠物体力奖励发放失败", [
  535. 'userId' => $userId,
  536. 'petId' => $item->targetId,
  537. 'staminaAmount' => $item->quantity,
  538. 'sourceType' => $sourceType,
  539. 'sourceId' => $sourceId,
  540. 'error' => $e->getMessage()
  541. ]);
  542. throw new Exception("宠物体力奖励发放失败: " . $e->getMessage());
  543. }
  544. }
  545. /**
  546. * 处理神像奖励(农场buff)
  547. *
  548. * @param int $userId 用户ID
  549. * @param RewardItemDto $item 奖励项
  550. * @param string $sourceType 来源类型
  551. * @param int $sourceId 来源ID
  552. * @return void
  553. * @throws Exception
  554. */
  555. private function processFarmShrineReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  556. {
  557. try {
  558. // 获取神像类型和持续时间
  559. $shrineType = $item->targetId; // 神像类型(1=丰收之神,2=雨露之神,3=屠草之神,4=拭虫之神)
  560. $durationHours = $item->param2 > 0 ? $item->param2 : 24; // 持续时间(小时),默认24小时
  561. $activationCount = $item->quantity > 0 ? $item->quantity : 1; // 激活次数,默认1次
  562. // 验证神像类型是否有效
  563. if (!in_array($shrineType, [1, 2, 3, 4])) {
  564. throw new Exception("无效的神像类型: {$shrineType}");
  565. }
  566. // 验证用户是否存在
  567. $farmUser = \App\Module\Farm\Models\FarmUser::where('user_id', $userId)->first();
  568. if (!$farmUser) {
  569. throw new Exception("用户农场数据不存在,用户ID: {$userId}");
  570. }
  571. // 循环激活神像(支持多次激活)
  572. for ($i = 0; $i < $activationCount; $i++) {
  573. // 使用BuffService激活神像
  574. $buff = \App\Module\Farm\Services\BuffService::activateBuff($userId, $shrineType, $durationHours);
  575. if (!$buff) {
  576. throw new Exception("神像激活失败,神像类型: {$shrineType},用户ID: {$userId}");
  577. }
  578. Log::info("神像奖励激活成功", [
  579. 'userId' => $userId,
  580. 'shrineType' => $shrineType,
  581. 'shrineName' => \App\Module\Farm\Enums\BUFF_TYPE::getName($shrineType),
  582. 'durationHours' => $durationHours,
  583. 'activationIndex' => $i + 1,
  584. 'totalActivations' => $activationCount,
  585. 'expireTime' => $buff->expire_time ? $buff->expire_time->toDateTimeString() : null,
  586. 'sourceType' => $sourceType,
  587. 'sourceId' => $sourceId
  588. ]);
  589. }
  590. Log::info("神像奖励发放完成", [
  591. 'userId' => $userId,
  592. 'shrineType' => $shrineType,
  593. 'shrineName' => \App\Module\Farm\Enums\BUFF_TYPE::getName($shrineType),
  594. 'durationHours' => $durationHours,
  595. 'totalActivations' => $activationCount,
  596. 'sourceType' => $sourceType,
  597. 'sourceId' => $sourceId
  598. ]);
  599. } catch (Exception $e) {
  600. Log::error("神像奖励发放失败", [
  601. 'userId' => $userId,
  602. 'shrineType' => $item->targetId,
  603. 'durationHours' => $item->param2,
  604. 'quantity' => $item->quantity,
  605. 'sourceType' => $sourceType,
  606. 'sourceId' => $sourceId,
  607. 'error' => $e->getMessage()
  608. ]);
  609. throw new Exception("神像奖励发放失败: " . $e->getMessage());
  610. }
  611. }
  612. /**
  613. * 处理其他类型奖励
  614. *
  615. * @param int $userId 用户ID
  616. * @param RewardItemDto $item 奖励项
  617. * @param string $sourceType 来源类型
  618. * @param int $sourceId 来源ID
  619. * @return void
  620. */
  621. private function processOtherReward(int $userId, RewardItemDto $item, string $sourceType, int $sourceId): void
  622. {
  623. // 其他类型奖励的处理逻辑
  624. // 这里可以根据具体需求实现,比如称号、成就等
  625. Log::info("其他类型奖励处理", [
  626. 'userId' => $userId,
  627. 'rewardType' => $item->rewardType,
  628. 'targetId' => $item->targetId,
  629. 'quantity' => $item->quantity,
  630. 'param1' => $item->param1,
  631. 'param2' => $item->param2,
  632. 'extraData' => $item->extraData,
  633. 'sourceType' => $sourceType,
  634. 'sourceId' => $sourceId
  635. ]);
  636. // 目前只记录日志,具体实现可以根据业务需求扩展
  637. // 例如:
  638. // - 称号奖励:更新用户称号
  639. // - 成就奖励:解锁成就
  640. // - 特殊权限:更新用户权限
  641. }
  642. }