Initial commit: 投注游戏平台初始化
This commit is contained in:
Executable
+274
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class AdminController extends AdminBaseController {
|
||||
/**
|
||||
* 构造函数 - 验证管理员权限
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 管理员列表页面
|
||||
*/
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
// 只查询管理员角色的用户
|
||||
$admins = $db->select('users', '*', [
|
||||
'role' => 'admin',
|
||||
'ORDER' => ['id' => 'DESC']
|
||||
]);
|
||||
$this->render('Admin/admin.php', [
|
||||
'admins' => $admins,
|
||||
'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();
|
||||
$admin = $db->get('users', '*', [
|
||||
'id' => $id,
|
||||
'role' => 'admin' // 确保只获取管理员
|
||||
]);
|
||||
|
||||
if ($admin) {
|
||||
// 移除密码字段,避免泄露
|
||||
unset($admin['password']);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $admin
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '管理员不存在'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function update() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = $_POST;
|
||||
if (empty($data)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '未接收到数据'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
$id = isset($data['id']) ? (int)$data['id'] : 0;
|
||||
$isEditMode = $id > 0;
|
||||
|
||||
$username = trim($data['username'] ?? '');
|
||||
$email = trim($data['email'] ?? '');
|
||||
$status = isset($data['status']) ? (int)$data['status'] : 0;
|
||||
|
||||
if (empty($username) || empty($email)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户名和邮箱不能为空'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '邮箱格式不正确'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 统一查重(排除当前 ID)
|
||||
$exists = $db->get('users', '*', [
|
||||
'AND' => [
|
||||
'OR' => [
|
||||
'username' => $username,
|
||||
'email' => $email
|
||||
],
|
||||
'id[!]' => $id
|
||||
]
|
||||
]);
|
||||
|
||||
if ($exists) {
|
||||
if ($exists['username'] === $username) {
|
||||
$msg = '用户名已存在';
|
||||
} elseif ($exists['email'] === $email) {
|
||||
$msg = '邮箱已被使用';
|
||||
} else {
|
||||
$msg = '用户名或邮箱已被占用';
|
||||
}
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $msg
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备数据 - 强制角色为 admin
|
||||
$adminData = [
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'role' => 'admin', // 强制设置为管理员角色
|
||||
'status' => $status,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
try {
|
||||
if ($isEditMode) {
|
||||
// 验证是否真的是管理员
|
||||
$existingAdmin = $db->get('users', 'id', [
|
||||
'id' => $id,
|
||||
'role' => 'admin'
|
||||
]);
|
||||
|
||||
if (!$existingAdmin) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '该用户不是管理员'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果密码不为空,更新密码
|
||||
if (!empty($data['password'])) {
|
||||
if (strlen($data['password']) < 8) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '密码长度至少8位'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$adminData['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$db->update('users', $adminData, ['id' => $id]);
|
||||
$message = '管理员更新成功';
|
||||
} else {
|
||||
// 新增时必须有密码
|
||||
if (empty($data['password']) || strlen($data['password']) < 8) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '密码长度至少8位'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$adminData['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$adminData['created_at'] = date('Y-m-d H:i:s');
|
||||
|
||||
$id = $db->insert('users', $adminData);
|
||||
$message = '管理员创建成功';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'data' => ['id' => $id]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '操作失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除管理员
|
||||
*/
|
||||
public function delete($id) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的管理员ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁止删除自己
|
||||
session_start();
|
||||
if ($id == $_SESSION['user_id']) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '不能删除当前登录的管理员'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 确保只删除管理员角色
|
||||
$adminExists = $db->get('users', 'id', [
|
||||
'id' => $id,
|
||||
'role' => 'admin'
|
||||
]);
|
||||
|
||||
if (!$adminExists) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '管理员不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否还有其他管理员
|
||||
$adminCount = $db->count('users', [
|
||||
'role' => 'admin'
|
||||
]);
|
||||
|
||||
if ($adminCount <= 1) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '不能删除最后一个管理员'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db->delete('users', [
|
||||
'id' => $id,
|
||||
'role' => 'admin'
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '管理员已删除'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '删除失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class AgentController extends AdminBaseController {
|
||||
private $db;
|
||||
public function __construct(Database $db) { $this->db = $db; }
|
||||
|
||||
// 后台代理管理
|
||||
public function index() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$agents = $this->db->select('agents', '*', ['ORDER' => ['id' => 'DESC']]);
|
||||
foreach ($agents as &$a) {
|
||||
$a['user'] = $this->db->get('users', ['username','balance'], ['id' => $a['user_id']]);
|
||||
$a['player_count'] = $this->db->count('users', ['agent_id' => $a['id']]);
|
||||
}
|
||||
$this->render('Admin/agents.php', compact('agents'));
|
||||
}
|
||||
|
||||
public function update() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id > 0) {
|
||||
$this->db->update('agents', [
|
||||
'commission_rate' => floatval($data['commission_rate'] ?? 0),
|
||||
'rebate_rate' => floatval($data['rebate_rate'] ?? 0),
|
||||
'status' => (int)($data['status'] ?? 1),
|
||||
], ['id' => $id]);
|
||||
} else {
|
||||
// 创建代理:先创建用户,再创建代理记录
|
||||
$userId = (int)($data['user_id'] ?? 0);
|
||||
if (!$userId) { $this->json(['status'=>'error','message'=>'User ID required']); return; }
|
||||
$code = strtoupper(substr(md5(uniqid()), 0, 8));
|
||||
$this->db->insert('agents', [
|
||||
'user_id' => $userId,
|
||||
'parent_id' => !empty($data['parent_id']) ? (int)$data['parent_id'] : null,
|
||||
'level' => !empty($data['parent_id']) ? 2 : 1,
|
||||
'agent_code' => $code,
|
||||
'commission_rate' => floatval($data['commission_rate'] ?? 0),
|
||||
'rebate_rate' => floatval($data['rebate_rate'] ?? 0),
|
||||
'status' => 1,
|
||||
]);
|
||||
$this->db->update('users', ['role' => 'agent'], ['id' => $userId]);
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
public function delete($id) {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$agent = $this->db->get('agents', '*', ['id' => (int)$id]);
|
||||
if ($agent) {
|
||||
$this->db->update('users', ['role' => 'user', 'agent_id' => null], ['agent_id' => $agent['id']]);
|
||||
$this->db->delete('agents', ['id' => (int)$id]);
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
// 代理赔率设置
|
||||
public function odds($agentId) {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$odds = $this->db->select('agent_odds', '*', ['agent_id' => (int)$agentId]);
|
||||
$this->json(['status' => 'success', 'data' => $odds]);
|
||||
}
|
||||
|
||||
public function updateOdds() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$agentId = (int)($data['agent_id'] ?? 0);
|
||||
$odds = $data['odds'] ?? [];
|
||||
$gameId = (int)($data['game_id'] ?? 0);
|
||||
|
||||
foreach ($odds as $o) {
|
||||
$existing = $this->db->get('agent_odds', 'id', [
|
||||
'agent_id' => $agentId, 'game_id' => $gameId,
|
||||
'bet_type' => $o['bet_type'], 'bet_target' => $o['bet_target']
|
||||
]);
|
||||
if ($existing) {
|
||||
$this->db->update('agent_odds', ['odds' => floatval($o['odds'])], ['id' => $existing]);
|
||||
} else {
|
||||
$this->db->insert('agent_odds', [
|
||||
'agent_id' => $agentId, 'game_id' => $gameId,
|
||||
'bet_type' => $o['bet_type'], 'bet_target' => $o['bet_target'],
|
||||
'odds' => floatval($o['odds']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
// 佣金记录
|
||||
public function commissions($agentId) {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$records = $this->db->select('agent_commissions', '*', [
|
||||
'agent_id' => (int)$agentId, 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 100
|
||||
]);
|
||||
$this->json(['status' => 'success', 'data' => $records]);
|
||||
}
|
||||
|
||||
private function json($data) { header('Content-Type: application/json'); echo json_encode($data); }
|
||||
}
|
||||
Executable
+179
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace App\Controllers\admin;
|
||||
use \Db\Database;
|
||||
class AuthController {
|
||||
public function loginPage($error = '') {
|
||||
include __DIR__ . '/../../Views/Admin/login.php';
|
||||
}
|
||||
|
||||
public function loginSubmit() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
// 检查 JSON 格式
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '提交的数据格式有误'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 提取输入参数
|
||||
$usernameOrEmail = trim($data['username_or_email'] ?? '');
|
||||
$password = $data['password'] ?? '';
|
||||
|
||||
if (empty($usernameOrEmail) || empty($password)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '请输入用户名/邮箱和密码'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 查询用户(匹配用户名或邮箱,忽略邮箱大小写)
|
||||
$user = $db->get('users', '*', [
|
||||
'OR' => [
|
||||
'username' => $usernameOrEmail,
|
||||
'email' => strtolower($usernameOrEmail)
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$user) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否禁用(假设 status=0 表示禁用)
|
||||
if (isset($user['status']) && $user['status'] == 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '该账户未启用,请联系管理员'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!password_verify($password, $user['password'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '密码错误'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置会话
|
||||
session_start();
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
$_SESSION['last_activity'] = time();
|
||||
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
|
||||
$_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '登录成功,正在跳转...',
|
||||
'redirect' => '/admin/dashboard'
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout() {
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: /admin/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
public function registerSubmit() {
|
||||
header('Content-Type: application/json');
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
$username = trim($data['username'] ?? '');
|
||||
$email = strtolower(trim($data['email'] ?? '')); // 统一转小写
|
||||
$password = $data['password'] ?? '';
|
||||
$confirm = $data['confirm_password'] ?? '';
|
||||
|
||||
if (!$username || !$email || !$password || !$confirm) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '请完整填写注册信息'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '邮箱格式不正确'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($password !== $confirm) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '两次密码输入不一致'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 检查用户名或邮箱是否已存在(邮箱忽略大小写)
|
||||
$existing = $db->get('users', '*', [
|
||||
'OR' => [
|
||||
'username' => $username,
|
||||
'email' => $email
|
||||
]
|
||||
]);
|
||||
|
||||
if ($existing) {
|
||||
if ($existing['username'] === $username) {
|
||||
$msg = '用户名已存在';
|
||||
} elseif (strtolower($existing['email']) === $email) {
|
||||
$msg = '邮箱已存在';
|
||||
} else {
|
||||
$msg = '用户名或邮箱已存在';
|
||||
}
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $msg
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$insertResult = $db->insert('users', [
|
||||
'username' => $username,
|
||||
'email' => $email, // 已转小写
|
||||
'password' => $hashedPassword,
|
||||
'role' => 'user', // 默认角色
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
if ($insertResult) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '注册成功,请登录',
|
||||
'redirect' => '/admin/login'
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '注册失败,请稍后重试'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
/**
|
||||
* 自动开期设置控制器
|
||||
*/
|
||||
class AutoPeriodController extends AdminBaseController {
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动开期设置页面
|
||||
*/
|
||||
public function index() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
|
||||
// 获取所有游戏
|
||||
$games = $this->db->select('games', '*', [
|
||||
'status' => 1,
|
||||
'ORDER' => ['id' => 'ASC']
|
||||
]);
|
||||
|
||||
// 获取最近的自动开期日志
|
||||
$logs = [];
|
||||
try {
|
||||
$logs = $this->db->select('auto_period_log', '*', [
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 50
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能不存在
|
||||
}
|
||||
|
||||
$this->render('Admin/auto_period.php', compact('games', 'logs'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游戏的自动开期设置
|
||||
*/
|
||||
public function update() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($data)) $data = $_POST;
|
||||
|
||||
$gameId = (int)($data['game_id'] ?? 0);
|
||||
if ($gameId <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => '无效的游戏ID']);
|
||||
return;
|
||||
}
|
||||
|
||||
$game = $this->db->get('games', '*', ['id' => $gameId]);
|
||||
if (!$game) {
|
||||
echo json_encode(['success' => false, 'message' => '游戏不存在']);
|
||||
return;
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
|
||||
// 自动开期开关
|
||||
if (isset($data['auto_period_enabled'])) {
|
||||
$updateData['auto_period_enabled'] = (int)$data['auto_period_enabled'] ? 1 : 0;
|
||||
}
|
||||
|
||||
// 每期时长(秒)
|
||||
if (isset($data['period_duration'])) {
|
||||
$duration = (int)$data['period_duration'];
|
||||
if ($duration < 60) $duration = 60; // 最少1分钟
|
||||
if ($duration > 3600) $duration = 3600; // 最多1小时
|
||||
$updateData['period_duration'] = $duration;
|
||||
}
|
||||
|
||||
// 封盘提前时间(秒)
|
||||
if (isset($data['lock_before_end'])) {
|
||||
$lockTime = (int)$data['lock_before_end'];
|
||||
if ($lockTime < 5) $lockTime = 5;
|
||||
if ($lockTime > 120) $lockTime = 120;
|
||||
$updateData['lock_before_end'] = $lockTime;
|
||||
}
|
||||
|
||||
if (empty($updateData)) {
|
||||
echo json_encode(['success' => false, 'message' => '无更新内容']);
|
||||
return;
|
||||
}
|
||||
|
||||
$updateData['updated_at'] = date('Y-m-d H:i:s');
|
||||
|
||||
try {
|
||||
$this->db->update('games', $updateData, ['id' => $gameId]);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '设置已保存',
|
||||
'data' => $updateData
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'message' => '保存失败: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用/禁用
|
||||
*/
|
||||
public function toggleAll() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$enabled = (int)($data['enabled'] ?? 0);
|
||||
|
||||
try {
|
||||
$this->db->update('games', [
|
||||
'auto_period_enabled' => $enabled,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], ['status' => 1]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $enabled ? '已全部启用自动开期' : '已全部关闭自动开期'
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'message' => '操作失败: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动开期运行状态
|
||||
*/
|
||||
public function status() {
|
||||
$this->checkLogin();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$games = $this->db->select('games', ['id', 'name', 'type', 'auto_period_enabled', 'period_duration', 'lock_before_end'], [
|
||||
'status' => 1,
|
||||
'ORDER' => ['id' => 'ASC']
|
||||
]);
|
||||
|
||||
// 每个游戏的当前期号
|
||||
foreach ($games as &$g) {
|
||||
$g['current_period'] = $this->db->get('periods', ['id', 'period_number', 'status', 'start_time', 'end_time'], [
|
||||
'game_id' => $g['id'],
|
||||
'status' => ['pending', 'locked', 'drawn'],
|
||||
'ORDER' => ['id' => 'DESC']
|
||||
]);
|
||||
}
|
||||
|
||||
// 最近日志
|
||||
$recentLogs = [];
|
||||
try {
|
||||
$recentLogs = $this->db->select('auto_period_log', '*', [
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 10
|
||||
]);
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'games' => $games,
|
||||
'logs' => $recentLogs,
|
||||
'server_time' => date('Y-m-d H:i:s'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class BetController extends AdminBaseController {
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 投注记录列表页面
|
||||
*/
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
|
||||
// 分页参数
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
$limit = 20;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// 筛选参数
|
||||
$where = [];
|
||||
if (isset($_GET['user_id']) && !empty($_GET['user_id'])) {
|
||||
$where['bets.user_id'] = $_GET['user_id'];
|
||||
}
|
||||
if (isset($_GET['period_number']) && !empty($_GET['period_number'])) {
|
||||
$where['bets.period_number'] = $_GET['period_number'];
|
||||
}
|
||||
if (isset($_GET['status']) && !empty($_GET['status'])) {
|
||||
$where['bets.status'] = $_GET['status'];
|
||||
}
|
||||
|
||||
// 排序
|
||||
$where['ORDER'] = ['bets.id' => 'DESC'];
|
||||
$where['LIMIT'] = [$offset, $limit];
|
||||
|
||||
try {
|
||||
// 获取投注记录,关联用户表和游戏表
|
||||
$bets = $db->select('bets', [
|
||||
'[>]users' => ['user_id' => 'id'],
|
||||
'[>]games' => ['game_id' => 'id']
|
||||
], [
|
||||
'bets.id',
|
||||
'bets.user_id',
|
||||
'users.username',
|
||||
'users.balance', // 当前余额
|
||||
'games.name(game_name)', // 游戏名称
|
||||
'bets.period_number',
|
||||
'bets.bet_type',
|
||||
'bets.bet_value',
|
||||
'bets.amount',
|
||||
'bets.odds',
|
||||
'bets.win_amount',
|
||||
'bets.status',
|
||||
'bets.created_at',
|
||||
'bets.settled_at'
|
||||
], $where);
|
||||
|
||||
// 获取总记录数用于分页(移除LIMIT)
|
||||
unset($where['LIMIT']);
|
||||
$total = $db->count('bets', [
|
||||
'[>]users' => ['user_id' => 'id'],
|
||||
'[>]games' => ['game_id' => 'id']
|
||||
], '*', $where);
|
||||
|
||||
$totalPages = ceil($total / $limit);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
// 记录错误以便调试
|
||||
error_log("BetController Error: " . $e->getMessage());
|
||||
$bets = [];
|
||||
$total = 0;
|
||||
$totalPages = 0;
|
||||
}
|
||||
|
||||
$this->render('Admin/bets.php', [
|
||||
'bets' => $bets,
|
||||
'currentPage' => $page,
|
||||
'totalPages' => $totalPages,
|
||||
'title' => '投注记录'
|
||||
]);
|
||||
}
|
||||
}
|
||||
+734
@@ -0,0 +1,734 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class DicePeriodController extends AdminBaseController {
|
||||
private $gameType = 'dice';
|
||||
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 骰子游戏期号列表页面
|
||||
*/
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
try {
|
||||
// 获取骰子游戏列表
|
||||
$gamesList = $db->select('games', ['id', 'name'], [
|
||||
'type' => $this->gameType,
|
||||
'status' => 1,
|
||||
'ORDER' => ['id' => 'ASC']
|
||||
]);
|
||||
|
||||
if (!is_array($gamesList)) {
|
||||
$gamesList = [];
|
||||
}
|
||||
|
||||
// 获取所有骰子游戏的game_id
|
||||
$gameIds = array_column($gamesList, 'id');
|
||||
|
||||
// 获取骰子游戏的期号列表
|
||||
$periods = [];
|
||||
if (!empty($gameIds)) {
|
||||
$periods = $db->select('periods', '*', [
|
||||
'game_id' => $gameIds,
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 100
|
||||
]);
|
||||
}
|
||||
|
||||
if (!is_array($periods)) {
|
||||
$periods = [];
|
||||
}
|
||||
|
||||
// 构建游戏名称映射
|
||||
$games = [];
|
||||
foreach ($gamesList as $game) {
|
||||
$games[$game['id']] = $game['name'];
|
||||
}
|
||||
|
||||
// 为每个骰子游戏获取当前期号
|
||||
$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/dice_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) {
|
||||
// 验证是否为骰子游戏期号
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if ($game && $game['type'] === $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $period
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于骰子游戏'
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不存在'
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $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;
|
||||
}
|
||||
|
||||
// 验证是否为骰子游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于骰子游戏'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查期号状态
|
||||
if ($period['status'] === 'settled') {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '该期号已结算,无法修改'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$auto = isset($data['auto']) ? (bool)$data['auto'] : false;
|
||||
|
||||
// 骰子游戏开奖逻辑
|
||||
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();
|
||||
|
||||
// 验证游戏类型
|
||||
$game = $db->get('games', ['type', 'stream_url'], ['id' => $gameId]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏不属于骰子游戏'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查该游戏是否有未结束的期号
|
||||
$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 = !empty($game['stream_url']) ? $game['stream_url'] : null;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
// 验证是否为骰子游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
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();
|
||||
|
||||
// 获取本期所有待结算注单
|
||||
$bets = $db->select('bets', '*', [
|
||||
'period_id' => $id,
|
||||
'status' => 'pending'
|
||||
]);
|
||||
|
||||
// 遍历注单进行结算
|
||||
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]);
|
||||
|
||||
// 提交事务
|
||||
$db->medoo->pdo->commit();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '结算成功,请点击"开始下注"启动下一期',
|
||||
'data' => [
|
||||
'current_period_id' => $id
|
||||
]
|
||||
]);
|
||||
} 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;
|
||||
}
|
||||
|
||||
// 验证是否为骰子游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成期号
|
||||
*/
|
||||
private function generatePeriodNumber($db, $gameId) {
|
||||
$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) {
|
||||
$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'];
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 获取赔率配置
|
||||
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;
|
||||
if (isset($oddsCache[$gameId][$key])) {
|
||||
return $oddsCache[$gameId][$key];
|
||||
}
|
||||
$allKey = $type . '_all';
|
||||
return $oddsCache[$gameId][$allKey] ?? 0;
|
||||
};
|
||||
|
||||
// 骰子游戏结算逻辑
|
||||
$dice1 = (int)$period['dice1'];
|
||||
$dice2 = (int)$period['dice2'];
|
||||
$dice3 = (int)$period['dice3'];
|
||||
$total = (int)$period['total'];
|
||||
|
||||
switch ($betType) {
|
||||
case 'xiu': // 小 (4-10)
|
||||
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)
|
||||
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;
|
||||
$baseOdds = $getOdds('dice', $betValue);
|
||||
$payoutMultiplier = $baseOdds * $count;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'combo': // 豹子
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算开奖结果
|
||||
*/
|
||||
private function calculateResult($dice1, $dice2, $dice3, $total) {
|
||||
// 检查是否为爆子
|
||||
if ($dice1 === $dice2 && $dice2 === $dice3) {
|
||||
if ($dice1 <= 3) {
|
||||
return 'Xỉu';
|
||||
} else {
|
||||
return 'Tài';
|
||||
}
|
||||
}
|
||||
|
||||
// 普通大小判断
|
||||
if ($total >= 4 && $total <= 10) {
|
||||
return 'Xỉu';
|
||||
} else {
|
||||
return 'Tài';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class EmployeeController extends AdminBaseController {
|
||||
private $db;
|
||||
public function __construct(Database $db) { $this->db = $db; }
|
||||
|
||||
public function index() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$employees = $this->db->select('employees', '*', ['ORDER' => ['id' => 'DESC']]);
|
||||
$this->render('Admin/employees.php', compact('employees'));
|
||||
}
|
||||
|
||||
public function update() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$fields = [
|
||||
'emp_code' => trim($data['emp_code'] ?? ''),
|
||||
'username' => trim($data['username'] ?? ''),
|
||||
'real_name' => trim($data['real_name'] ?? ''),
|
||||
'shift' => $data['shift'] ?? 'all',
|
||||
'permissions' => json_encode($data['permissions'] ?? ['deposit','withdraw']),
|
||||
'status' => (int)($data['status'] ?? 1),
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
if (!empty($data['password'])) $fields['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$this->db->update('employees', $fields, ['id' => $id]);
|
||||
} else {
|
||||
if (empty($data['password'])) { $this->json(['status'=>'error','message'=>'Password required']); return; }
|
||||
$fields['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$this->db->insert('employees', $fields);
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
public function delete($id) {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$this->db->delete('employees', ['id' => (int)$id]);
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
// 员工登录(独立入口)
|
||||
public function loginPage() {
|
||||
include ROOT_PATH . 'App/Views/Admin/employee_login.php';
|
||||
}
|
||||
|
||||
public function loginSubmit() {
|
||||
header('Content-Type: application/json');
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$db = new Database();
|
||||
$emp = $db->get('employees', '*', ['username' => trim($data['username'] ?? '')]);
|
||||
if (!$emp || !password_verify($data['password'] ?? '', $emp['password'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid credentials']); return;
|
||||
}
|
||||
if (!$emp['status']) {
|
||||
echo json_encode(['success' => false, 'message' => 'Account disabled']); return;
|
||||
}
|
||||
// 班次检查
|
||||
$hour = (int)date('H');
|
||||
if ($emp['shift'] === 'day' && ($hour < 8 || $hour >= 20)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Not your shift']); return;
|
||||
}
|
||||
if ($emp['shift'] === 'night' && ($hour >= 8 && $hour < 20)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Not your shift']); return;
|
||||
}
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
$_SESSION['emp_id'] = $emp['id'];
|
||||
$_SESSION['emp_code'] = $emp['emp_code'];
|
||||
$_SESSION['emp_permissions'] = json_decode($emp['permissions'], true) ?: [];
|
||||
$_SESSION['emp_last_activity'] = time();
|
||||
$db->update('employees', ['last_login' => date('Y-m-d H:i:s')], ['id' => $emp['id']]);
|
||||
echo json_encode(['success' => true, 'redirect' => '/employee/dashboard']);
|
||||
}
|
||||
|
||||
// 员工操作面板(仅上下分)
|
||||
public function dashboard() {
|
||||
$this->checkEmployee();
|
||||
$users = $this->db->select('users', ['id','username','balance','is_virtual'], [
|
||||
'is_virtual' => 0, 'ORDER' => ['username' => 'ASC']
|
||||
]);
|
||||
$logs = $this->db->select('employee_logs', '*', [
|
||||
'emp_id' => $_SESSION['emp_id'],
|
||||
'ORDER' => ['id' => 'DESC'], 'LIMIT' => 50
|
||||
]);
|
||||
include ROOT_PATH . 'App/Views/Admin/employee_dashboard.php';
|
||||
}
|
||||
|
||||
public function adjustBalance() {
|
||||
$this->checkEmployee();
|
||||
header('Content-Type: application/json');
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$userId = (int)($data['user_id'] ?? 0);
|
||||
$action = $data['action'] ?? ''; // deposit / withdraw
|
||||
$amount = abs(floatval($data['amount'] ?? 0));
|
||||
$remark = trim($data['remark'] ?? '');
|
||||
|
||||
$perms = $_SESSION['emp_permissions'] ?? [];
|
||||
if (!in_array($action, ['deposit','withdraw']) || !in_array($action, $perms)) {
|
||||
echo json_encode(['success' => false, 'message' => 'No permission']); return;
|
||||
}
|
||||
if ($amount <= 0 || $userId <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid input']); return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->db->medoo->pdo->beginTransaction();
|
||||
$stmt = $this->db->medoo->pdo->prepare("SELECT balance FROM users WHERE id = :id FOR UPDATE");
|
||||
$stmt->execute([':id' => $userId]);
|
||||
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
if (!$user) { $this->db->medoo->pdo->rollBack(); echo json_encode(['success'=>false,'message'=>'User not found']); return; }
|
||||
|
||||
$old = (float)$user['balance'];
|
||||
$new = $action === 'deposit' ? $old + $amount : $old - $amount;
|
||||
if ($new < 0) { $this->db->medoo->pdo->rollBack(); echo json_encode(['success'=>false,'message'=>'Insufficient balance']); return; }
|
||||
|
||||
$this->db->update('users', ['balance' => $new], ['id' => $userId]);
|
||||
$this->db->insert('transactions', [
|
||||
'user_id' => $userId, 'type' => 'manual_' . $action,
|
||||
'amount' => $action === 'deposit' ? $amount : -$amount,
|
||||
'balance_before' => $old, 'balance_after' => $new,
|
||||
'description' => "Employee {$_SESSION['emp_code']}: $remark",
|
||||
'operator_id' => $_SESSION['emp_id'], 'operator_type' => 'employee',
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$this->db->insert('employee_logs', [
|
||||
'emp_id' => $_SESSION['emp_id'], 'action' => $action,
|
||||
'target_user_id' => $userId, 'amount' => $amount,
|
||||
'remark' => $remark, 'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$this->db->medoo->pdo->commit();
|
||||
echo json_encode(['success' => true, 'new_balance' => $new]);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->db->medoo->pdo->inTransaction()) $this->db->medoo->pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => 'Error']);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkEmployee() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
if (empty($_SESSION['emp_id'])) { header('Location: /employee/login'); exit; }
|
||||
if (time() - ($_SESSION['emp_last_activity'] ?? 0) > 7200) {
|
||||
unset($_SESSION['emp_id']); header('Location: /employee/login'); exit;
|
||||
}
|
||||
$_SESSION['emp_last_activity'] = time();
|
||||
}
|
||||
|
||||
private function json($data) { header('Content-Type: application/json'); echo json_encode($data); }
|
||||
}
|
||||
Executable
+357
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class FinanceController extends AdminBaseController {
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 财务管理页面
|
||||
*/
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
try {
|
||||
// 获取资金流水列表
|
||||
$transactions = $db->select('transactions', [
|
||||
'[>]users' => ['user_id' => 'id']
|
||||
], [
|
||||
'transactions.id',
|
||||
'transactions.user_id',
|
||||
'users.username',
|
||||
'transactions.type',
|
||||
'transactions.amount',
|
||||
'transactions.balance_before',
|
||||
'transactions.balance_after',
|
||||
'transactions.related_id',
|
||||
'transactions.description',
|
||||
'transactions.created_at'
|
||||
], [
|
||||
'ORDER' => ['transactions.id' => 'DESC'],
|
||||
'LIMIT' => 100
|
||||
]);
|
||||
|
||||
if (!is_array($transactions)) {
|
||||
$transactions = [];
|
||||
}
|
||||
|
||||
// 计算统计信息
|
||||
$stats = $this->calculateStats($db);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$transactions = [];
|
||||
$stats = [
|
||||
'total_deposit' => 0,
|
||||
'total_withdraw' => 0,
|
||||
'total_bet' => 0,
|
||||
'total_win' => 0,
|
||||
'today_deposit' => 0,
|
||||
'today_withdraw' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('Admin/finance.php', [
|
||||
'transactions' => $transactions,
|
||||
'stats' => $stats,
|
||||
'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 {
|
||||
$transaction = $db->get('transactions', [
|
||||
'[>]users' => ['user_id' => 'id']
|
||||
], [
|
||||
'transactions.id',
|
||||
'transactions.user_id',
|
||||
'users.username',
|
||||
'transactions.type',
|
||||
'transactions.amount',
|
||||
'transactions.balance_before',
|
||||
'transactions.balance_after',
|
||||
'transactions.related_id',
|
||||
'transactions.description',
|
||||
'transactions.created_at'
|
||||
], [
|
||||
'transactions.id' => $id
|
||||
]);
|
||||
|
||||
if ($transaction) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $transaction
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '流水记录不存在'
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '获取数据失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理充值
|
||||
*/
|
||||
public function deposit() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '数据格式错误'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = isset($data['user_id']) ? (int)$data['user_id'] : 0;
|
||||
$amount = isset($data['amount']) ? floatval($data['amount']) : 0;
|
||||
$description = trim($data['description'] ?? '');
|
||||
|
||||
if ($userId <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的用户ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($amount <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '充值金额必须大于0'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
try {
|
||||
// 获取用户信息
|
||||
$user = $db->get('users', ['id', 'balance', 'username'], ['id' => $userId]);
|
||||
if (!$user) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$balanceBefore = floatval($user['balance']);
|
||||
$balanceAfter = $balanceBefore + $amount;
|
||||
|
||||
// 更新用户余额
|
||||
$db->update('users', [
|
||||
'balance' => $balanceAfter,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], ['id' => $userId]);
|
||||
|
||||
// 记录资金流水
|
||||
$transactionId = $db->insert('transactions', [
|
||||
'user_id' => $userId,
|
||||
'type' => 'deposit',
|
||||
'amount' => $amount,
|
||||
'balance_before' => $balanceBefore,
|
||||
'balance_after' => $balanceAfter,
|
||||
'description' => $description ?: '管理员充值',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '充值成功',
|
||||
'data' => [
|
||||
'transaction_id' => $transactionId,
|
||||
'balance_before' => $balanceBefore,
|
||||
'balance_after' => $balanceAfter
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '充值失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提现
|
||||
*/
|
||||
public function withdraw() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '数据格式错误'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = isset($data['user_id']) ? (int)$data['user_id'] : 0;
|
||||
$amount = isset($data['amount']) ? floatval($data['amount']) : 0;
|
||||
$description = trim($data['description'] ?? '');
|
||||
|
||||
if ($userId <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的用户ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($amount <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '提现金额必须大于0'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
try {
|
||||
// 获取用户信息
|
||||
$user = $db->get('users', ['id', 'balance', 'username'], ['id' => $userId]);
|
||||
if (!$user) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$balanceBefore = floatval($user['balance']);
|
||||
|
||||
// 检查余额是否充足
|
||||
if ($balanceBefore < $amount) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户余额不足,当前余额:' . number_format($balanceBefore, 2)
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$balanceAfter = $balanceBefore - $amount;
|
||||
|
||||
// 更新用户余额
|
||||
$db->update('users', [
|
||||
'balance' => $balanceAfter,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], ['id' => $userId]);
|
||||
|
||||
// 记录资金流水
|
||||
$transactionId = $db->insert('transactions', [
|
||||
'user_id' => $userId,
|
||||
'type' => 'withdraw',
|
||||
'amount' => -$amount, // 提现为负数
|
||||
'balance_before' => $balanceBefore,
|
||||
'balance_after' => $balanceAfter,
|
||||
'description' => $description ?: '管理员提现',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '提现成功',
|
||||
'data' => [
|
||||
'transaction_id' => $transactionId,
|
||||
'balance_before' => $balanceBefore,
|
||||
'balance_after' => $balanceAfter
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '提现失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算财务统计
|
||||
*/
|
||||
private function calculateStats($db) {
|
||||
try {
|
||||
// 总充值 (包含普通充值和人工加款)
|
||||
$totalDeposit = $db->sum('transactions', 'amount', [
|
||||
'type' => ['deposit', 'manual_deposit']
|
||||
]) ?: 0;
|
||||
|
||||
// 总提现(取绝对值,包含普通提现和人工扣款)
|
||||
$totalWithdraw = abs($db->sum('transactions', 'amount', [
|
||||
'type' => ['withdraw', 'manual_withdraw']
|
||||
]) ?: 0);
|
||||
|
||||
// 总投注(取绝对值)
|
||||
$totalBet = abs($db->sum('transactions', 'amount', [
|
||||
'type' => 'bet'
|
||||
]) ?: 0);
|
||||
|
||||
// 总中奖
|
||||
$totalWin = $db->sum('transactions', 'amount', [
|
||||
'type' => 'win'
|
||||
]) ?: 0;
|
||||
|
||||
// 今日充值 (包含普通充值和人工加款)
|
||||
$todayDeposit = $db->sum('transactions', 'amount', [
|
||||
'type' => ['deposit', 'manual_deposit'],
|
||||
'created_at[>=]' => date('Y-m-d 00:00:00')
|
||||
]) ?: 0;
|
||||
|
||||
// 今日提现(取绝对值,包含普通提现和人工扣款)
|
||||
$todayWithdraw = abs($db->sum('transactions', 'amount', [
|
||||
'type' => ['withdraw', 'manual_withdraw'],
|
||||
'created_at[>=]' => date('Y-m-d 00:00:00')
|
||||
]) ?: 0);
|
||||
|
||||
return [
|
||||
'total_deposit' => floatval($totalDeposit),
|
||||
'total_withdraw' => floatval($totalWithdraw),
|
||||
'total_bet' => floatval($totalBet),
|
||||
'total_win' => floatval($totalWin),
|
||||
'today_deposit' => floatval($todayDeposit),
|
||||
'today_withdraw' => floatval($todayWithdraw)
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'total_deposit' => 0,
|
||||
'total_withdraw' => 0,
|
||||
'total_bet' => 0,
|
||||
'total_win' => 0,
|
||||
'today_deposit' => 0,
|
||||
'today_withdraw' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+414
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class GameController extends AdminBaseController {
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
try {
|
||||
$games = $db->select('games', '*', [
|
||||
'ORDER' => ['id' => 'DESC']
|
||||
]);
|
||||
if (!is_array($games)) {
|
||||
$games = [];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$games = [];
|
||||
}
|
||||
|
||||
$this->render('Admin/games.php', [
|
||||
'games' => $games,
|
||||
'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 {
|
||||
$game = $db->get('games', '*', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
if ($game) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $game
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏不存在'
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '获取数据失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新游戏
|
||||
*/
|
||||
public function update() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = $_POST;
|
||||
if (empty($data)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '未接收到数据'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
$id = isset($data['id']) ? (int)$data['id'] : 0;
|
||||
$isEditMode = $id > 0;
|
||||
|
||||
$name = trim($data['name'] ?? '');
|
||||
$code = trim($data['code'] ?? '');
|
||||
$streamUrl = trim($data['stream_url'] ?? '');
|
||||
$type = trim($data['type'] ?? 'dice');
|
||||
$sortOrder = isset($data['sort_order']) ? (int)$data['sort_order'] : 0;
|
||||
$status = isset($data['status']) ? (int)$data['status'] : 1; // 默认启用
|
||||
$featured = isset($data['featured']) ? (int)$data['featured'] : 1; // 默认显示在游戏大厅
|
||||
$image = trim($data['image'] ?? '');
|
||||
|
||||
// 处理标签(复选框数组转逗号分隔字符串)
|
||||
$tags = '';
|
||||
if (isset($data['tags']) && is_array($data['tags'])) {
|
||||
$tags = implode(',', array_map('trim', $data['tags']));
|
||||
} elseif (isset($data['tags'])) {
|
||||
$tags = trim($data['tags']);
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏名称不能为空'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 自动生成游戏标识(如果为空)
|
||||
if (empty($code)) {
|
||||
// 将中文转换为拼音或使用拼音首字母,这里简化为使用时间戳+随机数
|
||||
// 实际可以使用拼音库,这里使用简化方式:去除特殊字符,转为小写,空格转下划线
|
||||
$code = $this->generateGameCode($name, $db, $id);
|
||||
}
|
||||
|
||||
// 检查code是否已存在(排除当前ID)
|
||||
try {
|
||||
$exists = $db->get('games', 'id', [
|
||||
'code' => $code,
|
||||
'id[!]' => $id
|
||||
]);
|
||||
|
||||
if ($exists) {
|
||||
// 如果已存在,添加随机后缀
|
||||
$code = $code . '_' . time();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 如果表不存在或其他错误,继续执行
|
||||
}
|
||||
|
||||
// 准备数据
|
||||
$gameData = [
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
'stream_url' => $streamUrl,
|
||||
'type' => $type,
|
||||
'tags' => $tags,
|
||||
'sort_order' => $sortOrder,
|
||||
'status' => $status,
|
||||
'featured' => $featured,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// 如果提供了图片路径,则添加
|
||||
if (!empty($image)) {
|
||||
$gameData['image'] = $image;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($isEditMode) {
|
||||
$db->update('games', $gameData, ['id' => $id]);
|
||||
$message = '游戏更新成功';
|
||||
} else {
|
||||
$gameData['created_at'] = date('Y-m-d H:i:s');
|
||||
$id = $db->insert('games', $gameData);
|
||||
$message = '游戏创建成功';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'data' => ['id' => $id]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '操作失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除游戏
|
||||
*/
|
||||
public function delete($id) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的游戏ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
try {
|
||||
$db->delete('games', ['id' => $id]);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '游戏已删除'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '删除失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换游戏启用状态
|
||||
*/
|
||||
public function toggleStatus($id) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的游戏ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
try {
|
||||
$game = $db->get('games', ['status'], ['id' => $id]);
|
||||
if (!$game) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$newStatus = $game['status'] ? 0 : 1;
|
||||
$db->update('games', [
|
||||
'status' => $newStatus,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], ['id' => $id]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $newStatus ? '游戏已启用' : '游戏已禁用',
|
||||
'data' => ['status' => $newStatus]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '操作失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取游戏赔率配置
|
||||
*/
|
||||
public function getOdds($gameId) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($gameId) || !is_numeric($gameId)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的游戏ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
try {
|
||||
// Check if game exists
|
||||
$game = $db->get('games', ['id', 'name'], ['id' => $gameId]);
|
||||
if (!$game) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get odds
|
||||
$odds = $db->select('game_odds', '*', [
|
||||
'game_id' => $gameId
|
||||
]);
|
||||
|
||||
// If no odds found, return defaults structure (or empty array)
|
||||
// The frontend should handle empty/missing odds
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'game' => $game,
|
||||
'odds' => $odds
|
||||
]
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '获取赔率失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游戏赔率
|
||||
*/
|
||||
public function updateOdds() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($data)) {
|
||||
$data = $_POST;
|
||||
}
|
||||
|
||||
$gameId = isset($data['game_id']) ? (int)$data['game_id'] : 0;
|
||||
$oddsData = isset($data['odds']) ? $data['odds'] : [];
|
||||
|
||||
if (empty($gameId) || empty($oddsData)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '参数不完整'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
try {
|
||||
// Begin transaction if supported (PDO usually does)
|
||||
// For simple database wrapper, we just loop update
|
||||
|
||||
foreach ($oddsData as $item) {
|
||||
$type = $item['type'];
|
||||
$target = $item['target'];
|
||||
$odds = floatval($item['odds']);
|
||||
|
||||
// Check if exists
|
||||
$exists = $db->has('game_odds', [
|
||||
'game_id' => $gameId,
|
||||
'type' => $type,
|
||||
'target' => $target
|
||||
]);
|
||||
|
||||
if ($exists) {
|
||||
$db->update('game_odds', [
|
||||
'odds' => $odds,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], [
|
||||
'game_id' => $gameId,
|
||||
'type' => $type,
|
||||
'target' => $target
|
||||
]);
|
||||
} else {
|
||||
$db->insert('game_odds', [
|
||||
'game_id' => $gameId,
|
||||
'type' => $type,
|
||||
'target' => $target,
|
||||
'odds' => $odds,
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '赔率配置已保存'
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '保存失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动生成游戏标识
|
||||
* @param string $name 游戏名称
|
||||
* @param Database $db 数据库实例
|
||||
* @param int $excludeId 排除的ID(编辑时使用)
|
||||
* @return string 生成的游戏标识
|
||||
*/
|
||||
private function generateGameCode($name, $db, $excludeId = 0) {
|
||||
// 去除特殊字符,转为小写,空格和中文标点转下划线
|
||||
$code = strtolower($name);
|
||||
// 将中文字符转为拼音(简化处理:直接使用拼音首字母或时间戳)
|
||||
// 这里使用简化的方式:去除所有非字母数字字符,替换为下划线
|
||||
$code = preg_replace('/[^a-z0-9_]+/', '_', $code);
|
||||
$code = preg_replace('/_+/', '_', $code); // 多个下划线合并为一个
|
||||
$code = trim($code, '_'); // 去除首尾下划线
|
||||
|
||||
// 如果转换后为空,使用时间戳
|
||||
if (empty($code)) {
|
||||
$code = 'game_' . time();
|
||||
}
|
||||
|
||||
// 限制长度
|
||||
if (strlen($code) > 50) {
|
||||
$code = substr($code, 0, 50);
|
||||
}
|
||||
|
||||
// 检查是否已存在,如果存在则添加随机数
|
||||
try {
|
||||
$exists = $db->get('games', 'id', [
|
||||
'code' => $code,
|
||||
'id[!]' => $excludeId
|
||||
]);
|
||||
|
||||
if ($exists) {
|
||||
$code = $code . '_' . substr(time(), -6); // 添加时间戳后缀
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
use App\Core\PluginManager;
|
||||
|
||||
class HomeController extends AdminBaseController {
|
||||
protected $pluginManager;
|
||||
protected $db;
|
||||
|
||||
public function __construct() {
|
||||
global $pluginManager;
|
||||
$this->pluginManager = $pluginManager;
|
||||
$this->checkLogin();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
$plugins = $this->pluginManager->getPluginStatusList();
|
||||
$stats = $this->calculateDashboardStats();
|
||||
|
||||
$this->render('Admin/dashboard.php', [
|
||||
'plugins' => $plugins,
|
||||
'stats' => $stats,
|
||||
'title' => '控制台'
|
||||
]);
|
||||
}
|
||||
|
||||
private function calculateDashboardStats() {
|
||||
$db = new Database();
|
||||
$todayStart = date('Y-m-d 00:00:00');
|
||||
|
||||
try {
|
||||
// 1. 今日投注总额
|
||||
$todayBetAmount = $db->sum('bets', 'amount', [
|
||||
'created_at[>=]' => $todayStart
|
||||
]) ?: 0;
|
||||
|
||||
// 2. 今日已派彩金额 (已结算且中奖的)
|
||||
$todayPayoutAmount = $db->sum('bets', 'win_amount', [
|
||||
'settled_at[>=]' => $todayStart,
|
||||
'status' => 'win'
|
||||
]) ?: 0;
|
||||
|
||||
// 3. 待处理开奖 (已封盘 waiting for draw, 或 已开奖 waiting for settle)
|
||||
// status: pending -> locked -> drawn -> settled
|
||||
$pendingDrawCount = $db->count('periods', [
|
||||
'status' => ['locked', 'drawn']
|
||||
]);
|
||||
|
||||
// 4. 待处理提现 (目前没有提现申请表,暂定为0)
|
||||
// 如果后续有 withdrawals 表或 transactions status 字段,需在此修改
|
||||
$pendingWithdrawCount = 0;
|
||||
|
||||
return [
|
||||
'today_bet_amount' => $todayBetAmount,
|
||||
'today_payout_amount' => $todayPayoutAmount,
|
||||
'pending_draw_count' => $pendingDrawCount,
|
||||
'pending_withdraw_count' => $pendingWithdrawCount
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'today_bet_amount' => 0,
|
||||
'today_payout_amount' => 0,
|
||||
'pending_draw_count' => 0,
|
||||
'pending_withdraw_count' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+376
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database; // Medoo数据库实例
|
||||
use \Exception;
|
||||
|
||||
class ImagesController extends AdminBaseController {
|
||||
protected $db;
|
||||
protected $uploadDir;
|
||||
protected array $uploadConfig;
|
||||
|
||||
// 允许的图片MIME类型
|
||||
const ALLOWED_MIME = [
|
||||
'image/jpeg', 'image/png', 'image/gif',
|
||||
'image/webp', 'image/svg+xml'
|
||||
];
|
||||
const MAX_UPLOAD_SIZE = 10485760; // 上传大小限制(10MB)
|
||||
const MAX_IMAGE_SIZE = 800; // 主图最大边长(等同缩略图大小)
|
||||
|
||||
public function __construct() {
|
||||
$this->checkLogin(); // 登录验证
|
||||
$this->db = new Database(); // 初始化数据库连接
|
||||
$domain = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http")
|
||||
. "://" . $_SERVER['HTTP_HOST'];
|
||||
|
||||
$this->uploadConfig = [
|
||||
'upload_dir' => $_SERVER['DOCUMENT_ROOT'] . '/Storage/images/',
|
||||
'url' => $domain . '/Storage/images/',
|
||||
'max_size' => 10 * 1024 * 1024, // 10MB
|
||||
'allowed_mimes' => [
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/icon'
|
||||
],
|
||||
'min_quality' => 60,
|
||||
'max_quality' => 100,
|
||||
'default_quality' => 80
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片管理首页(仅渲染前端页面,数据使用AJAX加载)
|
||||
*/
|
||||
public function index() {
|
||||
// 渲染页面模板
|
||||
$this->render('Admin/images.php', [
|
||||
'title' => '图片管理'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片列表查询
|
||||
*/
|
||||
public function list() {
|
||||
|
||||
|
||||
try {
|
||||
// 处理分页参数
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
$limit = isset($_GET['limit']) ?
|
||||
min(100, max(1, (int)$_GET['limit'])) : 20;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// 查询总数
|
||||
$total = $this->db->count('images');
|
||||
|
||||
// 查询当前页数据
|
||||
$data = $this->db->select('images', [
|
||||
'id', 'name', 'url',
|
||||
'size', 'width', 'height', 'upload_time'
|
||||
], [
|
||||
'LIMIT' => [$offset, $limit],
|
||||
'ORDER' => ['upload_time' => 'DESC']
|
||||
]);
|
||||
|
||||
// 返回结果
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'page' => $page,
|
||||
'total' => $total,
|
||||
'data' => $data ?: []
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->jsonError('获取数据失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片上传处理
|
||||
*/
|
||||
public function upload() {
|
||||
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
|
||||
$this->jsonError('文件上传失败: ' . $this->getUploadError($_FILES['image']['error'] ?? -1));
|
||||
}
|
||||
|
||||
$file = $_FILES['image'];
|
||||
|
||||
$params = [
|
||||
'quality' => isset($_POST['quality']) ?
|
||||
min($this->uploadConfig['max_quality'],
|
||||
max($this->uploadConfig['min_quality'], (int)$_POST['quality'])) :
|
||||
$this->uploadConfig['default_quality'],
|
||||
'width' => isset($_POST['width']) && $_POST['width'] !== '' ? max(1, (int)$_POST['width']) : 0,
|
||||
'height' => isset($_POST['height']) && $_POST['height'] !== '' ? max(1, (int)$_POST['height']) : 0
|
||||
];
|
||||
|
||||
if ($file['size'] > $this->uploadConfig['max_size']) {
|
||||
$this->jsonError("文件过大,最大支持" . $this->formatFileSize($this->uploadConfig['max_size']));
|
||||
}
|
||||
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
if (!in_array($mime, $this->uploadConfig['allowed_mimes'])) {
|
||||
$this->jsonError('不支持的文件类型,仅允许: ' . implode(', ', $this->uploadConfig['allowed_mimes']));
|
||||
}
|
||||
|
||||
try {
|
||||
$uploadDir = rtrim($this->uploadConfig['upload_dir'], '/') . '/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
$originalName = basename($file['name']);
|
||||
$originalExt = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
$filename = date('YmdHis') . '_' . uniqid() . '.' . $originalExt;
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
list($origWidth, $origHeight) = getimagesize($file['tmp_name']);
|
||||
list($newWidth, $newHeight, $srcX, $srcY, $cropW, $cropH) = $this->calculateDimensions(
|
||||
$origWidth, $origHeight,
|
||||
$params['width'], $params['height']
|
||||
);
|
||||
|
||||
// 判断是否需要裁剪
|
||||
if ($params['width'] > 0 && $params['height'] > 0) {
|
||||
$success = $this->processImageWithCrop(
|
||||
$file['tmp_name'], $mime, $targetPath,
|
||||
$newWidth, $newHeight,
|
||||
$srcX, $srcY, $cropW, $cropH,
|
||||
$params['quality']
|
||||
);
|
||||
} else {
|
||||
$success = $this->processImage(
|
||||
$file['tmp_name'], $mime, $targetPath,
|
||||
$newWidth, $newHeight, $params['quality']
|
||||
);
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new Exception('图片处理失败');
|
||||
}
|
||||
|
||||
$record = [
|
||||
'name' => $originalName,
|
||||
'newname' => $filename,
|
||||
'url' => rtrim($this->uploadConfig['url'], '/') . '/' . $filename,
|
||||
'size' => filesize($targetPath),
|
||||
'width' => $newWidth,
|
||||
'height' => $newHeight,
|
||||
'upload_time' => time()
|
||||
];
|
||||
|
||||
$imageId = $this->db->insert('images', $record);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => $record + ['id' => $imageId]
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($targetPath) && file_exists($targetPath)) {
|
||||
unlink($targetPath);
|
||||
}
|
||||
$this->jsonError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function calculateDimensions($origW, $origH, $targetW, $targetH) {
|
||||
if ($targetW <= 0 && $targetH <= 0) {
|
||||
return [$origW, $origH, 0, 0, $origW, $origH];
|
||||
}
|
||||
|
||||
// 只指定宽
|
||||
if ($targetW > 0 && $targetH <= 0) {
|
||||
$newW = $targetW;
|
||||
$newH = (int)($origH * ($targetW / $origW));
|
||||
return [$newW, $newH, 0, 0, $origW, $origH];
|
||||
}
|
||||
|
||||
// 只指定高
|
||||
if ($targetH > 0 && $targetW <= 0) {
|
||||
$newH = $targetH;
|
||||
$newW = (int)($origW * ($targetH / $origH));
|
||||
return [$newW, $newH, 0, 0, $origW, $origH];
|
||||
}
|
||||
|
||||
// 指定宽高 → 居中裁剪
|
||||
$srcRatio = $origW / $origH;
|
||||
$dstRatio = $targetW / $targetH;
|
||||
|
||||
if ($srcRatio > $dstRatio) {
|
||||
$cropH = $origH;
|
||||
$cropW = (int)($origH * $dstRatio);
|
||||
$srcX = (int)(($origW - $cropW) / 2);
|
||||
$srcY = 0;
|
||||
} else {
|
||||
$cropW = $origW;
|
||||
$cropH = (int)($origW / $dstRatio);
|
||||
$srcX = 0;
|
||||
$srcY = (int)(($origH - $cropH) / 2);
|
||||
}
|
||||
|
||||
return [$targetW, $targetH, $srcX, $srcY, $cropW, $cropH];
|
||||
}
|
||||
|
||||
|
||||
// 处理图片,带裁剪逻辑
|
||||
private function processImageWithCrop($srcPath, $mime, $destPath, $width, $height, $srcX, $srcY, $cropW, $cropH, $quality) {
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $src = imagecreatefromjpeg($srcPath); break;
|
||||
case 'image/png': $src = imagecreatefrompng($srcPath); imagesavealpha($src, true); break;
|
||||
case 'image/gif': $src = imagecreatefromgif($srcPath); break;
|
||||
case 'image/webp': $src = imagecreatefromwebp($srcPath); break;
|
||||
default: throw new Exception("不支持的图片格式: {$mime}");
|
||||
}
|
||||
|
||||
if (!$src) throw new Exception('无法解析图片内容');
|
||||
|
||||
$dest = imagecreatetruecolor($width, $height);
|
||||
|
||||
if ($mime === 'image/png' || $mime === 'image/gif') {
|
||||
imagealphablending($dest, false);
|
||||
imagesavealpha($dest, true);
|
||||
$transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
|
||||
imagefilledrectangle($dest, 0, 0, $width, $height, $transparent);
|
||||
}
|
||||
|
||||
imagecopyresampled(
|
||||
$dest, $src,
|
||||
0, 0,
|
||||
$srcX, $srcY,
|
||||
$width, $height,
|
||||
$cropW, $cropH
|
||||
);
|
||||
|
||||
$result = false;
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $result = imagejpeg($dest, $destPath, $quality); break;
|
||||
case 'image/png': $result = imagepng($dest, $destPath); break;
|
||||
case 'image/gif': $result = imagegif($dest, $destPath); break;
|
||||
case 'image/webp': $result = imagewebp($dest, $destPath, $quality); break;
|
||||
}
|
||||
|
||||
imagedestroy($src);
|
||||
imagedestroy($dest);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// 工具函数:图片处理(普通格式,不裁剪)
|
||||
private function processImage($srcPath, $mime, $destPath, $width, $height, $quality) {
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $src = imagecreatefromjpeg($srcPath); break;
|
||||
case 'image/png': $src = imagecreatefrompng($srcPath); imagesavealpha($src, true); break;
|
||||
case 'image/gif': $src = imagecreatefromgif($srcPath); break;
|
||||
case 'image/webp': $src = imagecreatefromwebp($srcPath); break;
|
||||
default: throw new Exception("不支持的图片格式: {$mime}");
|
||||
}
|
||||
|
||||
if (!$src) throw new Exception('无法解析图片内容');
|
||||
|
||||
$dest = imagecreatetruecolor($width, $height);
|
||||
|
||||
if ($mime === 'image/png' || $mime === 'image/gif') {
|
||||
imagealphablending($dest, false);
|
||||
imagesavealpha($dest, true);
|
||||
$transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
|
||||
imagefilledrectangle($dest, 0, 0, $width, $height, $transparent);
|
||||
}
|
||||
|
||||
imagecopyresampled($dest, $src, 0, 0, 0, 0, $width, $height, imagesx($src), imagesy($src));
|
||||
|
||||
$result = false;
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $result = imagejpeg($dest, $destPath, $quality); break;
|
||||
case 'image/png': $result = imagepng($dest, $destPath); break;
|
||||
case 'image/gif': $result = imagegif($dest, $destPath); break;
|
||||
case 'image/webp': $result = imagewebp($dest, $destPath, $quality); break;
|
||||
}
|
||||
|
||||
imagedestroy($src);
|
||||
imagedestroy($dest);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 工具函数:上传错误信息
|
||||
private function getUploadError($code) {
|
||||
$errors = [
|
||||
UPLOAD_ERR_INI_SIZE => '超过php.ini限制',
|
||||
UPLOAD_ERR_FORM_SIZE => '超过表单限制',
|
||||
UPLOAD_ERR_PARTIAL => '文件仅部分上传',
|
||||
UPLOAD_ERR_NO_FILE => '未上传文件',
|
||||
UPLOAD_ERR_NO_TMP_DIR => '缺少临时目录',
|
||||
UPLOAD_ERR_CANT_WRITE => '写入文件失败',
|
||||
UPLOAD_ERR_EXTENSION => '被扩展阻止'
|
||||
];
|
||||
return $errors[$code] ?? "未知错误(代码: {$code})";
|
||||
}
|
||||
|
||||
// 工具函数:格式化文件大小
|
||||
private function formatFileSize($bytes) {
|
||||
if ($bytes < 1024) return $bytes . 'B';
|
||||
if ($bytes < 1048576) return round($bytes / 1024, 1) . 'KB';
|
||||
return round($bytes / 1048576, 1) . 'MB';
|
||||
}
|
||||
|
||||
// 工具函数:JSON错误响应
|
||||
private function jsonError($message) {
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => $message
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
public function delete() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// 获取 JSON 输入
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!isset($input['ids']) || !is_array($input['ids'])) {
|
||||
echo json_encode(['status' => 'error', 'message' => '缺少参数']);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将字符串数组转换为整数数组
|
||||
$ids = array_map('intval', $input['ids']);
|
||||
$ids = array_filter($ids); // 去除无效值
|
||||
|
||||
if (empty($ids)) {
|
||||
echo json_encode(['status' => 'error', 'message' => '无效的 ID 列表']);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询图片路径(如果你想同时删除图片文件)
|
||||
$images = $this->db->select('images', ['id', 'newname'], [
|
||||
'id' => $ids
|
||||
]);
|
||||
|
||||
foreach ($images as $img) {
|
||||
$file = $this->uploadConfig['upload_dir'] . $img['newname'];
|
||||
if (file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
$this->db->delete('images', ['id' => $ids]);
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 'error', 'message' => '删除失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+776
@@ -0,0 +1,776 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
class InstallController
|
||||
{
|
||||
protected $step;
|
||||
protected $configFile = ROOT_PATH . "Db/config.php";
|
||||
protected $installedLockFile = ROOT_PATH . "Db/installed.lock";
|
||||
protected $db;
|
||||
protected $message = '';
|
||||
protected $messageType = ''; // success, error, warning
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// 关键修复:步骤4即使已安装也允许访问
|
||||
$currentStep = $_POST['step'] ?? $_GET['step'] ?? 1;
|
||||
|
||||
// 只有非步骤4且已安装时才显示已安装页面
|
||||
if ($currentStep != 4 && $this->isInstalled()) {
|
||||
$this->step = 5; // 已安装状态
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理消息提示
|
||||
if (isset($_SESSION['install_message'])) {
|
||||
$this->message = $_SESSION['install_message'];
|
||||
$this->messageType = $_SESSION['install_message_type'];
|
||||
unset($_SESSION['install_message'], $_SESSION['install_message_type']);
|
||||
}
|
||||
|
||||
// 设置当前步骤
|
||||
$this->step = $currentStep;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
// 已安装则显示提示(步骤4除外)
|
||||
if ($this->step == 5) {
|
||||
$this->renderHeader();
|
||||
$this->alreadyInstalled();
|
||||
$this->renderFooter();
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理表单提交(在输出任何内容之前)
|
||||
$this->handleSubmission();
|
||||
|
||||
// 输出页面
|
||||
$this->renderHeader();
|
||||
|
||||
switch ($this->step) {
|
||||
case 1:
|
||||
$this->checkEnvironment();
|
||||
break;
|
||||
case 2:
|
||||
$this->databaseForm();
|
||||
break;
|
||||
case 3:
|
||||
$this->adminForm();
|
||||
break;
|
||||
case 4:
|
||||
$this->finish();
|
||||
break;
|
||||
default:
|
||||
$this->setMessage('非法步骤', 'error');
|
||||
$this->showMessage();
|
||||
}
|
||||
|
||||
$this->renderFooter();
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
protected function handleSubmission()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentStep = $_POST['step'] ?? 1;
|
||||
|
||||
switch ($currentStep) {
|
||||
case 2:
|
||||
$this->processDatabaseForm();
|
||||
break;
|
||||
case 3:
|
||||
$this->processAdminForm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数据库表单提交
|
||||
protected function processDatabaseForm()
|
||||
{
|
||||
$host = $_POST['db_host'] ?? '';
|
||||
$name = $_POST['db_name'] ?? '';
|
||||
$user = $_POST['db_user'] ?? '';
|
||||
$pass = $_POST['db_pass'] ?? '';
|
||||
|
||||
try {
|
||||
// 连接数据库服务器
|
||||
$pdo = new \PDO("mysql:host=$host;charset=utf8mb4", $user, $pass);
|
||||
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// 检查数据库是否存在
|
||||
$stmt = $pdo->query("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
||||
$dbExists = $stmt->fetchColumn() !== false;
|
||||
|
||||
// 如果数据库存在且不为空,提示用户
|
||||
if ($dbExists) {
|
||||
$stmt = $pdo->query("USE $name");
|
||||
$stmt = $pdo->query("SHOW TABLES");
|
||||
if ($stmt->fetchColumn() !== false) {
|
||||
$this->setSessionMessage(
|
||||
"警告:数据库 '$name' 已存在且包含表。继续安装可能会覆盖现有数据。",
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 创建数据库
|
||||
$pdo->exec("CREATE DATABASE `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
}
|
||||
|
||||
// 连接到指定数据库
|
||||
$this->db = new \PDO("mysql:host=$host;dbname=$name;charset=utf8mb4", $user, $pass);
|
||||
$this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// 保存配置文件
|
||||
$config = "<?php\nreturn [\n";
|
||||
$config .= " 'db_host' => '" . addslashes($host) . "',\n";
|
||||
$config .= " 'db_name' => '" . addslashes($name) . "',\n";
|
||||
$config .= " 'db_user' => '" . addslashes($user) . "',\n";
|
||||
$config .= " 'db_pass' => '" . addslashes($pass) . "',\n];\n";
|
||||
|
||||
if (file_put_contents($this->configFile, $config) === false) {
|
||||
throw new \Exception("无法写入配置文件,请检查权限");
|
||||
}
|
||||
|
||||
$this->setSessionMessage('数据库配置成功!即将进入管理员设置', 'success');
|
||||
header("Location: ?step=3");
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
$this->setSessionMessage('数据库配置失败:' . $e->getMessage(), 'error');
|
||||
header("Location: ?step=2");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// 处理管理员表单提交
|
||||
protected function processAdminForm()
|
||||
{
|
||||
try {
|
||||
if (!file_exists($this->configFile)) {
|
||||
throw new \Exception("数据库配置文件不存在,请先完成数据库设置");
|
||||
}
|
||||
|
||||
$cfg = require $this->configFile;
|
||||
$this->db = new \PDO(
|
||||
"mysql:host={$cfg['db_host']};dbname={$cfg['db_name']};charset=utf8mb4",
|
||||
$cfg['db_user'],
|
||||
$cfg['db_pass']
|
||||
);
|
||||
$this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// 创建images表
|
||||
$this->db->exec("
|
||||
CREATE TABLE IF NOT EXISTS `images` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '文件原名',
|
||||
`newname` varchar(255) NOT NULL COMMENT '新文件名',
|
||||
`url` text NOT NULL COMMENT 'URL',
|
||||
`size` int(11) NOT NULL COMMENT '文件大小(字节)',
|
||||
`width` int(11) NOT NULL COMMENT '宽度(像素)',
|
||||
`height` int(11) NOT NULL COMMENT '高度(像素)',
|
||||
`upload_time` int(11) NOT NULL COMMENT '上传时间戳',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
");
|
||||
|
||||
// 创建plugins表
|
||||
$this->db->exec("
|
||||
CREATE TABLE IF NOT EXISTS `plugins` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) NOT NULL COMMENT '插件唯一标识,如 SamplePlugin',
|
||||
`title` varchar(255) NOT NULL COMMENT '插件显示名称',
|
||||
`description` text COMMENT '插件描述',
|
||||
`version` varchar(20) DEFAULT NULL COMMENT '插件版本号',
|
||||
`author` varchar(100) DEFAULT NULL COMMENT '作者',
|
||||
`url` varchar(128) NOT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '启用状态,1=启用,0=禁用',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件管理表';
|
||||
");
|
||||
|
||||
// 创建users表
|
||||
$this->db->exec("
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`email` varchar(100) NOT NULL,
|
||||
`avatar` VARCHAR(128) DEFAULT NULL,
|
||||
`balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '用户余额',
|
||||
`status` int(11) NOT NULL DEFAULT '0',
|
||||
`role` varchar(20) NOT NULL DEFAULT 'user',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
");
|
||||
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$email = $_POST['email'] ?? ''; // 新增邮箱字段
|
||||
|
||||
// 验证逻辑更新
|
||||
if (!$username) {
|
||||
throw new \Exception("用户名不能为空");
|
||||
}
|
||||
|
||||
if (!$password) {
|
||||
throw new \Exception("密码不能为空");
|
||||
}
|
||||
|
||||
if (strlen($password) < 8) {
|
||||
throw new \Exception("密码长度不能少于8位");
|
||||
}
|
||||
|
||||
if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \Exception("请输入有效的邮箱地址");
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
throw new \Exception("用户名 '$username' 已存在,请选择其他用户名");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
throw new \Exception("邮箱 '$email' 已被使用,请选择其他邮箱");
|
||||
}
|
||||
|
||||
$hash = password_hash($password, PASSWORD_BCRYPT);
|
||||
// 插入管理员数据(包含邮箱和管理员角色)
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT INTO users (username, password, email, role, status)
|
||||
VALUES (?, ?, ?, 'admin', 1)
|
||||
");
|
||||
$stmt->execute([$username, $hash, $email]);
|
||||
|
||||
// 创建临时标记文件
|
||||
file_put_contents(ROOT_PATH . "Db/install_completed.tmp", date('Y-m-d H:i:s') . "\n");
|
||||
|
||||
$this->setSessionMessage('管理员账号创建成功!安装已完成', 'success');
|
||||
header("Location: ?step=4");
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
$this->setSessionMessage('管理员账号创建失败:' . $e->getMessage(), 'error');
|
||||
header("Location: ?step=3");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 渲染页面头部和样式
|
||||
protected function renderHeader()
|
||||
{
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>系统安装向导</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#3b82f6',
|
||||
secondary: '#10b981',
|
||||
danger: '#ef4444',
|
||||
warning: '#f59e0b',
|
||||
neutral: '#f3f4f6',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.step-active { @apply bg-primary text-white border-primary; }
|
||||
.step-passed { @apply bg-secondary text-white border-secondary; }
|
||||
.step-pending { @apply bg-gray-100 text-gray-400 border-gray-200; }
|
||||
.card { @apply bg-white rounded-lg shadow-md overflow-hidden transition-all duration-300 hover:shadow-lg; }
|
||||
.btn { @apply px-4 py-2 rounded-md font-medium transition-all duration-200; }
|
||||
.btn-primary { @apply bg-primary text-white hover:bg-primary/90; }
|
||||
.btn-secondary { @apply bg-gray-600 text-white hover:bg-gray-700; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen font-sans">
|
||||
<div class="container mx-auto px-4 py-8 max-w-3xl">
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
|
||||
<i class="fa fa-cogs text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-gray-800">系统安装向导</h1>
|
||||
<p class="text-gray-500 mt-2">请按照步骤完成系统安装</p>
|
||||
</div>
|
||||
|
||||
<!-- 步骤指示器 -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 rounded-full border-2 flex items-center justify-center
|
||||
<?php echo $this->step == 1 ? 'step-active' : ($this->step > 1 ? 'step-passed' : 'step-pending'); ?>">
|
||||
<i class="fa fa-check"></i>
|
||||
</div>
|
||||
<span class="mt-2 text-sm font-medium text-gray-600">环境检测</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 h-1 mx-4 bg-gray-200 relative">
|
||||
<div class="absolute inset-0 bg-primary transition-all duration-500
|
||||
<?php echo $this->step > 1 ? 'w-full' : 'w-0'; ?>"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 rounded-full border-2 flex items-center justify-center
|
||||
<?php echo $this->step == 2 ? 'step-active' : ($this->step > 2 ? 'step-passed' : 'step-pending'); ?>">
|
||||
<i class="fa fa-database"></i>
|
||||
</div>
|
||||
<span class="mt-2 text-sm font-medium text-gray-600">数据库设置</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 h-1 mx-4 bg-gray-200 relative">
|
||||
<div class="absolute inset-0 bg-primary transition-all duration-500
|
||||
<?php echo $this->step > 2 ? 'w-full' : 'w-0'; ?>"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 rounded-full border-2 flex items-center justify-center
|
||||
<?php echo $this->step == 3 ? 'step-active' : ($this->step > 3 ? 'step-passed' : 'step-pending'); ?>">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<span class="mt-2 text-sm font-medium text-gray-600">管理员设置</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 h-1 mx-4 bg-gray-200 relative">
|
||||
<div class="absolute inset-0 bg-primary transition-all duration-500
|
||||
<?php echo $this->step > 3 ? 'w-full' : 'w-0'; ?>"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 rounded-full border-2 flex items-center justify-center
|
||||
<?php echo $this->step == 4 ? 'step-active' : ($this->step > 4 ? 'step-passed' : 'step-pending'); ?>">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
</div>
|
||||
<span class="mt-2 text-sm font-medium text-gray-600">完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<?php $this->showMessage(); ?>
|
||||
|
||||
<div class="card p-6">
|
||||
<?php
|
||||
}
|
||||
|
||||
// 渲染页面底部
|
||||
protected function renderFooter()
|
||||
{
|
||||
?>
|
||||
</div>
|
||||
<div class="mt-8 text-center text-gray-500 text-sm">
|
||||
<p>© 2025 系统安装向导</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
// 显示消息提示
|
||||
protected function showMessage()
|
||||
{
|
||||
if (empty($this->message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$icon = '';
|
||||
$class = '';
|
||||
|
||||
switch ($this->messageType) {
|
||||
case 'success':
|
||||
$icon = 'fa-check-circle';
|
||||
$class = 'bg-green-50 border-green-200 text-green-700';
|
||||
break;
|
||||
case 'error':
|
||||
$icon = 'fa-exclamation-circle';
|
||||
$class = 'bg-red-50 border-red-200 text-red-700';
|
||||
break;
|
||||
case 'warning':
|
||||
$icon = 'fa-exclamation-triangle';
|
||||
$class = 'bg-yellow-50 border-yellow-200 text-yellow-700';
|
||||
break;
|
||||
default:
|
||||
$icon = 'fa-info-circle';
|
||||
$class = 'bg-blue-50 border-blue-200 text-blue-700';
|
||||
}
|
||||
|
||||
echo "<div class='mb-6 p-4 border rounded-lg $class'>
|
||||
<i class='fa $icon mr-2'></i>$this->message
|
||||
</div>";
|
||||
}
|
||||
|
||||
// 设置消息
|
||||
protected function setMessage($message, $type = 'info')
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->messageType = $type;
|
||||
}
|
||||
|
||||
// 设置会话消息(用于跳转后显示)
|
||||
protected function setSessionMessage($message, $type = 'info')
|
||||
{
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$_SESSION['install_message'] = $message;
|
||||
$_SESSION['install_message_type'] = $type;
|
||||
}
|
||||
|
||||
// 检查是否已安装
|
||||
protected function isInstalled()
|
||||
{
|
||||
return file_exists($this->installedLockFile);
|
||||
}
|
||||
|
||||
// 已安装提示
|
||||
protected function alreadyInstalled()
|
||||
{
|
||||
echo "<div class='text-center py-8'>
|
||||
<div class='inline-flex items-center justify-center w-20 h-20 rounded-full bg-warning/10 text-warning mb-6'>
|
||||
<i class='fa fa-exclamation-triangle text-3xl'></i>
|
||||
</div>
|
||||
<h2 class='text-2xl font-bold text-gray-800 mb-3'>系统已安装</h2>
|
||||
<p class='text-gray-600 mb-8 max-w-md mx-auto'>
|
||||
检测到系统已经安装,如需重新安装,请先删除安装锁定文件:<br>
|
||||
<code class='bg-gray-100 px-2 py-1 rounded text-sm'>{$this->installedLockFile}</code>
|
||||
</p>
|
||||
|
||||
<div class='flex justify-center'>
|
||||
<a href='/admin/login' class='btn btn-primary inline-flex items-center justify-center'>
|
||||
<i class='fa fa-sign-in mr-2'></i> 登录后台
|
||||
</a>
|
||||
</div>
|
||||
</div>";
|
||||
}
|
||||
|
||||
// 第一步:检测环境
|
||||
protected function checkEnvironment()
|
||||
{
|
||||
$phpVersion = PHP_VERSION;
|
||||
$extensions = [
|
||||
'pdo_mysql' => 'MySQL PDO 驱动(必须)',
|
||||
'mbstring' => '多字节字符串支持(必须)',
|
||||
'json' => 'JSON 解析支持(必须)',
|
||||
'openssl' => 'OpenSSL 加密扩展(必须)',
|
||||
'fileinfo' => '文件信息扩展(必须)',
|
||||
'pdo' => 'PDO 基础扩展(可选,一般随 pdo_mysql 自动启用)'
|
||||
];
|
||||
$writableDirs = [
|
||||
ROOT_PATH . "Db" => "数据库配置目录",
|
||||
ROOT_PATH . "Storage" => "文件存储目录"
|
||||
];
|
||||
|
||||
// 检查PHP版本
|
||||
$phpVersionOk = version_compare($phpVersion, '8.0.0', '>=');
|
||||
|
||||
// 检查扩展
|
||||
$allExtensionsOk = true;
|
||||
$extensionResults = [];
|
||||
foreach ($extensions as $ext => $desc) {
|
||||
$installed = extension_loaded($ext);
|
||||
if (!$installed && $ext !== 'pdo') { // pdo 可选
|
||||
$allExtensionsOk = false;
|
||||
}
|
||||
$extensionResults[] = [
|
||||
'name' => $ext,
|
||||
'desc' => $desc,
|
||||
'installed' => $installed
|
||||
];
|
||||
}
|
||||
|
||||
// 检查目录写权限(真实写入测试)
|
||||
$allDirsWritable = true;
|
||||
$dirResults = [];
|
||||
foreach ($writableDirs as $dir => $desc) {
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$testFile = $dir . '/.test';
|
||||
$canWrite = @file_put_contents($testFile, 'test') !== false;
|
||||
if ($canWrite) {
|
||||
unlink($testFile);
|
||||
} else {
|
||||
$allDirsWritable = false;
|
||||
}
|
||||
$dirResults[] = [
|
||||
'path' => $dir,
|
||||
'desc' => $desc,
|
||||
'writable' => $canWrite
|
||||
];
|
||||
}
|
||||
|
||||
// 检查 PHP 配置
|
||||
$fileUploadsOk = ini_get('file_uploads');
|
||||
|
||||
echo "<h2 class='text-2xl font-bold text-gray-800 mb-6 flex items-center'>
|
||||
<i class='fa fa-server text-primary mr-3'></i>环境检测
|
||||
</h2>";
|
||||
|
||||
// PHP版本检查
|
||||
echo "<div class='mb-4 p-4 border rounded-lg " . ($phpVersionOk ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50') . "'>
|
||||
<div class='flex justify-between items-center'>
|
||||
<div>
|
||||
<h3 class='font-medium text-gray-800'>PHP版本</h3>
|
||||
<p class='text-sm text-gray-600 mt-1'>需要PHP 8.0.0或更高版本</p>
|
||||
</div>
|
||||
<span class='text-lg " . ($phpVersionOk ? 'text-secondary' : 'text-danger') . "'>
|
||||
" . ($phpVersionOk ? '✅' : '❌') . "
|
||||
</span>
|
||||
</div>
|
||||
<p class='mt-2 text-sm text-gray-700'>当前版本: $phpVersion</p>
|
||||
</div>";
|
||||
|
||||
// 扩展检查
|
||||
echo "<div class='mb-4'>
|
||||
<h3 class='font-medium text-gray-800 mb-3'>必要扩展</h3>
|
||||
<div class='grid grid-cols-1 md:grid-cols-2 gap-3'>";
|
||||
foreach ($extensionResults as $ext) {
|
||||
echo "<div class='p-3 border rounded-lg " . ($ext['installed'] ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50') . "'>
|
||||
<div class='flex justify-between items-center'>
|
||||
<span>{$ext['desc']} ({$ext['name']})</span>
|
||||
<span class='" . ($ext['installed'] ? 'text-secondary' : 'text-danger') . "'>
|
||||
" . ($ext['installed'] ? '✅' : '❌') . "
|
||||
</span>
|
||||
</div>";
|
||||
if (!$ext['installed']) {
|
||||
echo "<p class='text-xs text-gray-500 mt-1'>Linux 安装命令示例: <code>apt install php-{$ext['name']}</code></p>";
|
||||
}
|
||||
echo "</div>";
|
||||
}
|
||||
echo "</div></div>";
|
||||
|
||||
// 文件权限检查
|
||||
echo "<div class='mb-6'>
|
||||
<h3 class='font-medium text-gray-800 mb-3'>文件权限</h3>
|
||||
<div class='space-y-3'>";
|
||||
foreach ($dirResults as $dir) {
|
||||
echo "<div class='p-3 border rounded-lg " . ($dir['writable'] ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50') . "'>
|
||||
<div class='flex justify-between items-center'>
|
||||
<span class='text-sm truncate max-w-[70%]'>{$dir['desc']} ({$dir['path']})</span>
|
||||
<span class='" . ($dir['writable'] ? 'text-secondary' : 'text-danger') . "'>
|
||||
" . ($dir['writable'] ? '✅' : '❌') . "
|
||||
</span>
|
||||
</div>
|
||||
</div>";
|
||||
}
|
||||
echo "</div></div>";
|
||||
|
||||
// PHP 配置检查
|
||||
echo "<div class='mb-6 p-4 border rounded-lg " . ($fileUploadsOk ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50') . "'>
|
||||
<div class='flex justify-between items-center'>
|
||||
<span>文件上传 (file_uploads)</span>
|
||||
<span class='" . ($fileUploadsOk ? 'text-secondary' : 'text-danger') . "'>
|
||||
" . ($fileUploadsOk ? '✅' : '❌') . "
|
||||
</span>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
// 下一步按钮
|
||||
$allChecksPassed = $phpVersionOk && $allExtensionsOk && $allDirsWritable && $fileUploadsOk;
|
||||
echo "<div class='flex justify-end mt-8'>";
|
||||
if ($allChecksPassed) {
|
||||
echo "<a href='?step=2' class='btn btn-primary flex items-center'>
|
||||
<span>下一步:数据库设置</span>
|
||||
<i class='fa fa-arrow-right ml-2'></i>
|
||||
</a>";
|
||||
} else {
|
||||
echo "<button class='btn bg-gray-300 text-gray-500 cursor-not-allowed' disabled>
|
||||
<span>请先解决所有问题</span>
|
||||
<i class='fa fa-arrow-right ml-2'></i>
|
||||
</button>";
|
||||
}
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
|
||||
// 第二步:数据库表单
|
||||
protected function databaseForm()
|
||||
{
|
||||
echo "<h2 class='text-2xl font-bold text-gray-800 mb-6 flex items-center'>
|
||||
<i class='fa fa-database text-primary mr-3'></i>数据库设置
|
||||
</h2>";
|
||||
|
||||
echo '<form method="post" class="space-y-4">
|
||||
<input type="hidden" name="step" value="2">
|
||||
|
||||
<div>
|
||||
<label for="db_host" class="block text-sm font-medium text-gray-700 mb-1">数据库主机</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-server -mt-5"></i>
|
||||
</div>
|
||||
<input type="text" id="db_host" name="db_host" value="localhost" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
<p class="mt-1 text-xs text-gray-500">通常为 localhost 或 127.0.0.1</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="db_name" class="block text-sm font-medium text-gray-700 mb-1">数据库名称</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-database"></i>
|
||||
</div>
|
||||
<input type="text" id="db_name" name="db_name" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="db_user" class="block text-sm font-medium text-gray-700 mb-1">数据库用户名</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<input type="text" id="db_user" name="db_user" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="db_pass" class="block text-sm font-medium text-gray-700 mb-1">数据库密码</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-lock -mt-5"></i>
|
||||
</div>
|
||||
<input type="password" id="db_pass" name="db_pass"
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
<p class="mt-1 text-xs text-gray-500">如果数据库无密码,请留空</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between mt-8">
|
||||
<a href="?step=1" class="btn btn-secondary flex items-center">
|
||||
<i class="fa fa-arrow-left mr-2"></i>
|
||||
<span>上一步</span>
|
||||
</a>
|
||||
|
||||
<button type="submit" class="btn btn-primary flex items-center">
|
||||
<span>保存并测试</span>
|
||||
<i class="fa fa-arrow-right ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>';
|
||||
}
|
||||
|
||||
// 第三步:创建管理员
|
||||
protected function adminForm()
|
||||
{
|
||||
// 检查配置文件是否存在
|
||||
if (!file_exists($this->configFile)) {
|
||||
$this->setMessage('数据库配置文件不存在,请先完成数据库设置', 'error');
|
||||
$this->showMessage();
|
||||
echo "<div class='flex justify-start mt-8'>
|
||||
<a href='?step=2' class='btn btn-secondary flex items-center'>
|
||||
<i class='fa fa-arrow-left mr-2'></i>
|
||||
<span>返回数据库设置</span>
|
||||
</a>
|
||||
</div>";
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
||||
<i class="fa fa-user text-primary mr-3"></i>创建管理员账号
|
||||
</h2>';
|
||||
|
||||
echo '<form method="post" class="space-y-4">
|
||||
<input type="hidden" name="step" value="3">
|
||||
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">管理员用户名</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-user-circle"></i>
|
||||
</div>
|
||||
<input type="text" id="username" name="username" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-user-circle"></i>
|
||||
</div>
|
||||
<input type="text" id="email" name="email" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
</div>
|
||||
</div> <div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">管理员密码</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
||||
<i class="fa fa-shield"></i>
|
||||
</div>
|
||||
<input type="password" id="password" name="password" required
|
||||
class="w-full pl-10 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
|
||||
<p class="mt-1 text-xs text-gray-500">请设置强密码(至少8位,包含字母和数字)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between mt-8">
|
||||
<a href="?step=2" class="btn btn-secondary flex items-center">
|
||||
<i class="fa fa-arrow-left mr-2"></i>
|
||||
<span>上一步</span>
|
||||
</a>
|
||||
|
||||
<button type="submit" class="btn btn-primary flex items-center">
|
||||
<span>创建管理员</span>
|
||||
<i class="fa fa-check ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>';
|
||||
}
|
||||
|
||||
// 第四步:完成安装
|
||||
protected function finish()
|
||||
{
|
||||
// 关键修复:在完成页面创建最终的安装锁定文件
|
||||
if (file_exists(ROOT_PATH . "Db/install_completed.tmp")) {
|
||||
rename(ROOT_PATH . "Db/install_completed.tmp", $this->installedLockFile);
|
||||
}
|
||||
|
||||
echo "<div class='text-center py-8'>
|
||||
<div class='inline-flex items-center justify-center w-20 h-20 rounded-full bg-secondary/10 text-secondary mb-6'>
|
||||
<i class='fa fa-check-circle text-3xl'></i>
|
||||
</div>
|
||||
<h2 class='text-2xl font-bold text-gray-800 mb-3'>安装完成!</h2>
|
||||
<p class='text-gray-600 mb-8 max-w-md mx-auto'>管理员账号已创建,系统安装成功,您可以登录后台开始使用了。</p>
|
||||
|
||||
<div class='flex flex-col sm:flex-row justify-center gap-4'>
|
||||
<a href='/admin/login' class='btn btn-primary inline-flex items-center justify-center'>
|
||||
<i class='fa fa-sign-in mr-2'></i> 登录后台
|
||||
</a>
|
||||
<a href='/' class='btn btn-secondary inline-flex items-center justify-center'>
|
||||
<i class='fa fa-home mr-2'></i> 访问首页
|
||||
</a>
|
||||
</div>
|
||||
</div>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use App\Core\PK10Algorithm;
|
||||
use Db\Database;
|
||||
|
||||
class PK10PeriodController extends AdminBaseController {
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $db) {
|
||||
$this->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);
|
||||
$this->db->insert('periods', [
|
||||
'game_id' => $gameId,
|
||||
'period_number' => $periodNumber,
|
||||
'status' => 'pending',
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$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->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)) {
|
||||
$result = PK10Algorithm::generateControlledResult($bets, $waterConfig);
|
||||
} 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->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->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']]);
|
||||
|
||||
// 非虚拟账户才更新余额
|
||||
if (!(int)$bet['is_virtual']) {
|
||||
$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->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 json(array $data) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
}
|
||||
}
|
||||
Executable
+957
@@ -0,0 +1,957 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+217
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
use App\Core\PluginManager;
|
||||
|
||||
class PluginController extends AdminBaseController {
|
||||
protected $pluginManager;
|
||||
protected $db;
|
||||
|
||||
public function __construct() {
|
||||
global $pluginManager;
|
||||
$this->pluginManager = $pluginManager;
|
||||
$this->db = $this->pluginManager->getDB();
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
|
||||
}
|
||||
|
||||
public function manage() {
|
||||
$plugins = $this->pluginManager->getPluginStatusList();
|
||||
$icons = $this->pluginManager->getAllPluginIcons();
|
||||
|
||||
foreach ($plugins as &$plugin) {
|
||||
$pluginName = $plugin['name']; // 获取插件名
|
||||
$plugin['icon'] = $icons[$pluginName] ?? 'fa fa-plug';
|
||||
}
|
||||
unset($plugin);
|
||||
|
||||
$this->render('Admin/plugins.php', [
|
||||
'plugins' => $plugins,
|
||||
'title' => '插件管理'
|
||||
]);
|
||||
}
|
||||
|
||||
public function upload() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_FILES['plugin_zip']) || $_FILES['plugin_zip']['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => '插件上传失败']);
|
||||
return;
|
||||
}
|
||||
|
||||
$zipPath = $_FILES['plugin_zip']['tmp_name'];
|
||||
$fileName = basename($_FILES['plugin_zip']['name']);
|
||||
$pluginName = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
|
||||
$pluginDir = __DIR__ . '/../../Plugins/' . $pluginName;
|
||||
if (is_dir($pluginDir)) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'message' => '插件已存在,请先删除或改名']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mkdir($pluginDir, 0777, true) && !is_dir($pluginDir)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => '无法创建插件目录']);
|
||||
return;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
if ($zip->open($zipPath) === TRUE) {
|
||||
$zip->extractTo($pluginDir);
|
||||
$zip->close();
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => '解压失败']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists($pluginDir . '/mian.php')) {
|
||||
$this->deleteFolder($pluginDir);
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => '无效插件包,缺少 mian.php']);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pluginManager->clearCache();
|
||||
$plugins = $this->pluginManager->scanPlugins();
|
||||
if (!isset($plugins[$pluginName])) {
|
||||
$this->deleteFolder($pluginDir);
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' =>$this->pluginManager->getLastError() ?: "插件缺少必要信息,请检查插件是否规范!"
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => '插件上传成功']);
|
||||
}
|
||||
|
||||
|
||||
public function toggleStatus($name) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$installedPlugins = $this->pluginManager->getInstalledPlugins();
|
||||
if (!isset($installedPlugins[$name])) {
|
||||
echo json_encode(['success' => false, 'message' => "插件 {$name} 不存在"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$currentStatus = $installedPlugins[$name]['status'];
|
||||
$newStatus = $currentStatus ? 0 : 1;
|
||||
|
||||
$this->db->update('plugins', ['status' => $newStatus], ['name' => $name]);
|
||||
|
||||
$msg = $newStatus ? "插件 {$name} 已启用" : "插件 {$name} 已禁用";
|
||||
echo json_encode(['success' => true, 'message' => $msg]);
|
||||
}
|
||||
|
||||
public function install($name) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$allPlugins = $this->pluginManager->getAllPlugins();
|
||||
if (!isset($allPlugins[$name])) {
|
||||
echo json_encode(['success' => false, 'message' => "插件 {$name} 不存在"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->pluginManager->installPlugin($name);
|
||||
if ($result !== true) {
|
||||
echo json_encode(['success' => false, 'message' => $result]);
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginFile = $allPlugins[$name]['path'] . '/mian.php';
|
||||
if (file_exists($pluginFile)) {
|
||||
$pluginInfo = require $pluginFile;
|
||||
if (isset($pluginInfo['activate']) && is_callable($pluginInfo['activate'])) {
|
||||
call_user_func($pluginInfo['activate'], $this->db);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "插件 {$name} 安装成功"]);
|
||||
}
|
||||
|
||||
public function uninstall($name) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$allPlugins = $this->pluginManager->getAllPlugins();
|
||||
if (!isset($allPlugins[$name])) {
|
||||
echo json_encode(['success' => false, 'message' => "插件 {$name} 不存在"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginFile = $allPlugins[$name]['path'] . '/mian.php';
|
||||
if (file_exists($pluginFile)) {
|
||||
$pluginInfo = require $pluginFile;
|
||||
if (isset($pluginInfo['deactivate']) && is_callable($pluginInfo['deactivate'])) {
|
||||
call_user_func($pluginInfo['deactivate'], $this->db);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pluginManager->uninstallPlugin($name);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "插件 {$name} 已卸载。"]);
|
||||
}
|
||||
|
||||
|
||||
public function delete($name) {
|
||||
if (!$name) {
|
||||
return json_encode(['success' => false, 'message' => '插件名称不能为空']);
|
||||
}
|
||||
|
||||
$pluginDir = PLUGIN_PATH . $name;
|
||||
|
||||
// 判断插件目录是否存在
|
||||
if (!is_dir($pluginDir)) {
|
||||
return json_encode(['success' => false, 'message' => '插件目录不存在']);
|
||||
}
|
||||
|
||||
// 尝试删除目录
|
||||
if ($this->deleteFolder($pluginDir)) {
|
||||
echo json_encode(['success' => true, 'message' => "插件 {$name} 已卸载并删除"]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => "插件 {$name} 删除失败,请检查权限或是否被占用"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function deleteFolder($dir) {
|
||||
if (!file_exists($dir)) {
|
||||
return true; // 不存在当作删除成功
|
||||
}
|
||||
|
||||
// 如果是文件或符号链接,直接删除
|
||||
if (is_file($dir) || is_link($dir)) {
|
||||
return @unlink($dir);
|
||||
}
|
||||
|
||||
// 扫描目录内容
|
||||
$items = array_diff(scandir($dir), ['.', '..']);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$path = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
|
||||
if (is_dir($path)) {
|
||||
if (!$this->deleteFolder($path)) {
|
||||
return false; // 子目录删除失败
|
||||
}
|
||||
} else {
|
||||
if (!@unlink($path)) {
|
||||
return false; // 删除文件失败
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除目录本身
|
||||
return @rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class ReportController extends AdminBaseController {
|
||||
private $db;
|
||||
public function __construct(Database $db) { $this->db = $db; }
|
||||
|
||||
public function index() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$dateFrom = $_GET['from'] ?? date('Y-m-d', strtotime('-7 days'));
|
||||
$dateTo = $_GET['to'] ?? date('Y-m-d');
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
|
||||
// 总投注(排除虚拟)
|
||||
$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;
|
||||
|
||||
// 总充值
|
||||
$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
|
||||
]);
|
||||
|
||||
// 每日明细
|
||||
$dailyStats = [];
|
||||
$start = new \DateTime($dateFrom);
|
||||
$end = new \DateTime($dateTo);
|
||||
$end->modify('+1 day');
|
||||
$interval = new \DateInterval('P1D');
|
||||
$period = new \DatePeriod($start, $interval, $end);
|
||||
foreach ($period as $day) {
|
||||
$d = $day->format('Y-m-d');
|
||||
$dr = [$d . ' 00:00:00', $d . ' 23:59:59'];
|
||||
$dailyStats[] = [
|
||||
'date' => $d,
|
||||
'bets' => $this->db->sum('bets', 'amount', ['is_virtual'=>0,'created_at[<>]'=>$dr]) ?: 0,
|
||||
'wins' => $this->db->sum('bets', 'win_amount', ['is_virtual'=>0,'status'=>'win','created_at[<>]'=>$dr]) ?: 0,
|
||||
'deposits' => $this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%deposit%','amount[>]'=>0,'created_at[<>]'=>$dr]) ?: 0,
|
||||
'withdraws' => abs($this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%withdraw%','amount[<]'=>0,'created_at[<>]'=>$dr]) ?: 0),
|
||||
];
|
||||
}
|
||||
|
||||
// 代理报表
|
||||
$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,
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('Admin/reports.php', compact(
|
||||
'dateFrom','dateTo','totalBet','totalWin','profit',
|
||||
'totalDeposit','totalWithdraw','totalCommission','totalRebate',
|
||||
'totalUsers','newUsers','dailyStats','agentStats'
|
||||
));
|
||||
}
|
||||
|
||||
private function json($data) { header('Content-Type: application/json'); echo json_encode($data); }
|
||||
}
|
||||
Executable
+273
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
use \Exception;
|
||||
|
||||
class SettingsController extends AdminBaseController {
|
||||
protected $db;
|
||||
protected $uploadConfig;
|
||||
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->db = new Database();
|
||||
$this->initSettingsTable();
|
||||
|
||||
$this->uploadConfig = [
|
||||
'upload_dir' => dirname(dirname(dirname(__DIR__))) . '/Static/img/',
|
||||
'max_size' => 5 * 1024 * 1024, // 5MB
|
||||
'allowed_mimes' => [
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/x-icon', 'image/vnd.microsoft.icon'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化系统设置表
|
||||
*/
|
||||
private function initSettingsTable() {
|
||||
try {
|
||||
$this->db->query("
|
||||
CREATE TABLE IF NOT EXISTS `system_settings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`setting_key` varchar(100) NOT NULL,
|
||||
`setting_value` text,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_setting_key` (`setting_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
");
|
||||
} catch (Exception $e) {
|
||||
// 表已存在或其他错误,忽略
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统设置页面
|
||||
*/
|
||||
public function index() {
|
||||
$settings = $this->getAllSettings();
|
||||
|
||||
$this->render('Admin/settings.php', [
|
||||
'title' => '系统设置',
|
||||
'settings' => $settings
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设置
|
||||
*/
|
||||
public function get() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$settings = $this->getAllSettings();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => $settings
|
||||
]);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
$this->jsonError('获取设置失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存系统设置
|
||||
*/
|
||||
public function save() {
|
||||
header('Content-Type: application/json');
|
||||
$this->checkAdmin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->jsonError('仅支持POST请求');
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$data) {
|
||||
$data = $_POST;
|
||||
}
|
||||
|
||||
// 处理logo上传(只支持site_logo和site_favicon)
|
||||
$logoTypes = ['site_logo', 'site_favicon'];
|
||||
foreach ($logoTypes as $logoType) {
|
||||
$fileKey = $logoType . '_file';
|
||||
if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) {
|
||||
$_POST['logo_type'] = $logoType;
|
||||
$logoUrl = $this->handleLogoUpload($_FILES[$fileKey]);
|
||||
$this->saveSetting($logoType, $logoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存其他设置
|
||||
$allowedKeys = [
|
||||
'site_title', 'site_description', 'site_keywords',
|
||||
'site_logo', 'site_favicon', 'site_copyright',
|
||||
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass',
|
||||
'smtp_from', 'smtp_from_name', 'smtp_encryption',
|
||||
];
|
||||
|
||||
foreach ($allowedKeys as $key) {
|
||||
if (isset($data[$key])) {
|
||||
$this->saveSetting($key, $data[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除SettingsHelper缓存
|
||||
\App\Core\SettingsHelper::clearCache();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => '设置保存成功',
|
||||
'data' => $this->getAllSettings()
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->jsonError('保存设置失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理logo上传
|
||||
*/
|
||||
private function handleLogoUpload($file) {
|
||||
// 验证文件大小
|
||||
if ($file['size'] > $this->uploadConfig['max_size']) {
|
||||
throw new Exception('文件过大,最大支持5MB');
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
if (!in_array($mime, $this->uploadConfig['allowed_mimes'])) {
|
||||
throw new Exception('不支持的文件类型,仅允许: JPG, PNG, GIF, WEBP, ICO');
|
||||
}
|
||||
|
||||
// 确保上传目录存在
|
||||
$uploadDir = rtrim($this->uploadConfig['upload_dir'], '/') . '/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
$originalName = basename($file['name']);
|
||||
$originalExt = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
$filename = 'logo_' . date('YmdHis') . '_' . uniqid() . '.' . $originalExt;
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
// 移动文件
|
||||
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
||||
throw new Exception('文件上传失败');
|
||||
}
|
||||
|
||||
// 返回相对路径(不含域名,兼容任何域名/端口)
|
||||
return '/Static/img/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个设置值
|
||||
*/
|
||||
private function getSetting($key, $default = '') {
|
||||
try {
|
||||
$setting = $this->db->get('system_settings', 'setting_value', [
|
||||
'setting_key' => $key
|
||||
]);
|
||||
return $setting !== false ? $setting : $default;
|
||||
} catch (Exception $e) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存单个设置
|
||||
*/
|
||||
private function saveSetting($key, $value) {
|
||||
try {
|
||||
$existing = $this->db->get('system_settings', 'id', [
|
||||
'setting_key' => $key
|
||||
]);
|
||||
|
||||
if ($existing) {
|
||||
// 更新
|
||||
$this->db->update('system_settings', [
|
||||
'setting_value' => $value
|
||||
], [
|
||||
'setting_key' => $key
|
||||
]);
|
||||
} else {
|
||||
// 插入
|
||||
$this->db->insert('system_settings', [
|
||||
'setting_key' => $key,
|
||||
'setting_value' => $value
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('保存设置失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设置(以关联数组形式返回)
|
||||
*/
|
||||
private function getAllSettings() {
|
||||
try {
|
||||
$settings = $this->db->select('system_settings', ['setting_key', 'setting_value']);
|
||||
$result = [];
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$result[$setting['setting_key']] = $setting['setting_value'];
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
$defaults = [
|
||||
'site_title' => 'Tài Xỉu Online - TM68',
|
||||
'site_description' => 'Trang game tài xỉu đổi thưởng, nạp rút nhanh chóng',
|
||||
'site_keywords' => 'tài xỉu, game online, đổi thưởng',
|
||||
'site_logo' => '/Static/tm68/logo_tm68.png.png',
|
||||
'site_favicon' => '/Static/css/favicon.ico',
|
||||
'site_copyright' => '© 2024 TM68. All rights reserved.'
|
||||
];
|
||||
|
||||
foreach ($defaults as $key => $default) {
|
||||
if (!isset($result[$key])) {
|
||||
$result[$key] = $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMTP 测试发信
|
||||
*/
|
||||
public function smtpTest() {
|
||||
header('Content-Type: application/json');
|
||||
$this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$email = trim($data['email'] ?? '');
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->jsonError('Invalid email');
|
||||
}
|
||||
\App\Core\SettingsHelper::clearCache();
|
||||
$result = \App\Core\Mailer::test($email);
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON错误响应
|
||||
*/
|
||||
private function jsonError($message) {
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => $message
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
Executable
+359
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class UserController extends AdminBaseController {
|
||||
/**
|
||||
* 构造函数 - 验证管理员权限
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户列表页面
|
||||
*/
|
||||
public function index() {
|
||||
$this->checkAdmin();
|
||||
$db = new Database();
|
||||
// 只查询普通用户,排除管理员
|
||||
$users = $db->select('users', '*', [
|
||||
'role[!]' => 'admin',
|
||||
'ORDER' => ['id' => 'DESC']
|
||||
]);
|
||||
$this->render('Admin/user.php', [
|
||||
'users' => $users,
|
||||
'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();
|
||||
$user = $db->get('users', '*', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
if ($user) {
|
||||
// 移除密码字段,避免泄露
|
||||
unset($user['password']);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $user
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function update() {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = $_POST;
|
||||
if (empty($data)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '未接收到数据'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
$id = isset($data['id']) ? (int)$data['id'] : 0;
|
||||
$isEditMode = $id > 0;
|
||||
|
||||
$username = trim($data['username'] ?? '');
|
||||
$email = trim($data['email'] ?? '');
|
||||
$role = $data['role'] ?? 'user';
|
||||
$status = isset($data['status']) ? (int)$data['status'] : 0;
|
||||
|
||||
if (empty($username) || empty($email)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户名和邮箱不能为空'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '邮箱格式不正确'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 统一查重(排除当前 ID)
|
||||
$exists = $db->get('users', '*', [
|
||||
'AND' => [
|
||||
'OR' => [
|
||||
'username' => $username,
|
||||
'email' => $email
|
||||
],
|
||||
'id[!]' => $id
|
||||
]
|
||||
]);
|
||||
|
||||
if ($exists) {
|
||||
if ($exists['username'] === $username) {
|
||||
$msg = '用户名已存在';
|
||||
} elseif ($exists['email'] === $email) {
|
||||
$msg = '邮箱已被使用';
|
||||
} else {
|
||||
$msg = '用户名或邮箱已被占用';
|
||||
}
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $msg
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备数据
|
||||
$userData = [
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'role' => $role,
|
||||
'status' => $status,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
try {
|
||||
if ($isEditMode) {
|
||||
// 如果密码不为空,更新密码
|
||||
if (!empty($data['password'])) {
|
||||
if (strlen($data['password']) < 8) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '密码长度至少8位'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$userData['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$db->update('users', $userData, ['id' => $id]);
|
||||
$message = '用户更新成功';
|
||||
} else {
|
||||
// 新增时必须有密码
|
||||
if (empty($data['password']) || strlen($data['password']) < 8) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '密码长度至少8位'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$userData['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$userData['created_at'] = date('Y-m-d H:i:s');
|
||||
|
||||
$id = $db->insert('users', $userData);
|
||||
$message = '用户创建成功';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'data' => ['id' => $id]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '操作失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调整用户余额
|
||||
*/
|
||||
public function adjustBalance($id) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的用户ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '数据格式错误'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$amount = isset($data['amount']) ? floatval($data['amount']) : 0;
|
||||
$action = $data['action'] ?? ''; // 'increase' or 'decrease'
|
||||
|
||||
if ($amount <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '金额必须大于0'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array($action, ['increase', 'decrease'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的操作类型'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 获取当前用户信息
|
||||
$user = $db->get('users', ['id', 'balance', 'username'], [
|
||||
'id' => $id,
|
||||
'role[!]' => 'admin' // 只能调整普通用户的余额
|
||||
]);
|
||||
|
||||
if (!$user) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在或无法操作'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$currentBalance = floatval($user['balance'] ?? 0);
|
||||
$newBalance = $action === 'increase'
|
||||
? $currentBalance + $amount
|
||||
: $currentBalance - $amount;
|
||||
|
||||
// 检查减少后余额不能为负数
|
||||
if ($newBalance < 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '余额不足,当前余额:' . number_format($currentBalance, 2)
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db->medoo->pdo->beginTransaction();
|
||||
|
||||
// 更新用户余额
|
||||
$db->update('users', [
|
||||
'balance' => $newBalance,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
], ['id' => $id]);
|
||||
|
||||
// 写入资金流水
|
||||
// 修改这里:使用 manual_deposit 和 manual_withdraw 区分人工操作
|
||||
$transactionType = $action === 'increase' ? 'manual_deposit' : 'manual_withdraw';
|
||||
$transactionAmount = $action === 'increase' ? $amount : -$amount;
|
||||
$description = $action === 'increase' ? '管理员手动增加余额' : '管理员手动减少余额';
|
||||
|
||||
$db->insert('transactions', [
|
||||
'user_id' => $id,
|
||||
'type' => $transactionType,
|
||||
'amount' => $transactionAmount,
|
||||
'balance_before' => $currentBalance,
|
||||
'balance_after' => $newBalance,
|
||||
'description' => $description,
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
$db->medoo->pdo->commit();
|
||||
|
||||
$actionText = $action === 'increase' ? '增加' : '减少';
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "余额{$actionText}成功",
|
||||
'data' => [
|
||||
'old_balance' => $currentBalance,
|
||||
'new_balance' => $newBalance,
|
||||
'amount' => $amount
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$db->medoo->pdo->rollBack();
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '操作失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
public function delete($id) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '无效的用户ID'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁止删除自己
|
||||
session_start();
|
||||
if ($id == $_SESSION['user_id']) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '不能删除当前登录用户'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new Database();
|
||||
$userExists = $db->get('users', 'id', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
if (!$userExists) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '用户不存在'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db->delete('users', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '用户已删除'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '删除失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class VirtualAccountController extends AdminBaseController {
|
||||
private $db;
|
||||
public function __construct(Database $db) { $this->db = $db; }
|
||||
|
||||
public function index() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$virtuals = $this->db->select('users', '*', ['is_virtual' => 1, 'ORDER' => ['id' => 'DESC']]);
|
||||
$this->render('Admin/virtual_accounts.php', compact('virtuals'));
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($data['username'] ?? '');
|
||||
if (empty($username)) { $this->json(['status'=>'error','message'=>'Username required']); return; }
|
||||
if ($this->db->get('users', 'id', ['username' => $username])) {
|
||||
$this->json(['status'=>'error','message'=>'Username exists']); return;
|
||||
}
|
||||
|
||||
$this->db->insert('users', [
|
||||
'username' => $username,
|
||||
'password' => password_hash($data['password'] ?? '123456', PASSWORD_DEFAULT),
|
||||
'email' => $username . '@virtual.local',
|
||||
'role' => 'user', 'status' => 1, 'is_virtual' => 1,
|
||||
'email_verified' => 1,
|
||||
'balance' => floatval($data['balance'] ?? 100000),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
public function adjustBalance() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$amount = floatval($data['amount'] ?? 0);
|
||||
$user = $this->db->get('users', '*', ['id' => $id, 'is_virtual' => 1]);
|
||||
if (!$user) { $this->json(['status'=>'error','message'=>'Not found']); return; }
|
||||
$new = (float)$user['balance'] + $amount;
|
||||
$this->db->update('users', ['balance' => $new], ['id' => $id]);
|
||||
$this->json(['status' => 'success', 'new_balance' => $new]);
|
||||
}
|
||||
|
||||
public function delete($id) {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$this->db->delete('users', ['id' => (int)$id, 'is_virtual' => 1]);
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
private function json($data) { header('Content-Type: application/json'); echo json_encode($data); }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class WaterController extends AdminBaseController {
|
||||
private $db;
|
||||
public function __construct(Database $db) { $this->db = $db; }
|
||||
|
||||
public function index() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$gameId = $this->getGameId();
|
||||
$configs = $this->db->select('water_control', '*', ['game_id' => $gameId]);
|
||||
$limits = $this->db->select('bet_limits', '*', ['game_id' => $gameId]);
|
||||
$this->render('Admin/water_control.php', compact('configs', 'limits', 'gameId'));
|
||||
}
|
||||
|
||||
public function updateWater() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
foreach ($data['items'] ?? [] as $item) {
|
||||
$existing = $this->db->get('water_control', 'id', [
|
||||
'game_id' => (int)$item['game_id'], 'bet_type' => $item['bet_type']
|
||||
]);
|
||||
$fields = [
|
||||
'win_rate_pct' => floatval($item['win_rate_pct']),
|
||||
'enabled' => (int)($item['enabled'] ?? 0),
|
||||
];
|
||||
if ($existing) {
|
||||
$this->db->update('water_control', $fields, ['id' => $existing]);
|
||||
} else {
|
||||
$fields['game_id'] = (int)$item['game_id'];
|
||||
$fields['bet_type'] = $item['bet_type'];
|
||||
$this->db->insert('water_control', $fields);
|
||||
}
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
public function updateLimits() {
|
||||
$this->checkLogin(); $this->checkAdmin();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
foreach ($data['items'] ?? [] as $item) {
|
||||
$existing = $this->db->get('bet_limits', 'id', [
|
||||
'game_id' => (int)$item['game_id'], 'bet_type' => $item['bet_type']
|
||||
]);
|
||||
$fields = [
|
||||
'min_amount' => floatval($item['min_amount']),
|
||||
'max_amount' => floatval($item['max_amount']),
|
||||
'max_per_period' => floatval($item['max_per_period'] ?? 500000),
|
||||
];
|
||||
if ($existing) {
|
||||
$this->db->update('bet_limits', $fields, ['id' => $existing]);
|
||||
} else {
|
||||
$fields['game_id'] = (int)$item['game_id'];
|
||||
$fields['bet_type'] = $item['bet_type'];
|
||||
$this->db->insert('bet_limits', $fields);
|
||||
}
|
||||
}
|
||||
$this->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
private function getGameId(): int {
|
||||
if (!empty($_GET['game_id'])) return (int)$_GET['game_id'];
|
||||
return (int)($this->db->get('games', 'id', ['code' => 'pk10']) ?: 0);
|
||||
}
|
||||
private function json($d) { header('Content-Type: application/json'); echo json_encode($d); }
|
||||
}
|
||||
+714
@@ -0,0 +1,714 @@
|
||||
<?php
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Core\AdminBaseController;
|
||||
use Db\Database;
|
||||
|
||||
class XocdiaPeriodController extends AdminBaseController {
|
||||
private $gameType = 'xocdia';
|
||||
|
||||
public function __construct() {
|
||||
$this->checkLogin();
|
||||
$this->checkAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóc Đĩa 游戏期号列表页面
|
||||
*/
|
||||
public function index() {
|
||||
$db = new Database();
|
||||
try {
|
||||
// 获取 Xóc Đĩa 游戏列表
|
||||
$gamesList = $db->select('games', ['id', 'name'], [
|
||||
'type' => $this->gameType,
|
||||
'status' => 1,
|
||||
'ORDER' => ['id' => 'ASC']
|
||||
]);
|
||||
|
||||
if (!is_array($gamesList)) {
|
||||
$gamesList = [];
|
||||
}
|
||||
|
||||
// 获取所有 Xóc Đĩa 游戏的game_id
|
||||
$gameIds = array_column($gamesList, 'id');
|
||||
|
||||
// 获取 Xóc Đĩa 游戏的期号列表
|
||||
$periods = [];
|
||||
if (!empty($gameIds)) {
|
||||
$periods = $db->select('periods', '*', [
|
||||
'game_id' => $gameIds,
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 100
|
||||
]);
|
||||
}
|
||||
|
||||
if (!is_array($periods)) {
|
||||
$periods = [];
|
||||
}
|
||||
|
||||
// 构建游戏名称映射
|
||||
$games = [];
|
||||
foreach ($gamesList as $game) {
|
||||
$games[$game['id']] = $game['name'];
|
||||
}
|
||||
|
||||
// 为每个 Xóc Đĩa 游戏获取当前期号
|
||||
$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/xocdia_periods.php', [
|
||||
'currentPeriods' => $currentPeriods,
|
||||
'periods' => $periods,
|
||||
'games' => $games,
|
||||
'gamesList' => $gamesList,
|
||||
'title' => 'Xóc Đĩa 游戏期号管理'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个期号数据
|
||||
*/
|
||||
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) {
|
||||
// 验证是否为 Xóc Đĩa 游戏期号
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if ($game && $game['type'] === $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $period
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于 Xóc Đĩa 游戏'
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不存在'
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $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;
|
||||
}
|
||||
|
||||
// 验证是否为 Xóc Đĩa 游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于 Xóc Đĩa 游戏'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查期号状态
|
||||
if ($period['status'] === 'settled') {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '该期号已结算,无法修改'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$auto = isset($data['auto']) ? (bool)$data['auto'] : false;
|
||||
|
||||
// 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),
|
||||
'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
|
||||
];
|
||||
|
||||
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();
|
||||
|
||||
// 验证游戏类型
|
||||
$game = $db->get('games', ['type', 'stream_url'], ['id' => $gameId]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '游戏不属于 Xóc Đĩa 游戏'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查该游戏是否有未结束的期号
|
||||
$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 = !empty($game['stream_url']) ? $game['stream_url'] : null;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
// 验证是否为 Xóc Đĩa 游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于 Xóc Đĩa 游戏'
|
||||
]);
|
||||
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();
|
||||
|
||||
// 获取本期所有待结算注单
|
||||
$bets = $db->select('bets', '*', [
|
||||
'period_id' => $id,
|
||||
'status' => 'pending'
|
||||
]);
|
||||
|
||||
// 遍历注单进行结算
|
||||
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]);
|
||||
|
||||
// 提交事务
|
||||
$db->medoo->pdo->commit();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '结算成功,请点击"开始下注"启动下一期',
|
||||
'data' => [
|
||||
'current_period_id' => $id
|
||||
]
|
||||
]);
|
||||
} 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;
|
||||
}
|
||||
|
||||
// 验证是否为 Xóc Đĩa 游戏
|
||||
$game = $db->get('games', ['type'], ['id' => $period['game_id']]);
|
||||
if (!$game || $game['type'] !== $this->gameType) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '期号不属于 Xóc Đĩa 游戏'
|
||||
]);
|
||||
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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成期号
|
||||
*/
|
||||
private function generatePeriodNumber($db, $gameId) {
|
||||
$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) {
|
||||
$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'];
|
||||
|
||||
$db = new Database();
|
||||
|
||||
// 获取赔率配置
|
||||
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;
|
||||
if (isset($oddsCache[$gameId][$key])) {
|
||||
return $oddsCache[$gameId][$key];
|
||||
}
|
||||
$allKey = $type . '_all';
|
||||
return $oddsCache[$gameId][$allKey] ?? 0;
|
||||
};
|
||||
|
||||
// Xóc Đĩa 游戏结算逻辑
|
||||
$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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 Xóc Đĩa 开奖结果
|
||||
*/
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user