['code' => 500, 'message' => 'Internal error: ' . $e->getMessage()]], JSON_UNESCAPED_UNICODE); exit; } // ===== 查询余额 ===== function balanceQuery($token) { $player = getParam('player'); $password = getParam('password'); $ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; if (!$player) error('缺少player'); // ★ 安全验证:必须验证token且token对应的玩家必须与查询的玩家匹配 $tokenInfo = null; $queriedPlayer = $player; // 最终查询的玩家名 // 1. 检查是否为管理后台session if (session_status() === PHP_SESSION_NONE) session_start(); $isAdmin = isset($_SESSION['admin_auth']) && $_SESSION['admin_auth']; if ($isAdmin) { // 管理员:可以查任意玩家 $tokenInfo = ['player' => null, 'purpose' => 'admin']; } elseif ($token) { // 2. 验证token并获取token对应的玩家名 $tempTokenInfo = validateToken($token); if ($tempTokenInfo) { $tokenPlayer = $tempTokenInfo['player']; $tokenPurpose = $tempTokenInfo['purpose'] ?? ''; if ($tokenPurpose === 'admin' || $tokenPurpose === 'all') { // 管理员token:可以查任意玩家 $tokenInfo = $tempTokenInfo; } elseif ($tokenPlayer === $player) { // 普通token:只能查自己的余额 // 进一步验证weblogin token的访问权限 $accessResult = validateWebAccess($token, 'view', $password, $ipAddress); if ($accessResult['ok']) { $queriedPlayer = $accessResult['player']; $tokenInfo = ['player' => $queriedPlayer, 'purpose' => 'weblogin']; } elseif ($accessResult['mode'] === 'need_password') { jsonResponse([ 'success' => false, 'need_password' => true, 'player' => $accessResult['player'], 'message' => $accessResult['message'] ], 401); } else { error($accessResult['message'], 401); } } else { error('无权查询其他玩家余额', 403); } } else { error('无效token,请先登录', 401); } } else { error('查询余额需要有效token,请先登录', 401); } // 查询债券余额(从Web端的债券缓存表) $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, updated_at FROM bond_cache WHERE player_name = :name"); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $result = $stmt->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); // 同时查询积分 $stmt2 = $db->prepare("SELECT points FROM users WHERE player_name = :name"); $stmt2->bindValue(':name', $player, SQLITE3_TEXT); $result2 = $stmt2->execute(); $row2 = $result2->fetchArray(SQLITE3_ASSOC); $bonds = $row ? $row['amount'] : 0; $points = $row2 ? $row2['points'] : 0; $bondsUpdated = $row ? $row['updated_at'] : 0; // 计算数据新鲜度 $freshness = ''; if ($bondsUpdated > 0) { $age = time() - $bondsUpdated; if ($age < 300) $freshness = '刚刚同步'; elseif ($age < 3600) $freshness = floor($age / 60) . '分钟前同步'; elseif ($age < 86400) $freshness = floor($age / 3600) . '小时前同步'; else $freshness = floor($age / 86400) . '天前同步(数据可能过期)'; } else { $freshness = '尚未同步'; } success([ 'player' => $player, 'bonds' => $bonds, 'points' => $points, 'bonds_updated' => $bondsUpdated, 'freshness' => $freshness ]); } // ===== 充值债券 ===== function balanceRecharge($token) { $player = getParam('player'); $amount = (int)getParam('amount', 0); $operator = getParam('operator', 'web_admin'); if (!$player) error('缺少player'); if ($amount <= 0) error('充值金额必须大于0'); // 需要token $tokenInfo = null; $isPreview = true; if ($token) { $tokenInfo = validateToken($token); if ($tokenInfo) { $isPreview = false; // 权限检查:只有管理员token才能充值 if ($tokenInfo['purpose'] !== 'admin' && $tokenInfo['purpose'] !== 'all') { error('充值需要管理员权限'); } validateAndUseToken($token); } } $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); $currentBalance = $row ? $row['amount'] : 0; $newBalance = $currentBalance + $amount; if ($isPreview) { preview([ 'player' => $player, 'amount' => $amount, 'balance_before' => $currentBalance, 'balance_after' => $newBalance, 'operator' => $operator ], '预览模式 - 充值不会生效'); } // 执行充值 if ($row) { $stmt = $db->prepare("UPDATE bond_cache SET amount = :amount, updated_at = :time WHERE player_name = :name"); $stmt->bindValue(':amount', $newBalance, SQLITE3_INTEGER); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $stmt->execute(); } else { $stmt = $db->prepare("INSERT INTO bond_cache (player_name, amount, updated_at) VALUES (:name, :amount, :time)"); $stmt->bindValue(':name', $player, SQLITE3_TEXT); $stmt->bindValue(':amount', $newBalance, SQLITE3_INTEGER); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->execute(); } // 记录流水 $stmt = $db->prepare("INSERT INTO web_transactions (player_name, type, amount, reason, status, created_at) VALUES (:player, 'admin_recharge', :amount, :reason, 'pending', :time)"); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':amount', $amount, SQLITE3_INTEGER); $stmt->bindValue(':reason', "管理员充值: {$operator}", SQLITE3_TEXT); $stmt->bindValue(':time', time(), SQLITE3_INTEGER); $stmt->execute(); success([ 'player' => $player, 'amount' => $amount, 'balance_before' => $currentBalance, 'balance_after' => $newBalance ], '充值成功'); } // ===== 查看流水 ===== function balanceTransactions($token) { $player = getParam('player'); $limit = (int)getParam('limit', 50); // 需要token $tokenInfo = null; if ($token) { $tokenInfo = validateToken($token); } if (!$tokenInfo) { error('查看流水需要有效token', 401); } $db = getDB(); $txs = []; if ($player) { // 查看指定玩家流水 if ($tokenInfo['player'] !== $player && $tokenInfo['purpose'] !== 'admin' && $tokenInfo['purpose'] !== 'all') { error('无权查看其他玩家流水'); } // Web端交易 try { $stmt = $db->prepare("SELECT *, 'web' as source 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); $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) $txs[] = $row; } catch (\Throwable $e) { /* ignore */ } // 游戏内交易 try { $stmt = $db->prepare("SELECT java_id as id, player_name, type, amount, operator, reason, '' as detail, tx_time as created_at, 'game' as source, balance_before, balance_after FROM game_transactions WHERE player_name = :player ORDER BY tx_time DESC LIMIT :limit"); $stmt->bindValue(':player', $player, SQLITE3_TEXT); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) $txs[] = $row; } catch (\Throwable $e) { /* ignore */ } } else { // 管理员查看全服流水 if ($tokenInfo['purpose'] !== 'admin' && $tokenInfo['purpose'] !== 'all') { error('查看全服流水需要管理员权限'); } // Web端交易 try { $stmt = $db->prepare("SELECT *, 'web' as source FROM web_transactions ORDER BY created_at DESC LIMIT :limit"); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) $txs[] = $row; } catch (\Throwable $e) { /* ignore */ } // 游戏内交易 try { $stmt = $db->prepare("SELECT java_id as id, player_name, type, amount, operator, reason, '' as detail, tx_time as created_at, 'game' as source, balance_before, balance_after FROM game_transactions ORDER BY tx_time DESC LIMIT :limit"); $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) $txs[] = $row; } catch (\Throwable $e) { /* ignore */ } } // 按时间排序(倒序) usort($txs, function($a, $b) { return ($b['created_at'] ?? 0) - ($a['created_at'] ?? 0); }); $txs = array_slice($txs, 0, (int)$limit); success($txs); } // ===== Weblogin Token验证(用于API调用) ===== function validateWebloginTokenForAPI($webToken) { $db = getDB(); try { $stmt = $db->prepare("SELECT * FROM weblogin_tokens WHERE web_token = :token"); $stmt->bindValue(':token', $webToken, SQLITE3_TEXT); $result = $stmt->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); if (!$row) return false; $createdAt = (int)$row['created_at']; $expireSeconds = (int)$row['expire_seconds']; if (time() - $createdAt > $expireSeconds) return false; return [ 'player' => $row['player_name'], 'purpose' => 'weblogin', 'created_at' => $createdAt, 'expires_at' => $createdAt + $expireSeconds ]; } catch (Exception $e) { return false; } }