LotteryChanceService.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. <?php
  2. namespace app\common\Service\lottery;
  3. use app\common\model\lottery\LotteryActivity;
  4. use app\common\model\lottery\LotteryCondition;
  5. use app\common\model\lottery\LotteryUserChance;
  6. use app\common\model\lottery\LotteryDrawRecord;
  7. use app\common\model\User;
  8. use app\common\model\Order;
  9. use app\common\Enum\LotteryEnum;
  10. use think\Exception;
  11. use think\Db;
  12. /**
  13. * 抽奖机会服务类
  14. * 处理用户获得抽奖机会的逻辑
  15. *
  16. * 主要功能:
  17. * - 订单/充值后自动分发抽奖机会
  18. * - 条件验证和机会计算
  19. * - 用户机会管理
  20. * - 批量操作和统计
  21. */
  22. class LotteryChanceService
  23. {
  24. // ============ 常量定义 ============
  25. /** @var int 默认批量处理数量 */
  26. const DEFAULT_BATCH_SIZE = 100;
  27. /** @var int 最大批量处理数量 */
  28. const MAX_BATCH_SIZE = 1000;
  29. /** @var string 触发类型:订单 */
  30. const TRIGGER_TYPE_ORDER = 'order';
  31. /** @var string 触发类型:充值 */
  32. const TRIGGER_TYPE_RECHARGE = 'recharge';
  33. /** @var string 触发类型:累计消费 */
  34. const TRIGGER_TYPE_ACCUMULATE = 'accumulate';
  35. /** @var string 触发类型:手动 */
  36. const TRIGGER_TYPE_MANUAL = 'manual';
  37. // ============ 主要业务方法 ============
  38. /**
  39. * 订单完成后检查并分发抽奖机会
  40. *
  41. * @param array $orderInfo 订单信息 ['id', 'total_amount', 'goods' => [['goods_id']]]
  42. * @param int $userId 用户ID
  43. * @return array 获得的抽奖机会信息
  44. * @throws Exception
  45. */
  46. public static function checkAndGrantChanceForOrder($orderInfo, $userId)
  47. {
  48. if (empty($orderInfo) || empty($userId)) {
  49. throw new Exception('订单信息或用户ID不能为空');
  50. }
  51. $grantedChances = [];
  52. try {
  53. // 获取所有正在进行的抽奖活动
  54. $activities = LotteryService::getRunningActivities();
  55. foreach ($activities as $activity) {
  56. try {
  57. $chances = static::processActivityForOrder($activity, $orderInfo, $userId);
  58. if ($chances > 0) {
  59. $grantedChances[] = [
  60. 'activity_id' => $activity->id,
  61. 'activity_name' => $activity->name,
  62. 'chances' => $chances,
  63. 'granted_time' => time()
  64. ];
  65. }
  66. } catch (Exception $e) {
  67. // 记录错误但不影响其他活动的处理
  68. trace("抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  69. }
  70. }
  71. } catch (Exception $e) {
  72. trace("订单抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  73. throw $e;
  74. }
  75. return $grantedChances;
  76. }
  77. /**
  78. * 充值完成后检查并分发抽奖机会
  79. *
  80. * @param array $rechargeInfo 充值信息 ['amount', 'type' => 'recharge']
  81. * @param int $userId 用户ID
  82. * @return array 获得的抽奖机会信息
  83. * @throws Exception
  84. */
  85. public static function checkAndGrantChanceForRecharge($rechargeInfo, $userId)
  86. {
  87. if (empty($rechargeInfo) || empty($userId)) {
  88. throw new Exception('充值信息或用户ID不能为空');
  89. }
  90. $grantedChances = [];
  91. try {
  92. // 获取所有正在进行的抽奖活动
  93. $activities = LotteryService::getRunningActivities();
  94. foreach ($activities as $activity) {
  95. try {
  96. $chances = static::processActivityForRecharge($activity, $rechargeInfo, $userId);
  97. if ($chances > 0) {
  98. $grantedChances[] = [
  99. 'activity_id' => $activity->id,
  100. 'activity_name' => $activity->name,
  101. 'chances' => $chances,
  102. 'granted_time' => time()
  103. ];
  104. }
  105. } catch (Exception $e) {
  106. // 记录错误但不影响其他活动的处理
  107. trace("充值抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  108. }
  109. }
  110. } catch (Exception $e) {
  111. trace("充值抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  112. throw $e;
  113. }
  114. return $grantedChances;
  115. }
  116. /**
  117. * 手动给用户增加抽奖机会(管理员操作)
  118. *
  119. * @param int $activityId 活动ID
  120. * @param int $userId 用户ID
  121. * @param int $chances 机会次数
  122. * @param string $reason 原因
  123. * @param int $adminId 管理员ID
  124. * @return bool
  125. * @throws Exception
  126. */
  127. public static function manualGrantChance($activityId, $userId, $chances, $reason = '', $adminId = 0)
  128. {
  129. if ($chances <= 0) {
  130. throw new Exception('抽奖机会数量必须大于0');
  131. }
  132. $activity = LotteryActivity::find($activityId);
  133. if (!$activity) {
  134. throw new Exception('活动不存在');
  135. }
  136. $user = User::find($userId);
  137. if (!$user) {
  138. throw new Exception('用户不存在');
  139. }
  140. $detail = [
  141. 'trigger_type' => static::TRIGGER_TYPE_MANUAL,
  142. 'reason' => $reason,
  143. 'admin_id' => $adminId,
  144. 'granted_time' => time()
  145. ];
  146. return static::grantChanceToUser($activityId, $userId, $chances, $detail);
  147. }
  148. /**
  149. * 批量初始化用户抽奖机会(活动开始时)
  150. *
  151. * @param int $activityId 活动ID
  152. * @param array $userIds 用户ID数组
  153. * @param int $initChances 初始机会数
  154. * @param int $batchSize 批量处理大小
  155. * @return array 处理结果
  156. * @throws Exception
  157. */
  158. public static function batchInitChances($activityId, $userIds, $initChances = 1, $batchSize = null)
  159. {
  160. if (empty($userIds)) {
  161. throw new Exception('用户ID列表不能为空');
  162. }
  163. $activity = LotteryActivity::find($activityId);
  164. if (!$activity) {
  165. throw new Exception('活动不存在');
  166. }
  167. $batchSize = $batchSize ?: static::DEFAULT_BATCH_SIZE;
  168. $batchSize = min($batchSize, static::MAX_BATCH_SIZE);
  169. $result = [
  170. 'total_users' => count($userIds),
  171. 'success_count' => 0,
  172. 'failed_count' => 0,
  173. 'errors' => []
  174. ];
  175. // 分批处理
  176. $batches = array_chunk($userIds, $batchSize);
  177. foreach ($batches as $batch) {
  178. try {
  179. $success = static::batchCreateChances($activityId, $batch, $initChances);
  180. if ($success) {
  181. $result['success_count'] += count($batch);
  182. } else {
  183. $result['failed_count'] += count($batch);
  184. $result['errors'][] = "批次处理失败: " . implode(',', $batch);
  185. }
  186. } catch (Exception $e) {
  187. $result['failed_count'] += count($batch);
  188. $result['errors'][] = "批次处理异常: " . $e->getMessage();
  189. }
  190. }
  191. return $result;
  192. }
  193. // ============ 私有处理方法 ============
  194. /**
  195. * 处理订单相关的活动
  196. *
  197. * @param LotteryActivity $activity 活动对象
  198. * @param array $orderInfo 订单信息
  199. * @param int $userId 用户ID
  200. * @return int 获得的抽奖机会数量
  201. */
  202. private static function processActivityForOrder($activity, $orderInfo, $userId)
  203. {
  204. // 检查用户是否符合活动参与资格
  205. if (!static::checkUserQualification($activity, $userId)) {
  206. return 0;
  207. }
  208. // 获取活动的参与条件
  209. $conditions = static::getValidConditions($activity->id);
  210. $totalChances = 0;
  211. foreach ($conditions as $condition) {
  212. // 跳过充值条件
  213. if ($condition->type == LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
  214. continue;
  215. }
  216. $chances = static::processConditionForOrder($condition, $orderInfo, $userId);
  217. $totalChances += $chances;
  218. }
  219. // 如果获得了抽奖机会,记录到数据库
  220. if ($totalChances > 0) {
  221. static::grantChanceToUser($activity->id, $userId, $totalChances, [
  222. 'trigger_type' => static::TRIGGER_TYPE_ORDER,
  223. 'order_id' => $orderInfo['id'] ?? 0,
  224. 'order_amount' => $orderInfo['total_amount'] ?? 0,
  225. 'granted_time' => time()
  226. ]);
  227. }
  228. return $totalChances;
  229. }
  230. /**
  231. * 处理充值相关的活动
  232. *
  233. * @param LotteryActivity $activity 活动对象
  234. * @param array $rechargeInfo 充值信息
  235. * @param int $userId 用户ID
  236. * @return int 获得的抽奖机会数量
  237. */
  238. private static function processActivityForRecharge($activity, $rechargeInfo, $userId)
  239. {
  240. // 检查用户是否符合活动参与资格
  241. if (!static::checkUserQualification($activity, $userId)) {
  242. return 0;
  243. }
  244. // 获取活动的参与条件
  245. $conditions = static::getValidConditions($activity->id);
  246. $totalChances = 0;
  247. foreach ($conditions as $condition) {
  248. // 只处理充值条件
  249. if ($condition->type != LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
  250. continue;
  251. }
  252. $chances = static::processConditionForRecharge($condition, $rechargeInfo, $userId);
  253. $totalChances += $chances;
  254. }
  255. // 如果获得了抽奖机会,记录到数据库
  256. if ($totalChances > 0) {
  257. static::grantChanceToUser($activity->id, $userId, $totalChances, [
  258. 'trigger_type' => static::TRIGGER_TYPE_RECHARGE,
  259. 'recharge_amount' => $rechargeInfo['amount'] ?? 0,
  260. 'granted_time' => time()
  261. ]);
  262. }
  263. return $totalChances;
  264. }
  265. /**
  266. * 处理订单条件
  267. *
  268. * @param LotteryCondition $condition 条件对象
  269. * @param array $orderInfo 订单信息
  270. * @param int $userId 用户ID
  271. * @return int 获得的抽奖机会数量
  272. */
  273. private static function processConditionForOrder($condition, $orderInfo, $userId)
  274. {
  275. $chances = 0;
  276. switch ($condition->type) {
  277. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  278. if (static::validateGoodsCondition($condition, $orderInfo)) {
  279. $chances = static::getRewardTimes($condition);
  280. }
  281. break;
  282. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  283. if (static::validateOrderAmountCondition($condition, $orderInfo)) {
  284. $chances = static::getRewardTimes($condition);
  285. // 如果可重复获得,根据金额倍数计算次数
  286. if (static::canRepeat($condition)) {
  287. $multiple = floor($orderInfo['total_amount'] / $condition->condition_value);
  288. $chances *= $multiple;
  289. }
  290. }
  291. break;
  292. case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
  293. if (static::validateAccumulateCondition($condition, $userId, $condition->activity_id)) {
  294. // 检查是否已经因为累计消费获得过机会
  295. if (!static::hasGrantedForAccumulate($condition->activity_id, $userId)) {
  296. $chances = static::getRewardTimes($condition);
  297. }
  298. }
  299. break;
  300. }
  301. return $chances;
  302. }
  303. /**
  304. * 处理充值条件
  305. *
  306. * @param LotteryCondition $condition 条件对象
  307. * @param array $rechargeInfo 充值信息
  308. * @param int $userId 用户ID
  309. * @return int 获得的抽奖机会数量
  310. */
  311. private static function processConditionForRecharge($condition, $rechargeInfo, $userId)
  312. {
  313. $chances = 0;
  314. if (static::validateRechargeCondition($condition, ['type' => 'recharge', 'amount' => $rechargeInfo['amount']])) {
  315. $chances = static::getRewardTimes($condition);
  316. // 如果可重复获得,根据金额倍数计算次数
  317. if (static::canRepeat($condition)) {
  318. $multiple = floor($rechargeInfo['amount'] / $condition->condition_value);
  319. $chances *= $multiple;
  320. }
  321. }
  322. return $chances;
  323. }
  324. // ============ 用户资格检查方法 ============
  325. /**
  326. * 检查用户资格
  327. *
  328. * @param LotteryActivity $activity 活动对象
  329. * @param int $userId 用户ID
  330. * @return bool
  331. */
  332. private static function checkUserQualification($activity, $userId)
  333. {
  334. // 检查用户群体限制
  335. switch ($activity->user_limit_type) {
  336. case LotteryEnum::USER_LIMIT_ALL:
  337. return true;
  338. case LotteryEnum::USER_LIMIT_LEVEL:
  339. return static::checkUserLevel($userId, $activity->user_limit_value);
  340. case LotteryEnum::USER_LIMIT_TAG:
  341. return static::checkUserTag($userId, $activity->user_limit_value);
  342. default:
  343. return false;
  344. }
  345. }
  346. /**
  347. * 检查用户等级
  348. *
  349. * @param int $userId 用户ID
  350. * @param mixed $limitValue 限制值
  351. * @return bool
  352. */
  353. private static function checkUserLevel($userId, $limitValue)
  354. {
  355. if (empty($limitValue)) {
  356. return true;
  357. }
  358. $user = User::find($userId);
  359. if (!$user) {
  360. return false;
  361. }
  362. $limitLevels = is_array($limitValue) ? $limitValue : [$limitValue];
  363. return in_array($user->level, $limitLevels);
  364. }
  365. /**
  366. * 检查用户标签
  367. *
  368. * @param int $userId 用户ID
  369. * @param mixed $limitValue 限制值
  370. * @return bool
  371. */
  372. private static function checkUserTag($userId, $limitValue)
  373. {
  374. if (empty($limitValue)) {
  375. return true;
  376. }
  377. // TODO: 根据实际的用户标签系统实现
  378. // 这里需要根据具体的用户标签表结构来实现
  379. // 暂时返回true,避免影响现有功能
  380. return true;
  381. }
  382. /**
  383. * 检查是否已因累计消费获得过机会
  384. *
  385. * @param int $activityId 活动ID
  386. * @param int $userId 用户ID
  387. * @return bool
  388. */
  389. private static function hasGrantedForAccumulate($activityId, $userId)
  390. {
  391. $userChance = static::getUserChance($activityId, $userId);
  392. if (!$userChance) {
  393. return false;
  394. }
  395. $getDetail = $userChance->get_detail_data;
  396. foreach ($getDetail as $detail) {
  397. if (($detail['trigger_type'] ?? '') === static::TRIGGER_TYPE_ACCUMULATE) {
  398. return true;
  399. }
  400. }
  401. return false;
  402. }
  403. // ============ 条件验证方法 ============
  404. /**
  405. * 验证订单是否满足条件
  406. *
  407. * @param LotteryCondition $condition 条件对象
  408. * @param array $orderInfo 订单信息
  409. * @return bool
  410. */
  411. public static function validateOrder(LotteryCondition $condition, $orderInfo)
  412. {
  413. switch ($condition->type) {
  414. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  415. return static::validateGoodsCondition($condition, $orderInfo);
  416. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  417. return static::validateOrderAmountCondition($condition, $orderInfo);
  418. case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
  419. return static::validateRechargeCondition($condition, $orderInfo);
  420. default:
  421. return false;
  422. }
  423. }
  424. /**
  425. * 验证商品条件
  426. *
  427. * @param LotteryCondition $condition 条件对象
  428. * @param array $orderInfo 订单信息
  429. * @return bool
  430. */
  431. public static function validateGoodsCondition(LotteryCondition $condition, $orderInfo)
  432. {
  433. if (empty($orderInfo['goods']) || empty($condition->goods_ids_list)) {
  434. return false;
  435. }
  436. $orderGoodsIds = array_column($orderInfo['goods'], 'goods_id');
  437. $conditionGoodsIds = $condition->goods_ids_list;
  438. $intersection = array_intersect($orderGoodsIds, $conditionGoodsIds);
  439. if ($condition->goods_rule == LotteryEnum::GOODS_RULE_INCLUDE) {
  440. // 指定商品参与:订单中必须包含指定商品
  441. return !empty($intersection);
  442. } else {
  443. // 指定商品不可参与:订单中不能包含指定商品
  444. return empty($intersection);
  445. }
  446. }
  447. /**
  448. * 验证订单金额条件
  449. *
  450. * @param LotteryCondition $condition 条件对象
  451. * @param array $orderInfo 订单信息
  452. * @return bool
  453. */
  454. public static function validateOrderAmountCondition(LotteryCondition $condition, $orderInfo)
  455. {
  456. $orderAmount = $orderInfo['total_amount'] ?? 0;
  457. return $orderAmount >= $condition->condition_value;
  458. }
  459. /**
  460. * 验证充值条件
  461. *
  462. * @param LotteryCondition $condition 条件对象
  463. * @param array $orderInfo 订单信息
  464. * @return bool
  465. */
  466. public static function validateRechargeCondition(LotteryCondition $condition, $orderInfo)
  467. {
  468. if (($orderInfo['type'] ?? '') !== 'recharge') {
  469. return false;
  470. }
  471. $rechargeAmount = $orderInfo['amount'] ?? 0;
  472. return $rechargeAmount >= $condition->condition_value;
  473. }
  474. /**
  475. * 验证累计消费条件
  476. *
  477. * @param LotteryCondition $condition 条件对象
  478. * @param int $userId 用户ID
  479. * @param int $activityId 活动ID
  480. * @return bool
  481. */
  482. public static function validateAccumulateCondition(LotteryCondition $condition, $userId, $activityId)
  483. {
  484. $activity = LotteryActivity::find($activityId);
  485. if (!$activity) {
  486. return false;
  487. }
  488. // 计算活动期间用户累计消费
  489. $totalAmount = Order::where('user_id', $userId)
  490. ->where('status', 'paid')
  491. ->where('createtime', '>=', $activity->start_time)
  492. ->where('createtime', '<=', $activity->end_time)
  493. ->sum('total_amount');
  494. return $totalAmount >= $condition->condition_value;
  495. }
  496. // ============ 条件管理方法 ============
  497. /**
  498. * 获取活动的有效条件
  499. *
  500. * @param int $activityId 活动ID
  501. * @return array
  502. */
  503. public static function getValidConditions($activityId)
  504. {
  505. return LotteryCondition::where('activity_id', $activityId)
  506. ->where('status', 1)
  507. ->order('id', 'asc')
  508. ->select();
  509. }
  510. /**
  511. * 检查条件是否可重复获得奖励
  512. *
  513. * @param LotteryCondition $condition 条件对象
  514. * @return bool
  515. */
  516. public static function canRepeat(LotteryCondition $condition)
  517. {
  518. return $condition->is_repeatable == 1;
  519. }
  520. /**
  521. * 获取奖励次数
  522. *
  523. * @param LotteryCondition $condition 条件对象
  524. * @return int
  525. */
  526. public static function getRewardTimes(LotteryCondition $condition)
  527. {
  528. return $condition->reward_times ?: 1;
  529. }
  530. /**
  531. * 批量验证订单条件
  532. *
  533. * @param int $activityId 活动ID
  534. * @param array $orderInfo 订单信息
  535. * @param int $userId 用户ID
  536. * @return array 满足的条件列表
  537. */
  538. public static function batchValidateConditions($activityId, $orderInfo, $userId)
  539. {
  540. $conditions = static::getValidConditions($activityId);
  541. $validConditions = [];
  542. foreach ($conditions as $condition) {
  543. if (static::validateOrder($condition, $orderInfo)) {
  544. $validConditions[] = [
  545. 'condition_id' => $condition->id,
  546. 'condition_name' => $condition->name,
  547. 'condition_type' => $condition->type,
  548. 'condition_type_text' => $condition->type_text,
  549. 'reward_times' => static::getRewardTimes($condition),
  550. 'can_repeat' => static::canRepeat($condition)
  551. ];
  552. }
  553. }
  554. return $validConditions;
  555. }
  556. /**
  557. * 获取条件统计信息
  558. *
  559. * @param int $activityId 活动ID
  560. * @return array
  561. */
  562. public static function getConditionStatistics($activityId)
  563. {
  564. $conditions = static::getValidConditions($activityId);
  565. $statistics = [
  566. 'total_conditions' => count($conditions),
  567. 'goods_conditions' => 0,
  568. 'amount_conditions' => 0,
  569. 'recharge_conditions' => 0,
  570. 'accumulate_conditions' => 0
  571. ];
  572. foreach ($conditions as $condition) {
  573. switch ($condition->type) {
  574. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  575. $statistics['goods_conditions']++;
  576. break;
  577. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  578. $statistics['amount_conditions']++;
  579. break;
  580. case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
  581. $statistics['recharge_conditions']++;
  582. break;
  583. case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
  584. $statistics['accumulate_conditions']++;
  585. break;
  586. }
  587. }
  588. return $statistics;
  589. }
  590. // ============ 用户机会管理方法 ============
  591. /**
  592. * 获取用户抽奖机会
  593. *
  594. * @param int $activityId 活动ID
  595. * @param int $userId 用户ID
  596. * @return LotteryUserChance|null
  597. */
  598. public static function getUserChance($activityId, $userId)
  599. {
  600. return LotteryUserChance::where('activity_id', $activityId)
  601. ->where('user_id', $userId)
  602. ->find();
  603. }
  604. /**
  605. * 获取用户在指定活动中的抽奖机会详情
  606. *
  607. * @param int $activityId 活动ID
  608. * @param int $userId 用户ID
  609. * @return array
  610. */
  611. public static function getUserChanceDetail($activityId, $userId)
  612. {
  613. $userChance = static::getUserChance($activityId, $userId);
  614. if (!$userChance) {
  615. return [
  616. 'total_chances' => 0,
  617. 'used_chances' => 0,
  618. 'remain_chances' => 0,
  619. 'get_detail' => []
  620. ];
  621. }
  622. return [
  623. 'total_chances' => $userChance->total_chances,
  624. 'used_chances' => $userChance->used_chances,
  625. 'remain_chances' => $userChance->remain_chances,
  626. 'last_get_time' => $userChance->last_get_time,
  627. 'last_use_time' => $userChance->last_use_time,
  628. 'get_detail' => $userChance->get_detail_data
  629. ];
  630. }
  631. /**
  632. * 给用户分发抽奖机会
  633. *
  634. * @param int $activityId 活动ID
  635. * @param int $userId 用户ID
  636. * @param int $chances 机会次数
  637. * @param array $detail 获得详情
  638. * @return LotteryUserChance|bool
  639. */
  640. private static function grantChanceToUser($activityId, $userId, $chances, $detail)
  641. {
  642. return static::addChance($activityId, $userId, $chances, $detail);
  643. }
  644. /**
  645. * 增加抽奖机会
  646. *
  647. * @param int $activityId 活动ID
  648. * @param int $userId 用户ID
  649. * @param int $times 机会次数
  650. * @param array $detail 获得详情
  651. * @return LotteryUserChance|bool
  652. */
  653. public static function addChance($activityId, $userId, $times = 1, $detail = [])
  654. {
  655. if ($times <= 0) {
  656. return false;
  657. }
  658. $chance = static::getUserChance($activityId, $userId);
  659. if (!$chance) {
  660. // 创建新记录
  661. $data = [
  662. 'activity_id' => $activityId,
  663. 'user_id' => $userId,
  664. 'total_chances' => $times,
  665. 'used_chances' => 0,
  666. 'remain_chances' => $times,
  667. 'last_get_time' => time(),
  668. 'get_detail' => json_encode([$detail])
  669. ];
  670. return LotteryUserChance::create($data);
  671. } else {
  672. // 更新现有记录
  673. $chance->total_chances += $times;
  674. $chance->remain_chances += $times;
  675. $chance->last_get_time = time();
  676. // 更新获得详情
  677. $getDetail = $chance->get_detail_data;
  678. $getDetail[] = $detail;
  679. $chance->get_detail = json_encode($getDetail);
  680. return $chance->save() ? $chance : false;
  681. }
  682. }
  683. /**
  684. * 使用抽奖机会
  685. *
  686. * @param LotteryUserChance $userChance 用户机会对象
  687. * @param int $times 使用次数
  688. * @return bool
  689. */
  690. public static function useChance(LotteryUserChance $userChance, $times = 1)
  691. {
  692. if ($userChance->remain_chances < $times) {
  693. return false;
  694. }
  695. $userChance->used_chances += $times;
  696. $userChance->remain_chances -= $times;
  697. $userChance->last_use_time = time();
  698. return $userChance->save();
  699. }
  700. /**
  701. * 检查是否有剩余机会
  702. *
  703. * @param LotteryUserChance $userChance 用户机会对象
  704. * @param int $times 需要的机会次数
  705. * @return bool
  706. */
  707. public static function hasChance(LotteryUserChance $userChance, $times = 1)
  708. {
  709. return $userChance->remain_chances >= $times;
  710. }
  711. /**
  712. * 重置抽奖机会(用于测试或特殊情况)
  713. *
  714. * @param LotteryUserChance $userChance 用户机会对象
  715. * @return bool
  716. */
  717. public static function resetChance(LotteryUserChance $userChance)
  718. {
  719. $userChance->used_chances = 0;
  720. $userChance->remain_chances = $userChance->total_chances;
  721. return $userChance->save();
  722. }
  723. // ============ 统计和查询方法 ============
  724. /**
  725. * 获取活动总参与人数
  726. *
  727. * @param int $activityId 活动ID
  728. * @return int
  729. */
  730. public static function getActivityParticipants($activityId)
  731. {
  732. return LotteryUserChance::where('activity_id', $activityId)->count();
  733. }
  734. /**
  735. * 获取用户在多个活动中的机会统计
  736. *
  737. * @param int $userId 用户ID
  738. * @param array $activityIds 活动ID数组
  739. * @return array
  740. */
  741. public static function getUserChancesStats($userId, $activityIds = [])
  742. {
  743. $query = LotteryUserChance::where('user_id', $userId);
  744. if (!empty($activityIds)) {
  745. $query->where('activity_id', 'in', $activityIds);
  746. }
  747. return $query->field([
  748. 'activity_id',
  749. 'total_chances',
  750. 'used_chances',
  751. 'remain_chances'
  752. ])
  753. ->select();
  754. }
  755. /**
  756. * 获取用户在所有活动中的机会概览
  757. *
  758. * @param int $userId 用户ID
  759. * @return array
  760. */
  761. public static function getUserAllChancesOverview($userId)
  762. {
  763. $chances = LotteryUserChance::where('user_id', $userId)
  764. ->with(['activity'])
  765. ->select();
  766. $overview = [
  767. 'total_activities' => count($chances),
  768. 'total_chances' => 0,
  769. 'total_used' => 0,
  770. 'total_remain' => 0,
  771. 'activities' => []
  772. ];
  773. foreach ($chances as $chance) {
  774. $overview['total_chances'] += $chance->total_chances;
  775. $overview['total_used'] += $chance->used_chances;
  776. $overview['total_remain'] += $chance->remain_chances;
  777. $overview['activities'][] = [
  778. 'activity_id' => $chance->activity_id,
  779. 'activity_name' => $chance->activity->name ?? '',
  780. 'total_chances' => $chance->total_chances,
  781. 'used_chances' => $chance->used_chances,
  782. 'remain_chances' => $chance->remain_chances,
  783. 'last_get_time' => $chance->last_get_time,
  784. 'last_use_time' => $chance->last_use_time
  785. ];
  786. }
  787. return $overview;
  788. }
  789. /**
  790. * 检查用户是否可以参与活动
  791. *
  792. * @param int $activityId 活动ID
  793. * @param int $userId 用户ID
  794. * @return bool
  795. */
  796. public static function canUserParticipate($activityId, $userId)
  797. {
  798. $userChance = static::getUserChance($activityId, $userId);
  799. return $userChance && static::hasChance($userChance, 1);
  800. }
  801. /**
  802. * 获取活动参与用户列表
  803. *
  804. * @param int $activityId 活动ID
  805. * @param int $page 页码
  806. * @param int $limit 每页数量
  807. * @return array
  808. */
  809. public static function getActivityParticipantsList($activityId, $page = 1, $limit = 20)
  810. {
  811. return LotteryUserChance::where('activity_id', $activityId)
  812. ->with(['user'])
  813. ->order('createtime', 'desc')
  814. ->page($page, $limit)
  815. ->select();
  816. }
  817. /**
  818. * 获取活动参与统计
  819. *
  820. * @param int $activityId 活动ID
  821. * @return array
  822. */
  823. public static function getActivityParticipationStats($activityId)
  824. {
  825. $stats = LotteryUserChance::where('activity_id', $activityId)
  826. ->field([
  827. 'COUNT(*) as total_participants',
  828. 'SUM(total_chances) as total_chances_granted',
  829. 'SUM(used_chances) as total_chances_used',
  830. 'SUM(remain_chances) as total_chances_remain'
  831. ])
  832. ->find();
  833. return [
  834. 'total_participants' => $stats['total_participants'] ?? 0,
  835. 'total_chances_granted' => $stats['total_chances_granted'] ?? 0,
  836. 'total_chances_used' => $stats['total_chances_used'] ?? 0,
  837. 'total_chances_remain' => $stats['total_chances_remain'] ?? 0,
  838. 'usage_rate' => $stats['total_chances_granted'] > 0 ?
  839. round(($stats['total_chances_used'] / $stats['total_chances_granted']) * 100, 2) : 0
  840. ];
  841. }
  842. // ============ 批量操作方法 ============
  843. /**
  844. * 批量创建用户机会(用于活动启动时)
  845. *
  846. * @param int $activityId 活动ID
  847. * @param array $userIds 用户ID数组
  848. * @param int $times 机会次数
  849. * @return bool
  850. */
  851. public static function batchCreateChances($activityId, $userIds, $times = 1)
  852. {
  853. if (empty($userIds)) {
  854. return false;
  855. }
  856. $data = [];
  857. $now = time();
  858. foreach ($userIds as $userId) {
  859. $data[] = [
  860. 'activity_id' => $activityId,
  861. 'user_id' => $userId,
  862. 'total_chances' => $times,
  863. 'used_chances' => 0,
  864. 'remain_chances' => $times,
  865. 'last_get_time' => $now,
  866. 'createtime' => $now,
  867. 'updatetime' => $now
  868. ];
  869. }
  870. return LotteryUserChance::insertAll($data);
  871. }
  872. /**
  873. * 批量检查用户资格
  874. *
  875. * @param int $activityId 活动ID
  876. * @param array $userIds 用户ID数组
  877. * @return array 符合条件的用户ID数组
  878. */
  879. public static function batchCheckUserQualification($activityId, $userIds)
  880. {
  881. $activity = LotteryActivity::find($activityId);
  882. if (!$activity) {
  883. return [];
  884. }
  885. $qualifiedUsers = [];
  886. foreach ($userIds as $userId) {
  887. if (static::checkUserQualification($activity, $userId)) {
  888. $qualifiedUsers[] = $userId;
  889. }
  890. }
  891. return $qualifiedUsers;
  892. }
  893. /**
  894. * 批量验证条件
  895. *
  896. * @param int $activityId 活动ID
  897. * @param array $orderInfo 订单信息
  898. * @param array $userIds 用户ID数组
  899. * @return array 每个用户满足的条件
  900. */
  901. public static function batchValidateConditionsForUsers($activityId, $orderInfo, $userIds)
  902. {
  903. $results = [];
  904. foreach ($userIds as $userId) {
  905. $results[$userId] = static::batchValidateConditions($activityId, $orderInfo, $userId);
  906. }
  907. return $results;
  908. }
  909. }