NewUserSummaryService.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\DB;
  4. /**
  5. * 新用户汇总:区间集合查询,按天输出(无按天 PHP 循环打库)
  6. *
  7. * 口径:
  8. * - H5/APP:register_domain 是否为包名风格(com.xxx.yyy)
  9. * - 活跃会员:当日新注册且当日下注 >= 50
  10. * - 有效会员:当日新注册且当日存款 >= 50
  11. * - 公司盈利:当日新用户当日 投注额 - 派奖
  12. */
  13. class NewUserSummaryService
  14. {
  15. public const ACTIVE_BET_THRESHOLD = 50;
  16. public const VALID_DEPOSIT_THRESHOLD = 50;
  17. /** MySQL:包名风格域名视为 APP */
  18. private const IS_APP_SQL = "(register_domain REGEXP '^(com|org|net|io|cn)\\\\.[a-zA-Z0-9_]+(\\\\.[a-zA-Z0-9_]+)+$')";
  19. /**
  20. * @return array{start_time:string,end_time:string,rules:array,data:list<array>}
  21. */
  22. public static function summarize(string $startDate, string $endDate): array
  23. {
  24. $rangeStart = $startDate . ' 00:00:00';
  25. $rangeEnd = $endDate . ' 23:59:59';
  26. $isApp = self::IS_APP_SQL;
  27. // 当日新注册用户(供后续 join)
  28. $newUsers = DB::table('users')
  29. ->selectRaw("
  30. DATE(created_at) as reg_date,
  31. COALESCE(NULLIF(member_id, ''), user_id) as member_key,
  32. CASE WHEN {$isApp} THEN 1 ELSE 0 END as is_app
  33. ")
  34. ->whereBetween('created_at', [$rangeStart, $rangeEnd]);
  35. // 1) 注册按天
  36. $registerByDay = DB::table('users')
  37. ->selectRaw("
  38. DATE(created_at) as d,
  39. SUM(CASE WHEN {$isApp} THEN 1 ELSE 0 END) as app_register,
  40. SUM(CASE WHEN {$isApp} THEN 0 ELSE 1 END) as h5_register
  41. ")
  42. ->whereBetween('created_at', [$rangeStart, $rangeEnd])
  43. ->groupByRaw('DATE(created_at)')
  44. ->get()
  45. ->keyBy(fn ($r) => (string)$r->d);
  46. // 2) 首存:新用户首次正充值落在注册日
  47. $firstDepositSub = DB::table('balance_logs')
  48. ->selectRaw('member_id, MIN(created_at) as first_at')
  49. ->where('amount', '>', 0)
  50. ->where(function ($q) {
  51. $q->whereIn('change_type', ['充值', '人工充值', '三方充值'])
  52. ->orWhere(function ($q2) {
  53. $q2->where('change_type', 'like', '%充值%')
  54. ->where('change_type', 'not like', '%返现%')
  55. ->where('change_type', 'not like', '%退%');
  56. });
  57. })
  58. ->whereIn('member_id', (clone $newUsers)->selectRaw('COALESCE(NULLIF(member_id, \'\'), user_id)'))
  59. ->groupBy('member_id');
  60. $firstDepositByDay = DB::query()
  61. ->fromSub($firstDepositSub, 'fd')
  62. ->joinSub($newUsers, 'nu', 'nu.member_key', '=', 'fd.member_id')
  63. ->whereRaw('DATE(fd.first_at) = nu.reg_date')
  64. ->selectRaw("
  65. nu.reg_date as d,
  66. SUM(CASE WHEN nu.is_app = 1 THEN 1 ELSE 0 END) as app_first_deposit,
  67. SUM(CASE WHEN nu.is_app = 0 THEN 1 ELSE 0 END) as h5_first_deposit
  68. ")
  69. ->groupBy('nu.reg_date')
  70. ->get()
  71. ->keyBy(fn ($r) => (string)$r->d);
  72. // 3) 流水:新用户在其注册日的 投注/存款/派奖(每人一行)
  73. $memberFlow = DB::table('balance_logs as bl')
  74. ->joinSub($newUsers, 'nu', 'nu.member_key', '=', 'bl.member_id')
  75. ->whereRaw('DATE(bl.created_at) = nu.reg_date')
  76. ->whereBetween('bl.created_at', [$rangeStart, $rangeEnd])
  77. ->selectRaw("
  78. nu.reg_date as d,
  79. bl.member_id,
  80. SUM(CASE
  81. WHEN bl.change_type LIKE '%投注%'
  82. AND bl.change_type NOT LIKE '%退款%'
  83. AND bl.change_type NOT LIKE '%中奖%'
  84. THEN ABS(bl.amount) ELSE 0 END) as bet_amt,
  85. SUM(CASE
  86. WHEN bl.amount > 0 AND (
  87. bl.change_type IN ('充值','人工充值','三方充值')
  88. OR (
  89. bl.change_type LIKE '%充值%'
  90. AND bl.change_type NOT LIKE '%返现%'
  91. AND bl.change_type NOT LIKE '%退%'
  92. )
  93. ) THEN bl.amount ELSE 0 END) as deposit_amt,
  94. SUM(CASE
  95. WHEN bl.change_type LIKE '%中奖%' THEN bl.amount ELSE 0 END) as win_amt
  96. ")
  97. ->groupBy('nu.reg_date', 'bl.member_id')
  98. ->get();
  99. $flowByDay = [];
  100. foreach ($memberFlow as $row) {
  101. $d = (string)$row->d;
  102. if (!isset($flowByDay[$d])) {
  103. $flowByDay[$d] = [
  104. 'bet_total' => 0.0,
  105. 'deposit_total' => 0.0,
  106. 'win_total' => 0.0,
  107. 'active_member' => 0,
  108. 'valid_member' => 0,
  109. ];
  110. }
  111. $bet = (float)$row->bet_amt;
  112. $deposit = (float)$row->deposit_amt;
  113. $win = (float)$row->win_amt;
  114. $flowByDay[$d]['bet_total'] += $bet;
  115. $flowByDay[$d]['deposit_total'] += $deposit;
  116. $flowByDay[$d]['win_total'] += $win;
  117. if ($bet >= self::ACTIVE_BET_THRESHOLD) {
  118. $flowByDay[$d]['active_member']++;
  119. }
  120. if ($deposit >= self::VALID_DEPOSIT_THRESHOLD) {
  121. $flowByDay[$d]['valid_member']++;
  122. }
  123. }
  124. $data = [];
  125. $cursor = strtotime($startDate);
  126. $endTs = strtotime($endDate);
  127. while ($cursor <= $endTs) {
  128. $d = date('Y-m-d', $cursor);
  129. $reg = $registerByDay[$d] ?? null;
  130. $fd = $firstDepositByDay[$d] ?? null;
  131. $flow = $flowByDay[$d] ?? [
  132. 'bet_total' => 0.0,
  133. 'deposit_total' => 0.0,
  134. 'win_total' => 0.0,
  135. 'active_member' => 0,
  136. 'valid_member' => 0,
  137. ];
  138. $betTotal = round((float)$flow['bet_total'], 2);
  139. $depositTotal = round((float)$flow['deposit_total'], 2);
  140. $winTotal = round((float)$flow['win_total'], 2);
  141. $data[] = [
  142. 'date' => $d,
  143. 'h5_register' => (int)($reg->h5_register ?? 0),
  144. 'app_register' => (int)($reg->app_register ?? 0),
  145. 'h5_first_deposit' => (int)($fd->h5_first_deposit ?? 0),
  146. 'app_first_deposit' => (int)($fd->app_first_deposit ?? 0),
  147. 'active_member' => (int)$flow['active_member'],
  148. 'deposit_total' => $depositTotal,
  149. 'bet_total' => $betTotal,
  150. 'company_profit' => round($betTotal - $winTotal, 2),
  151. 'valid_member' => (int)$flow['valid_member'],
  152. ];
  153. $cursor = strtotime('+1 day', $cursor);
  154. }
  155. return [
  156. 'start_time' => $startDate,
  157. 'end_time' => $endDate,
  158. 'rules' => [
  159. 'active_member' => '新注册会员当日下注>=¥' . self::ACTIVE_BET_THRESHOLD,
  160. 'valid_member' => '新注册会员当日存款>=¥' . self::VALID_DEPOSIT_THRESHOLD,
  161. ],
  162. 'data' => $data,
  163. ];
  164. }
  165. }