db = $db; } /** * 前台用户报表:按投注类型分组统计 * * @param int $userId 用户ID * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @return array */ public function getUserReport(int $userId, string $dateFrom, string $dateTo): array { $rangeFrom = $dateFrom . ' 00:00:00'; $rangeTo = $dateTo . ' 23:59:59'; $pdo = $this->db->medoo->pdo; try { // 投注统计(有效流水 = 已结算投注额,排除 pending) $stmt = $pdo->prepare(" SELECT bet_type as type, COUNT(*) as bet_count, SUM(amount) as amount, SUM(CASE WHEN status IN ('win','lose') THEN amount ELSE 0 END) as effective_flow, SUM(CASE WHEN status='win' THEN win_amount WHEN status='lose' THEN -amount ELSE 0 END) as win_loss FROM bets WHERE user_id = ? AND created_at BETWEEN ? AND ? GROUP BY bet_type ORDER BY bet_type "); $stmt->execute([$userId, $rangeFrom, $rangeTo]); $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); // 退水(代理返佣给该用户的金额) $rebateTotal = 0; try { $stmt2 = $pdo->prepare(" SELECT COALESCE(SUM(commission), 0) as total FROM agent_commissions WHERE from_user_id = ? AND type = 'rebate' AND created_at BETWEEN ? AND ? "); $stmt2->execute([$userId, $rangeFrom, $rangeTo]); $rebateTotal = floatval($stmt2->fetch(\PDO::FETCH_ASSOC)['total'] ?? 0); } catch (\Exception $e) { $rebateTotal = 0; } // 将退水按比例分配到各类型(或全部放在第一行) $totalBet = array_sum(array_column($rows, 'amount')); foreach ($rows as &$row) { $row['amount'] = floatval($row['amount']); $row['effective_flow'] = floatval($row['effective_flow']); $row['win_loss'] = floatval($row['win_loss']); // 按投注额占比分配退水 $ratio = $totalBet > 0 ? $row['amount'] / $totalBet : 0; $row['rebate'] = round($rebateTotal * $ratio, 2); } unset($row); return $rows; } catch (\Exception $e) { return []; } } /** * 后台总体统计 * * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @return array */ public function getOverviewStats(string $dateFrom, string $dateTo): array { $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; $pdo = $this->db->medoo->pdo; // 总投注(排除虚拟) $totalBet = $this->db->sum('bets', 'amount', [ 'is_virtual' => 0, 'created_at[<>]' => $range ]) ?: 0; // 总派奖 $totalWin = $this->db->sum('bets', 'win_amount', [ 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range ]) ?: 0; // 平台利润 $profit = $totalBet - $totalWin; $profitRate = $totalBet > 0 ? round($profit / $totalBet * 100, 2) : 0; // 总充值 $totalDeposit = $this->db->sum('transactions', 'amount', [ 'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0, 'created_at[<>]' => $range ]) ?: 0; // 总提现 $totalWithdraw = abs($this->db->sum('transactions', 'amount', [ 'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0, 'created_at[<>]' => $range ]) ?: 0); // 代理佣金 $totalCommission = $this->db->sum('agent_commissions', 'commission', [ 'created_at[<>]' => $range ]) ?: 0; // 反水 $totalRebate = $this->db->sum('agent_commissions', 'commission', [ 'type' => 'rebate', 'created_at[<>]' => $range ]) ?: 0; // 用户统计 $totalUsers = $this->db->count('users', ['is_virtual' => 0]); $newUsers = $this->db->count('users', [ 'is_virtual' => 0, 'created_at[<>]' => $range ]); // 活跃用户数(有投注记录的非虚拟用户) $activeUsers = 0; try { $stmt = $pdo->prepare("SELECT COUNT(DISTINCT user_id) as cnt FROM bets WHERE is_virtual = 0 AND created_at BETWEEN ? AND ?"); $stmt->execute([$range[0], $range[1]]); $activeUsers = (int)($stmt->fetch(\PDO::FETCH_ASSOC)['cnt'] ?? 0); } catch (\Exception $e) { $activeUsers = 0; } return compact( 'totalBet', 'totalWin', 'profit', 'profitRate', 'totalDeposit', 'totalWithdraw', 'totalCommission', 'totalRebate', 'totalUsers', 'newUsers', 'activeUsers' ); } /** * 日明细统计 * * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @return array */ public function getDailyStats(string $dateFrom, string $dateTo): array { $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; $pdo = $this->db->medoo->pdo; $dailyStats = []; try { $stmt = $pdo->prepare(" SELECT DATE(created_at) as date, SUM(amount) as total_bet, SUM(CASE WHEN status='win' THEN win_amount ELSE 0 END) as total_win, COUNT(*) as bet_count, SUM(CASE WHEN status='win' THEN 1 ELSE 0 END) as win_count FROM bets WHERE is_virtual = 0 AND created_at BETWEEN ? AND ? GROUP BY DATE(created_at) ORDER BY date DESC "); $stmt->execute([$range[0], $range[1]]); $dailyStats = $stmt->fetchAll(\PDO::FETCH_ASSOC); } catch (\Exception $e) { return []; } // 补充每日充提数据 foreach ($dailyStats as &$day) { $dr = [$day['date'] . ' 00:00:00', $day['date'] . ' 23:59:59']; $day['deposits'] = $this->db->sum('transactions', 'amount', [ 'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0, 'created_at[<>]' => $dr ]) ?: 0; $day['withdraws'] = abs($this->db->sum('transactions', 'amount', [ 'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0, 'created_at[<>]' => $dr ]) ?: 0); $day['total_bet'] = floatval($day['total_bet'] ?? 0); $day['total_win'] = floatval($day['total_win'] ?? 0); $day['bet_count'] = intval($day['bet_count'] ?? 0); $day['win_count'] = intval($day['win_count'] ?? 0); $day['profit'] = $day['total_bet'] - $day['total_win']; $day['win_rate'] = $day['bet_count'] > 0 ? round($day['win_count'] / $day['bet_count'] * 100, 1) : 0; } unset($day); return $dailyStats; } /** * 用户输赢明细(含日维度分组 + 用户汇总) * * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @return array ['raw' => 原始行, 'summary' => 按用户汇总] */ public function getUserWinLoss(string $dateFrom, string $dateTo): array { $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; $pdo = $this->db->medoo->pdo; $userWinLoss = []; try { $stmt = $pdo->prepare(" SELECT u.id as user_id, u.username, u.balance, DATE(b.created_at) as date, SUM(b.amount) as total_bet, SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win, COUNT(*) as bet_count, SUM(CASE WHEN b.status='win' THEN 1 ELSE 0 END) as win_count FROM bets b JOIN users u ON b.user_id = u.id WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ? GROUP BY b.user_id, DATE(b.created_at) ORDER BY u.username, date DESC "); $stmt->execute([$range[0], $range[1]]); $userWinLoss = $stmt->fetchAll(\PDO::FETCH_ASSOC); } catch (\Exception $e) { return ['raw' => [], 'summary' => []]; } // 按用户汇总 $userSummary = []; foreach ($userWinLoss as $row) { $uid = $row['user_id']; if (!isset($userSummary[$uid])) { $userSummary[$uid] = [ 'username' => $row['username'], 'balance' => floatval($row['balance']), 'total_bet' => 0, 'total_win' => 0, 'bet_count' => 0, 'win_count' => 0, 'days' => [] ]; } $bet = floatval($row['total_bet']); $win = floatval($row['total_win']); $userSummary[$uid]['total_bet'] += $bet; $userSummary[$uid]['total_win'] += $win; $userSummary[$uid]['bet_count'] += intval($row['bet_count']); $userSummary[$uid]['win_count'] += intval($row['win_count']); $userSummary[$uid]['days'][] = [ 'date' => $row['date'], 'bet' => $bet, 'win' => $win, 'profit' => $bet - $win, 'count' => intval($row['bet_count']), ]; } // 排序:按总投注额降序 uasort($userSummary, function($a, $b) { return $b['total_bet'] <=> $a['total_bet']; }); return ['raw' => $userWinLoss, 'summary' => $userSummary]; } /** * 用户排行(投注额 TOP N) * * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @param int $limit 条数限制,0=不限 * @return array */ public function getTopUsers(string $dateFrom, string $dateTo, int $limit = 20): array { $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; $pdo = $this->db->medoo->pdo; $sql = " SELECT u.username, SUM(b.amount) as total_bet, SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win, SUM(b.amount) - SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as profit, COUNT(*) as bet_count FROM bets b JOIN users u ON b.user_id = u.id WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ? GROUP BY b.user_id ORDER BY total_bet DESC "; if ($limit > 0) { $sql .= " LIMIT " . intval($limit); } try { $stmt = $pdo->prepare($sql); $stmt->execute([$range[0], $range[1]]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } catch (\Exception $e) { return []; } } /** * 代理报表 * * @param string $dateFrom 开始日期 (Y-m-d) * @param string $dateTo 结束日期 (Y-m-d) * @return array */ public function getAgentStats(string $dateFrom, string $dateTo): array { $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; $agentStats = []; $agents = $this->db->select('agents', '*', ['status' => 1]); foreach ($agents as $ag) { $playerIds = $this->db->select('users', 'id', ['agent_id' => $ag['id'], 'is_virtual' => 0]); $agBet = 0; $agWin = 0; if (!empty($playerIds)) { $agBet = $this->db->sum('bets', 'amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'created_at[<>]' => $range]) ?: 0; $agWin = $this->db->sum('bets', 'win_amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range]) ?: 0; } $agComm = $this->db->sum('agent_commissions', 'commission', ['agent_id' => $ag['id'], 'created_at[<>]' => $range]) ?: 0; $agentStats[] = [ 'agent' => $ag, 'user' => $this->db->get('users', ['username'], ['id' => $ag['user_id']]), 'players' => count($playerIds), 'bets' => $agBet, 'wins' => $agWin, 'commission' => $agComm, 'profit' => $agBet - $agWin - $agComm, ]; } return $agentStats; } }