ReferralLogic.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <?php
  2. namespace App\Module\Promotion\Logics;
  3. use App\Module\Promotion\Enums\REFERRAL_LEVEL;
  4. use App\Module\Promotion\Models\PromotionReferralChange;
  5. use App\Module\Promotion\Models\PromotionUserReferral;
  6. use App\Module\Promotion\Models\PromotionUserRelationCache;
  7. use Illuminate\Support\Facades\Log;
  8. use Illuminate\Support\Facades\Redis;
  9. /**
  10. * 推荐关系逻辑类
  11. *
  12. * 处理用户推荐关系的核心业务逻辑,包括建立推荐关系、修改推荐关系、
  13. * 查询推荐关系、检查循环推荐等功能。该类仅供内部使用,不对外提供服务。
  14. */
  15. class ReferralLogic
  16. {
  17. /**
  18. * 建立推荐关系
  19. *
  20. * @param int $userId 用户ID
  21. * @param int $referrerId 推荐人ID
  22. * @return bool
  23. */
  24. public function createReferralRelation(int $userId, int $referrerId): bool
  25. {
  26. // 验证是否已存在推荐关系
  27. if ($this->hasReferrer($userId)) {
  28. Log::warning("用户 {$userId} 已有推荐人,不能重复设置");
  29. return false;
  30. }
  31. // 验证是否形成循环推荐
  32. if ($this->checkCircularReferral($userId, $referrerId)) {
  33. Log::warning("用户 {$userId} 和推荐人 {$referrerId} 形成循环推荐关系");
  34. return false;
  35. }
  36. try {
  37. // 创建直推关系
  38. $referral = new PromotionUserReferral();
  39. $referral->user_id = $userId;
  40. $referral->referrer_id = $referrerId;
  41. $result = $referral->save();
  42. if ($result) {
  43. // 更新关系缓存
  44. $this->updateRelationCache($userId, null, $referrerId);
  45. }
  46. return $result;
  47. } catch (\Exception $e) {
  48. Log::error("创建推荐关系失败: " . $e->getMessage());
  49. return false;
  50. }
  51. }
  52. /**
  53. * 更新推荐关系
  54. *
  55. * @param int $userId 用户ID
  56. * @param int $newReferrerId 新推荐人ID
  57. * @param string $reason 修改原因
  58. * @param int $operatorId 操作人ID
  59. * @return bool
  60. */
  61. public function updateReferralRelation(int $userId, int $newReferrerId, string $reason, int $operatorId): bool
  62. {
  63. // 获取旧的推荐人
  64. $oldReferrerId = $this->getDirectReferrerId($userId);
  65. // 如果新旧推荐人相同,无需更新
  66. if ($oldReferrerId == $newReferrerId) {
  67. return true;
  68. }
  69. // 验证是否形成循环推荐
  70. if ($this->checkCircularReferral($userId, $newReferrerId)) {
  71. Log::warning("用户 {$userId} 和新推荐人 {$newReferrerId} 形成循环推荐关系");
  72. return false;
  73. }
  74. // 检查事务是否已开启
  75. \UCore\Db\Helper::check_tr();
  76. try {
  77. // 删除旧的推荐关系
  78. if ($oldReferrerId) {
  79. PromotionUserReferral::where('user_id', $userId)->delete();
  80. }
  81. // 创建新的推荐关系
  82. $referral = new PromotionUserReferral();
  83. $referral->user_id = $userId;
  84. $referral->referrer_id = $newReferrerId;
  85. $result = $referral->save();
  86. if ($result) {
  87. // 记录修改历史
  88. $change = new PromotionReferralChange();
  89. $change->user_id = $userId;
  90. $change->old_referrer_id = $oldReferrerId;
  91. $change->new_referrer_id = $newReferrerId;
  92. $change->change_time = now();
  93. $change->change_reason = $reason;
  94. $change->changed_by = $operatorId;
  95. $change->save();
  96. // 更新关系缓存
  97. $this->updateRelationCache($userId, $oldReferrerId, $newReferrerId);
  98. }
  99. return $result;
  100. } catch (\Exception $e) {
  101. Log::error("更新推荐关系失败: " . $e->getMessage());
  102. return false;
  103. }
  104. }
  105. /**
  106. * 获取用户的直接推荐人ID
  107. *
  108. * @param int $userId 用户ID
  109. * @return int|null
  110. */
  111. public function getDirectReferrerId(int $userId): ?int
  112. {
  113. $referral = PromotionUserReferral::where('user_id', $userId)->first();
  114. return $referral ? $referral->referrer_id : null;
  115. }
  116. /**
  117. * 检查用户是否已有推荐人
  118. *
  119. * @param int $userId 用户ID
  120. * @return bool
  121. */
  122. public function hasReferrer(int $userId): bool
  123. {
  124. return PromotionUserReferral::where('user_id', $userId)->exists();
  125. }
  126. /**
  127. * 获取用户的直接下级
  128. *
  129. * @param int $userId 用户ID
  130. * @return array
  131. */
  132. public function getDirectMembers(int $userId): array
  133. {
  134. return PromotionUserReferral::where('referrer_id', $userId)
  135. ->with('user')
  136. ->get()
  137. ->toArray();
  138. }
  139. /**
  140. * 获取用户的所有上级(包括直接和间接)
  141. *
  142. * @param int $userId 用户ID
  143. * @param int $maxLevel 最大层级
  144. * @return array
  145. */
  146. public function getAllReferrers(int $userId, int $maxLevel = 20): array
  147. {
  148. // 尝试从缓存获取
  149. $cacheKey = "promotion:user:{$userId}:all_referrers";
  150. $cachedData = Redis::get($cacheKey);
  151. if ($cachedData !== null) {
  152. return json_decode($cachedData, true);
  153. }
  154. // 从关系缓存表获取
  155. $relations = PromotionUserRelationCache::where('user_id', $userId)
  156. ->where('depth', '<=', $maxLevel)
  157. ->orderBy('depth')
  158. ->get();
  159. $referrers = [];
  160. foreach ($relations as $relation) {
  161. $referrers[] = [
  162. 'user_id' => $relation->related_user_id,
  163. 'level' => $relation->level,
  164. 'depth' => $relation->depth,
  165. 'path' => $relation->path,
  166. ];
  167. }
  168. // 缓存结果
  169. Redis::setex($cacheKey, 86400, json_encode($referrers)); // 缓存1天
  170. return $referrers;
  171. }
  172. /**
  173. * 获取用户的所有团队成员(包括直接和间接)
  174. *
  175. * @param int $userId 用户ID
  176. * @param int $maxLevel 最大层级
  177. * @param int $page 页码
  178. * @param int $pageSize 每页数量
  179. * @return array
  180. */
  181. public function getAllPromotionMembers(int $userId, int $maxLevel = 20, int $page = 1, int $pageSize = 20): array
  182. {
  183. // 从关系缓存表获取
  184. $query = PromotionUserRelationCache::where('related_user_id', $userId)
  185. ->where('depth', '<=', $maxLevel);
  186. $total = $query->count();
  187. $relations = $query->orderBy('depth')
  188. ->offset(($page - 1) * $pageSize)
  189. ->limit($pageSize)
  190. ->with('user')
  191. ->get();
  192. $members = [];
  193. foreach ($relations as $relation) {
  194. if ($relation->user) {
  195. $members[] = [
  196. 'user_id' => $relation->user_id,
  197. 'username' => $relation->user->username ?? '',
  198. 'level' => $relation->level,
  199. 'depth' => $relation->depth,
  200. 'path' => $relation->path,
  201. 'created_at' => $relation->created_at,
  202. ];
  203. }
  204. }
  205. return [
  206. 'total' => $total,
  207. 'page' => $page,
  208. 'page_size' => $pageSize,
  209. 'total_pages' => ceil($total / $pageSize),
  210. 'members' => $members
  211. ];
  212. }
  213. /**
  214. * 检查是否形成循环推荐
  215. *
  216. * @param int $userId 用户ID
  217. * @param int $referrerId 推荐人ID
  218. * @return bool
  219. */
  220. public function checkCircularReferral(int $userId, int $referrerId): bool
  221. {
  222. // 如果用户ID和推荐人ID相同,直接形成循环
  223. if ($userId == $referrerId) {
  224. return true;
  225. }
  226. // 获取推荐人的所有上级
  227. $referrerReferrers = $this->getAllReferrers($referrerId);
  228. // 检查用户是否在推荐人的上级中
  229. foreach ($referrerReferrers as $referrer) {
  230. if ($referrer['user_id'] == $userId) {
  231. return true;
  232. }
  233. }
  234. return false;
  235. }
  236. /**
  237. * 更新关系缓存
  238. *
  239. * @param int $userId 用户ID
  240. * @param int|null $oldReferrerId 旧推荐人ID
  241. * @param int $newReferrerId 新推荐人ID
  242. * @return void
  243. */
  244. private function updateRelationCache(int $userId, ?int $oldReferrerId, int $newReferrerId): void
  245. {
  246. // 检查事务是否已开启
  247. \UCore\Db\Helper::check_tr();
  248. try {
  249. // 1. 删除用户及其所有下级与旧上级及其所有上级的关系缓存
  250. if ($oldReferrerId) {
  251. // 获取用户的所有下级ID
  252. $downlineIds = PromotionUserRelationCache::where('related_user_id', $userId)
  253. ->pluck('user_id')
  254. ->toArray();
  255. $downlineIds[] = $userId; // 包括用户自身
  256. // 获取旧上级的所有上级ID
  257. $upperReferrerIds = PromotionUserRelationCache::where('user_id', $oldReferrerId)
  258. ->pluck('related_user_id')
  259. ->toArray();
  260. $upperReferrerIds[] = $oldReferrerId; // 包括直接上级
  261. // 删除关系缓存
  262. PromotionUserRelationCache::whereIn('user_id', $downlineIds)
  263. ->whereIn('related_user_id', $upperReferrerIds)
  264. ->delete();
  265. // 清除Redis缓存
  266. foreach ($downlineIds as $downlineId) {
  267. Redis::del("promotion:user:{$downlineId}:all_referrers");
  268. }
  269. foreach ($upperReferrerIds as $upperReferrerId) {
  270. Redis::del("promotion:user:{$upperReferrerId}:all_members");
  271. }
  272. }
  273. // 2. 为用户创建与新上级的直接关系缓存
  274. $directRelation = new PromotionUserRelationCache();
  275. $directRelation->user_id = $userId;
  276. $directRelation->related_user_id = $newReferrerId;
  277. $directRelation->level = REFERRAL_LEVEL::DIRECT;
  278. $directRelation->path = (string)$newReferrerId;
  279. $directRelation->depth = 1;
  280. $directRelation->save();
  281. // 3. 获取新上级的所有上级
  282. $upperRelations = PromotionUserRelationCache::where('user_id', $newReferrerId)->get();
  283. // 4. 为用户创建与新上级的所有上级的间接关系缓存
  284. foreach ($upperRelations as $upperRelation) {
  285. $indirectRelation = new PromotionUserRelationCache();
  286. $indirectRelation->user_id = $userId;
  287. $indirectRelation->related_user_id = $upperRelation->related_user_id;
  288. $indirectRelation->level = REFERRAL_LEVEL::INDIRECT;
  289. $indirectRelation->path = $newReferrerId . ',' . $upperRelation->path;
  290. $indirectRelation->depth = $upperRelation->depth + 1;
  291. $indirectRelation->save();
  292. }
  293. // 5. 获取用户的所有下级
  294. $downlineRelations = PromotionUserRelationCache::where('related_user_id', $userId)->get();
  295. // 6. 为用户的所有下级创建与新上级及其所有上级的关系缓存
  296. foreach ($downlineRelations as $downlineRelation) {
  297. // 创建下级与新上级的关系
  298. $downlineToReferrer = new PromotionUserRelationCache();
  299. $downlineToReferrer->user_id = $downlineRelation->user_id;
  300. $downlineToReferrer->related_user_id = $newReferrerId;
  301. $downlineToReferrer->level = REFERRAL_LEVEL::INDIRECT;
  302. $downlineToReferrer->path = $downlineRelation->path . ',' . $newReferrerId;
  303. $downlineToReferrer->depth = $downlineRelation->depth + 1;
  304. $downlineToReferrer->save();
  305. // 创建下级与新上级的所有上级的关系
  306. foreach ($upperRelations as $upperRelation) {
  307. $downlineToUpper = new PromotionUserRelationCache();
  308. $downlineToUpper->user_id = $downlineRelation->user_id;
  309. $downlineToUpper->related_user_id = $upperRelation->related_user_id;
  310. $downlineToUpper->level = REFERRAL_LEVEL::INDIRECT;
  311. $downlineToUpper->path = $downlineRelation->path . ',' . $newReferrerId . ',' . $upperRelation->path;
  312. $downlineToUpper->depth = $downlineRelation->depth + $upperRelation->depth + 1;
  313. $downlineToUpper->save();
  314. }
  315. // 清除下级的Redis缓存
  316. Redis::del("promotion:user:{$downlineRelation->user_id}:all_referrers");
  317. }
  318. // 7. 清除新上级及其所有上级的Redis缓存
  319. Redis::del("promotion:user:{$newReferrerId}:all_members");
  320. foreach ($upperRelations as $upperRelation) {
  321. Redis::del("promotion:user:{$upperRelation->related_user_id}:all_members");
  322. }
  323. } catch (\Exception $e) {
  324. Log::error("更新关系缓存失败: " . $e->getMessage());
  325. }
  326. }
  327. /**
  328. * 统计用户的直推人数
  329. *
  330. * @param int $userId 用户ID
  331. * @return int
  332. */
  333. public function countDirectReferrals(int $userId): int
  334. {
  335. return PromotionUserReferral::where('referrer_id', $userId)->count();
  336. }
  337. /**
  338. * 统计用户的团队总人数
  339. *
  340. * @param int $userId 用户ID
  341. * @return int
  342. */
  343. public function countPromotionMembers(int $userId): int
  344. {
  345. return PromotionUserRelationCache::where('related_user_id', $userId)->count();
  346. }
  347. }