LotteryChanceService.php 49 KB

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