LotteryChanceService.php 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525
  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\LotteryUserChanceRecord;
  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. use app\common\exception\BusinessException;
  13. use app\common\Enum\OrderEnum;
  14. use app\common\Service\Lottery\LotteryActivityService;
  15. /**
  16. * 抽奖机会服务类
  17. * 处理用户获得抽奖机会的逻辑
  18. *
  19. * 主要功能:
  20. * - 订单/充值后自动分发抽奖机会
  21. * - 条件验证和机会计算
  22. * - 用户机会管理
  23. * - 批量操作和统计
  24. */
  25. class LotteryChanceService
  26. {
  27. // ============ 常量定义 ============
  28. /** @var int 默认批量处理数量 */
  29. const DEFAULT_BATCH_SIZE = 100;
  30. /** @var int 最大批量处理数量 */
  31. const MAX_BATCH_SIZE = 1000;
  32. /**
  33. * 订单完成后检查并分发抽奖机会(单个活动版本)
  34. * 查询一个正在进行的活动,返回单个结果
  35. *
  36. * @param array $orderInfo 订单信息 ['id', 'total_amount', 'goods' => [['goods_id']]]
  37. * @param int $userId 用户ID
  38. * @return array|null 获得的抽奖机会信息,如果没有则返回null
  39. * @throws Exception
  40. */
  41. public static function checkAndGrantChanceForOrderOne($orderInfo, $userId)
  42. {
  43. if (empty($orderInfo) || empty($userId)) {
  44. throw new BusinessException('订单信息或用户ID不能为空');
  45. }
  46. // try {
  47. // 获取当前正在进行的单个抽奖活动
  48. $activity = LotteryActivityService::getCurrentRunningActivity();
  49. // 如果没有正在进行的活动,返回null
  50. if (!$activity) {
  51. return null;
  52. }
  53. // 处理当前活动
  54. $chances = static::processActivityForOrder($activity, $orderInfo, $userId);
  55. if ($chances > 0) {
  56. return [
  57. 'lottery_id' => $activity->id,
  58. 'lottery_name' => $activity->name,
  59. 'lottery_type' => $activity->lottery_type,
  60. 'chances' => $chances,
  61. 'granted_time' => time()
  62. ];
  63. }
  64. return null;
  65. // } catch (Exception $e) {
  66. // trace("订单抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  67. // throw new BusinessException('订单抽奖机会分发失败');
  68. // }
  69. }
  70. /**
  71. * 订单完成后检查并分发抽奖机会 多个的
  72. * @param array $orderInfo 订单信息 ['id', 'total_amount', 'goods' => [['goods_id']]]
  73. * @param int $userId 用户ID
  74. * @return array 获得的抽奖机会信息
  75. * @throws Exception
  76. */
  77. public static function checkAndGrantChanceForOrder($orderInfo, $userId)
  78. {
  79. if (empty($orderInfo) || empty($userId)) {
  80. throw new BusinessException('订单信息或用户ID不能为空');
  81. }
  82. $grantedChances = [];
  83. try {
  84. // 获取所有正在进行的抽奖活动 一段时间 有且只有一个抽奖活动
  85. $activities = LotteryActivityService::getRunningActivities();
  86. foreach ($activities as $activity) {
  87. try {
  88. $chances = static::processActivityForOrder($activity, $orderInfo, $userId);
  89. if ($chances > 0) {
  90. $grantedChances[] = [
  91. 'activity_id' => $activity->id,
  92. 'activity_name' => $activity->name,
  93. 'chances' => $chances,
  94. 'granted_time' => time()
  95. ];
  96. }
  97. } catch (Exception $e) {
  98. // 记录错误但不影响其他活动的处理
  99. trace("抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  100. }
  101. }
  102. } catch (Exception $e) {
  103. trace("订单抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  104. throw $e;
  105. }
  106. return $grantedChances;
  107. }
  108. /**
  109. * 充值完成后检查并分发抽奖机会(单个活动版本)
  110. * 查询一个正在进行的活动,返回单个结果
  111. *
  112. * @param array $rechargeInfo 充值信息 ['amount', 'type' => 'recharge']
  113. * @param int $userId 用户ID
  114. * @return array|null 获得的抽奖机会信息,如果没有则返回null
  115. * @throws Exception
  116. */
  117. public static function checkAndGrantChanceForRechargeOne($rechargeInfo, $userId)
  118. {
  119. if (empty($rechargeInfo) || empty($userId)) {
  120. throw new Exception('充值信息或用户ID不能为空');
  121. }
  122. try {
  123. // 获取当前正在进行的单个抽奖活动
  124. $activity = LotteryActivityService::getCurrentRunningActivity();
  125. // 如果没有正在进行的活动,返回null
  126. if (!$activity) {
  127. return null;
  128. }
  129. // 处理当前活动
  130. $chances = static::processActivityForRecharge($activity, $rechargeInfo, $userId);
  131. if ($chances > 0) {
  132. return [
  133. 'activity_id' => $activity->id,
  134. 'activity_name' => $activity->name,
  135. 'chances' => $chances,
  136. 'granted_time' => time()
  137. ];
  138. }
  139. return null;
  140. } catch (Exception $e) {
  141. trace("充值抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  142. throw $e;
  143. }
  144. }
  145. /**
  146. * 充值完成后检查并分发抽奖机会(多个活动版本)
  147. *
  148. * @param array $rechargeInfo 充值信息 ['amount', 'type' => 'recharge']
  149. * @param int $userId 用户ID
  150. * @return array 获得的抽奖机会信息
  151. * @throws Exception
  152. */
  153. public static function checkAndGrantChanceForRecharge($rechargeInfo, $userId)
  154. {
  155. if (empty($rechargeInfo) || empty($userId)) {
  156. throw new Exception('充值信息或用户ID不能为空');
  157. }
  158. $grantedChances = [];
  159. try {
  160. // 获取所有正在进行的抽奖活动
  161. $activities = LotteryActivityService::getRunningActivities();
  162. foreach ($activities as $activity) {
  163. try {
  164. $chances = static::processActivityForRecharge($activity, $rechargeInfo, $userId);
  165. if ($chances > 0) {
  166. $grantedChances[] = [
  167. 'activity_id' => $activity->id,
  168. 'activity_name' => $activity->name,
  169. 'chances' => $chances,
  170. 'granted_time' => time()
  171. ];
  172. }
  173. } catch (Exception $e) {
  174. // 记录错误但不影响其他活动的处理
  175. trace("充值抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  176. }
  177. }
  178. } catch (Exception $e) {
  179. trace("充值抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
  180. throw $e;
  181. }
  182. return $grantedChances;
  183. }
  184. /**
  185. * 手动给用户增加抽奖机会(管理员操作) *
  186. * @param int $activityId 活动ID
  187. * @param int $userId 用户ID
  188. * @param int $chances 机会次数
  189. * @param string $reason 原因
  190. * @param int $adminId 管理员ID
  191. * @return bool
  192. * @throws Exception
  193. */
  194. public static function manualGrantChance($activityId, $userId, $chances, $reason = '', $adminId = 0)
  195. {
  196. if ($chances <= 0) {
  197. throw new Exception('抽奖机会数量必须大于0');
  198. }
  199. $activity = LotteryActivity::find($activityId);
  200. if (!$activity) {
  201. throw new Exception('活动不存在');
  202. }
  203. $user = User::find($userId);
  204. if (!$user) {
  205. throw new Exception('用户不存在');
  206. }
  207. $detail = [
  208. 'reason' => $reason,
  209. 'admin_id' => $adminId,
  210. 'granted_time' => time()
  211. ];
  212. return static::grantChanceToUser($activityId, $userId, $chances, $detail);
  213. }
  214. /**
  215. * 批量初始化用户抽奖机会(活动开始时)
  216. *
  217. * @param int $activityId 活动ID
  218. * @param array $userIds 用户ID数组
  219. * @param int $initChances 初始机会数
  220. * @param int $batchSize 批量处理大小
  221. * @return array 处理结果
  222. * @throws Exception
  223. */
  224. public static function batchInitChances($activityId, $userIds, $initChances = 1, $batchSize = null)
  225. {
  226. if (empty($userIds)) {
  227. throw new Exception('用户ID列表不能为空');
  228. }
  229. $activity = LotteryActivity::find($activityId);
  230. if (!$activity) {
  231. throw new Exception('活动不存在');
  232. }
  233. $batchSize = $batchSize ?: static::DEFAULT_BATCH_SIZE;
  234. $batchSize = min($batchSize, static::MAX_BATCH_SIZE);
  235. $result = [
  236. 'total_users' => count($userIds),
  237. 'success_count' => 0,
  238. 'failed_count' => 0,
  239. 'errors' => []
  240. ];
  241. // 分批处理
  242. $batches = array_chunk($userIds, $batchSize);
  243. foreach ($batches as $batch) {
  244. try {
  245. $success = static::batchCreateChances($activityId, $batch, $initChances);
  246. if ($success) {
  247. $result['success_count'] += count($batch);
  248. } else {
  249. $result['failed_count'] += count($batch);
  250. $result['errors'][] = "批次处理失败: " . implode(',', $batch);
  251. }
  252. } catch (Exception $e) {
  253. $result['failed_count'] += count($batch);
  254. $result['errors'][] = "批次处理异常: " . $e->getMessage();
  255. }
  256. }
  257. return $result;
  258. }
  259. // ============ 私有处理方法 ============
  260. /**
  261. * 处理订单相关的活动
  262. *
  263. * @param LotteryActivity $activity 活动对象
  264. * @param array $orderInfo 订单信息
  265. * @param int $userId 用户ID
  266. * @return int 获得的抽奖机会数量
  267. */
  268. private static function processActivityForOrder($activity, $orderInfo, $userId)
  269. {
  270. // 检查用户是否符合活动参与资格
  271. if (!static::checkUserQualification($activity, $userId)) {
  272. return 0;
  273. }
  274. // 获取活动的参与条件
  275. $conditions = static::getValidConditions($activity->id);
  276. $totalChances = 0;
  277. $chanceDetails = [];
  278. foreach ($conditions as $condition) {
  279. // 跳过充值条件
  280. if ($condition->type == LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
  281. continue;
  282. }
  283. $result = static::processConditionForOrder($condition, $orderInfo, $userId);
  284. if ($result['chances'] > 0) {
  285. $totalChances += $result['chances'];
  286. $result['detail']['chances'] = $result['chances'];
  287. $chanceDetails[] = $result['detail'];
  288. }
  289. }
  290. // 如果获得了抽奖机会,记录到数据库
  291. if ($totalChances > 0) {
  292. // 为每个条件分别记录
  293. foreach ($chanceDetails as $detail) {
  294. static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
  295. 'condition_id' => $detail['condition_id'],
  296. 'condition_type' => $detail['condition_type'],
  297. 'condition_value' => $detail['condition_value'],
  298. 'order_id' => $orderInfo['id'] ?? 0,
  299. 'granted_time' => time()
  300. ]);
  301. }
  302. }
  303. return $totalChances;
  304. }
  305. /**
  306. * 处理充值相关的活动
  307. *
  308. * @param LotteryActivity $activity 活动对象
  309. * @param array $rechargeInfo 充值信息
  310. * @param int $userId 用户ID
  311. * @return int 获得的抽奖机会数量
  312. */
  313. private static function processActivityForRecharge($activity, $rechargeInfo, $userId)
  314. {
  315. // 检查用户是否符合活动参与资格
  316. if (!static::checkUserQualification($activity, $userId)) {
  317. return 0;
  318. }
  319. // 获取活动的参与条件
  320. $conditions = static::getValidConditions($activity->id);
  321. $totalChances = 0;
  322. $chanceDetails = [];
  323. foreach ($conditions as $condition) {
  324. // 只处理充值条件
  325. if ($condition->type != LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
  326. continue;
  327. }
  328. $result = static::processConditionForRecharge($condition, $rechargeInfo, $userId);
  329. if ($result['chances'] > 0) {
  330. $totalChances += $result['chances'];
  331. $result['detail']['chances'] = $result['chances'];
  332. $chanceDetails[] = $result['detail'];
  333. }
  334. }
  335. // 如果获得了抽奖机会,记录到数据库
  336. if ($totalChances > 0) {
  337. // 为每个条件分别记录
  338. foreach ($chanceDetails as $detail) {
  339. static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
  340. 'condition_id' => $detail['condition_id'],
  341. 'condition_type' => $detail['condition_type'],
  342. 'condition_value' => $detail['condition_value'],
  343. 'recharge_amount' => $rechargeInfo['amount'] ?? 0,
  344. 'granted_time' => time()
  345. ]);
  346. }
  347. }
  348. return $totalChances;
  349. }
  350. /**
  351. * 处理订单条件
  352. *
  353. * @param LotteryCondition $condition 条件对象
  354. * @param array $orderInfo 订单信息
  355. * @param int $userId 用户ID
  356. * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
  357. */
  358. private static function processConditionForOrder($condition, $orderInfo, $userId)
  359. {
  360. $chances = 0;
  361. $detail = [
  362. 'condition_id' => $condition->id,
  363. 'condition_type' => $condition->type,
  364. 'condition_value' => $condition->condition_value,
  365. ];
  366. switch ($condition->type) {
  367. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  368. if (static::validateGoodsCondition($condition, $orderInfo)) {
  369. $chances = static::getRewardTimes($condition);
  370. }
  371. break;
  372. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  373. if (static::validateOrderAmountCondition($condition, $orderInfo)) {
  374. $chances = static::getRewardTimes($condition);
  375. // 如果可重复获得,根据金额倍数计算次数
  376. if (static::canRepeat($condition)) {
  377. // 使用bcdiv进行精确的金额除法运算,然后取整
  378. $multiple = floor(bcdiv($orderInfo['total_amount'], $condition->condition_value, 2));
  379. $chances *= $multiple;
  380. }
  381. }
  382. break;
  383. case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
  384. if (static::validateAccumulateCondition($condition, $userId, $condition->activity_id)) {
  385. // 检查是否已经因为累计消费获得过机会
  386. if (!static::hasGrantedForAccumulate($condition->activity_id, $userId)) {
  387. $chances = static::getRewardTimes($condition);
  388. }
  389. }
  390. break;
  391. }
  392. return [
  393. 'chances' => $chances,
  394. 'detail' => $detail
  395. ];
  396. }
  397. /**
  398. * 处理充值条件
  399. *
  400. * @param LotteryCondition $condition 条件对象
  401. * @param array $rechargeInfo 充值信息
  402. * @param int $userId 用户ID
  403. * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
  404. */
  405. private static function processConditionForRecharge($condition, $rechargeInfo, $userId)
  406. {
  407. $chances = 0;
  408. $detail = [
  409. 'condition_id' => $condition->id,
  410. 'condition_type' => $condition->type,
  411. 'condition_value' => $condition->condition_value,
  412. ];
  413. if (static::validateRechargeCondition($condition, ['type' => 'recharge', 'amount' => $rechargeInfo['amount']])) {
  414. $chances = static::getRewardTimes($condition);
  415. // 如果可重复获得,根据金额倍数计算次数
  416. if (static::canRepeat($condition)) {
  417. // 使用bcdiv进行精确的金额除法运算,然后取整
  418. $multiple = floor(bcdiv($rechargeInfo['amount'], $condition->condition_value, 2));
  419. $chances *= $multiple;
  420. }
  421. }
  422. return [
  423. 'chances' => $chances,
  424. 'detail' => $detail
  425. ];
  426. }
  427. // ============ 用户资格检查方法 ============
  428. /**
  429. * 检查用户资格
  430. *
  431. * @param LotteryActivity $activity 活动对象
  432. * @param int $userId 用户ID
  433. * @return bool
  434. */
  435. private static function checkUserQualification($activity, $userId)
  436. {
  437. // 检查用户群体限制
  438. switch ($activity->user_limit_type) {
  439. case LotteryEnum::USER_LIMIT_ALL:
  440. return true;
  441. case LotteryEnum::USER_LIMIT_LEVEL:
  442. return static::checkUserLevel($userId, $activity->user_limit_value);
  443. case LotteryEnum::USER_LIMIT_TAG:
  444. return static::checkUserTag($userId, $activity->user_limit_value);
  445. default:
  446. return false;
  447. }
  448. }
  449. /**
  450. * 检查用户等级
  451. *
  452. * @param int $userId 用户ID
  453. * @param mixed $limitValue 限制值
  454. * @return bool
  455. */
  456. private static function checkUserLevel($userId, $limitValue)
  457. {
  458. if (empty($limitValue)) {
  459. return true;
  460. }
  461. $user = User::find($userId);
  462. if (!$user) {
  463. return false;
  464. }
  465. $limitLevels = is_array($limitValue) ? $limitValue : [$limitValue];
  466. return in_array($user->level, $limitLevels);
  467. }
  468. /**
  469. * 检查用户标签
  470. *
  471. * @param int $userId 用户ID
  472. * @param mixed $limitValue 限制值
  473. * @return bool
  474. */
  475. private static function checkUserTag($userId, $limitValue)
  476. {
  477. if (empty($limitValue)) {
  478. return true;
  479. }
  480. // TODO: 根据实际的用户标签系统实现
  481. // 这里需要根据具体的用户标签表结构来实现
  482. // 暂时返回true,避免影响现有功能
  483. return true;
  484. }
  485. /**
  486. * 检查是否已因累计消费获得过机会
  487. *
  488. * @param int $activityId 活动ID
  489. * @param int $userId 用户ID
  490. * @return bool
  491. */
  492. private static function hasGrantedForAccumulate($activityId, $userId)
  493. {
  494. $record = LotteryUserChanceRecord::where('activity_id', $activityId)
  495. ->where('user_id', $userId)
  496. ->where('get_type', LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT)
  497. ->find();
  498. return $record ? true : false;
  499. }
  500. // ============ 条件验证方法 ============
  501. /**
  502. * 验证订单是否满足条件
  503. *
  504. * @param LotteryCondition $condition 条件对象
  505. * @param array $orderInfo 订单信息
  506. * @return bool
  507. */
  508. public static function validateOrder(LotteryCondition $condition, $orderInfo)
  509. {
  510. switch ($condition->type) {
  511. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  512. return static::validateGoodsCondition($condition, $orderInfo);
  513. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  514. return static::validateOrderAmountCondition($condition, $orderInfo);
  515. case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
  516. return static::validateRechargeCondition($condition, $orderInfo);
  517. default:
  518. return false;
  519. }
  520. }
  521. /**
  522. * 验证商品条件
  523. *
  524. * @param LotteryCondition $condition 条件对象
  525. * @param array $orderInfo 订单信息
  526. * @return bool
  527. */
  528. public static function validateGoodsCondition(LotteryCondition $condition, $orderInfo)
  529. {
  530. if (empty($orderInfo['goods']) || empty($condition->goods_ids_list)) {
  531. return false;
  532. }
  533. $orderGoodsIds = array_column($orderInfo['goods'], 'goods_id');
  534. $conditionGoodsIds = $condition->goods_ids_list;
  535. $intersection = array_intersect($orderGoodsIds, $conditionGoodsIds);
  536. if ($condition->goods_rule == LotteryEnum::GOODS_RULE_INCLUDE) {
  537. // 指定商品参与:订单中必须包含指定商品
  538. return !empty($intersection);
  539. } else {
  540. // 指定商品不可参与:订单中不能包含指定商品
  541. return empty($intersection);
  542. }
  543. }
  544. /**
  545. * 验证订单金额条件
  546. *
  547. * @param LotteryCondition $condition 条件对象
  548. * @param array $orderInfo 订单信息
  549. * @return bool
  550. */
  551. public static function validateOrderAmountCondition(LotteryCondition $condition, $orderInfo)
  552. {
  553. $orderAmount = $orderInfo['total_amount'] ?? 0;
  554. // 使用bccomp进行精确的金额比较,返回值:-1(小于)、0(等于)、1(大于)
  555. return bccomp($orderAmount, $condition->condition_value, 2) >= 0;
  556. }
  557. /**
  558. * 验证充值条件
  559. *
  560. * @param LotteryCondition $condition 条件对象
  561. * @param array $orderInfo 订单信息
  562. * @return bool
  563. */
  564. public static function validateRechargeCondition(LotteryCondition $condition, $orderInfo)
  565. {
  566. if (($orderInfo['type'] ?? '') !== 'recharge') {
  567. return false;
  568. }
  569. $rechargeAmount = $orderInfo['amount'] ?? 0;
  570. // 使用bccomp进行精确的金额比较
  571. return bccomp($rechargeAmount, $condition->condition_value, 2) >= 0;
  572. }
  573. /**
  574. * 验证累计消费条件
  575. *
  576. * @param LotteryCondition $condition 条件对象
  577. * @param int $userId 用户ID
  578. * @param int $activityId 活动ID
  579. * @return bool
  580. */
  581. public static function validateAccumulateCondition(LotteryCondition $condition, $userId, $activityId)
  582. {
  583. $activity = LotteryActivity::find($activityId);
  584. if (!$activity) {
  585. return false;
  586. }
  587. // 计算活动期间用户累计消费
  588. $totalAmount = Order::where('user_id', $userId)
  589. ->where('order_status', OrderEnum::STATUS_PAY)
  590. ->where('pay_time', '>=', $activity->start_time)
  591. ->where('pay_time', '<=', $activity->end_time)
  592. ->sum('amount');
  593. // 使用bccomp进行精确的金额比较
  594. return bccomp($totalAmount, $condition->condition_value, 2) >= 0;
  595. }
  596. // ============ 条件管理方法 ============
  597. /**
  598. * 获取活动的有效条件
  599. *
  600. * @param int $activityId 活动ID
  601. * @return array
  602. */
  603. public static function getValidConditions($activityId)
  604. {
  605. return LotteryCondition::where('activity_id', $activityId)
  606. ->where('status', 1)
  607. ->order('id', 'asc')
  608. ->select();
  609. }
  610. /**
  611. * 检查条件是否可重复获得奖励
  612. *
  613. * @param LotteryCondition $condition 条件对象
  614. * @return bool
  615. */
  616. public static function canRepeat(LotteryCondition $condition)
  617. {
  618. return $condition->is_repeatable == 1;
  619. }
  620. /**
  621. * 获取奖励次数
  622. *
  623. * @param LotteryCondition $condition 条件对象
  624. * @return int
  625. */
  626. public static function getRewardTimes(LotteryCondition $condition)
  627. {
  628. return $condition->reward_times ?: 1;
  629. }
  630. /**
  631. * 批量验证订单条件
  632. *
  633. * @param int $activityId 活动ID
  634. * @param array $orderInfo 订单信息
  635. * @param int $userId 用户ID
  636. * @return array 满足的条件列表
  637. */
  638. public static function batchValidateConditions($activityId, $orderInfo, $userId)
  639. {
  640. $conditions = static::getValidConditions($activityId);
  641. $validConditions = [];
  642. foreach ($conditions as $condition) {
  643. if (static::validateOrder($condition, $orderInfo)) {
  644. $validConditions[] = [
  645. 'condition_id' => $condition->id,
  646. 'condition_name' => $condition->name,
  647. 'condition_type' => $condition->type,
  648. 'condition_type_text' => $condition->type_text,
  649. 'reward_times' => static::getRewardTimes($condition),
  650. 'can_repeat' => static::canRepeat($condition)
  651. ];
  652. }
  653. }
  654. return $validConditions;
  655. }
  656. /**
  657. * 获取条件统计信息
  658. *
  659. * @param int $activityId 活动ID
  660. * @return array
  661. */
  662. public static function getConditionStatistics($activityId)
  663. {
  664. $conditions = static::getValidConditions($activityId);
  665. $statistics = [
  666. 'total_conditions' => count($conditions),
  667. 'goods_conditions' => 0,
  668. 'amount_conditions' => 0,
  669. 'recharge_conditions' => 0,
  670. 'accumulate_conditions' => 0
  671. ];
  672. foreach ($conditions as $condition) {
  673. switch ($condition->type) {
  674. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  675. $statistics['goods_conditions']++;
  676. break;
  677. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  678. $statistics['amount_conditions']++;
  679. break;
  680. case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
  681. $statistics['recharge_conditions']++;
  682. break;
  683. case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
  684. $statistics['accumulate_conditions']++;
  685. break;
  686. }
  687. }
  688. return $statistics;
  689. }
  690. // ============ 用户机会管理方法 ============
  691. /**
  692. * 获取用户抽奖机会
  693. *
  694. * @param int $activityId 活动ID
  695. * @param int $userId 用户ID
  696. * @return LotteryUserChance|null
  697. */
  698. public static function getUserChance($activityId, $userId)
  699. {
  700. return LotteryUserChance::where('activity_id', $activityId)
  701. ->where('user_id', $userId)
  702. ->find();
  703. }
  704. /**
  705. * 根据详情确定机会获取类型
  706. *
  707. * @param array $detail 获得详情
  708. * @return int 获取类型
  709. */
  710. private static function getChanceGetTypeFromDetail($detail)
  711. {
  712. // 如果有条件类型,直接使用条件类型对应的获取类型
  713. if (isset($detail['condition_type'])) {
  714. switch ($detail['condition_type']) {
  715. case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
  716. return LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS;
  717. case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
  718. return LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT;
  719. case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
  720. return LotteryEnum::CHANCE_GET_TYPE_RECHARGE;
  721. case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
  722. return LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT;
  723. }
  724. }
  725. // 如果没有条件类型,检查是否是管理员赠送
  726. if (isset($detail['admin_id']) && $detail['admin_id'] > 0) {
  727. return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
  728. }
  729. // 默认返回管理员赠送
  730. return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
  731. }
  732. /**
  733. * 获取用户在指定活动中的抽奖机会详情
  734. *
  735. * @param int $activityId 活动ID
  736. * @param int $userId 用户ID
  737. * @return array
  738. */
  739. public static function getUserChanceDetail($activityId, $userId)
  740. {
  741. $userChance = static::getUserChance($activityId, $userId);
  742. if (!$userChance) {
  743. return [
  744. 'total_chances' => 0,
  745. 'used_chances' => 0,
  746. 'remain_chances' => 0,
  747. 'get_records' => []
  748. ];
  749. }
  750. // 获取机会获得记录(使用服务类方法)
  751. $getRecords = static::getUserChanceRecord($userId, $activityId);
  752. return [
  753. 'total_chances' => $userChance->total_chances,
  754. 'used_chances' => $userChance->used_chances,
  755. 'remain_chances' => $userChance->remain_chances,
  756. 'last_get_time' => $userChance->last_get_time,
  757. 'last_use_time' => $userChance->last_use_time,
  758. 'get_records' => $getRecords
  759. ];
  760. }
  761. /**
  762. * 给用户分发抽奖机会
  763. *
  764. * @param int $activityId 活动ID
  765. * @param int $userId 用户ID
  766. * @param int $chances 机会次数
  767. * @param array $detail 获得详情
  768. * @return LotteryUserChance|bool
  769. */
  770. private static function grantChanceToUser($activityId, $userId, $chances, $detail)
  771. {
  772. return static::addChance($activityId, $userId, $chances, $detail);
  773. }
  774. /**
  775. * 增加抽奖机会
  776. *
  777. * @param int $activityId 活动ID
  778. * @param int $userId 用户ID
  779. * @param int $times 机会次数
  780. * @param array $detail 获得详情
  781. * @return LotteryUserChance|bool
  782. */
  783. public static function addChance($activityId, $userId, $times = 1, $detail = [])
  784. {
  785. if ($times <= 0) {
  786. return false;
  787. }
  788. try {
  789. Db::startTrans();
  790. $chance = static::getUserChance($activityId, $userId);
  791. if (!$chance) {
  792. // 创建新记录
  793. $data = [
  794. 'activity_id' => $activityId,
  795. 'user_id' => $userId,
  796. 'total_chances' => $times,
  797. 'used_chances' => 0,
  798. 'remain_chances' => $times,
  799. 'last_get_time' => time(),
  800. 'get_detail' => json_encode([]) // 保留字段但不使用
  801. ];
  802. $chance = LotteryUserChance::create($data);
  803. } else {
  804. // 更新现有记录
  805. $chance->total_chances += $times;
  806. $chance->remain_chances += $times;
  807. $chance->last_get_time = time();
  808. $chance->save();
  809. }
  810. // 创建获得记录
  811. $recordData = [
  812. 'activity_id' => $activityId,
  813. 'user_id' => $userId,
  814. 'get_type' => static::getChanceGetTypeFromDetail($detail),
  815. 'chances' => $times,
  816. 'condition_id' => $detail['condition_id'] ?? null,
  817. 'condition_value' => $detail['condition_value'] ?? null,
  818. 'order_id' => $detail['order_id'] ?? null,
  819. 'recharge_amount' => $detail['recharge_amount'] ?? null,
  820. 'admin_id' => $detail['admin_id'] ?? null,
  821. 'reason' => $detail['reason'] ?? null,
  822. 'remark' => $detail['remark'] ?? null,
  823. 'get_time' => time()
  824. ];
  825. $validation = self::validateChanceRecord($recordData);
  826. if ($validation !== true) {
  827. throw new Exception($validation);
  828. }
  829. LotteryUserChanceRecord::create($recordData);
  830. Db::commit();
  831. return $chance;
  832. } catch (Exception $e) {
  833. Db::rollback();
  834. throw $e;
  835. }
  836. }
  837. /**
  838. * 使用抽奖机会
  839. *
  840. * @param LotteryUserChance $userChance 用户机会对象
  841. * @param int $times 使用次数
  842. * @return bool
  843. */
  844. public static function useChance(LotteryUserChance $userChance, $times = 1)
  845. {
  846. if ($userChance->remain_chances < $times) {
  847. return false;
  848. }
  849. $userChance->used_chances += $times;
  850. $userChance->remain_chances -= $times;
  851. $userChance->last_use_time = time();
  852. return $userChance->save();
  853. }
  854. /**
  855. * 检查是否有剩余机会
  856. *
  857. * @param LotteryUserChance $userChance 用户机会对象
  858. * @param int $times 需要的机会次数
  859. * @return bool
  860. */
  861. public static function hasChance(LotteryUserChance $userChance, $times = 1)
  862. {
  863. return $userChance->remain_chances >= $times;
  864. }
  865. /**
  866. * 重置抽奖机会(用于测试或特殊情况)
  867. *
  868. * @param LotteryUserChance $userChance 用户机会对象
  869. * @return bool
  870. */
  871. public static function resetChance(LotteryUserChance $userChance)
  872. {
  873. $userChance->used_chances = 0;
  874. $userChance->remain_chances = $userChance->total_chances;
  875. return $userChance->save();
  876. }
  877. // ============ 统计和查询方法 ============
  878. /**
  879. * 获取活动总参与人数
  880. *
  881. * @param int $activityId 活动ID
  882. * @return int
  883. */
  884. public static function getActivityParticipants($activityId)
  885. {
  886. return LotteryUserChance::where('activity_id', $activityId)->count();
  887. }
  888. /**
  889. * 获取用户在多个活动中的机会统计
  890. *
  891. * @param int $userId 用户ID
  892. * @param array $activityIds 活动ID数组
  893. * @return array
  894. */
  895. public static function getUserChancesStats($userId, $activityIds = [])
  896. {
  897. $query = LotteryUserChance::where('user_id', $userId);
  898. if (!empty($activityIds)) {
  899. $query->where('activity_id', 'in', $activityIds);
  900. }
  901. return $query->field([
  902. 'activity_id',
  903. 'total_chances',
  904. 'used_chances',
  905. 'remain_chances'
  906. ])
  907. ->select();
  908. }
  909. /**
  910. * 获取用户在所有活动中的机会概览
  911. *
  912. * @param int $userId 用户ID
  913. * @return array
  914. */
  915. public static function getUserAllChancesOverview($userId)
  916. {
  917. $chances = LotteryUserChance::where('user_id', $userId)
  918. ->with(['activity'])
  919. ->select();
  920. $overview = [
  921. 'total_activities' => count($chances),
  922. 'total_chances' => 0,
  923. 'total_used' => 0,
  924. 'total_remain' => 0,
  925. 'activities' => []
  926. ];
  927. foreach ($chances as $chance) {
  928. $overview['total_chances'] += $chance->total_chances;
  929. $overview['total_used'] += $chance->used_chances;
  930. $overview['total_remain'] += $chance->remain_chances;
  931. $overview['activities'][] = [
  932. 'activity_id' => $chance->activity_id,
  933. 'activity_name' => $chance->activity->name ?? '',
  934. 'total_chances' => $chance->total_chances,
  935. 'used_chances' => $chance->used_chances,
  936. 'remain_chances' => $chance->remain_chances,
  937. 'last_get_time' => $chance->last_get_time,
  938. 'last_use_time' => $chance->last_use_time
  939. ];
  940. }
  941. return $overview;
  942. }
  943. /**
  944. * 检查用户是否可以参与活动
  945. *
  946. * @param int $activityId 活动ID
  947. * @param int $userId 用户ID
  948. * @return bool
  949. */
  950. public static function canUserParticipate($activityId, $userId)
  951. {
  952. $userChance = static::getUserChance($activityId, $userId);
  953. return $userChance && static::hasChance($userChance, 1);
  954. }
  955. /**
  956. * 获取活动参与用户列表
  957. *
  958. * @param int $activityId 活动ID
  959. * @param int $page 页码
  960. * @param int $limit 每页数量
  961. * @return array
  962. */
  963. public static function getActivityParticipantsList($activityId, $page = 1, $limit = 20)
  964. {
  965. return LotteryUserChance::where('activity_id', $activityId)
  966. ->with(['user'])
  967. ->order('createtime', 'desc')
  968. ->page($page, $limit)
  969. ->select();
  970. }
  971. /**
  972. * 获取活动参与统计
  973. *
  974. * @param int $activityId 活动ID
  975. * @return array
  976. */
  977. public static function getActivityParticipationStats($activityId)
  978. {
  979. $stats = LotteryUserChance::where('activity_id', $activityId)
  980. ->field([
  981. 'COUNT(*) as total_participants',
  982. 'SUM(total_chances) as total_chances_granted',
  983. 'SUM(used_chances) as total_chances_used',
  984. 'SUM(remain_chances) as total_chances_remain'
  985. ])
  986. ->find();
  987. return [
  988. 'total_participants' => $stats['total_participants'] ?? 0,
  989. 'total_chances_granted' => $stats['total_chances_granted'] ?? 0,
  990. 'total_chances_used' => $stats['total_chances_used'] ?? 0,
  991. 'total_chances_remain' => $stats['total_chances_remain'] ?? 0,
  992. 'usage_rate' => $stats['total_chances_granted'] > 0 ?
  993. round(($stats['total_chances_used'] / $stats['total_chances_granted']) * 100, 2) : 0
  994. ];
  995. }
  996. // ============ 批量操作方法 ============
  997. /**
  998. * 批量创建用户机会(用于活动启动时)
  999. *
  1000. * @param int $activityId 活动ID
  1001. * @param array $userIds 用户ID数组
  1002. * @param int $times 机会次数
  1003. * @return bool
  1004. */
  1005. public static function batchCreateChances($activityId, $userIds, $times = 1)
  1006. {
  1007. if (empty($userIds)) {
  1008. return false;
  1009. }
  1010. $data = [];
  1011. $now = time();
  1012. foreach ($userIds as $userId) {
  1013. $data[] = [
  1014. 'activity_id' => $activityId,
  1015. 'user_id' => $userId,
  1016. 'total_chances' => $times,
  1017. 'used_chances' => 0,
  1018. 'remain_chances' => $times,
  1019. 'last_get_time' => $now,
  1020. 'createtime' => $now,
  1021. 'updatetime' => $now
  1022. ];
  1023. }
  1024. return LotteryUserChance::insertAll($data);
  1025. }
  1026. /**
  1027. * 批量检查用户资格
  1028. *
  1029. * @param int $activityId 活动ID
  1030. * @param array $userIds 用户ID数组
  1031. * @return array 符合条件的用户ID数组
  1032. */
  1033. public static function batchCheckUserQualification($activityId, $userIds)
  1034. {
  1035. $activity = LotteryActivity::find($activityId);
  1036. if (!$activity) {
  1037. return [];
  1038. }
  1039. $qualifiedUsers = [];
  1040. foreach ($userIds as $userId) {
  1041. if (static::checkUserQualification($activity, $userId)) {
  1042. $qualifiedUsers[] = $userId;
  1043. }
  1044. }
  1045. return $qualifiedUsers;
  1046. }
  1047. /**
  1048. * 批量验证条件
  1049. *
  1050. * @param int $activityId 活动ID
  1051. * @param array $orderInfo 订单信息
  1052. * @param array $userIds 用户ID数组
  1053. * @return array 每个用户满足的条件
  1054. */
  1055. public static function batchValidateConditionsForUsers($activityId, $orderInfo, $userIds)
  1056. {
  1057. $results = [];
  1058. foreach ($userIds as $userId) {
  1059. $results[$userId] = static::batchValidateConditions($activityId, $orderInfo, $userId);
  1060. }
  1061. return $results;
  1062. }
  1063. // ============ 机会获取记录管理方法 ============
  1064. /**
  1065. * 获取用户机会获取记录
  1066. *
  1067. * @param int $activityId 活动ID
  1068. * @param int $userId 用户ID
  1069. * @param int $page 页码
  1070. * @param int $limit 每页数量
  1071. * @return array
  1072. */
  1073. public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
  1074. {
  1075. return LotteryUserChanceRecord::where('activity_id', $activityId)
  1076. ->where('user_id', $userId)
  1077. ->with(['condition', 'order', 'admin'])
  1078. ->order('get_time desc')
  1079. ->page($page, $limit)
  1080. ->select();
  1081. }
  1082. /**
  1083. * 获取活动机会获取统计
  1084. *
  1085. * @param int $activityId 活动ID
  1086. * @return array
  1087. */
  1088. public static function getActivityChanceRecordStats($activityId)
  1089. {
  1090. $records = LotteryUserChanceRecord::where('activity_id', $activityId)->select();
  1091. $stats = [
  1092. 'total_records' => count($records),
  1093. 'total_chances' => 0,
  1094. 'type_stats' => [],
  1095. ];
  1096. foreach ($records as $record) {
  1097. $stats['total_chances'] += $record->chances;
  1098. $getType = $record->get_type;
  1099. if (!isset($stats['type_stats'][$getType])) {
  1100. $stats['type_stats'][$getType] = [
  1101. 'type' => $getType,
  1102. 'type_text' => LotteryEnum::getChanceGetTypeText($getType),
  1103. 'count' => 0,
  1104. 'chances' => 0,
  1105. ];
  1106. }
  1107. $stats['type_stats'][$getType]['count']++;
  1108. $stats['type_stats'][$getType]['chances'] += $record->chances;
  1109. }
  1110. return $stats;
  1111. }
  1112. /**
  1113. * 获取用户机会获取统计
  1114. *
  1115. * @param int $userId 用户ID
  1116. * @param int $activityId 活动ID(可选)
  1117. * @return array
  1118. */
  1119. public static function getUserChanceRecord($userId, $activityId = null)
  1120. {
  1121. $query = LotteryUserChanceRecord::where('user_id', $userId);
  1122. if ($activityId) {
  1123. $query->where('activity_id', $activityId);
  1124. }
  1125. $records = $query->select();
  1126. $stats = [
  1127. 'total_records' => count($records),
  1128. 'total_chances' => 0,
  1129. 'type_stats' => [],
  1130. 'recent_records' => [],
  1131. ];
  1132. foreach ($records as $record) {
  1133. $stats['total_chances'] += $record->chances;
  1134. $getType = $record->get_type;
  1135. if (!isset($stats['type_stats'][$getType])) {
  1136. $stats['type_stats'][$getType] = [
  1137. 'type' => $getType,
  1138. 'type_text' => LotteryEnum::getChanceGetTypeText($getType),
  1139. 'count' => 0,
  1140. 'chances' => 0,
  1141. ];
  1142. }
  1143. $stats['type_stats'][$getType]['count']++;
  1144. $stats['type_stats'][$getType]['chances'] += $record->chances;
  1145. }
  1146. // 获取最近的记录
  1147. $stats['recent_records'] = LotteryUserChanceRecord::where('user_id', $userId)
  1148. ->with(['activity'])
  1149. ->order('get_time desc')
  1150. ->limit(5)
  1151. ->select();
  1152. return $stats;
  1153. }
  1154. /**
  1155. * 批量创建机会获取记录
  1156. *
  1157. * @param array $records 记录数组
  1158. * @return bool
  1159. */
  1160. public static function batchCreateChanceRecords($records)
  1161. {
  1162. if (empty($records)) {
  1163. return false;
  1164. }
  1165. // 为每条记录添加创建时间
  1166. foreach ($records as &$record) {
  1167. $record['createtime'] = time();
  1168. $record['get_time'] = $record['get_time'] ?? time();
  1169. }
  1170. return LotteryUserChanceRecord::insertAll($records);
  1171. }
  1172. /**
  1173. * 验证机会获取记录数据
  1174. *
  1175. * @param array $data 记录数据
  1176. * @return bool|string true表示验证通过,string表示错误信息
  1177. */
  1178. public static function validateChanceRecord($data)
  1179. {
  1180. // 验证必填字段
  1181. if (empty($data['activity_id'])) {
  1182. return '活动ID不能为空';
  1183. }
  1184. if (empty($data['user_id'])) {
  1185. return '用户ID不能为空';
  1186. }
  1187. if (empty($data['get_type'])) {
  1188. return '获取类型不能为空';
  1189. }
  1190. if (empty($data['chances']) || $data['chances'] <= 0) {
  1191. return '机会次数必须大于0';
  1192. }
  1193. // 验证获取类型是否有效
  1194. if (!LotteryEnum::isValidChanceGetType($data['get_type'])) {
  1195. return '无效的获取类型';
  1196. }
  1197. // 根据获取类型验证相关字段
  1198. switch ($data['get_type']) {
  1199. case LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS:
  1200. case LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT:
  1201. if (empty($data['order_id'])) {
  1202. return '订单ID不能为空';
  1203. }
  1204. break;
  1205. case LotteryEnum::CHANCE_GET_TYPE_RECHARGE:
  1206. if (empty($data['recharge_amount']) || $data['recharge_amount'] <= 0) {
  1207. return '充值金额必须大于0';
  1208. }
  1209. break;
  1210. case LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT:
  1211. if (empty($data['admin_id'])) {
  1212. return '管理员ID不能为空';
  1213. }
  1214. break;
  1215. }
  1216. return true;
  1217. }
  1218. /**
  1219. * 创建单条机会获取记录
  1220. *
  1221. * @param array $data 记录数据
  1222. * @return bool|int 成功返回记录ID,失败返回false
  1223. */
  1224. public static function createChanceRecord($data)
  1225. {
  1226. // 验证数据
  1227. $validation = static::validateChanceRecord($data);
  1228. if ($validation !== true) {
  1229. throw new BusinessException($validation);
  1230. }
  1231. // 设置默认值
  1232. $data['createtime'] = time();
  1233. $data['get_time'] = $data['get_time'] ?? time();
  1234. $record = new LotteryUserChanceRecord($data);
  1235. if ($record->save()) {
  1236. return $record->id;
  1237. }
  1238. return false;
  1239. }
  1240. /**
  1241. * 获取用户在某个活动中的最新记录
  1242. *
  1243. * @param int $activityId 活动ID
  1244. * @param int $userId 用户ID
  1245. * @param int $limit 限制数量
  1246. * @return array
  1247. */
  1248. public static function getUserLatestRecords($activityId, $userId, $limit = 10)
  1249. {
  1250. return LotteryUserChanceRecord::where('activity_id', $activityId)
  1251. ->where('user_id', $userId)
  1252. ->with(['condition', 'order', 'admin'])
  1253. ->order('get_time desc')
  1254. ->limit($limit)
  1255. ->select();
  1256. }
  1257. /**
  1258. * 获取活动中某种获取类型的记录统计
  1259. *
  1260. * @param int $activityId 活动ID
  1261. * @param int $getType 获取类型
  1262. * @return array
  1263. */
  1264. public static function getActivityRecordsByType($activityId, $getType)
  1265. {
  1266. $records = LotteryUserChanceRecord::where('activity_id', $activityId)
  1267. ->where('get_type', $getType)
  1268. ->select();
  1269. $stats = [
  1270. 'total_records' => count($records),
  1271. 'total_chances' => 0,
  1272. 'users' => [],
  1273. ];
  1274. foreach ($records as $record) {
  1275. $stats['total_chances'] += $record->chances;
  1276. if (!isset($stats['users'][$record->user_id])) {
  1277. $stats['users'][$record->user_id] = [
  1278. 'user_id' => $record->user_id,
  1279. 'records' => 0,
  1280. 'chances' => 0,
  1281. ];
  1282. }
  1283. $stats['users'][$record->user_id]['records']++;
  1284. $stats['users'][$record->user_id]['chances'] += $record->chances;
  1285. }
  1286. return $stats;
  1287. }
  1288. /**
  1289. * 检查用户订单是否已经分发过抽奖机会
  1290. *
  1291. * @param array $orderInfo 订单信息
  1292. * @param int $userId 用户ID
  1293. * @return bool 是否已分发过
  1294. */
  1295. public static function isGrantedChanceForOrder($orderInfo, $userId)
  1296. {
  1297. if (empty($orderInfo) || empty($userId) || empty($orderInfo['id'])) {
  1298. return false;
  1299. }
  1300. // 查询是否存在该订单的机会记录
  1301. $count = LotteryUserChanceRecord::where('user_id', $userId)
  1302. ->where('order_id', $orderInfo['id'])
  1303. ->count();
  1304. return $count > 0;
  1305. }
  1306. /**
  1307. * 检查用户是否对指定条件已经获得过抽奖机会(用于防重复)
  1308. *
  1309. * @param int $activityId 活动ID
  1310. * @param int $userId 用户ID
  1311. * @param int $conditionId 条件ID
  1312. * @param string $conditionValue 条件值
  1313. * @return bool 是否已获得过
  1314. */
  1315. public static function isGrantedChanceForCondition($activityId, $userId, $conditionId, $conditionValue = '')
  1316. {
  1317. $where = [
  1318. 'activity_id' => $activityId,
  1319. 'user_id' => $userId,
  1320. 'condition_id' => $conditionId
  1321. ];
  1322. // 如果有条件值,也要匹配
  1323. if (!empty($conditionValue)) {
  1324. $where['condition_value'] = $conditionValue;
  1325. }
  1326. $count = LotteryUserChanceRecord::where($where)->count();
  1327. return $count > 0;
  1328. }
  1329. }