ReferralCodeLogic.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php
  2. namespace App\Module\Promotion\Logics;
  3. use App\Module\Promotion\Enums\REFERRAL_CODE_STATUS;
  4. use App\Module\Promotion\Models\PromotionReferralCode;
  5. use App\Module\Promotion\Models\PromotionReferralCodeUsage;
  6. use Illuminate\Support\Facades\Log;
  7. use Illuminate\Support\Str;
  8. /**
  9. * 推荐码逻辑类
  10. *
  11. * 处理推荐码的核心业务逻辑,包括生成推荐码、验证推荐码、
  12. * 使用推荐码等功能。该类仅供内部使用,不对外提供服务。
  13. */
  14. class ReferralCodeLogic
  15. {
  16. /**
  17. * @var ReferralLogic
  18. */
  19. protected $referralLogic;
  20. /**
  21. * 构造函数
  22. *
  23. * @param ReferralLogic $referralLogic
  24. */
  25. public function __construct(ReferralLogic $referralLogic)
  26. {
  27. $this->referralLogic = $referralLogic;
  28. }
  29. /**
  30. * 生成推荐码
  31. *
  32. * @param int $userId 用户ID
  33. * @param \DateTime|null $expireTime 过期时间
  34. * @return string|null
  35. */
  36. public function generateReferralCode(int $userId, ?\DateTime $expireTime = null): ?string
  37. {
  38. try {
  39. // 检查用户是否已有有效的推荐码
  40. $existingCode = $this->getUserActiveCode($userId);
  41. if ($existingCode) {
  42. return $existingCode->code;
  43. }
  44. // 生成唯一的推荐码
  45. $code = $this->generateUniqueCode();
  46. // 创建推荐码记录
  47. $referralCode = new PromotionReferralCode();
  48. $referralCode->user_id = $userId;
  49. $referralCode->code = $code;
  50. $referralCode->usage_count = 0;
  51. $referralCode->status = REFERRAL_CODE_STATUS::ACTIVE;
  52. $referralCode->expire_time = $expireTime;
  53. if ($referralCode->save()) {
  54. return $code;
  55. }
  56. return null;
  57. } catch (\Exception $e) {
  58. Log::error("生成推荐码失败: " . $e->getMessage());
  59. return null;
  60. }
  61. }
  62. /**
  63. * 生成唯一的推荐码
  64. *
  65. * @param int $length 推荐码长度
  66. * @return string
  67. */
  68. private function generateUniqueCode(int $length = 8): string
  69. {
  70. $attempts = 0;
  71. $maxAttempts = 10;
  72. do {
  73. // 生成随机字符串
  74. $code = strtoupper(Str::random($length));
  75. // 检查是否已存在
  76. $exists = PromotionReferralCode::where('code', $code)->exists();
  77. $attempts++;
  78. } while ($exists && $attempts < $maxAttempts);
  79. // 如果多次尝试后仍然存在冲突,添加时间戳确保唯一性
  80. if ($exists) {
  81. $code = strtoupper(substr(Str::random($length - 4), 0, $length - 4) . substr(time(), -4));
  82. }
  83. return $code;
  84. }
  85. /**
  86. * 获取用户的有效推荐码
  87. *
  88. * @param int $userId 用户ID
  89. * @return PromotionReferralCode|null
  90. */
  91. public function getUserActiveCode(int $userId): ?PromotionReferralCode
  92. {
  93. $code = PromotionReferralCode::where('user_id', $userId)
  94. ->where('status', REFERRAL_CODE_STATUS::ACTIVE)
  95. ->where(function ($query) {
  96. $query->whereNull('expire_time')
  97. ->orWhere('expire_time', '>', now());
  98. })
  99. ->first();
  100. return $code;
  101. }
  102. /**
  103. * 验证推荐码
  104. *
  105. * @param string $code 推荐码
  106. * @return array 包含验证结果和推荐码信息
  107. */
  108. public function validateReferralCode(string $code): array
  109. {
  110. try {
  111. // 查找推荐码
  112. $referralCode = PromotionReferralCode::where('code', $code)->first();
  113. if (!$referralCode) {
  114. return [
  115. 'valid' => false,
  116. 'message' => '推荐码不存在',
  117. 'code' => null
  118. ];
  119. }
  120. // 检查状态
  121. if ($referralCode->status != REFERRAL_CODE_STATUS::ACTIVE) {
  122. return [
  123. 'valid' => false,
  124. 'message' => '推荐码已禁用',
  125. 'code' => $referralCode
  126. ];
  127. }
  128. // 检查过期时间
  129. if ($referralCode->expire_time && $referralCode->expire_time->isPast()) {
  130. return [
  131. 'valid' => false,
  132. 'message' => '推荐码已过期',
  133. 'code' => $referralCode
  134. ];
  135. }
  136. return [
  137. 'valid' => true,
  138. 'message' => '推荐码有效',
  139. 'code' => $referralCode
  140. ];
  141. } catch (\Exception $e) {
  142. Log::error("验证推荐码失败: " . $e->getMessage());
  143. return [
  144. 'valid' => false,
  145. 'message' => '验证推荐码时发生错误',
  146. 'code' => null
  147. ];
  148. }
  149. }
  150. /**
  151. * 使用推荐码
  152. *
  153. * @param string $code 推荐码
  154. * @param int $userId 使用者ID
  155. * @param string $ipAddress IP地址
  156. * @param string $userAgent 用户代理
  157. * @return array 包含使用结果和信息
  158. */
  159. public function useReferralCode(string $code, int $userId, string $ipAddress, string $userAgent): array
  160. {
  161. try {
  162. // 验证推荐码
  163. $validation = $this->validateReferralCode($code);
  164. if (!$validation['valid']) {
  165. return [
  166. 'success' => false,
  167. 'message' => $validation['message']
  168. ];
  169. }
  170. $referralCode = $validation['code'];
  171. // 检查用户是否使用自己的推荐码
  172. if ($referralCode->user_id == $userId) {
  173. return [
  174. 'success' => false,
  175. 'message' => '不能使用自己的推荐码'
  176. ];
  177. }
  178. // 检查用户是否已有推荐人
  179. if ($this->referralLogic->hasReferrer($userId)) {
  180. return [
  181. 'success' => false,
  182. 'message' => '用户已有推荐人,不能使用推荐码'
  183. ];
  184. }
  185. // 检查是否形成循环推荐
  186. if ($this->referralLogic->checkCircularReferral($userId, $referralCode->user_id)) {
  187. return [
  188. 'success' => false,
  189. 'message' => '使用此推荐码会形成循环推荐关系'
  190. ];
  191. }
  192. // 检查事务是否已开启
  193. \UCore\Db\Helper::check_tr();
  194. // 记录使用记录
  195. $usage = new PromotionReferralCodeUsage();
  196. $usage->code = $code;
  197. $usage->code_owner_id = $referralCode->user_id;
  198. $usage->user_id = $userId;
  199. $usage->ip_address = $ipAddress;
  200. $usage->user_agent = $userAgent;
  201. $usage->status = PromotionReferralCodeUsage::STATUS_SUCCESS;
  202. $usage->result = '成功使用推荐码';
  203. $usage->save();
  204. // 更新推荐码使用次数
  205. $referralCode->usage_count += 1;
  206. $referralCode->save();
  207. // 建立推荐关系
  208. $result = $this->referralLogic->createReferralRelation($userId, $referralCode->user_id);
  209. if (!$result) {
  210. // 如果建立推荐关系失败,更新使用记录状态
  211. $usage->status = PromotionReferralCodeUsage::STATUS_FAILED;
  212. $usage->result = '建立推荐关系失败';
  213. $usage->save();
  214. return [
  215. 'success' => false,
  216. 'message' => '建立推荐关系失败'
  217. ];
  218. }
  219. return [
  220. 'success' => true,
  221. 'message' => '成功使用推荐码并建立推荐关系',
  222. 'referrer_id' => $referralCode->user_id
  223. ];
  224. } catch (\Exception $e) {
  225. Log::error("使用推荐码失败: " . $e->getMessage());
  226. return [
  227. 'success' => false,
  228. 'message' => '使用推荐码时发生错误'
  229. ];
  230. }
  231. }
  232. /**
  233. * 禁用推荐码
  234. *
  235. * @param string $code 推荐码
  236. * @return bool
  237. */
  238. public function disableReferralCode(string $code): bool
  239. {
  240. try {
  241. $referralCode = PromotionReferralCode::where('code', $code)->first();
  242. if (!$referralCode) {
  243. return false;
  244. }
  245. $referralCode->status = REFERRAL_CODE_STATUS::DISABLED;
  246. return $referralCode->save();
  247. } catch (\Exception $e) {
  248. Log::error("禁用推荐码失败: " . $e->getMessage());
  249. return false;
  250. }
  251. }
  252. /**
  253. * 清理过期的推荐码
  254. *
  255. * @return int 清理的数量
  256. */
  257. public function cleanExpiredReferralCodes(): int
  258. {
  259. try {
  260. return PromotionReferralCode::where('status', REFERRAL_CODE_STATUS::ACTIVE)
  261. ->whereNotNull('expire_time')
  262. ->where('expire_time', '<', now())
  263. ->update(['status' => REFERRAL_CODE_STATUS::DISABLED]);
  264. } catch (\Exception $e) {
  265. Log::error("清理过期推荐码失败: " . $e->getMessage());
  266. return 0;
  267. }
  268. }
  269. }