Files
pk10/App/Controllers/Admin/PeriodController.php
T

958 lines
32 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Controllers\Admin;
use App\Core\AdminBaseController;
use Db\Database;
class PeriodController extends AdminBaseController {
public function __construct() {
$this->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';
}
}
}