checkLogin(); $this->checkAdmin(); } /** * 期号列表页面 */ public function index() { $db = new Database(); try { // 获取所有期号列表 $periods = $db->select('periods', '*', [ 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 100 ]); if (!is_array($periods)) { $periods = []; } // 获取游戏列表(用于关联显示) $games = []; $gamesList = []; try { $gamesList = $db->select('games', ['id', 'name'], [ 'status' => 1, 'ORDER' => ['id' => 'ASC'] ]); if (is_array($gamesList)) { foreach ($gamesList as $game) { $games[$game['id']] = $game['name']; } } } catch (\Throwable $e) { // 忽略错误 } // 为每个游戏获取当前期号 $currentPeriods = []; foreach ($gamesList as $game) { $currentPeriod = $db->get('periods', '*', [ 'game_id' => $game['id'], 'ORDER' => ['id' => 'DESC'] ]); if ($currentPeriod) { $currentPeriods[$game['id']] = $currentPeriod; } } } catch (\Throwable $e) { $periods = []; $games = []; $gamesList = []; $currentPeriods = []; } $this->render('Admin/periods.php', [ 'currentPeriods' => $currentPeriods, // 改为多个游戏的当前期号 'periods' => $periods, 'games' => $games, 'gamesList' => $gamesList, // 完整的游戏列表 'title' => '期号管理' ]); } /** * 获取单个期号数据 */ public function get($id) { header('Content-Type: application/json'); if (empty($id) || !is_numeric($id)) { echo json_encode([ 'success' => false, 'message' => '无效的期号ID' ]); return; } $db = new Database(); try { $period = $db->get('periods', '*', [ 'id' => $id ]); if ($period) { echo json_encode([ 'success' => true, 'data' => $period ]); } else { echo json_encode([ 'success' => false, 'message' => '期号不存在' ]); } } catch (\Throwable $e) { echo json_encode([ 'success' => false, 'message' => '获取数据失败:' . $e->getMessage() ]); } } /** * 创建期号 */ public function create() { header('Content-Type: application/json'); $data = $_POST; if (empty($data)) { echo json_encode([ 'success' => false, 'message' => '未接收到数据' ]); return; } $db = new Database(); $gameId = isset($data['game_id']) ? (int)$data['game_id'] : null; // 如果关联了游戏,从游戏中获取直播流地址 $streamUrl = null; if ($gameId) { try { $game = $db->get('games', ['stream_url'], ['id' => $gameId]); if ($game && !empty($game['stream_url'])) { $streamUrl = $game['stream_url']; } } catch (\Throwable $e) { // 忽略错误,继续使用 null } } // 获取当前登录的管理员ID $createdBy = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null; // 生成期号 $periodNumber = $this->generatePeriodNumber($db, $gameId); // 准备数据 $periodData = [ 'period_number' => $periodNumber, 'status' => 'pending', 'game_id' => $gameId ?: null, 'stream_url' => $streamUrl, 'start_time' => date('Y-m-d H:i:s'), 'created_by' => $createdBy, 'auto_generated' => 0, // 手动创建 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; try { $id = $db->insert('periods', $periodData); echo json_encode([ 'success' => true, 'message' => '期号创建成功', 'data' => ['id' => $id, 'period_number' => $periodNumber] ]); } catch (\Exception $e) { echo json_encode([ 'success' => false, 'message' => '创建失败:' . $e->getMessage() ]); } } /** * 录入开奖结果 */ public function draw() { header('Content-Type: application/json'); $data = $_POST; if (empty($data)) { $input = file_get_contents('php://input'); $data = json_decode($input, true); } if (empty($data)) { echo json_encode([ 'success' => false, 'message' => '未接收到数据' ]); return; } $db = new Database(); $id = isset($data['id']) ? (int)$data['id'] : 0; if ($id <= 0) { echo json_encode([ 'success' => false, 'message' => '无效的期号ID' ]); return; } // 获取期号信息 $period = $db->get('periods', '*', ['id' => $id]); if (!$period) { echo json_encode([ 'success' => false, 'message' => '期号不存在' ]); return; } // 检查期号状态 if ($period['status'] === 'settled') { echo json_encode([ 'success' => false, 'message' => '该期号已结算,无法修改' ]); return; } // 获取游戏类型 $game = $db->get('games', ['type'], ['id' => $period['game_id']]); $gameType = $game['type'] ?? 'dice'; $auto = isset($data['auto']) ? (bool)$data['auto'] : false; // 根据游戏类型处理开奖结果 if ($gameType === 'xocdia') { // Xóc Đĩa 开奖逻辑 if ($auto) { // 自动生成:4个硬币随机红/白 $coins = []; for ($i = 0; $i < 4; $i++) { $coins[] = (rand(0, 1) === 0) ? 'red' : 'white'; } } else { // 手动输入:从前端获取 $coins = isset($data['coins']) ? $data['coins'] : []; if (!is_array($coins) || count($coins) !== 4) { echo json_encode([ 'success' => false, 'message' => 'Xóc Đĩa 必须提供4个硬币的颜色(red/white)' ]); return; } // 验证颜色 foreach ($coins as $coin) { if (!in_array($coin, ['red', 'white'])) { echo json_encode([ 'success' => false, 'message' => '硬币颜色只能是 red 或 white' ]); return; } } } // 计算结果 $redCount = count(array_filter($coins, fn($c) => $c === 'red')); $result = $this->calculateXocdiaResult($redCount); // 获取当前登录的管理员ID(审核人) $approvedBy = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null; // 更新期号数据 $updateData = [ 'result' => json_encode($coins), // 存储JSON数组 'dice1' => $redCount, // 复用字段:红色数量 'dice2' => 4 - $redCount, // 复用字段:白色数量 'dice3' => null, 'total' => null, 'status' => 'drawn', 'draw_time' => date('Y-m-d H:i:s'), 'approved_by' => $approvedBy, 'updated_at' => date('Y-m-d H:i:s') ]; $responseData = [ 'coins' => $coins, 'red_count' => $redCount, 'white_count' => 4 - $redCount, 'result' => $result ]; } else { // 骰子游戏开奖逻辑(原逻辑) if ($auto) { $dice1 = rand(1, 6); $dice2 = rand(1, 6); $dice3 = rand(1, 6); } else { $dice1 = isset($data['dice1']) ? (int)$data['dice1'] : 0; $dice2 = isset($data['dice2']) ? (int)$data['dice2'] : 0; $dice3 = isset($data['dice3']) ? (int)$data['dice3'] : 0; // 验证骰子点数 if ($dice1 < 1 || $dice1 > 6 || $dice2 < 1 || $dice2 > 6 || $dice3 < 1 || $dice3 > 6) { echo json_encode([ 'success' => false, 'message' => '骰子点数必须在1-6之间' ]); return; } } // 计算总和 $total = $dice1 + $dice2 + $dice3; // 计算结果 $result = $this->calculateResult($dice1, $dice2, $dice3, $total); // 获取当前登录的管理员ID(审核人) $approvedBy = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null; // 更新期号数据 $updateData = [ 'dice1' => $dice1, 'dice2' => $dice2, 'dice3' => $dice3, 'total' => $total, 'result' => $result, 'status' => 'drawn', 'draw_time' => date('Y-m-d H:i:s'), 'approved_by' => $approvedBy, 'updated_at' => date('Y-m-d H:i:s') ]; $responseData = [ 'dice1' => $dice1, 'dice2' => $dice2, 'dice3' => $dice3, 'total' => $total, 'result' => $result ]; } try { $db->update('periods', $updateData, ['id' => $id]); echo json_encode([ 'success' => true, 'message' => '开奖结果已录入', 'data' => $responseData ]); } catch (\Exception $e) { echo json_encode([ 'success' => false, 'message' => '录入失败:' . $e->getMessage() ]); } } /** * 启动新一期(开始下注) */ public function start() { header('Content-Type: application/json'); $data = $_POST; if (empty($data)) { $input = file_get_contents('php://input'); $data = json_decode($input, true); } $gameId = isset($data['game_id']) ? (int)$data['game_id'] : 0; if ($gameId <= 0) { echo json_encode([ 'success' => false, 'message' => '请选择游戏' ]); return; } $db = new Database(); // 检查该游戏是否有未结束的期号 $activePeriod = $db->get('periods', '*', [ 'game_id' => $gameId, 'status' => ['pending', 'locked', 'drawn'] ]); if ($activePeriod) { echo json_encode([ 'success' => false, 'message' => '该游戏当前有未结束的期号,无法开始新一轮' ]); return; } // 生成期号 $periodNumber = $this->generatePeriodNumber($db, $gameId); // 从游戏配置��取stream_url $streamUrl = null; $game = $db->get('games', ['stream_url'], ['id' => $gameId]); if ($game && !empty($game['stream_url'])) { $streamUrl = $game['stream_url']; } $periodData = [ 'period_number' => $periodNumber, 'status' => 'pending', 'game_id' => $gameId, 'stream_url' => $streamUrl, 'start_time' => date('Y-m-d H:i:s'), 'auto_generated' => 0, // 手动点击开始 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; try { $id = $db->insert('periods', $periodData); echo json_encode([ 'success' => true, 'message' => '新一期已启动,开始下注', 'data' => [ 'id' => $id, 'period_number' => $periodNumber, 'start_time' => $periodData['start_time'] ] ]); } catch (\Exception $e) { echo json_encode([ 'success' => false, 'message' => '启动失败:' . $e->getMessage() ]); } } /** * 确认开奖并结算(自动创建下一期) */ public function settle() { ob_clean(); header('Content-Type: application/json'); $data = $_POST; if (empty($data)) { $input = file_get_contents('php://input'); $data = json_decode($input, true); } $id = isset($data['id']) ? (int)$data['id'] : 0; if ($id <= 0) { echo json_encode([ 'success' => false, 'message' => '无效的期号ID' ]); return; } $db = new Database(); // 获取期号信息 $period = $db->get('periods', '*', ['id' => $id]); if (!$period) { echo json_encode([ 'success' => false, 'message' => '期号不存在' ]); return; } // 检查期号状态 if ($period['status'] !== 'drawn') { echo json_encode([ 'success' => false, 'message' => '该期号还未开奖,无法结算' ]); return; } if ($period['status'] === 'settled') { echo json_encode([ 'success' => false, 'message' => '该期号已结算' ]); return; } try { // 开启事务 $db->medoo->pdo->beginTransaction(); // 1. 获取本期所有待结算注单 $bets = $db->select('bets', '*', [ 'period_id' => $id, 'status' => 'pending' ]); // 2. 遍历注单进行结算 if ($bets) { foreach ($bets as $bet) { $checkResult = $this->checkWin($bet, $period); $isWin = $checkResult['win']; $winAmount = $checkResult['amount']; // 纯赢金额 $now = date('Y-m-d H:i:s'); if ($isWin) { $payout = $bet['amount'] + $winAmount; // 本金 + 盈利 // 更新注单状态 $db->update('bets', [ 'status' => 'win', 'win_amount' => $winAmount, 'settled_at' => $now, 'updated_at' => $now ], ['id' => $bet['id']]); // 更新用户余额 $db->update('users', [ 'balance[+]' => $payout ], ['id' => $bet['user_id']]); // 获取更新后的余额(用于记录流水) $user = $db->get('users', ['balance'], ['id' => $bet['user_id']]); $balanceAfter = $user['balance']; $balanceBefore = $balanceAfter - $payout; // 写入资金流水 $db->insert('transactions', [ 'user_id' => $bet['user_id'], 'type' => 'win', 'amount' => $payout, 'balance_before' => $balanceBefore, 'balance_after' => $balanceAfter, 'related_id' => $bet['id'], 'description' => "中奖 - 期号: " . $period['period_number'], 'created_at' => $now ]); } else { // 未中奖 $db->update('bets', [ 'status' => 'lose', 'win_amount' => 0, 'settled_at' => $now, 'updated_at' => $now ], ['id' => $bet['id']]); } } } // 更新期号状态为已结算 $db->update('periods', [ 'status' => 'settled', 'updated_at' => date('Y-m-d H:i:s') ], ['id' => $id]); // 移除自动创建下一期的逻辑,改为由管理员手动点击"开始下注" /* // 自动创建下一期 $nextPeriodNumber = $this->generatePeriodNumber($db); // 如果当前期号没有 stream_url,尝试从关联游戏中获取 $nextStreamUrl = $period['stream_url']; if (empty($nextStreamUrl) && !empty($period['game_id'])) { try { $game = $db->get('games', ['stream_url'], ['id' => $period['game_id']]); if ($game && !empty($game['stream_url'])) { $nextStreamUrl = $game['stream_url']; } } catch (\Throwable $e) { // 忽略错误,继续使用原值 } } $nextPeriodData = [ 'period_number' => $nextPeriodNumber, 'status' => 'pending', 'game_id' => $period['game_id'], 'stream_url' => $nextStreamUrl, 'start_time' => date('Y-m-d H:i:s'), 'auto_generated' => 1, // 自动生成 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; $nextPeriodId = $db->insert('periods', $nextPeriodData); */ $nextPeriodId = 0; $nextPeriodNumber = ''; // 提交事务 $db->medoo->pdo->commit(); echo json_encode([ 'success' => true, 'message' => '结算成功,请点击"开始下注"启动下一期', 'data' => [ 'current_period_id' => $id, // 'next_period_id' => $nextPeriodId, // 'next_period_number' => $nextPeriodNumber ] ]); } catch (\Exception $e) { // 回滚事务 if (isset($db->medoo->pdo)) { $db->medoo->pdo->rollBack(); } echo json_encode([ 'success' => false, 'message' => '结算失败:' . $e->getMessage() ]); } } /** * 封盘 */ public function lock() { header('Content-Type: application/json'); $data = $_POST; if (empty($data)) { $input = file_get_contents('php://input'); $data = json_decode($input, true); } $id = isset($data['id']) ? (int)$data['id'] : 0; if ($id <= 0) { echo json_encode([ 'success' => false, 'message' => '无效的期号ID' ]); return; } $db = new Database(); // 获取期号信息 $period = $db->get('periods', '*', ['id' => $id]); if (!$period) { echo json_encode([ 'success' => false, 'message' => '期号不存在' ]); return; } // 检查期号状态 if ($period['status'] !== 'pending') { echo json_encode([ 'success' => false, 'message' => '该期号状态不允许封盘' ]); return; } try { $db->update('periods', [ 'status' => 'locked', 'end_time' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ], ['id' => $id]); echo json_encode([ 'success' => true, 'message' => '封盘成功' ]); } catch (\Exception $e) { echo json_encode([ 'success' => false, 'message' => '封盘失败:' . $e->getMessage() ]); } } /** * 生成期号 * 格式:G{GameID}{YYYYMMDD}{NNNN} * 例如:G1202512240001 */ private function generatePeriodNumber($db, $gameId = null) { // 如果没有指定游戏ID,默认使用1 $gameId = $gameId ?: 1; $now = new \DateTime(); $dateStr = $now->format('Ymd'); $prefix = "G{$gameId}{$dateStr}"; // 查询当天该游戏最大的期号 try { $lastPeriod = $db->get('periods', 'period_number', [ 'period_number[~]' => $prefix . '%', 'ORDER' => ['period_number' => 'DESC'] ]); if ($lastPeriod) { // 提取序号并+1 $lastSeq = substr($lastPeriod, strlen($prefix)); $newSeq = (int)$lastSeq + 1; $seqStr = str_pad((string)$newSeq, 4, '0', STR_PAD_LEFT); } else { // 当天第一期 $seqStr = '0001'; } return $prefix . $seqStr; } catch (\Throwable $e) { // 发生错误时回退到时间戳随机数,防止阻塞 return $prefix . time(); } } /** * 检查注单是否中奖 */ private function checkWin($bet, $period) { $betType = $bet['bet_type']; $betValue = $bet['bet_value']; $betAmount = (float)$bet['amount']; $gameId = $period['game_id'] ?? 1; $db = new Database(); // 获取游戏类型 $game = $db->get('games', ['type'], ['id' => $gameId]); $gameType = $game['type'] ?? 'dice'; // 获取该游戏的所有赔率配置 static $oddsCache = []; if (!isset($oddsCache[$gameId])) { $oddsData = $db->select('game_odds', '*', ['game_id' => $gameId]); $oddsCache[$gameId] = []; foreach ($oddsData as $odd) { $key = $odd['type'] . '_' . $odd['target']; $oddsCache[$gameId][$key] = (float)$odd['odds']; } } $isWin = false; $payoutMultiplier = 0; // 辅助函数:获取赔率 $getOdds = function($type, $target = 'all') use ($oddsCache, $gameId) { $key = $type . '_' . $target; // 如果找不到特定target的赔率,尝试查找 'all' if (isset($oddsCache[$gameId][$key])) { return $oddsCache[$gameId][$key]; } $allKey = $type . '_all'; return $oddsCache[$gameId][$allKey] ?? 0; }; // Xóc Đĩa 游戏结算逻辑 if ($gameType === 'xocdia') { // 从 result 字段解析硬币颜色数组 $coins = json_decode($period['result'], true); if (!is_array($coins) || count($coins) !== 4) { return ['win' => false, 'amount' => 0]; } $redCount = count(array_filter($coins, fn($c) => $c === 'red')); $whiteCount = 4 - $redCount; switch ($betType) { case 'chan': // 双(偶): 4红 或 4白 或 2红2白 if (in_array($redCount, [0, 2, 4])) { $isWin = true; $payoutMultiplier = $getOdds('chan', $betValue); } break; case 'le': // 单(奇): 3红1白 或 3白1红 if (in_array($redCount, [1, 3])) { $isWin = true; $payoutMultiplier = $getOdds('le', $betValue); } break; case 'exact': // 精确颜色组合 switch ($betValue) { case '4red': if ($redCount === 4) $isWin = true; break; case '4white': if ($redCount === 0) $isWin = true; break; case '3red1white': if ($redCount === 3) $isWin = true; break; case '3white1red': if ($redCount === 1) $isWin = true; break; } if ($isWin) { $payoutMultiplier = $getOdds('exact', $betValue); } break; } if ($isWin && $payoutMultiplier > 0) { return [ 'win' => true, 'amount' => $betAmount * $payoutMultiplier ]; } return ['win' => false, 'amount' => 0]; } // 骰子游戏结算逻辑(原逻辑) $dice1 = (int)$period['dice1']; $dice2 = (int)$period['dice2']; $dice3 = (int)$period['dice3']; $total = (int)$period['total']; switch ($betType) { case 'xiu': // 小 (4-10) // 爆子处理:1-3为小,4-6为大 if ($dice1 == $dice2 && $dice2 == $dice3) { if ($dice1 <= 3) $isWin = true; } else { if ($total >= 4 && $total <= 10) $isWin = true; } $payoutMultiplier = $getOdds('xiu'); break; case 'tai': // 大 (11-17) // 爆子处理:1-3为小,4-6为大 if ($dice1 == $dice2 && $dice2 == $dice3) { if ($dice1 >= 4) $isWin = true; } else { if ($total >= 11 && $total <= 17) $isWin = true; } $payoutMultiplier = $getOdds('tai'); break; case 'chan': // 偶数 if ($total % 2 == 0) $isWin = true; $payoutMultiplier = $getOdds('chan'); break; case 'le': // 奇数 if ($total % 2 != 0) $isWin = true; $payoutMultiplier = $getOdds('le'); break; case 'number': // 单数字 (Sum) if ($total == (int)$betValue) { $isWin = true; $payoutMultiplier = $getOdds('sum', $betValue); } break; case 'dice': // 单个骰子 $count = 0; if ($dice1 == (int)$betValue) $count++; if ($dice2 == (int)$betValue) $count++; if ($dice3 == (int)$betValue) $count++; if ($count > 0) { $isWin = true; // 单个骰子赔率通常是 1:1, 1:2, 1:3 // 但数据库中只存了基础赔率 (e.g. 0.97 or 1.0) // 这里我们假设数据库存的是 1赔X 的 X // 如果是双骰或三骰,通常规则是: // 1个: 1倍 // 2个: 2倍 // 3个: 3倍 // 我们读取基础赔率,然后乘以数量 $baseOdds = $getOdds('dice', $betValue); $payoutMultiplier = $baseOdds * $count; } break; case 'combo': // 豹子 (Specific Triple) // betValue 存储的是总和 (3, 6, ..., 18) 或 'any_triple' if ($betValue === 'any_triple') { if ($dice1 == $dice2 && $dice2 == $dice3) { $isWin = true; $payoutMultiplier = $getOdds('combo', 'any_triple'); } } else { if ($dice1 == $dice2 && $dice2 == $dice3 && $total == (int)$betValue) { $isWin = true; $payoutMultiplier = $getOdds('combo', 'specific_triple'); } } break; } if ($isWin && $payoutMultiplier > 0) { return [ 'win' => true, 'amount' => $betAmount * $payoutMultiplier ]; } return ['win' => false, 'amount' => 0]; } /** * 计算开奖结果(骰子游戏) * @param int $dice1 骰子1点数 * @param int $dice2 骰子2点数 * @param int $dice3 骰子3点数 * @param int $total 总和 * @return string 结果:Xỉu/Tài/Bão */ private function calculateResult($dice1, $dice2, $dice3, $total) { // 检查是否为爆子(三个骰子相同) if ($dice1 === $dice2 && $dice2 === $dice3) { // 爆子根据点数判断:1-3为Xỉu,4-6为Tài if ($dice1 <= 3) { return 'Xỉu'; } else { return 'Tài'; } } // 普通大小判断:4-10点为小(Xỉu),11-17点为大(Tài) if ($total >= 4 && $total <= 10) { return 'Xỉu'; } else { return 'Tài'; } } /** * 计算 Xóc Đĩa 开奖结果 * @param int $redCount 红色硬币数量 * @return string 结果描述 */ private function calculateXocdiaResult($redCount) { switch ($redCount) { case 0: return '4 Trắng (Chẵn)'; case 1: return '3 Trắng 1 Đỏ (Lẻ)'; case 2: return '2 Trắng 2 Đỏ (Chẵn)'; case 3: return '3 Đỏ 1 Trắng (Lẻ)'; case 4: return '4 Đỏ (Chẵn)'; default: return 'Không xác định'; } } }