db = $db; } public function index() { $this->checkLogin(); $gameId = $this->getGameId(); $periods = $this->db->select('periods', '*', [ 'game_id' => $gameId, 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 50 ]); // 附加pk10_results foreach ($periods as &$p) { $p['pk10'] = $this->db->get('pk10_results', '*', ['period_id' => $p['id']]); } $current = $this->db->get('periods', '*', [ 'game_id' => $gameId, 'status[!]' => 'settled', 'ORDER' => ['id' => 'DESC'] ]); if ($current) { $current['pk10'] = $this->db->get('pk10_results', '*', ['period_id' => $current['id']]); } $stats = [ 'total' => $this->db->count('periods', ['game_id' => $gameId]), 'pending' => $this->db->count('periods', ['game_id' => $gameId, 'status' => 'pending']), 'drawn' => $this->db->count('periods', ['game_id' => $gameId, 'status' => 'drawn']), 'settled' => $this->db->count('periods', ['game_id' => $gameId, 'status' => 'settled']), ]; $this->render('Admin/pk10_periods.php', compact('periods', 'current', 'stats', 'gameId')); } public function start() { $this->checkLogin(); $gameId = $this->getGameId(); // 检查是否有未结算期号 $active = $this->db->get('periods', 'id', [ 'game_id' => $gameId, 'status[!]' => 'settled' ]); if ($active) { $this->json(['status' => 'error', 'message' => 'There is an active period. Please settle it first.']); return; } $periodNumber = PK10Algorithm::generatePeriodNumber($gameId); $createdAt = date('Y-m-d H:i:s'); $this->db->insert('periods', [ 'game_id' => $gameId, 'period_number' => $periodNumber, 'status' => 'pending', 'start_time' => $createdAt, 'created_at' => $createdAt, ]); $periodId = (int)$this->db->id(); if ($periodId > 0) { $this->queueCountdownPushes($gameId, [ 'id' => $periodId, 'period_number' => $periodNumber, 'created_at' => $createdAt, ]); } $this->json(['status' => 'success', 'message' => 'New period started', 'period_number' => $periodNumber]); } public function lock() { $this->checkLogin(); $data = json_decode(file_get_contents('php://input'), true); $periodId = (int)($data['period_id'] ?? 0); $period = $this->db->get('periods', '*', ['id' => $periodId]); if (!$period || $period['status'] !== 'pending') { $this->json(['status' => 'error', 'message' => 'Invalid period or status']); return; } $this->db->update('periods', ['status' => 'locked'], ['id' => $periodId]); $this->queueBotPushByGame($period['game_id'], 'countdown', [ 'event' => 'period_locked', 'period_id' => (int)$period['id'], 'period_number' => (string)($period['period_number'] ?? ''), 'message' => 'Period locked', ], [ 'remind_close_countdown' => 1, ]); $this->json(['status' => 'success', 'message' => 'Period locked']); } public function draw() { $this->checkLogin(); $data = json_decode(file_get_contents('php://input'), true); $periodId = (int)($data['period_id'] ?? 0); $manual = $data['manual'] ?? false; $period = $this->db->get('periods', '*', ['id' => $periodId]); if (!$period || !in_array($period['status'], ['pending', 'locked'])) { $this->json(['status' => 'error', 'message' => 'Invalid period or status']); return; } $gameId = $period['game_id']; if ($manual && !empty($data['result'])) { // 手动补开奖 $result = array_map('intval', $data['result']); if (count($result) !== 10 || count(array_unique($result)) !== 10) { $this->json(['status' => 'error', 'message' => 'Result must be 10 unique numbers 1-10']); return; } } else { // 自动开奖(带放水机制) $waterConfig = []; $wc = $this->db->select('water_control', '*', ['game_id' => $gameId, 'enabled' => 1]); foreach ($wc as $w) { $waterConfig[$w['bet_type']] = (float)$w['win_rate_pct']; } $bets = $this->db->select('bets', '*', [ 'period_id' => $periodId, 'status' => 'pending', 'is_virtual' => 0 ]); if (!empty($waterConfig) && !empty($bets)) { $targetProfitRate = floatval(\App\Core\SettingsHelper::get('target_profit_rate')); $result = PK10Algorithm::generateControlledResult($bets, $waterConfig, 100, $targetProfitRate); } else { $result = PK10Algorithm::generateResult(); } } $sum = $result[0] + $result[1]; $resultJson = json_encode($result); $this->db->medoo->pdo->beginTransaction(); try { $this->db->update('periods', [ 'status' => 'drawn', 'result' => $resultJson, 'draw_time' => date('Y-m-d H:i:s'), ], ['id' => $periodId]); // 删除旧结果(如果手动修改) $this->db->delete('pk10_results', ['period_id' => $periodId]); $this->db->insert('pk10_results', [ 'period_id' => $periodId, 'rank_1' => $result[0], 'rank_2' => $result[1], 'rank_3' => $result[2], 'rank_4' => $result[3], 'rank_5' => $result[4], 'rank_6' => $result[5], 'rank_7' => $result[6], 'rank_8' => $result[7], 'rank_9' => $result[8], 'rank_10' => $result[9], 'champion_sum' => $sum, ]); $this->queueBotPushByGame($period['game_id'], 'draw', [ 'event' => 'period_drawn', 'period_id' => (int)$period['id'], 'period_number' => (string)($period['period_number'] ?? ''), 'result' => $result, 'champion_sum' => $sum, 'manual' => !empty($manual), ], [ 'remind_draw_result' => 1, ], (string)($period['period_number'] ?? '')); $this->db->medoo->pdo->commit(); } catch (\Throwable $e) { $this->db->medoo->pdo->rollBack(); $this->json(['status' => 'error', 'message' => 'Draw failed: ' . $e->getMessage()]); return; } $this->json(['status' => 'success', 'message' => 'Draw completed', 'result' => $result, 'sum' => $sum]); } public function settle() { $this->checkLogin(); $data = json_decode(file_get_contents('php://input'), true); $periodId = (int)($data['period_id'] ?? 0); $period = $this->db->get('periods', '*', ['id' => $periodId]); if (!$period || $period['status'] !== 'drawn') { $this->json(['status' => 'error', 'message' => 'Period must be drawn first']); return; } $result = json_decode($period['result'], true); if (!$result || count($result) !== 10) { $this->json(['status' => 'error', 'message' => 'Invalid result data']); return; } $bets = $this->db->select('bets', '*', ['period_id' => $periodId, 'status' => 'pending']); if (empty($bets)) { $this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]); $this->queueBotPushByGame($period['game_id'], 'system', [ 'event' => 'period_settled', 'period_id' => (int)$period['id'], 'period_number' => (string)($period['period_number'] ?? ''), 'wins' => 0, 'losses' => 0, 'total_payout' => 0, 'message' => 'No bets to settle', ], [], (string)($period['period_number'] ?? '')); $this->json(['status' => 'success', 'message' => 'No bets to settle', 'wins' => 0, 'losses' => 0]); return; } $wins = 0; $losses = 0; $totalPayout = 0; $this->db->medoo->pdo->beginTransaction(); try { foreach ($bets as $bet) { $isWin = PK10Algorithm::checkWin($result, $bet['bet_type'], $bet['bet_value']); $betAmount = (float)$bet['amount']; $odds = (float)$bet['odds']; if ($isWin) { $winAmount = $betAmount * $odds; $payout = $betAmount + $winAmount; $this->db->update('bets', [ 'status' => 'win', 'win_amount' => $winAmount, ], ['id' => $bet['id']]); $stmt = $this->db->medoo->pdo->prepare("SELECT balance FROM users WHERE id = :id FOR UPDATE"); $stmt->execute([':id' => $bet['user_id']]); $user = $stmt->fetch(\PDO::FETCH_ASSOC); $newBalance = (float)$user['balance'] + $payout; $this->db->update('users', ['balance' => $newBalance], ['id' => $bet['user_id']]); $this->db->insert('transactions', [ 'user_id' => $bet['user_id'], 'type' => 'win', 'amount' => $payout, 'balance_before' => $user['balance'], 'balance_after' => $newBalance, 'related_id' => $periodId, 'description' => 'PK10 Win - Period ' . $period['period_number'], 'is_virtual' => $bet['is_virtual'], 'created_at' => date('Y-m-d H:i:s'), ]); $totalPayout += $payout; $wins++; } else { $this->db->update('bets', ['status' => 'lose', 'win_amount' => 0], ['id' => $bet['id']]); $losses++; } } // 计算代理佣金 $this->settleAgentCommissions($bets, $periodId); $this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]); $this->queueBotPushByGame($period['game_id'], 'system', [ 'event' => 'period_settled', 'period_id' => (int)$period['id'], 'period_number' => (string)($period['period_number'] ?? ''), 'wins' => $wins, 'losses' => $losses, 'total_payout' => (float)$totalPayout, ], [], (string)($period['period_number'] ?? '')); $this->db->medoo->pdo->commit(); } catch (\Throwable $e) { $this->db->medoo->pdo->rollBack(); $this->json(['status' => 'error', 'message' => 'Settle failed: ' . $e->getMessage()]); return; } $this->json(['status' => 'success', 'message' => 'Settlement completed', 'wins' => $wins, 'losses' => $losses, 'total_payout' => $totalPayout]); } private function settleAgentCommissions(array $bets, int $periodId) { $agentBets = []; foreach ($bets as $bet) { if ((int)$bet['is_virtual']) continue; $user = $this->db->get('users', ['agent_id'], ['id' => $bet['user_id']]); if (!$user || !$user['agent_id']) continue; $agentId = $user['agent_id']; if (!isset($agentBets[$agentId])) $agentBets[$agentId] = 0; $agentBets[$agentId] += (float)$bet['amount']; } foreach ($agentBets as $agentId => $totalBet) { $agent = $this->db->get('agents', '*', ['id' => $agentId, 'status' => 1]); if (!$agent) continue; $commission = $totalBet * $agent['commission_rate'] / 100; if ($commission <= 0) continue; $this->db->insert('agent_commissions', [ 'agent_id' => $agentId, 'from_user_id' => 0, 'period_id' => $periodId, 'bet_amount' => $totalBet, 'commission' => $commission, 'type' => 'bet', 'created_at' => date('Y-m-d H:i:s'), ]); // 佣金加到代理用户余额 $agentUserId = $agent['user_id']; $stmt = $this->db->medoo->pdo->prepare("SELECT balance FROM users WHERE id = :id FOR UPDATE"); $stmt->execute([':id' => $agentUserId]); $au = $stmt->fetch(\PDO::FETCH_ASSOC); $newBal = (float)$au['balance'] + $commission; $this->db->update('users', ['balance' => $newBal], ['id' => $agentUserId]); $this->db->insert('transactions', [ 'user_id' => $agentUserId, 'type' => 'commission', 'amount' => $commission, 'balance_before' => $au['balance'], 'balance_after' => $newBal, 'related_id' => $periodId, 'description' => 'Agent commission', 'created_at' => date('Y-m-d H:i:s'), ]); } } public function get($id) { $this->checkLogin(); $period = $this->db->get('periods', '*', ['id' => (int)$id]); if ($period) { $period['pk10'] = $this->db->get('pk10_results', '*', ['period_id' => $period['id']]); } $this->json(['status' => 'success', 'data' => $period]); } private function getGameId(): int { $game = $this->db->get('games', 'id', ['code' => 'pk10']); return $game ? (int)$game : 0; } private function queueCountdownPushes(int $gameId, array $period): void { if ($gameId <= 0 || empty($period['id']) || empty($period['period_number'])) { return; } $groups = $this->db->select('bot_groups', ['id', 'countdown_config'], [ 'game_id' => $gameId, 'status' => 1, 'remind_close_countdown' => 1, ]) ?: []; if (empty($groups)) { return; } $game = $this->db->get('games', ['period_duration', 'lock_before_end'], ['id' => $gameId]); $periodDuration = (int)($game['period_duration'] ?? 300); $lockBeforeEnd = max(0, (int)($game['lock_before_end'] ?? 30)); $periodCreatedAt = strtotime((string)($period['created_at'] ?? '')); if ($periodCreatedAt <= 0) { $periodCreatedAt = time(); } $closeAtTs = $periodCreatedAt + max(0, $periodDuration - $lockBeforeEnd); $now = date('Y-m-d H:i:s'); foreach ($groups as $group) { $decoded = json_decode((string)($group['countdown_config'] ?? ''), true); if (!is_array($decoded) || empty($decoded)) { continue; } $secondsList = array_values(array_unique(array_filter(array_map('intval', $decoded), static function (int $seconds) use ($periodDuration, $lockBeforeEnd) { return $seconds > 0 && $seconds <= max(0, $periodDuration - $lockBeforeEnd); }))); rsort($secondsList); foreach ($secondsList as $seconds) { $dispatchAtTs = $closeAtTs - $seconds; if ($dispatchAtTs <= $periodCreatedAt) { continue; } $payload = [ 'event' => 'countdown_tick', 'period_id' => (int)$period['id'], 'period_number' => (string)$period['period_number'], 'countdown_seconds' => $seconds, 'dispatch_at' => date('Y-m-d H:i:s', $dispatchAtTs), 'message' => sprintf('Bet closes in %d seconds', $seconds), ]; $this->db->insert('bot_push_logs', [ 'group_id' => (int)$group['id'], 'period_number' => (string)$period['period_number'], 'push_type' => 'countdown', 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), 'status' => 'pending', 'created_at' => $now, 'updated_at' => $now, ]); } } } private function queueBotPushByGame(int $gameId, string $pushType, array $payload, array $groupFilters = [], ?string $periodNumber = null): void { if ($gameId <= 0) { return; } $where = array_merge(['game_id' => $gameId, 'status' => 1], $groupFilters); $groups = $this->db->select('bot_groups', ['id'], $where) ?: []; $now = date('Y-m-d H:i:s'); foreach ($groups as $group) { $this->db->insert('bot_push_logs', [ 'group_id' => (int)$group['id'], 'period_number' => $periodNumber !== null ? $periodNumber : ((string)($payload['period_number'] ?? '') ?: null), 'push_type' => $pushType, 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), 'status' => 'pending', 'created_at' => $now, 'updated_at' => $now, ]); } } private function json(array $data) { header('Content-Type: application/json'); echo json_encode($data); } }