UrsProfitLogic.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <?php
  2. namespace App\Module\UrsPromotion\Logics;
  3. use App\Module\Game\Enums\REWARD_SOURCE_TYPE;
  4. use App\Module\UrsPromotion\Models\UrsUserReferral;
  5. use App\Module\UrsPromotion\Models\UrsUserTalent;
  6. use App\Module\UrsPromotion\Models\UrsProfit;
  7. use App\Module\UrsPromotion\Models\UrsTalentConfig;
  8. use App\Module\UrsPromotion\Models\UrsUserMapping;
  9. use App\Module\UrsPromotion\Enums\UrsProfitType;
  10. use App\Module\UrsPromotion\Enums\UrsPromotionRelationLevel;
  11. use App\Module\Game\Services\RewardService;
  12. use App\Module\GameItems\Services\ItemService;
  13. use Illuminate\Support\Facades\DB;
  14. use Illuminate\Support\Facades\Log;
  15. /**
  16. * URS收益分成逻辑类
  17. *
  18. * 处理URS推广系统的收益分成逻辑,支持三代推广关系
  19. */
  20. class UrsProfitLogic
  21. {
  22. /**
  23. * 计算并分发URS推广收益(按人头奖励)
  24. *
  25. * @param int $userId 新注册用户ID
  26. * @param string $sourceType 收益来源类型
  27. * @param int $sourceId 收益来源ID
  28. * @return array 分成记录
  29. */
  30. public function distributePromotionReward(
  31. int $userId,
  32. string $sourceType,
  33. int $sourceId
  34. ): array {
  35. $profits = [];
  36. try {
  37. // 获取用户的推荐关系链(三代)
  38. $referralChain = $this->getUserReferralChain($userId);
  39. if (empty($referralChain)) {
  40. Log::info("用户 {$userId} 无推荐关系,无需分成");
  41. return $profits;
  42. }
  43. // 获取达人等级配置
  44. $talentConfigs = $this->getTalentConfigs();
  45. // 为每一级推荐人发放奖励
  46. foreach ($referralChain as $level => $referrerId) {
  47. $profit = $this->calculatePromotionReward(
  48. $referrerId,
  49. $userId,
  50. $sourceType,
  51. $sourceId,
  52. $level,
  53. $talentConfigs
  54. );
  55. if ($profit) {
  56. $profits[] = $profit;
  57. }
  58. }
  59. Log::info("用户 {$userId} 推广收益分发完成", [
  60. 'source_type' => $sourceType,
  61. 'source_id' => $sourceId,
  62. 'profits_count' => count($profits)
  63. ]);
  64. } catch (\Exception $e) {
  65. Log::error("URS推广收益分发失败", [
  66. 'user_id' => $userId,
  67. 'source_type' => $sourceType,
  68. 'source_id' => $sourceId,
  69. 'error' => $e->getMessage()
  70. ]);
  71. }
  72. return $profits;
  73. }
  74. /**
  75. * 计算并分发URS种植收益(按比例分成)
  76. *
  77. * @param int $userId 产生收益的用户ID
  78. * @param string $sourceType 收益来源类型
  79. * @param int $sourceId 收益来源ID
  80. * @param int $originalAmount 原始收益数量(整数)
  81. * @param int $itemId 收获的物品ID
  82. * @return array 分成记录
  83. */
  84. public function distributePlantingReward(
  85. int $userId,
  86. string $sourceType,
  87. int $sourceId,
  88. int $originalAmount,
  89. int $itemId
  90. ): array {
  91. $profits = [];
  92. try {
  93. // 获取用户的推荐关系链(三代)
  94. $referralChain = $this->getUserReferralChain($userId);
  95. if (empty($referralChain)) {
  96. Log::info("用户 {$userId} 无推荐关系,无需分成");
  97. return $profits;
  98. }
  99. // 获取达人等级配置
  100. $talentConfigs = $this->getTalentConfigs();
  101. // 为每一级推荐人计算分成
  102. foreach ($referralChain as $level => $referrerId) {
  103. $profit = $this->calculatePlantingReward(
  104. $referrerId,
  105. $userId,
  106. $sourceType,
  107. $sourceId,
  108. $level,
  109. $originalAmount, // 传递原始整数数量
  110. $talentConfigs,
  111. $itemId // 传递物品ID
  112. );
  113. if ($profit) {
  114. $profits[] = $profit;
  115. }
  116. }
  117. Log::info("用户 {$userId} 种植收益分成完成", [
  118. 'source_type' => $sourceType,
  119. 'source_id' => $sourceId,
  120. 'original_amount' => $originalAmount,
  121. 'profits_count' => count($profits)
  122. ]);
  123. } catch (\Exception $e) {
  124. Log::error("URS种植收益分成失败", [
  125. 'user_id' => $userId,
  126. 'source_type' => $sourceType,
  127. 'source_id' => $sourceId,
  128. 'error' => $e->getMessage()
  129. ]);
  130. throw $e;
  131. }
  132. return $profits;
  133. }
  134. /**
  135. * 获取用户的推荐关系链(三代)
  136. *
  137. * @param int $userId 用户ID
  138. * @return array [level => user_id] 1:直推 2:间推 3:三推
  139. */
  140. private function getUserReferralChain(int $userId): array
  141. {
  142. $chain = [];
  143. $currentUserId = $userId;
  144. // 最多查找三代
  145. for ($level = 1; $level <= UrsPromotionRelationLevel::getMaxLevel(); $level++) {
  146. $referral = UrsUserReferral::where('user_id', $currentUserId)
  147. ->where('status', UrsUserReferral::STATUS_VALID)
  148. ->first();
  149. if (!$referral) {
  150. break;
  151. }
  152. $chain[$level] = $referral->referrer_id;
  153. $currentUserId = $referral->referrer_id;
  154. }
  155. return $chain;
  156. }
  157. /**
  158. * 获取达人等级配置
  159. *
  160. * @return array [level => config]
  161. */
  162. private function getTalentConfigs(): array
  163. {
  164. static $configs = null;
  165. if ($configs === null) {
  166. $configs = UrsTalentConfig::where('status', UrsTalentConfig::STATUS_ENABLED)
  167. ->get()
  168. ->keyBy('level')
  169. ->toArray();
  170. }
  171. return $configs;
  172. }
  173. /**
  174. * 计算推广收益奖励(按人头)
  175. *
  176. * @param int $referrerId 推荐人ID
  177. * @param int $memberId 新注册用户ID
  178. * @param string $sourceType 收益来源类型
  179. * @param int $sourceId 收益来源ID
  180. * @param int $relationLevel 推荐层级
  181. * @param array $talentConfigs 达人等级配置
  182. * @return UrsProfit|null
  183. */
  184. private function calculatePromotionReward(
  185. int $referrerId,
  186. int $memberId,
  187. string $sourceType,
  188. int $sourceId,
  189. int $relationLevel,
  190. array $talentConfigs
  191. ): ?UrsProfit {
  192. // 获取推荐人的达人等级
  193. $talent = UrsUserTalent::where('user_id', $referrerId)->first();
  194. $talentLevel = $talent ? $talent->talent_level : 0;
  195. // 获取对应等级的配置
  196. $config = $talentConfigs[$talentLevel] ?? null;
  197. if (!$config) {
  198. Log::warning("推荐人 {$referrerId} 达人等级 {$talentLevel} 配置不存在");
  199. return null;
  200. }
  201. // 获取奖励组ID
  202. $rewardGroupId = $this->getPromotionRewardGroupId($config, $relationLevel);
  203. if (!$rewardGroupId) {
  204. Log::debug("推荐人 {$referrerId} 等级 {$talentLevel} 层级 {$relationLevel} 无奖励组配置");
  205. return null;
  206. }
  207. // 开启事务(奖励组系统要求在事务中执行)
  208. DB::beginTransaction();
  209. try {
  210. // 使用奖励组系统发放奖励
  211. $rewardResult = RewardService::grantReward(
  212. $referrerId,
  213. $rewardGroupId,
  214. REWARD_SOURCE_TYPE::ACHIEVEMENT,
  215. $sourceType,
  216. $sourceId
  217. );
  218. if (!$rewardResult->success) {
  219. DB::rollBack();
  220. Log::error("推广奖励发放失败", [
  221. 'referrer_id' => $referrerId,
  222. 'reward_group_id' => $rewardGroupId,
  223. 'error' => $rewardResult->errorMessage
  224. ]);
  225. return null;
  226. }
  227. // 计算奖励总金额(用于记录)
  228. $totalRewardAmount = $this->calculateTotalRewardAmount($rewardResult->items);
  229. // 获取产生收益的农场用户ID
  230. $memberMapping = UrsUserMapping::where('urs_user_id', $memberId)->first();
  231. $memberFarmUserId = $memberMapping ? $memberMapping->user_id : null;
  232. // 获取获得收益的农场用户ID
  233. $referrerMapping = UrsUserMapping::where('urs_user_id', $referrerId)->first();
  234. $referrerFarmUserId = $referrerMapping ? $referrerMapping->user_id : null;
  235. // 创建收益记录
  236. $profit = UrsProfit::create([
  237. 'urs_user_id' => $referrerId,
  238. 'urs_promotion_member_id' => $memberId,
  239. 'promotion_member_farm_user_id' => $memberFarmUserId,
  240. 'farm_user_id' => $referrerFarmUserId,
  241. 'source_id' => $sourceId,
  242. 'source_type' => $sourceType,
  243. 'profit_type' => UrsProfitType::PROMOTION_REWARD->value,
  244. 'relation_level' => $relationLevel,
  245. 'original_amount' => '0', // 推广收益无原始金额概念
  246. 'profit_amount' => $totalRewardAmount,
  247. 'profit_rate' => 0, // 推广收益无比例概念
  248. 'reward_group_id' => $rewardResult->groupId,
  249. 'talent_level' => $talentLevel,
  250. 'status' => UrsProfit::STATUS_NORMAL,
  251. ]);
  252. DB::commit();
  253. } catch (\Exception $e) {
  254. DB::rollBack();
  255. Log::error("推广奖励发放事务失败", [
  256. 'referrer_id' => $referrerId,
  257. 'reward_group_id' => $rewardGroupId,
  258. 'error' => $e->getMessage()
  259. ]);
  260. return null;
  261. }
  262. Log::info("URS推广收益记录创建", [
  263. 'profit_id' => $profit->id,
  264. 'referrer_id' => $referrerId,
  265. 'member_id' => $memberId,
  266. 'relation_level' => $relationLevel,
  267. 'talent_level' => $talentLevel,
  268. 'reward_group_id' => $rewardGroupId,
  269. 'reward_amount' => $totalRewardAmount
  270. ]);
  271. return $profit;
  272. }
  273. /**
  274. * 计算种植收益分成(按比例发放物品)
  275. *
  276. * @param int $referrerId 推荐人ID
  277. * @param int $memberId 团队成员ID
  278. * @param string $sourceType 收益来源类型
  279. * @param int $sourceId 收益来源ID
  280. * @param int $relationLevel 推荐层级
  281. * @param int $originalAmount 原始收益数量(整数)
  282. * @param array $talentConfigs 达人等级配置
  283. * @param int $itemId 收获的物品ID
  284. * @return UrsProfit|null
  285. */
  286. private function calculatePlantingReward(
  287. int $referrerId,
  288. int $memberId,
  289. string $sourceType,
  290. int $sourceId,
  291. int $relationLevel,
  292. int $originalAmount,
  293. array $talentConfigs,
  294. int $itemId
  295. ): ?UrsProfit {
  296. // 获取推荐人的达人等级
  297. $talent = UrsUserTalent::where('user_id', $referrerId)->first();
  298. $talentLevel = $talent ? $talent->talent_level : 0;
  299. // 获取对应等级的配置
  300. $config = $talentConfigs[$talentLevel] ?? null;
  301. if (!$config) {
  302. Log::warning("推荐人 {$referrerId} 达人等级 {$talentLevel} 配置不存在");
  303. return null;
  304. }
  305. // 获取分成比例
  306. $profitRate = $this->getPlantingRewardRate($config, $relationLevel);
  307. if ($profitRate <= 0) {
  308. Log::debug("推荐人 {$referrerId} 等级 {$talentLevel} 层级 {$relationLevel} 分成比例为0");
  309. return null;
  310. }
  311. // 计算应该奖励的物品数量(向下取整)
  312. $rewardQuantity = (int)floor($originalAmount * $profitRate);
  313. if ($rewardQuantity <= 0) {
  314. Log::debug("推荐人 {$referrerId} 计算出的奖励数量为0", [
  315. 'original_amount' => $originalAmount,
  316. 'profit_rate' => $profitRate,
  317. 'calculated_quantity' => $originalAmount * $profitRate
  318. ]);
  319. return null;
  320. }
  321. // 开启事务发放物品奖励
  322. try {
  323. // 使用物品模块服务发放物品
  324. $addResult = ItemService::addItem($referrerId, $itemId, $rewardQuantity, [
  325. 'source_type' => $sourceType,
  326. 'source_id' => $sourceId,
  327. 'source' => 'urs_planting_reward',
  328. 'details' => [
  329. 'member_id' => $memberId,
  330. 'relation_level' => $relationLevel,
  331. 'talent_level' => $talentLevel,
  332. 'profit_rate' => $profitRate,
  333. 'original_amount' => $originalAmount
  334. ]
  335. ]);
  336. if (!$addResult['success']) {
  337. Log::error("种植收益物品发放失败", [
  338. 'referrer_id' => $referrerId,
  339. 'item_id' => $itemId,
  340. 'quantity' => $rewardQuantity,
  341. 'error' => $addResult['message'] ?? '未知错误'
  342. ]);
  343. return null;
  344. }
  345. // 获取产生收益的农场用户ID
  346. $memberMapping = UrsUserMapping::where('urs_user_id', $memberId)->first();
  347. $memberFarmUserId = $memberMapping ? $memberMapping->user_id : null;
  348. // 获取获得收益的农场用户ID
  349. $referrerMapping = UrsUserMapping::where('urs_user_id', $referrerId)->first();
  350. $referrerFarmUserId = $referrerMapping ? $referrerMapping->user_id : null;
  351. // 创建收益记录
  352. $profit = UrsProfit::create([
  353. 'urs_user_id' => $referrerId,
  354. 'urs_promotion_member_id' => $memberId,
  355. 'promotion_member_farm_user_id' => $memberFarmUserId,
  356. 'farm_user_id' => $referrerFarmUserId,
  357. 'source_id' => $sourceId,
  358. 'source_type' => $sourceType,
  359. 'profit_type' => UrsProfitType::PLANTING_REWARD->value,
  360. 'relation_level' => $relationLevel,
  361. 'original_amount' => (string)$originalAmount,
  362. 'profit_amount' => (string)$rewardQuantity, // 记录实际发放的物品数量
  363. 'profit_rate' => $profitRate,
  364. 'reward_group_id' => null, // 种植收益不使用奖励组
  365. 'talent_level' => $talentLevel,
  366. 'status' => UrsProfit::STATUS_NORMAL,
  367. ]);
  368. } catch (\Exception $e) {
  369. Log::error("种植收益发放事务失败", [
  370. 'referrer_id' => $referrerId,
  371. 'item_id' => $itemId,
  372. 'quantity' => $rewardQuantity,
  373. 'error' => $e->getMessage()
  374. ]);
  375. throw $e;
  376. }
  377. Log::info("URS种植收益记录创建", [
  378. 'profit_id' => $profit->id,
  379. 'referrer_id' => $referrerId,
  380. 'member_id' => $memberId,
  381. 'relation_level' => $relationLevel,
  382. 'talent_level' => $talentLevel,
  383. 'profit_rate' => $profitRate,
  384. 'item_id' => $itemId,
  385. 'reward_quantity' => $rewardQuantity,
  386. 'original_amount' => $originalAmount
  387. ]);
  388. return $profit;
  389. }
  390. /**
  391. * 计算奖励总金额(用于记录)
  392. *
  393. * @param array $rewardItems 奖励项列表
  394. * @return string
  395. */
  396. private function calculateTotalRewardAmount(array $rewardItems): string
  397. {
  398. $totalAmount = '0';
  399. foreach ($rewardItems as $item) {
  400. // 计算货币类型的奖励金额
  401. if (in_array($item->rewardType, ['fund', 'currency', 'fund_config'])) {
  402. $totalAmount = bcadd($totalAmount, (string)$item->quantity, 10);
  403. }
  404. // 计算物品类型的奖励价值(按物品售价计算,如果没有售价则按数量计算)
  405. elseif ($item->rewardType === 'item') {
  406. // 获取物品售价作为价值参考
  407. $itemValue = $this->getItemValue($item->targetId);
  408. $itemTotalValue = bcmul((string)$itemValue, (string)$item->quantity, 10);
  409. $totalAmount = bcadd($totalAmount, $itemTotalValue, 10);
  410. }
  411. // 其他类型奖励按数量计算基础价值
  412. else {
  413. $totalAmount = bcadd($totalAmount, (string)$item->quantity, 10);
  414. }
  415. }
  416. return $totalAmount;
  417. }
  418. /**
  419. * 获取物品价值(用于奖励金额计算)
  420. *
  421. * @param int $itemId 物品ID
  422. * @return string
  423. */
  424. private function getItemValue(int $itemId): string
  425. {
  426. try {
  427. // 查询物品售价
  428. $item = \App\Module\GameItems\Models\Item::find($itemId);
  429. if ($item && $item->sell_price > 0) {
  430. return (string)$item->sell_price;
  431. }
  432. // 如果没有售价,返回默认价值(可以根据物品类型调整)
  433. return '1.0000000000';
  434. } catch (\Exception $e) {
  435. Log::warning("获取物品价值失败", [
  436. 'item_id' => $itemId,
  437. 'error' => $e->getMessage()
  438. ]);
  439. return '1.0000000000';
  440. }
  441. }
  442. /**
  443. * 获取推广收益奖励组ID
  444. *
  445. * @param array $config 达人等级配置
  446. * @param int $relationLevel 推荐层级
  447. * @return int|null
  448. */
  449. private function getPromotionRewardGroupId(array $config, int $relationLevel): ?int
  450. {
  451. // 使用新的独立字段结构
  452. switch ($relationLevel) {
  453. case 1: // 直推
  454. return $config['promotion_direct_group'] ?? null;
  455. case 2: // 间推
  456. return $config['promotion_indirect_group'] ?? null;
  457. case 3: // 三推
  458. return $config['promotion_third_group'] ?? null;
  459. default:
  460. return null;
  461. }
  462. }
  463. /**
  464. * 获取种植收益分成比例
  465. *
  466. * @param array $config 达人等级配置
  467. * @param int $relationLevel 推荐层级
  468. * @return float
  469. */
  470. private function getPlantingRewardRate(array $config, int $relationLevel): float
  471. {
  472. // 使用新的独立字段结构
  473. switch ($relationLevel) {
  474. case 1: // 直推
  475. return (float)($config['planting_direct_rate'] ?? 0);
  476. case 2: // 间推
  477. return (float)($config['planting_indirect_rate'] ?? 0);
  478. case 3: // 三推
  479. return (float)($config['planting_third_rate'] ?? 0);
  480. default:
  481. return 0;
  482. }
  483. }
  484. /**
  485. * 获取用户的收益统计
  486. *
  487. * @param int $userId 用户ID
  488. * @param UrsProfitType|null $profitType 收益类型
  489. * @param string|null $startDate 开始日期
  490. * @param string|null $endDate 结束日期
  491. * @return array
  492. */
  493. public function getUserProfitStats(
  494. int $userId,
  495. ?UrsProfitType $profitType = null,
  496. ?string $startDate = null,
  497. ?string $endDate = null
  498. ): array {
  499. $query = UrsProfit::where('urs_user_id', $userId)
  500. ->where('status', UrsProfit::STATUS_NORMAL);
  501. if ($profitType) {
  502. $query->where('profit_type', $profitType->value);
  503. }
  504. if ($startDate) {
  505. $query->where('created_at', '>=', $startDate);
  506. }
  507. if ($endDate) {
  508. $query->where('created_at', '<=', $endDate);
  509. }
  510. $profits = $query->get();
  511. $stats = [
  512. 'total_amount' => '0',
  513. 'total_count' => 0,
  514. 'by_type' => [],
  515. 'by_level' => [],
  516. ];
  517. foreach ($profits as $profit) {
  518. $stats['total_amount'] = bcadd($stats['total_amount'], $profit->profit_amount, 10);
  519. $stats['total_count']++;
  520. // 按收益类型统计
  521. $type = $profit->profit_type;
  522. if (!isset($stats['by_type'][$type])) {
  523. $stats['by_type'][$type] = ['amount' => '0', 'count' => 0];
  524. }
  525. $stats['by_type'][$type]['amount'] = bcadd($stats['by_type'][$type]['amount'], $profit->profit_amount, 10);
  526. $stats['by_type'][$type]['count']++;
  527. // 按推荐层级统计
  528. $level = $profit->relation_level;
  529. if (!isset($stats['by_level'][$level])) {
  530. $stats['by_level'][$level] = ['amount' => '0', 'count' => 0];
  531. }
  532. $stats['by_level'][$level]['amount'] = bcadd($stats['by_level'][$level]['amount'], $profit->profit_amount, 10);
  533. $stats['by_level'][$level]['count']++;
  534. }
  535. return $stats;
  536. }
  537. }