time()], '登录成功'); } else { error('密码错误', 401); } } function adminDoLogout() { session_destroy(); success(null, '已登出'); } function adminStatus() { success([ 'logged_in' => isAdminLoggedIn(), 'login_time' => $_SESSION['admin_login_time'] ?? 0 ]); } // ===== 债券操作 ===== function adminDeduct() { requireAdminSession(); $player = getParam('player'); $amount = (int)getParam('amount', 0); $reason = getParam('reason', '管理员扣除'); if (!$player) error('缺少player'); if ($amount <= 0) error('扣除金额必须大于0'); $db = getDB(); $db->exec("CREATE TABLE IF NOT EXISTS bond_cache (player_name TEXT PRIMARY KEY, amount INTEGER DEFAULT 0, updated_at INTEGER DEFAULT 0)"); $stmt = $db->prepare("SELECT amount FROM bond_cache WHERE player_name = :name"); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $result = $stmt->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); $current = $row ? $row['amount'] : 0; if ($current < $amount) { error("余额不足: 当前{$current},需扣除{$amount}"); } $newBalance = $current - $amount; $now = time(); if ($row) { $stmt = $db->prepare("UPDATE bond_cache SET amount = :amount, updated_at = :time WHERE player_name = :name"); } else { $stmt = $db->prepare("INSERT INTO bond_cache (player_name, amount, updated_at) VALUES (:name, :amount, :time)"); } $stmt->bindValue(':amount', $newBalance, SQLITE3_INTEGER); $stmt->bindValue(':time', $now, SQLITE3_INTEGER); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $stmt->execute(); // 记录流水 $stmt = $db->prepare("INSERT INTO web_transactions (player_name, type, amount, operator, reason, created_at) VALUES (:player, 'admin_deduct', :amount, 'admin', :reason, :time)"); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':amount', $amount, SQLITE3_INTEGER); $stmt->bindValue(':reason', $reason, SQLITE3_TEXT); $stmt->bindValue(':time', $now, SQLITE3_INTEGER); $stmt->execute(); success([ 'player' => $player, 'amount' => -$amount, 'balance_before' => $current, 'balance_after' => $newBalance ], '扣除成功'); } function adminAddBonds() { requireAdminSession(); $player = getParam('player'); $amount = (int)getParam('amount', 0); $reason = getParam('reason', '管理员充值'); if (!$player) error('缺少player'); if ($amount <= 0) error('金额必须大于0'); // 验证玩家名格式(3-16位字母数字下划线) if (!preg_match('/^[a-zA-Z0-9_]{3,16}$/', $player)) { error('玩家名格式不正确(3-16位字母数字下划线)'); } $db = getDB(); // 检查玩家是否在users表中存在 $checkStmt = $db->prepare("SELECT 1 FROM users WHERE player_name = :player"); $checkStmt->bindValue(':player', $player, SQLITE3_TEXT); $checkResult = $checkStmt->execute(); if (!$checkResult->fetchArray()) { error('玩家不存在: ' . $player); } $db->exec("CREATE TABLE IF NOT EXISTS bond_cache (player_name TEXT PRIMARY KEY, amount INTEGER DEFAULT 0, updated_at INTEGER DEFAULT 0)"); $stmt = $db->prepare("SELECT amount FROM bond_cache WHERE player_name = :name"); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $result = $stmt->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); $current = $row ? $row['amount'] : 0; $newBalance = $current + $amount; $now = time(); if ($row) { $stmt = $db->prepare("UPDATE bond_cache SET amount = :amount, updated_at = :time WHERE player_name = :name"); } else { $stmt = $db->prepare("INSERT INTO bond_cache (player_name, amount, updated_at) VALUES (:name, :amount, :time)"); } $stmt->bindValue(':amount', $newBalance, SQLITE3_INTEGER); $stmt->bindValue(':time', $now, SQLITE3_INTEGER); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $stmt->execute(); $stmt = $db->prepare("INSERT INTO web_transactions (player_name, type, amount, operator, reason, created_at) VALUES (:player, 'admin_give', :amount, 'admin', :reason, :time)"); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':amount', $amount, SQLITE3_INTEGER); $stmt->bindValue(':reason', $reason, SQLITE3_TEXT); $stmt->bindValue(':time', $now, SQLITE3_INTEGER); $stmt->execute(); success([ 'player' => $player, 'amount' => $amount, 'balance_before' => $current, 'balance_after' => $newBalance ], '充值成功'); } // ===== 商品管理 ===== function adminShopUpdate() { requireAdminSession(); $id = getParam('id'); $buyPrice = getParam('buy_price'); $sellPrice = getParam('sell_price'); $stock = getParam('stock'); if (!$id) error('缺少商品ID'); $db = getDB(); $sets = []; $params = [':id' => [$id, SQLITE3_TEXT]]; if ($buyPrice !== null) { $sets[] = 'buy_price = :bp'; $params[':bp'] = [(int)$buyPrice, SQLITE3_INTEGER]; } if ($sellPrice !== null) { $sets[] = 'sell_price = :sp'; $params[':sp'] = [(int)$sellPrice, SQLITE3_INTEGER]; } if ($stock !== null) { $sets[] = 'stock = :st'; $params[':st'] = [(int)$stock, SQLITE3_INTEGER]; } if (empty($sets)) error('没有要更新的字段'); $sql = "UPDATE shop_items SET " . implode(', ', $sets) . " WHERE id = :id"; $stmt = $db->prepare($sql); foreach ($params as $k => $v) { $stmt->bindValue($k, $v[0], $v[1]); } $stmt->execute(); success(['id' => $id], '商品更新成功'); } function adminShopAdd() { requireAdminSession(); $id = getParam('id'); $category = getParam('category', '默认'); $name = getParam('display_name'); $material = getParam('material', 'STONE'); $buyPrice = (int)getParam('buy_price', 0); $sellPrice = (int)getParam('sell_price', -1); $stock = (int)getParam('stock', -1); if (!$id) error('缺少商品ID'); if (!$name) error('缺少商品名'); $db = getDB(); $stmt = $db->prepare("INSERT OR REPLACE INTO shop_items (id, category, display_name, material, buy_price, sell_price, stock, last_sync) VALUES (:id, :cat, :name, :mat, :bp, :sp, :st, :time)"); $stmt->bindValue(':id', $id, SQLITE3_TEXT); $stmt->bindValue(':cat', $category, SQLITE3_TEXT); $stmt->bindValue(':name', $name, SQLITE3_TEXT); $stmt->bindValue(':mat', $material, SQLITE3_TEXT); $stmt->bindValue(':bp', $buyPrice, SQLITE3_INTEGER); $stmt->bindValue(':sp', $sellPrice, SQLITE3_INTEGER); $stmt->bindValue(':st', $stock, SQLITE3_INTEGER); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->execute(); success(['id' => $id], '商品添加成功'); } function adminShopRemove() { requireAdminSession(); $id = getParam('id'); if (!$id) error('缺少商品ID'); $db = getDB(); $stmt = $db->prepare("DELETE FROM shop_items WHERE id = :id"); $stmt->bindValue(':id', $id, SQLITE3_TEXT); $stmt->execute(); success(['id' => $id], '商品已删除'); } // ===== 统计 ===== function adminStats() { requireAdminSession(); $db = getDB(); $db->exec("CREATE TABLE IF NOT EXISTS bond_cache (player_name TEXT PRIMARY KEY, amount INTEGER DEFAULT 0, updated_at INTEGER DEFAULT 0)"); // 总债券 $result = $db->query("SELECT SUM(amount) as total_bonds, COUNT(*) as total_players FROM bond_cache"); $bonds = $result->fetchArray(SQLITE3_ASSOC); // 总CDK $result = $db->query("SELECT COUNT(*) as total, SUM(CASE WHEN used=0 THEN 1 ELSE 0 END) as unused FROM cdk"); $cdk = $result->fetchArray(SQLITE3_ASSOC); // 总交易 $result = $db->query("SELECT COUNT(*) as total FROM web_transactions"); $tx = $result->fetchArray(SQLITE3_ASSOC); // 总商品 $result = $db->query("SELECT COUNT(*) as total FROM shop_items"); $shop = $result->fetchArray(SQLITE3_ASSOC); // 总注册 $result = $db->query("SELECT COUNT(*) as total FROM users"); $users = $result->fetchArray(SQLITE3_ASSOC); success([ 'total_bonds' => $bonds['total_bonds'] ?? 0, 'total_players' => $bonds['total_players'] ?? 0, 'total_cdk' => $cdk['total'] ?? 0, 'unused_cdk' => $cdk['unused'] ?? 0, 'total_transactions' => $tx['total'] ?? 0, 'total_shop_items' => $shop['total'] ?? 0, 'total_users' => $users['total'] ?? 0 ]); } function adminAllTx() { requireAdminSession(); $limit = (int)getParam('limit', 100); $type = getParam('type'); $db = getDB(); if ($type) { $stmt = $db->prepare("SELECT * FROM web_transactions WHERE type = :type ORDER BY created_at DESC LIMIT :limit"); $stmt->bindValue(':type', $type, SQLITE3_TEXT); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); } else { $stmt = $db->prepare("SELECT * FROM web_transactions ORDER BY created_at DESC LIMIT :limit"); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); } $txs = []; $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $txs[] = $row; } success($txs); } function adminPlayerTx() { requireAdminSession(); $player = getParam('player'); if (!$player) error('缺少player'); $limit = (int)getParam('limit', 100); $db = getDB(); $stmt = $db->prepare("SELECT * FROM web_transactions WHERE player_name = :player ORDER BY created_at DESC LIMIT :limit"); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); $txs = []; $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $txs[] = $row; } success($txs); } // ===== 生成Token ===== function adminGenToken() { requireAdminSession(); $player = getParam('player', 'admin'); $purpose = getParam('purpose', 'all'); $expireSeconds = (int)getParam('expire', 600); if (!$player) error('缺少player'); // 生成随机token $token = bin2hex(random_bytes(32)); $db = getDB(); $now = time(); $stmt = $db->prepare("INSERT INTO tokens (token, player_name, purpose, created_at, expires_at, used) VALUES (:token, :player, :purpose, :created, :expires, 0)"); $stmt->bindValue(':token', $token, SQLITE3_TEXT); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':purpose', $purpose, SQLITE3_TEXT); $stmt->bindValue(':created', $now, SQLITE3_INTEGER); $stmt->bindValue(':expires', $now + $expireSeconds, SQLITE3_INTEGER); $stmt->execute(); success([ 'token' => $token, 'player' => $player, 'purpose' => $purpose, 'expires_at' => $now + $expireSeconds ], 'Token生成成功'); } // ===== 通知插件立即同步 ===== function adminNotifySync() { requireAdminSession(); // 写入同步通知文件 $notifyFile = __DIR__ . '/../../sync_notify.txt'; file_put_contents($notifyFile, time()); success([], '已通知插件立即同步'); } // ===== 立即同步数据 ===== function adminSyncNow() { requireAdminSession(); // 写入同步通知文件 $notifyFile = __DIR__ . '/../../sync_notify.txt'; file_put_contents($notifyFile, time()); success([], '已触发立即同步'); } // ===== 添加CDK ===== function adminCdkAdd() { requireAdminSession(); $code = getParam('code'); $amount = (int)getParam('amount', 0); $type = getParam('type', 'bond'); $description = getParam('description', ''); if (!$code) error('缺少CDK码'); if ($amount <= 0) error('金额必须大于0'); $db = getDB(); $db->exec("CREATE TABLE IF NOT EXISTS cdk (code TEXT PRIMARY KEY, amount INTEGER NOT NULL, type TEXT DEFAULT 'bond', description TEXT, used INTEGER DEFAULT 0, used_by TEXT, used_at INTEGER)"); // 检查是否已存在 $checkStmt = $db->prepare("SELECT 1 FROM cdk WHERE code = :code"); $checkStmt->bindValue(':code', $code, SQLITE3_TEXT); $checkResult = $checkStmt->execute(); if ($checkResult->fetchArray()) { error('CDK码已存在'); } $stmt = $db->prepare("INSERT INTO cdk (code, amount, type, description) VALUES (:code, :amount, :type, :desc)"); $stmt->bindValue(':code', $code, SQLITE3_TEXT); $stmt->bindValue(':amount', $amount, SQLITE3_INTEGER); $stmt->bindValue(':type', $type, SQLITE3_TEXT); $stmt->bindValue(':desc', $description, SQLITE3_TEXT); $stmt->execute(); success(['code' => $code, 'amount' => $amount], 'CDK添加成功'); } // ===== 批量生成CDK ===== function adminCdkBatch() { requireAdminSession(); $amount = (int)getParam('amount', 0); $count = (int)getParam('count', 1); if ($amount <= 0) error('金额必须大于0'); if ($count < 1 || $count > 100) error('数量必须在1-100之间'); $db = getDB(); $db->exec("CREATE TABLE IF NOT EXISTS cdk (code TEXT PRIMARY KEY, amount INTEGER NOT NULL, type TEXT DEFAULT 'bond', description TEXT, used INTEGER DEFAULT 0, used_by TEXT, used_at INTEGER, created_at INTEGER)"); $codes = []; for ($i = 0; $i < $count; $i++) { // 生成8位随机CDK码 $code = strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 8)); $stmt = $db->prepare("INSERT INTO cdk (code, amount, type, created_at) VALUES (:code, :amount, 'bond', :time)"); $stmt->bindValue(':code', $code, SQLITE3_TEXT); $stmt->bindValue(':amount', $amount, SQLITE3_INTEGER); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->execute(); $codes[] = $code; } success(['codes' => $codes, 'count' => count($codes)], '成功生成' . count($codes) . '个CDK'); } // ===== 列出用户 ===== function adminListUsers() { requireAdminSession(); $db = getDB(); $stmt = $db->prepare("SELECT * FROM users ORDER BY register_time DESC"); $result = $stmt->execute(); $users = []; while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $users[] = $row; } success($users); } // ===== 获取待审核重置请求 ===== function adminListResetRequests() { requireAdminSession(); $db = getDB(); $db->exec("CREATE TABLE IF NOT EXISTS password_reset_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, player_name TEXT NOT NULL, requested_email TEXT DEFAULT '', status TEXT DEFAULT 'pending', admin_approved INTEGER DEFAULT 0, admin_email TEXT DEFAULT '', created_at INTEGER NOT NULL, processed_at INTEGER DEFAULT 0 )"); $stmt = $db->prepare("SELECT * FROM password_reset_requests WHERE status = 'pending' ORDER BY created_at DESC"); $result = $stmt->execute(); $requests = []; while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $requests[] = $row; } success($requests); } // ===== 批准密码重置请求 ===== function adminApproveReset() { requireAdminSession(); $reqId = (int)getParam('id', 0); $adminEmail = getParam('admin_email', ''); if (!$reqId) error('缺少请求ID'); if (!$adminEmail || !filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) error('请输入有效的管理员邮箱'); $db = getDB(); // 获取请求信息 $stmt = $db->prepare("SELECT * FROM password_reset_requests WHERE id = :id AND status = 'pending'"); $stmt->bindValue(':id', $reqId, SQLITE3_INTEGER); $result = $stmt->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); if (!$row) error('请求不存在或已处理'); $player = $row['player_name']; // 生成临时密码 $saltBytes = random_bytes(16); $salt = base64_encode($saltBytes); $tempPassword = '123456aA'; $passwordHash = base64_encode(hash('sha256', $saltBytes . $tempPassword, true)); // 更新玩家数据 $stmt = $db->prepare("UPDATE users SET password_hash = :hash, password_salt = :salt, email = :email WHERE player_name = :player"); $stmt->bindValue(':hash', $passwordHash, SQLITE3_TEXT); $stmt->bindValue(':salt', $salt, SQLITE3_TEXT); $stmt->bindValue(':email', $adminEmail, SQLITE3_TEXT); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->execute(); // 标记请求为已批准 $stmt = $db->prepare("UPDATE password_reset_requests SET status = 'approved', admin_approved = 1, admin_email = :admin_email, processed_at = :time WHERE id = :id"); $stmt->bindValue(':admin_email', $adminEmail, SQLITE3_TEXT); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->bindValue(':id', $reqId, SQLITE3_INTEGER); $stmt->execute(); success(['player' => $player, 'temp_password' => $tempPassword], '已批准,临时密码已设置为: ' . $tempPassword); } // ===== 驳回密码重置请求 ===== function adminRejectReset() { requireAdminSession(); $reqId = (int)getParam('id', 0); if (!$reqId) error('缺少请求ID'); $db = getDB(); // 更新请求状态 $stmt = $db->prepare("UPDATE password_reset_requests SET status = 'rejected', processed_at = :time WHERE id = :id"); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->bindValue(':id', $reqId, SQLITE3_INTEGER); $stmt->execute(); success(null, '已驳回该请求'); }