diff --git a/.gitignore b/.gitignore
index ff4aa67..1c106f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,14 @@
*.db-journal
*.db-wal
*.db-shm
+upload_debug.php
+upload_via_http.php
+upload_debug.ps1
+debug_admin_api.php
+debug_online.php
+debug_region.php
+debug_region_search.php
+self_update.php
+version_check.php
+test_sync.php
+db/debug_ip_v2.php
diff --git a/admin.php b/admin.php
index 771714e..1ea6c57 100644
--- a/admin.php
+++ b/admin.php
@@ -2247,9 +2247,9 @@ async function loadLands(el) {
el.innerHTML = '
加载中...
';
try {
const [landsRes, shopRes, configRes] = await Promise.all([
- fetch('api/land_api.php?action=list_lands').then(r => r.json()),
- fetch('api/land_api.php?action=list_shop').then(r => r.json()),
- fetch('api/land_api.php?action=get_config').then(r => r.json())
+ fetch('api/land_api.php?action=list_lands&secret=sdf1_web_comm_2026_ypshidifu').then(r => r.json()),
+ fetch('api/land_api.php?action=list_shop&secret=sdf1_web_comm_2026_ypshidifu').then(r => r.json()),
+ fetch('api/land_api.php?action=get_config&secret=sdf1_web_comm_2026_ypshidifu').then(r => r.json())
]);
let html = '';
@@ -2368,12 +2368,12 @@ async function saveLandConfig() {
await fetch('api/land_api.php?action=update_config', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: 'key=land_price_per_sqm&value=' + encodeURIComponent(price)
+ body: 'key=land_price_per_sqm&value=' + encodeURIComponent(price) + '&secret=sdf1_web_comm_2026_ypshidifu'
});
await fetch('api/land_api.php?action=update_config', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: 'key=max_lands&value=' + encodeURIComponent(maxLands)
+ body: 'key=max_lands&value=' + encodeURIComponent(maxLands) + '&secret=sdf1_web_comm_2026_ypshidifu'
});
document.querySelector('.modal-close')?.click();
loadLands(document.getElementById('C'));
@@ -2388,7 +2388,7 @@ function changeLandOwner(name, currentOwner) {
fetch('api/land_api.php?action=update_land_owner', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: 'name=' + encodeURIComponent(name) + '&owner=' + encodeURIComponent(newOwner)
+ body: 'name=' + encodeURIComponent(name) + '&owner=' + encodeURIComponent(newOwner) + '&secret=sdf1_web_comm_2026_ypshidifu'
}).then(r => r.json()).then(d => {
if (d.success) loadLands(document.getElementById('C'));
else alert('失败: ' + (d.error||''));
@@ -2400,7 +2400,7 @@ function deleteLand(name) {
fetch('api/land_api.php?action=delete_land', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: 'name=' + encodeURIComponent(name)
+ body: 'name=' + encodeURIComponent(name) + '&secret=sdf1_web_comm_2026_ypshidifu'
}).then(r => r.json()).then(d => {
if (d.success) loadLands(document.getElementById('C'));
else alert('失败: ' + (d.error||''));
@@ -2412,7 +2412,7 @@ function deleteShopItem(id) {
fetch('api/land_api.php?action=delete_shop_item', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: 'id=' + id
+ body: 'id=' + id + '&secret=sdf1_web_comm_2026_ypshidifu'
}).then(r => r.json()).then(d => {
if (d.success) loadLands(document.getElementById('C'));
else alert('失败: ' + (d.error||''));
diff --git a/api/land_api.php b/api/land_api.php
index acc93e2..a133715 100644
--- a/api/land_api.php
+++ b/api/land_api.php
@@ -1,8 +1,8 @@
false, 'error' => 'invalid secret']);
exit;
}
+ } elseif (in_array($action, $adminActions)) {
+ // 管理面板:验证admin_token或secret
+ $adminToken = $_GET['admin_token'] ?? $_POST['admin_token'] ?? '';
+ if (!empty($adminToken)) {
+ // 用admin_token验证(admin.php的登录态)
+ $adminInfo = validateTokenSilent($adminToken);
+ if (!$adminInfo) {
+ echo json_encode(['success' => false, 'error' => '管理员认证失败', 'needLogin' => true]);
+ exit;
+ }
+ $isAdmin = true;
+ } elseif (!validateSecret($secret)) {
+ // 没有admin_token,尝试secret
+ echo json_encode(['success' => false, 'error' => '需要管理员认证']);
+ exit;
+ }
+ } elseif (in_array($action, $playerActions)) {
+ // 玩家端:需要token
+ $token = $_GET['token'] ?? '';
+ if (empty($token)) {
+ echo json_encode(['success' => false, 'error' => '需要登录', 'needLogin' => true]);
+ exit;
+ }
+ $tokenInfo = validateTokenSilent($token);
+ if (!$tokenInfo) {
+ echo json_encode(['success' => false, 'error' => 'token无效或已过期', 'needLogin' => true]);
+ exit;
+ }
+ $playerName = is_array($tokenInfo) ? $tokenInfo['player'] : $tokenInfo;
+ } else {
+ // 未知action:默认放行(部分公开GET读取)
+ if ($method === 'POST' && !validateSecret($secret)) {
+ echo json_encode(['success' => false, 'error' => 'invalid secret']);
+ exit;
+ }
}
$db = getDB();
@@ -43,6 +93,11 @@ try {
handleSyncShop($db, $_POST);
break;
+ // ===== Java端推送访客权限数据 =====
+ case 'sync_permissions':
+ handleSyncPermissions($db, $_POST);
+ break;
+
// ===== 管理面板:获取所有领地 =====
case 'list_lands':
if ($method !== 'GET') {
@@ -86,6 +141,46 @@ try {
handleDeleteShopItem($db, $_POST);
break;
+ // ===== 玩家端:我拥有的领地 =====
+ case 'my_lands':
+ handleMyLands($db, $playerName);
+ break;
+
+ // ===== 玩家端:领地详情(含访客列表)=====
+ case 'land_detail':
+ handleLandDetail($db, $playerName, $_GET['name'] ?? '');
+ break;
+
+ // ===== 玩家端:添加访客 =====
+ case 'add_visitor':
+ if ($method !== 'GET' && $method !== 'POST') {
+ echo json_encode(['success' => false, 'error' => 'GET/POST only']);
+ exit;
+ }
+ $req = $method === 'POST' ? $_POST : $_GET;
+ handleAddVisitor($db, $playerName, $req);
+ break;
+
+ // ===== 玩家端:移除访客 =====
+ case 'remove_visitor':
+ if ($method !== 'GET' && $method !== 'POST') {
+ echo json_encode(['success' => false, 'error' => 'GET/POST only']);
+ exit;
+ }
+ $req = $method === 'POST' ? $_POST : $_GET;
+ handleRemoveVisitor($db, $playerName, $req);
+ break;
+
+ // ===== 玩家端:访客列表 =====
+ case 'list_visitors':
+ handleListVisitors($db, $_GET['name'] ?? '');
+ break;
+
+ // ===== 玩家端:领地商店(购买访客权限)=====
+ case 'land_shop':
+ handleLandShop($db, $_GET['name'] ?? '');
+ break;
+
default:
echo json_encode(['success' => false, 'error' => 'unknown action']);
}
@@ -128,6 +223,20 @@ function initLandTables($db) {
key TEXT PRIMARY KEY,
value TEXT DEFAULT ''
)");
+
+ // ★ 访客权限表(Java端同步过来)
+ $db->exec("CREATE TABLE IF NOT EXISTS web_area_permissions (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ land_id INTEGER NOT NULL,
+ land_name TEXT DEFAULT '',
+ player_name TEXT NOT NULL,
+ role TEXT NOT NULL DEFAULT 'visitor',
+ permissions TEXT DEFAULT '',
+ granted_at INTEGER DEFAULT 0,
+ expires_at INTEGER DEFAULT 0,
+ synced_at INTEGER DEFAULT 0,
+ UNIQUE(land_id, player_name)
+ )");
}
function handleSyncLands($db, $post) {
@@ -280,6 +389,219 @@ function handleDeleteShopItem($db, $post) {
echo json_encode(['success' => true]);
}
+// ==================== Java端同步:访客权限 ====================
+
+function handleSyncPermissions($db, $post) {
+ $perms = json_decode($post['permissions'] ?? '[]', true);
+ if (!is_array($perms)) {
+ echo json_encode(['success' => false, 'error' => 'invalid permissions data']);
+ return;
+ }
+
+ $now = time();
+ $stmt = $db->prepare("INSERT OR REPLACE INTO web_area_permissions
+ (land_id, land_name, player_name, role, permissions, granted_at, expires_at, synced_at)
+ VALUES (:land_id, :land_name, :player, :role, :perms, :granted, :expires, :synced)");
+
+ foreach ($perms as $p) {
+ $stmt->bindValue(':land_id', (int)($p['land_id'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':land_name', $p['land_name'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':player', $p['player_name'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':role', $p['role'] ?? 'visitor', SQLITE3_TEXT);
+ $stmt->bindValue(':perms', $p['permissions'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':granted', (int)($p['granted_at'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':expires', (int)($p['expires_at'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
+ $stmt->execute();
+ }
+
+ echo json_encode(['success' => true, 'count' => count($perms)]);
+}
+
+// ==================== 玩家端:我拥有的领地 ====================
+
+function handleMyLands($db, $playerName) {
+ $stmt = $db->prepare("SELECT * FROM web_area_lands WHERE owner = :owner ORDER BY created_at DESC");
+ $stmt->bindValue(':owner', $playerName, SQLITE3_TEXT);
+ $result = $stmt->execute();
+
+ $lands = [];
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $lands[] = $row;
+ }
+
+ echo json_encode(['success' => true, 'lands' => $lands, 'player' => $playerName]);
+}
+
+// ==================== 玩家端:领地详情(含访客) ====================
+
+function handleLandDetail($db, $playerName, $landName) {
+ if (empty($landName)) {
+ echo json_encode(['success' => false, 'error' => '缺少领地名称']);
+ return;
+ }
+
+ // 获取领地信息
+ $stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
+ $stmt->bindValue(':name', $landName, SQLITE3_TEXT);
+ $result = $stmt->execute();
+ $land = $result->fetchArray(SQLITE3_ASSOC);
+
+ if (!$land) {
+ echo json_encode(['success' => false, 'error' => '领地不存在']);
+ return;
+ }
+
+ // 权限检查:只有所有者或管理员能查看详情
+ if ($land['owner'] !== $playerName) {
+ echo json_encode(['success' => false, 'error' => '你不是此领地的所有者']);
+ return;
+ }
+
+ // 获取访客列表
+ $stmt2 = $db->prepare("SELECT * FROM web_area_permissions WHERE land_id = :land_id ORDER BY granted_at DESC");
+ $stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
+ $result2 = $stmt2->execute();
+
+ $visitors = [];
+ while ($row = $result2->fetchArray(SQLITE3_ASSOC)) {
+ $visitors[] = $row;
+ }
+
+ echo json_encode(['success' => true, 'land' => $land, 'visitors' => $visitors]);
+}
+
+// ==================== 玩家端:添加访客 ====================
+
+function handleAddVisitor($db, $playerName, $req) {
+ $landName = $req['name'] ?? $req['land'] ?? '';
+ $visitor = $req['visitor'] ?? $req['player'] ?? '';
+
+ if (empty($landName) || empty($visitor)) {
+ echo json_encode(['success' => false, 'error' => '缺少领地名或玩家名']);
+ return;
+ }
+
+ // 获取领地
+ $stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
+ $stmt->bindValue(':name', $landName, SQLITE3_TEXT);
+ $result = $stmt->execute();
+ $land = $result->fetchArray(SQLITE3_ASSOC);
+
+ if (!$land) {
+ echo json_encode(['success' => false, 'error' => '领地不存在']);
+ return;
+ }
+
+ if ($land['owner'] !== $playerName) {
+ echo json_encode(['success' => false, 'error' => '只有领地所有者才能添加访客']);
+ return;
+ }
+
+ // 插入访客
+ $stmt2 = $db->prepare("INSERT OR REPLACE INTO web_area_permissions
+ (land_id, land_name, player_name, role, permissions, granted_at, expires_at, synced_at)
+ VALUES (:land_id, :land_name, :player, 'visitor', '', :now, 0, :now)");
+ $stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
+ $stmt2->bindValue(':land_name', $landName, SQLITE3_TEXT);
+ $stmt2->bindValue(':player', $visitor, SQLITE3_TEXT);
+ $stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
+ $stmt2->execute();
+
+ echo json_encode(['success' => true, 'message' => "已添加访客: $visitor"]);
+}
+
+// ==================== 玩家端:移除访客 ====================
+
+function handleRemoveVisitor($db, $playerName, $req) {
+ $landName = $req['name'] ?? $req['land'] ?? '';
+ $visitor = $req['visitor'] ?? $req['player'] ?? '';
+
+ if (empty($landName) || empty($visitor)) {
+ echo json_encode(['success' => false, 'error' => '缺少领地名或玩家名']);
+ return;
+ }
+
+ // 获取领地
+ $stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
+ $stmt->bindValue(':name', $landName, SQLITE3_TEXT);
+ $result = $stmt->execute();
+ $land = $result->fetchArray(SQLITE3_ASSOC);
+
+ if (!$land) {
+ echo json_encode(['success' => false, 'error' => '领地不存在']);
+ return;
+ }
+
+ if ($land['owner'] !== $playerName) {
+ echo json_encode(['success' => false, 'error' => '只有领地所有者才能移除访客']);
+ return;
+ }
+
+ $stmt2 = $db->prepare("DELETE FROM web_area_permissions WHERE land_id = :land_id AND player_name = :player");
+ $stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
+ $stmt2->bindValue(':player', $visitor, SQLITE3_TEXT);
+ $stmt2->execute();
+
+ echo json_encode(['success' => true, 'message' => "已移除访客: $visitor"]);
+}
+
+// ==================== 玩家端:访客列表 ====================
+
+function handleListVisitors($db, $landName) {
+ if (empty($landName)) {
+ echo json_encode(['success' => false, 'error' => '缺少领地名称']);
+ return;
+ }
+
+ $stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
+ $stmt->bindValue(':name', $landName, SQLITE3_TEXT);
+ $result = $stmt->execute();
+ $land = $result->fetchArray(SQLITE3_ASSOC);
+
+ if (!$land) {
+ echo json_encode(['success' => false, 'error' => '领地不存在']);
+ return;
+ }
+
+ $stmt2 = $db->prepare("SELECT * FROM web_area_permissions WHERE land_id = :land_id ORDER BY granted_at DESC");
+ $stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
+ $result2 = $stmt2->execute();
+
+ $visitors = [];
+ while ($row = $result2->fetchArray(SQLITE3_ASSOC)) {
+ $visitors[] = $row;
+ }
+
+ echo json_encode(['success' => true, 'visitors' => $visitors]);
+}
+
+// ==================== 玩家端:领地商店 ====================
+
+function handleLandShop($db, $landName) {
+ $sql = "SELECT * FROM web_area_shop WHERE status = 'active'";
+ $params = [];
+
+ if (!empty($landName)) {
+ $sql .= " AND land_name = :name";
+ $params[':name'] = $landName;
+ }
+ $sql .= " ORDER BY created_at DESC LIMIT 50";
+
+ $stmt = $db->prepare($sql);
+ foreach ($params as $k => $v) {
+ $stmt->bindValue($k, $v, SQLITE3_TEXT);
+ }
+ $result = $stmt->execute();
+
+ $items = [];
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $items[] = $row;
+ }
+
+ echo json_encode(['success' => true, 'items' => $items]);
+}
+
function validateSecret($secret) {
if (empty($secret)) return false;
$validSecrets = [
diff --git a/api/sync.php b/api/sync.php
index 85f4bd5..e994999 100644
--- a/api/sync.php
+++ b/api/sync.php
@@ -305,6 +305,9 @@ switch ($action) {
case 'sync_land_shop':
syncLandShop();
break;
+ case 'sync_permissions':
+ syncPermissions();
+ break;
default:
error('未知操作: ' . $action);
}
@@ -528,6 +531,50 @@ function syncLandShop() {
success("权限商店同步成功: {$count}个");
}
+// ===== 插件推送访客权限数据 =====
+function syncPermissions() {
+ $secret = getParam('secret');
+ if ($secret !== SECRET_KEY) error('密钥验证失败', 403);
+
+ $permsRaw = getParam('permissions');
+ if (!$permsRaw) error('缺少permissions参数');
+ $perms = is_array($permsRaw) ? $permsRaw : json_decode($permsRaw, true);
+ if (!is_array($perms)) error('permissions格式无效');
+
+ $db = getDB();
+ $db->exec("CREATE TABLE IF NOT EXISTS web_area_permissions (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ land_id INTEGER NOT NULL,
+ land_name TEXT DEFAULT '',
+ player_name TEXT NOT NULL,
+ role TEXT NOT NULL DEFAULT 'visitor',
+ permissions TEXT DEFAULT '',
+ granted_at INTEGER DEFAULT 0,
+ expires_at INTEGER DEFAULT 0,
+ synced_at INTEGER DEFAULT 0,
+ UNIQUE(land_id, player_name)
+ )");
+
+ $now = time();
+ $stmt = $db->prepare("INSERT OR REPLACE INTO web_area_permissions
+ (land_id, land_name, player_name, role, permissions, granted_at, expires_at, synced_at)
+ VALUES (:land_id, :land_name, :player, :role, :perms, :granted, :expires, :synced)");
+ $count = 0;
+ foreach ($perms as $p) {
+ $stmt->bindValue(':land_id', (int)($p['land_id'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':land_name', $p['land_name'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':player', $p['player_name'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':role', $p['role'] ?? 'visitor', SQLITE3_TEXT);
+ $stmt->bindValue(':perms', $p['permissions'] ?? '', SQLITE3_TEXT);
+ $stmt->bindValue(':granted', (int)($p['granted_at'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':expires', (int)($p['expires_at'] ?? 0), SQLITE3_INTEGER);
+ $stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
+ $stmt->execute();
+ $count++;
+ }
+ success("访客权限同步成功: {$count}个");
+}
+
// ===== 插件推送债券余额 =====
function syncBonds() {
// ★ 支持token或SECRET_KEY认证
diff --git a/core.php b/core.php
index 5872efa..9967dfe 100644
--- a/core.php
+++ b/core.php
@@ -37,11 +37,12 @@ function getDB() {
}
$db = new SQLite3(DB_PATH);
$db->enableExceptions(true);
- // 减少 PRAGMA 调用,避免可能的锁问题
+ // WAL模式 + 长等待超时防止database is locked
$db->exec('PRAGMA journal_mode=WAL');
- $db->exec('PRAGMA busy_timeout=15000'); // 数据库锁等待15秒
+ $db->exec('PRAGMA busy_timeout=30000'); // 30秒等待
$db->exec('PRAGMA synchronous=NORMAL');
$db->exec('PRAGMA cache_size=-64000'); // 64MB 缓存
+ $db->exec('PRAGMA wal_autocheckpoint=1000');
initTables($db);
migrateDatabase($db); // 迁移旧数据库
}
diff --git a/db/debug_ip_v2.php b/db/debug_ip_v2.php
deleted file mode 100644
index 4ac287a..0000000
--- a/db/debug_ip_v2.php
+++ /dev/null
@@ -1,615 +0,0 @@
- 0) {
- ob_end_clean();
-}
-
-// 现在开始输出HTML
-header('Content-Type: text/html; charset=utf-8');
-?>
-
-
-
-
-
IP查询调试 v2
-
-
-
-
🔍 IP查询调试页面 v2
-
-测试IP (" . count($allIps) . "个): " . implode(', ', $allIps) . "";
-
-// ===== 第1步:检查数据库中的当前状态 =====
-echo "
";
-echo "
📊 步骤1: 检查数据库当前状态 ";
-
-try {
- $db = getDB();
-
- foreach ($allIps as $ip) {
- echo "
$ip:
";
-
- $stmt = $db->prepare("SELECT * FROM player_ip_locations WHERE ip_address = :ip");
- $stmt->bindValue(':ip', $ip, SQLITE3_TEXT);
- $result = $stmt->execute();
- $row = $result->fetchArray(SQLITE3_ASSOC);
-
- if ($row) {
- echo "
" . json_encode($row, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . " ";
- } else {
- echo "
数据库中无此IP记录
";
- }
- }
-} catch (Exception $e) {
- echo "
错误: " . $e->getMessage() . "
";
-}
-echo "
";
-
-// ===== 第2步:测试4个API接口(直接调用,不包含admin.php) =====
-echo "
";
-echo "
🌐 步骤2: 测试4个API接口 ";
-
-// ★ 直接定义函数,不包含 admin.php
-function testFetchPconline($ip) {
- $url = 'https://whois.pconline.com.cn/ipJson.jsp?ip=' . urlencode($ip) . '&json=true';
- $ctx = stream_context_create([
- 'http' => [
- 'method' => 'GET',
- 'timeout' => 5,
- 'ignore_errors' => true,
- 'header' => "Accept: application/json\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 SDF1_IP_Lookup/1.0"
- ]
- ]);
- $response = @file_get_contents($url, false, $ctx);
-
- $result = [
- 'api' => 'PConline',
- 'url' => $url,
- 'raw_response' => $response ?: 'FAILED',
- 'parsed' => null,
- 'location' => null,
- 'error' => null
- ];
-
- if ($response === false || empty(trim($response))) {
- $result['error'] = '请求失败';
- return $result;
- }
-
- // ★ 修复编码问题:太平洋返回GBK编码,需要转换为UTF-8
- $response = trim($response);
- if (mb_detect_encoding($response, 'UTF-8', true) === false) {
- $response = mb_convert_encoding($response, 'UTF-8', 'GBK');
- $result['raw_response'] = $response; // 更新原始响应为转换后的
- }
-
- $json = $response;
- if (preg_match('/^\s*\(?[\'"]?(.*?)[\'"]?\)?\s*$/s', $json, $matches)) {
- $json = $matches[1];
- }
-
- $data = json_decode($json, true);
- $result['parsed'] = $data;
-
- if (!$data || !is_array($data)) {
- $result['error'] = 'JSON解析失败';
- return $result;
- }
-
- // 优先使用 addr 字段
- if (isset($data['addr']) && trim($data['addr']) !== '') {
- $addr = trim($data['addr']);
- if (strpos($addr, '中国 ') !== 0) {
- $result['location'] = $addr;
- return $result;
- }
- }
-
- // 尝试用 pro + city 手动拼接
- if (isset($data['pro']) && trim($data['pro']) !== '') {
- $loc = trim($data['pro']);
- if (isset($data['city']) && trim($data['city']) !== '') {
- $loc .= trim($data['city']);
- }
-
- if (isset($data['addr'])) {
- $parts = preg_split('/\s+/', trim($data['addr']), -1, PREG_SPLIT_NO_EMPTY);
- if (!empty($parts)) {
- $op = end($parts);
- if ($op !== '中国') {
- $loc .= ' ' . $op;
- }
- }
- }
-
- $result['location'] = strlen($loc) > 1 ? $loc : null;
- }
-
- return $result;
-}
-
-function testFetchBaidu($ip) {
- $url = 'https://opendata.baidu.com/api.php?query=' . urlencode($ip) . '&resource_id=6006&oe=utf8&format=json';
- $ctx = stream_context_create([
- 'http' => [
- 'method' => 'GET',
- 'timeout' => 5,
- 'ignore_errors' => true,
- 'header' => "Accept: application/json\r\nUser-Agent: Mozilla/5.0"
- ]
- ]);
- $response = @file_get_contents($url, false, $ctx);
-
- $result = [
- 'api' => 'Baidu',
- 'url' => $url,
- 'raw_response' => $response ?: 'FAILED',
- 'parsed' => null,
- 'location' => null,
- 'error' => null
- ];
-
- if ($response === false || empty(trim($response))) {
- $result['error'] = '请求失败';
- return $result;
- }
-
- $data = json_decode(trim($response), true);
- $result['parsed'] = $data;
-
- if ($data && isset($data['data']) && is_array($data['data']) && !empty($data['data'])) {
- $loc = $data['data'][0]['location'] ?? null;
- if ($loc && $loc !== '--' && strpos($loc, '中国 ') !== 0) {
- $result['location'] = $loc;
- }
- }
-
- return $result;
-}
-
-function testFetchAa1($ip) {
- $url = 'https://v.api.aa1.cn/api/ipcha-baidu/?ip=' . urlencode($ip);
- $ctx = stream_context_create([
- 'http' => [
- 'method' => 'GET',
- 'timeout' => 5,
- 'ignore_errors' => true,
- 'header' => "Accept: application/json\r\nUser-Agent: Mozilla/5.0"
- ]
- ]);
- $response = @file_get_contents($url, false, $ctx);
-
- $result = [
- 'api' => 'aa1.cn',
- 'url' => $url,
- 'raw_response' => $response ?: 'FAILED',
- 'parsed' => null,
- 'location' => null,
- 'error' => null
- ];
-
- if ($response === false || empty(trim($response))) {
- $result['error'] = '请求失败';
- return $result;
- }
-
- $data = json_decode(trim($response), true);
- $result['parsed'] = $data;
-
- if ($data && isset($data['code']) && $data['code'] == 1) {
- $loc = $data['ip_add'] ?? null;
- if ($loc && $loc !== '未知' && strpos($loc, '中国 ') !== 0) {
- $result['location'] = $loc;
- }
- }
-
- return $result;
-}
-
-function testFetchIp9($ip) {
- // ★ 使用正确的 API 地址:ip9.com.cn/get
- $url = 'https://ip9.com.cn/get?ip=' . urlencode($ip);
- $ctx = stream_context_create([
- 'http' => [
- 'method' => 'GET',
- 'timeout' => 5,
- 'ignore_errors' => true,
- 'header' => "Accept: application/json, text/plain, */*\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\nReferer: https://ip9.com.cn/\r\nOrigin: https://ip9.com.cn"
- ],
- 'ssl' => [
- 'verify_peer' => false,
- 'verify_peer_name' => false
- ]
- ]);
- $response = @file_get_contents($url, false, $ctx);
-
- $result = [
- 'api' => 'IP9',
- 'url' => $url,
- 'raw_response' => $response ?: 'FAILED',
- 'parsed' => null,
- 'location' => null,
- 'error' => null
- ];
-
- if ($response === false || empty(trim($response))) {
- $result['error'] = '请求失败';
- return $result;
- }
-
- $data = json_decode(trim($response), true);
- $result['parsed'] = $data;
-
- $debug = [];
-
- // IP9 返回格式有两种可能:
- // 旧版: {"ret":"200","data":[{"ip":"...","prov":"广东","city":"深圳","isp":"中国移动"}]}
- // 新版: {"ret":"200","data":{"ip":"...","prov":"广东","city":"深圳","isp":"中国移动"}}
- // ★ 兼容两种格式
- $item = null;
- if (isset($data['data'][0]) && is_array($data['data'][0])) {
- $item = $data['data'][0];
- $debug['format'] = 'array';
- } elseif (isset($data['data']) && is_array($data['data']) && isset($data['data']['prov'])) {
- $item = $data['data'];
- $debug['format'] = 'object';
- } else {
- $debug['format'] = 'unknown';
- $debug['data_type'] = gettype($data['data'] ?? null);
- }
-
- if ($item) {
- $loc = '';
- $debug['item_prov'] = $item['prov'] ?? 'NOT_SET';
- $debug['item_city'] = $item['city'] ?? 'NOT_SET';
- $debug['item_isp'] = $item['isp'] ?? 'NOT_SET';
-
- if (isset($item['prov'])) $loc .= $item['prov'];
- if (isset($item['city'])) $loc .= $item['city'];
- if (isset($item['isp'])) $loc .= ' ' . $item['isp'];
-
- $debug['loc'] = $loc;
- $debug['loc_len'] = strlen($loc);
-
- if (strlen($loc) > 0) {
- $result['location'] = $loc;
- }
- }
-
- $result['debug'] = $debug;
-
- return $result;
-}
-
-// 测试每个IP
-foreach ($allIps as $ip) {
- echo "
测试IP: $ip ";
-
- // API1: PConline
- echo "
";
- echo "
API1 - PConline:
";
- $start = microtime(true);
- $r1 = testFetchPconline($ip);
- $time1 = (microtime(true) - $start) * 1000;
- echo "
";
- echo "耗时: " . round($time1, 2) . "ms\n";
- echo "原始响应: " . substr($r1['raw_response'], 0, 500) . "\n";
- echo "解析结果: " . json_encode($r1['parsed'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
- echo "提取位置: " . ($r1['location'] ?? 'null') . "\n";
- echo "错误: " . ($r1['error'] ?? '无');
- echo " ";
- echo "
";
-
- // API2: Baidu
- echo "
";
- echo "
API2 - Baidu:
";
- $start = microtime(true);
- $r2 = testFetchBaidu($ip);
- $time2 = (microtime(true) - $start) * 1000;
- echo "
";
- echo "耗时: " . round($time2, 2) . "ms\n";
- echo "原始响应: " . substr($r2['raw_response'], 0, 500) . "\n";
- echo "解析结果: " . json_encode($r2['parsed'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
- echo "提取位置: " . ($r2['location'] ?? 'null') . "\n";
- echo "错误: " . ($r2['error'] ?? '无');
- echo " ";
- echo "
";
-
- // API3: aa1.cn
- echo "
";
- echo "
API3 - aa1.cn:
";
- $start = microtime(true);
- $r3 = testFetchAa1($ip);
- $time3 = (microtime(true) - $start) * 1000;
- echo "
";
- echo "耗时: " . round($time3, 2) . "ms\n";
- echo "原始响应: " . substr($r3['raw_response'], 0, 500) . "\n";
- echo "解析结果: " . json_encode($r3['parsed'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
- echo "提取位置: " . ($r3['location'] ?? 'null') . "\n";
- echo "错误: " . ($r3['error'] ?? '无');
- echo " ";
- echo "
";
-
- // API4: IP9
- echo "
";
- echo "
API4 - IP9:
";
- $start = microtime(true);
- $r4 = testFetchIp9($ip);
- $time4 = (microtime(true) - $start) * 1000;
- echo "
";
- echo "耗时: " . round($time4, 2) . "ms\n";
- echo "原始响应: " . substr($r4['raw_response'], 0, 500) . "\n";
- echo "解析结果: " . json_encode($r4['parsed'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
- echo "提取位置: " . ($r4['location'] ?? 'null') . "\n";
- echo "错误: " . ($r4['error'] ?? '无') . "\n";
- echo "调试信息: " . json_encode($r4['debug'] ?? [], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- echo " ";
- echo "
";
-
- echo "
";
-}
-echo "
";
-
-// ===== 第3步:模拟入库逻辑 =====
-echo "
";
-echo "
🔄 步骤3: 模拟入库逻辑 ";
-
-echo "
以下是服务器处理IP归属地的完整流程:
";
-
-// 模拟 queryIpLocation 函数的逻辑
-foreach ($allIps as $ip) {
- echo "
模拟IP: $ip ";
-
- echo "
1. 检查是否是内网IP
";
- if (!$ip || $ip === '-' || $ip === '127.0.0.1' || strpos($ip, '10.') === 0 || strpos($ip, '192.168.') === 0) {
- echo "
是内网IP,返回 '内网IP'
";
- continue;
- }
- echo "
不是内网IP,继续查询
";
-
- echo "
2. 检查数据库缓存
";
- $stmt = $db->prepare("SELECT location FROM player_ip_locations WHERE ip_address = :ip LIMIT 1");
- $stmt->bindValue(':ip', $ip, SQLITE3_TEXT);
- $result = $stmt->execute();
- $row = $result->fetchArray(SQLITE3_ASSOC);
-
- if ($row && isset($row['location'])) {
- $cachedLoc = $row['location'];
- echo "
找到缓存: $cachedLoc
";
-
- // ★ 使用统一校验函数
- $isValidFormat = isValidIpLocationFormat($cachedLoc);
- if (!$isValidFormat) {
- echo "
无效格式: " . htmlspecialchars($cachedLoc) . "
";
- }
-
- echo "
格式验证结果: " . ($isValidFormat ? "✅ 有效" : "❌ 无效") . "
";
-
- if (!$isValidFormat) {
- echo "
将删除此缓存并重新查询
";
- $stmt2 = $db->prepare("DELETE FROM player_ip_locations WHERE ip_address = :ip");
- $stmt2->bindValue(':ip', $ip, SQLITE3_TEXT);
- $stmt2->execute();
- } else {
- echo "
格式有效,直接使用缓存
";
- continue;
- }
- } else {
- echo "
无缓存,需要查询
";
- }
-
- echo "
3. 调用API查询(格式无效则继续下一个)
";
-
- // ★ 与 admin.php 的 doQueryIpLocation() 一致:每个API返回后先验证格式
- $location = null;
- $bestResult = null;
-
- // API1: PConline
- echo "
尝试 API1 (PConline)...
";
- $r1 = testFetchPconline($ip);
- if ($r1['location']) {
- echo "
返回: " . htmlspecialchars($r1['location']) . "
";
- if (isValidIpLocationFormat($r1['location'])) {
- $location = $r1['location'];
- echo "
✅ 格式有效: $location
";
- } else {
- echo "
⚠️ 格式无效,继续尝试下一个API
";
- $bestResult = $r1['location'];
- }
- } else {
- echo "
❌ API1 无结果
";
- }
-
- // API2: Baidu(仅当前面没有有效结果时才尝试)
- if (!$location) {
- echo "
尝试 API2 (Baidu)...
";
- $r2 = testFetchBaidu($ip);
- if ($r2['location']) {
- echo "
返回: " . htmlspecialchars($r2['location']) . "
";
- if (isValidIpLocationFormat($r2['location'])) {
- $location = $r2['location'];
- echo "
✅ 格式有效: $location
";
- } else {
- echo "
⚠️ 格式无效,继续尝试下一个API
";
- if (!$bestResult) $bestResult = $r2['location'];
- }
- } else {
- echo "
❌ API2 无结果
";
- }
- }
-
- // API3: aa1.cn(仅当前面没有有效结果时才尝试)
- if (!$location) {
- echo "
尝试 API3 (aa1.cn)...
";
- $r3 = testFetchAa1($ip);
- if ($r3['location']) {
- echo "
返回: " . htmlspecialchars($r3['location']) . "
";
- if (isValidIpLocationFormat($r3['location'])) {
- $location = $r3['location'];
- echo "
✅ 格式有效: $location
";
- } else {
- echo "
⚠️ 格式无效,继续尝试下一个API
";
- if (!$bestResult) $bestResult = $r3['location'];
- }
- } else {
- echo "
❌ API3 无结果
";
- }
- }
-
- // API4: IP9(仅当前面没有有效结果时才尝试)
- if (!$location) {
- echo "
尝试 API4 (IP9)...
";
- $r4 = testFetchIp9($ip);
- if ($r4['location']) {
- echo "
返回: " . htmlspecialchars($r4['location']) . "
";
- if (isValidIpLocationFormat($r4['location'])) {
- $location = $r4['location'];
- echo "
✅ 格式有效: $location
";
- } else {
- echo "
⚠️ 格式无效,继续尝试下一个API
";
- if (!$bestResult) $bestResult = $r4['location'];
- }
- } else {
- echo "
❌ API4 无结果
";
- }
- }
-
- // 所有API都没返回有效格式:标记为查询失败
- if (!$location) {
- $location = '查询失败';
- echo '
❌ 所有API均失败或格式无效,标记为"查询失败"
';
- }
-
- echo "
4. 格式验证和修复(与 admin.php 一致)
";
- echo "
原始位置: $location
";
-
- // 格式修复逻辑
- if ($location && $location !== '查询失败') {
- $loc = $location;
-
- // 1. 过滤英文格式
- if (strpos($loc, 'China - ') === 0 || strpos($loc, 'China -') === 0) {
- echo "
拒绝英文格式: $loc
";
- $location = null;
- }
-
- // 2. 过滤不完整的中文格式
- if (strpos($loc, '中国 - -') === 0 || strpos($loc, '中国 - -') === 0) {
- echo "
拒绝不完整格式: $loc
";
- $location = null;
- }
-
- // 3. 格式修复:确保"位置"和"运营商"之间有空格
- // 仅在没有空格时修复(如"广东省移动"→"广东省 移动")
- if ($location && strpos($loc, ' ') === false) {
- // 无空格,尝试添加空格
- if (preg_match('/^(.*?(?:省|市|州|盟|地区|区|县))(.+)$/u', $loc, $matches)) {
- $loc = $matches[1] . ' ' . $matches[2];
- echo "
格式修复: $location => $loc
";
- $location = $loc;
- }
- }
-
- // ★ 4. 存储前格式验证(使用统一校验函数)
- if ($location) {
- $shouldStore = isValidIpLocationFormat($location);
- if (!$shouldStore) {
- echo "
格式无效(不满足入库条件): " . htmlspecialchars($location) . "
";
- $location = null;
- }
- }
- }
-
- echo "
5. 存入数据库
";
- if ($location && $location !== '查询失败') {
- echo "
将存储: $location
";
-
- // 模拟存储(不实际执行)
- echo "
INSERT OR REPLACE INTO player_ip_locations (player_name, ip_address, location, updated_at) VALUES ('global', '$ip', '$location', " . time() . ") ";
- } else {
- echo "
不存储(查询失败或格式无效)
";
- }
-
- echo "
";
-}
-echo "
";
-
-// ===== 第4步:显示所有无效格式的记录 =====
-echo "
";
-echo "
❌ 步骤4: 显示所有无效格式的记录 ";
-
-$invalidStmt = $db->query("
- SELECT ip_address, location
- FROM player_ip_locations
- WHERE location LIKE 'China%'
- OR location = '-'
- OR location = '--'
- OR location = '查询失败'
- OR location = ''
- OR ip_address LIKE '127.%'
- OR ip_address LIKE '192.168.%'
- OR ip_address LIKE '10.%'
- OR location LIKE '中国 - -%'
- OR location LIKE '中国 - -%'
- OR (location NOT LIKE '% %' AND (location LIKE '%省%' OR location LIKE '%市%'))
-");
-$invalidCount = 0;
-while ($row = $invalidStmt->fetchArray(SQLITE3_ASSOC)) {
- echo "
" . $row['ip_address'] . " => " . $row['location'] . "
";
- $invalidCount++;
-}
-
-if ($invalidCount == 0) {
- echo "
没有发现无效格式的记录
";
-} else {
- echo "
发现 $invalidCount 条无效格式的记录
";
-}
-echo "
";
-
-echo "
";
-echo "
💡 此页面用于调试IP查询流程,检查每个API的返回结果和入库逻辑。
";
-?>
-
-
-
diff --git a/debug_admin_api.php b/debug_admin_api.php
deleted file mode 100644
index 3dfbc7f..0000000
--- a/debug_admin_api.php
+++ /dev/null
@@ -1,144 +0,0 @@
- 0) { @ob_end_clean(); }
-header('Content-Type: text/plain; charset=utf-8');
-error_reporting(E_ALL);
-ini_set('display_errors', '1');
-
-echo "=== Admin API Diagnostic ===\n\n";
-
-require_once __DIR__ . '/core.php';
-
-echo "1. Core loaded: " . (function_exists('getDB') ? 'YES' : 'NO') . "\n";
-echo "2. ADMIN_PASS defined: " . (defined('ADMIN_PASS') ? 'YES' : 'NO') . "\n";
-
-// Test database connection
-echo "\n--- Database ---\n";
-try {
- $db = getDB();
- echo "DB opened OK\n";
-
- // List all tables
- $result = $db->query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
- $tables = [];
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $tables[] = $row['name'];
- }
- echo "Tables (" . count($tables) . "): " . implode(', ', $tables) . "\n";
-
- // Check player_ip_changes structure
- if (in_array('player_ip_changes', $tables)) {
- $result = $db->query("PRAGMA table_info(player_ip_changes)");
- echo "\nplayer_ip_changes columns:\n";
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- echo " - {$row['name']} ({$row['type']}) PK=" . ($row['pk'] ? 'YES' : 'no') . "\n";
- }
- $cnt = $db->query("SELECT COUNT(*) as c FROM player_ip_changes")->fetchArray(SQLITE3_ASSOC)['c'];
- echo " Rows: $cnt\n";
- }
-
- // Check player_ip_locations structure
- if (in_array('player_ip_locations', $tables)) {
- $result = $db->query("PRAGMA table_info(player_ip_locations)");
- echo "\nplayer_ip_locations columns:\n";
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- echo " - {$row['name']} ({$row['type']}) PK=" . ($row['pk'] ? 'YES' : 'no') . "\n";
- }
- $cnt = $db->query("SELECT COUNT(*) as c FROM player_ip_locations")->fetchArray(SQLITE3_ASSOC)['c'];
- echo " Rows: $cnt\n";
- // Show a sample
- $result = $db->query("SELECT ip_address, location FROM player_ip_locations LIMIT 5");
- if ($result) {
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- echo " Sample: {$row['ip_address']} => {$row['location']}\n";
- }
- }
- }
-
- // Check users table
- if (in_array('users', $tables)) {
- $cnt = $db->query("SELECT COUNT(*) as c FROM users")->fetchArray(SQLITE3_ASSOC)['c'];
- echo "\nusers rows: $cnt\n";
- }
-
- // Check online_players
- if (in_array('online_players', $tables)) {
- $cnt = $db->query("SELECT COUNT(*) as c FROM online_players")->fetchArray(SQLITE3_ASSOC)['c'];
- echo "online_players rows: $cnt\n";
- }
-
- // Check web_session_log
- if (in_array('web_session_log', $tables)) {
- $cnt = $db->query("SELECT COUNT(*) as c FROM web_session_log")->fetchArray(SQLITE3_ASSOC)['c'];
- echo "web_session_log rows: $cnt\n";
- }
-
- // Test batchGetPlayerIps
- echo "\n--- Test batchGetPlayerIps ---\n";
- if (function_exists('batchGetPlayerIps') || class_exists('adminListUsersPaginated')) {
- echo "batchGetPlayerIps function NOT available in this scope\n";
- } else {
- echo "Function not available (defined in api/admin.php, not core.php)\n";
- }
-
- // Test simple queries that adminListUsersPaginated would run
- echo "\n--- Test Query Simulation ---\n";
- try {
- $sql = "SELECT player_name, register_time, last_login_time, points, total_online_time, email FROM users ORDER BY register_time DESC LIMIT 10 OFFSET 0";
- $result = $db->query($sql);
- $count = 0;
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $count++;
- }
- echo "Users query OK, got $count rows\n";
- } catch (\Throwable $e) {
- echo "Users query FAILED: " . $e->getMessage() . "\n";
- }
-
- // Test player_ip_changes query
- try {
- $stmt = $db->prepare("SELECT player_name, new_ip AS ip_address FROM player_ip_changes WHERE player_name IN ('test') ORDER BY changed_at DESC");
- echo "player_ip_changes simple query: OK (prepared)\n";
- } catch (\Throwable $e) {
- echo "player_ip_changes query FAILED: " . $e->getMessage() . "\n";
- }
-
- // Test player_ip_locations query
- try {
- $stmt = $db->prepare("SELECT ip_address, location FROM player_ip_locations WHERE ip_address IN ('1.2.3.4')");
- echo "player_ip_locations query: OK (prepared)\n";
- } catch (\Throwable $e) {
- echo "player_ip_locations query FAILED: " . $e->getMessage() . "\n";
- }
-
- // Test location search query
- try {
- $stmt = $db->prepare("SELECT player_name, new_ip FROM player_ip_changes WHERE new_ip != '' AND new_ip != '-' AND new_ip NOT LIKE '10.%' ORDER BY changed_at DESC LIMIT 5");
- $result = $stmt->execute();
- $ips = [];
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $ips[] = $row['new_ip'];
- }
- echo "Location search pre-query: OK, got " . count($ips) . " IPs\n";
- } catch (\Throwable $e) {
- echo "Location search pre-query FAILED: " . $e->getMessage() . "\n";
- }
-
- // Test region search SQL
- try {
- $searchBound = "%长沙%";
- $stmt = $db->prepare("SELECT COUNT(*) as cnt FROM player_ip_locations WHERE location LIKE :region");
- $stmt->bindValue(':region', $searchBound, SQLITE3_TEXT);
- $cnt = $stmt->execute()->fetchArray(SQLITE3_ASSOC)['cnt'];
- echo "Region cache check: $cnt matching records\n";
- } catch (\Throwable $e) {
- echo "Region cache check FAILED: " . $e->getMessage() . "\n";
- }
-
-} catch (\Throwable $e) {
- echo "\nFATAL ERROR: " . $e->getMessage() . "\n";
- echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
- echo "Stack: " . $e->getTraceAsString() . "\n";
-}
-
-echo "\n=== Done ===\n";
diff --git a/debug_region.php b/debug_region.php
deleted file mode 100644
index cc2d23e..0000000
--- a/debug_region.php
+++ /dev/null
@@ -1,142 +0,0 @@
-调试地区搜索";
-
-// 1. player_ip_locations 表内容
-echo "
player_ip_locations 表 ";
-try {
- $result = $db->query("SELECT COUNT(*) as cnt FROM player_ip_locations");
- $row = $result->fetchArray(SQLITE3_ASSOC);
- echo "总记录数: " . $row['cnt'] . "
";
-
- if ($row['cnt'] > 0) {
- echo "
";
- echo "ip_address location player_name updated_at ";
- $result = $db->query("SELECT * FROM player_ip_locations ORDER BY updated_at DESC LIMIT 20");
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- echo "";
- echo "" . htmlspecialchars($r['ip_address']) . " ";
- echo "" . htmlspecialchars($r['location']) . " ";
- echo "" . htmlspecialchars($r['player_name']) . " ";
- echo "" . date('Y-m-d H:i:s', $r['updated_at']) . " ";
- echo " ";
- }
- echo "
";
- } else {
- echo "表为空!
";
- }
-} catch (Exception $e) {
- echo "错误: " . $e->getMessage() . "
";
-}
-
-// 2. player_ip_changes 表内容
-echo "
player_ip_changes 表 ";
-try {
- $result = $db->query("SELECT COUNT(*) as cnt FROM player_ip_changes");
- $row = $result->fetchArray(SQLITE3_ASSOC);
- echo "总记录数: " . $row['cnt'] . "
";
-
- if ($row['cnt'] > 0) {
- echo "
";
- echo "player_name new_ip changed_at ";
- $result = $db->query("SELECT * FROM player_ip_changes ORDER BY changed_at DESC LIMIT 20");
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- echo "";
- echo "" . htmlspecialchars($r['player_name']) . " ";
- echo "" . htmlspecialchars($r['new_ip']) . " ";
- echo "" . date('Y-m-d H:i:s', $r['changed_at']) . " ";
- echo " ";
- }
- echo "
";
- } else {
- echo "表为空!
";
- }
-} catch (Exception $e) {
- echo "错误: " . $e->getMessage() . "
";
-}
-
-// 3. 地区搜索SQL测试
-echo "
地区搜索SQL测试 ";
-$search = '长沙';
-$regionKey = $search;
-
-// 测试JOIN
-$sql = "SELECT c.player_name FROM player_ip_changes c
- INNER JOIN player_ip_locations l ON c.new_ip = l.ip_address
- WHERE l.location LIKE '%$regionKey%' AND c.new_ip != '' AND c.new_ip != '-'
- GROUP BY c.player_name";
-echo "SQL:
" . htmlspecialchars($sql) . " ";
-
-try {
- $result = $db->query($sql);
- $count = 0;
- $players = [];
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- $count++;
- $players[] = $r['player_name'];
- }
- echo "匹配玩家数: $count
";
- if ($count > 0) {
- echo "匹配玩家: " . implode(', ', $players) . "
";
- }
-} catch (Exception $e) {
- echo "SQL错误: " . $e->getMessage() . "
";
-}
-
-// 4. 检查所有location中包含"长沙"的
-echo "
包含'长沙'的location记录 ";
-try {
- $result = $db->query("SELECT ip_address, location, player_name FROM player_ip_locations WHERE location LIKE '%长沙%'");
- $found = false;
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- $found = true;
- echo "IP: " . htmlspecialchars($r['ip_address']) . " → " . htmlspecialchars($r['location']) . "
";
- }
- if (!$found) {
- echo "没有找到包含'长沙'的location记录
";
- }
-} catch (Exception $e) {
- echo "错误: " . $e->getMessage() . "
";
-}
-
-// 5. 检查users表中是否有来自长沙的玩家(通过IP推断)
-echo "
测试:直接搜索users表 ";
-try {
- $result = $db->query("SELECT player_name, email FROM users LIMIT 10");
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- echo "玩家: " . htmlspecialchars($r['player_name']) . " (email: " . htmlspecialchars($r['email']) . ")
";
- }
-} catch (Exception $e) {
- echo "错误: " . $e->getMessage() . "
";
-}
-
-// 6. 尝试手动查询一个IP的归属地
-echo "
测试:手动查询IP归属地 ";
-try {
- $result = $db->query("SELECT DISTINCT new_ip FROM player_ip_changes WHERE new_ip != '' AND new_ip != '-' AND new_ip NOT LIKE '10.%' AND new_ip NOT LIKE '192.168.%' AND new_ip != '127.0.0.1' LIMIT 5");
- $ips = [];
- while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
- $ips[] = $r['new_ip'];
- }
-
- if (!empty($ips)) {
- echo "测试查询以下IP的归属地: " . implode(', ', $ips) . "
";
- require_once 'api/admin.php';
- foreach ($ips as $ip) {
- $loc = queryIpLocationWithTimeout($ip, 3);
- echo "IP: $ip → " . ($loc ? $loc['location'] : 'null') . "
";
- }
- } else {
- echo "没有找到可用的外部IP
";
- }
-} catch (Exception $e) {
- echo "错误: " . $e->getMessage() . "
";
-}
-?>
diff --git a/debug_region_search.php b/debug_region_search.php
deleted file mode 100644
index 82a838d..0000000
--- a/debug_region_search.php
+++ /dev/null
@@ -1,167 +0,0 @@
- 0) { @ob_end_clean(); }
-header('Content-Type: text/plain; charset=utf-8');
-error_reporting(E_ALL);
-ini_set('display_errors', '1');
-
-echo "=== Region Search Debug v2 ===\n\n";
-
-require_once __DIR__ . '/core.php';
-
-try {
- $db = getDB();
- echo "DB opened OK\n\n";
-
- // 1. Check tables and data
- echo "--- Data Overview ---\n";
- $tables = ['users', 'player_ip_changes', 'player_ip_locations', 'web_session_log'];
- foreach ($tables as $table) {
- try {
- $cnt = $db->query("SELECT COUNT(*) as c FROM $table")->fetchArray(SQLITE3_ASSOC)['c'];
- echo "$table: $cnt rows\n";
- } catch (\Throwable $e) {
- echo "$table: ERROR - " . $e->getMessage() . "\n";
- }
- }
-
- // 2. Check player_ip_locations samples
- echo "\n--- player_ip_locations Samples ---\n";
- try {
- $result = $db->query("SELECT ip_address, location, player_name FROM player_ip_locations LIMIT 10");
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- echo " {$row['ip_address']} => '{$row['location']}' (player: {$row['player_name']})\n";
- }
- } catch (\Throwable $e) {
- echo " ERROR: " . $e->getMessage() . "\n";
- }
-
- // 3. Test region search for "长沙"
- $regionKey = '长沙';
- echo "\n--- Testing Region Search: '$regionKey' ---\n";
-
- // Step 1: Find IPs with matching location
- try {
- $locStmt = $db->prepare("SELECT ip_address FROM player_ip_locations WHERE location LIKE :region");
- $locStmt->bindValue(':region', "%$regionKey%", SQLITE3_TEXT);
- $locResult = $locStmt->execute();
- $matchedIps = [];
- while ($locRow = $locResult->fetchArray(SQLITE3_ASSOC)) {
- $matchedIps[] = $locRow['ip_address'];
- }
- echo "Step 1: Found " . count($matchedIps) . " IPs with '$regionKey' location\n";
- if (!empty($matchedIps)) {
- echo " IPs: " . implode(', ', array_slice($matchedIps, 0, 10)) . "\n";
- }
- } catch (\Throwable $e) {
- echo "Step 1 ERROR: " . $e->getMessage() . "\n";
- $matchedIps = [];
- }
-
- // Step 2: Find players using those IPs
- $matchedPlayers = [];
- if (!empty($matchedIps)) {
- $ipPlaceholders = implode(',', array_fill(0, count($matchedIps), '?'));
-
- // From player_ip_changes
- try {
- $pStmt = $db->prepare("SELECT DISTINCT player_name FROM player_ip_changes WHERE new_ip IN ($ipPlaceholders)");
- $idx = 1;
- foreach ($matchedIps as $ip) { $pStmt->bindValue($idx++, $ip, SQLITE3_TEXT); }
- $pResult = $pStmt->execute();
- while ($pRow = $pResult->fetchArray(SQLITE3_ASSOC)) {
- $matchedPlayers[] = $pRow['player_name'];
- }
- echo "Step 2a (player_ip_changes): Found " . count($matchedPlayers) . " players\n";
- } catch (\Throwable $e) {
- echo "Step 2a ERROR: " . $e->getMessage() . "\n";
- }
-
- // From web_session_log
- try {
- $wStmt = $db->prepare("SELECT DISTINCT player_name FROM web_session_log WHERE ip_address IN ($ipPlaceholders)");
- $idx = 1;
- foreach ($matchedIps as $ip) { $wStmt->bindValue($idx++, $ip, SQLITE3_TEXT); }
- $wResult = $wStmt->execute();
- $beforeCount = count($matchedPlayers);
- while ($wRow = $wResult->fetchArray(SQLITE3_ASSOC)) {
- if (!in_array($wRow['player_name'], $matchedPlayers)) {
- $matchedPlayers[] = $wRow['player_name'];
- }
- }
- echo "Step 2b (web_session_log): Found " . (count($matchedPlayers) - $beforeCount) . " new players\n";
- } catch (\Throwable $e) {
- echo "Step 2b ERROR: " . $e->getMessage() . "\n";
- }
-
- echo "Step 2 Total: " . count($matchedPlayers) . " players\n";
- if (!empty($matchedPlayers)) {
- echo " Players: " . implode(', ', array_slice($matchedPlayers, 0, 10)) . "\n";
- }
- }
-
- // Step 3: Query users
- if (!empty($matchedPlayers)) {
- $namePlaceholders = implode(',', array_fill(0, count($matchedPlayers), '?'));
- try {
- $uStmt = $db->prepare("SELECT player_name, email FROM users WHERE player_name IN ($namePlaceholders) LIMIT 5");
- $idx = 1;
- foreach ($matchedPlayers as $pn) { $uStmt->bindValue($idx++, $pn, SQLITE3_TEXT); }
- $uResult = $uStmt->execute();
- $users = [];
- while ($uRow = $uResult->fetchArray(SQLITE3_ASSOC)) {
- $users[] = $uRow;
- }
- echo "Step 3: Found " . count($users) . " users\n";
- foreach ($users as $u) {
- echo " {$u['player_name']} ({$u['email']})\n";
- }
- } catch (\Throwable $e) {
- echo "Step 3 ERROR: " . $e->getMessage() . "\n";
- }
- } else {
- echo "Step 3: Skipped (no matching players)\n";
- }
-
- // 4. Also check what batchGetPlayerIps would return for all users
- echo "\n--- batchGetPlayerIps Check ---\n";
- try {
- $allUsers = $db->query("SELECT player_name FROM users")->fetchArray(SQLITE3_ASSOC);
- // Get a sample user
- $sampleUser = 'youpaishidifu';
- $stmt = $db->prepare("SELECT player_name, new_ip FROM player_ip_changes WHERE player_name = :name ORDER BY changed_at DESC LIMIT 1");
- $stmt->bindValue(':name', $sampleUser, SQLITE3_TEXT);
- $result = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
- if ($result) {
- echo "$sampleUser IP from player_ip_changes: {$result['new_ip']}\n";
- // Check if this IP has location
- $locStmt = $db->prepare("SELECT location FROM player_ip_locations WHERE ip_address = :ip");
- $locStmt->bindValue(':ip', $result['new_ip'], SQLITE3_TEXT);
- $locResult = $locStmt->execute()->fetchArray(SQLITE3_ASSOC);
- echo " Location: " . ($locResult ? $locResult['location'] : 'NOT FOUND') . "\n";
- } else {
- echo "$sampleUser NOT in player_ip_changes\n";
- // Check web_session_log
- $stmt2 = $db->prepare("SELECT ip_address FROM web_session_log WHERE player_name = :name ORDER BY login_time DESC LIMIT 1");
- $stmt2->bindValue(':name', $sampleUser, SQLITE3_TEXT);
- $result2 = $stmt2->execute()->fetchArray(SQLITE3_ASSOC);
- if ($result2) {
- echo "$sampleUser IP from web_session_log: {$result2['ip_address']}\n";
- $locStmt2 = $db->prepare("SELECT location FROM player_ip_locations WHERE ip_address = :ip");
- $locStmt2->bindValue(':ip', $result2['ip_address'], SQLITE3_TEXT);
- $locResult2 = $locStmt2->execute()->fetchArray(SQLITE3_ASSOC);
- echo " Location: " . ($locResult2 ? $locResult2['location'] : 'NOT FOUND') . "\n";
- } else {
- echo "$sampleUser NOT in web_session_log either\n";
- }
- }
- } catch (\Throwable $e) {
- echo "batchGetPlayerIps check ERROR: " . $e->getMessage() . "\n";
- }
-
-} catch (\Throwable $e) {
- echo "\nFATAL ERROR: " . $e->getMessage() . "\n";
-}
-
-echo "\n=== Done ===\n";
-?>
\ No newline at end of file
diff --git a/player.php b/player.php
index c849191..26d0f70 100644
--- a/player.php
+++ b/player.php
@@ -222,6 +222,34 @@ if ($currentVersion !== $BUILD_VERSION) {
from { opacity: 0; transform: translateY(30px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
+ /* ★ 毛玻璃Alert弹窗 */
+ .glass-alert-overlay {
+ display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0,0,0,0.45);
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
+ z-index: 5000; justify-content: center; align-items: center;
+ animation: glassFadeIn 0.25s ease;
+ }
+ .glass-alert-overlay.show { display: flex; }
+ .glass-alert-card {
+ background: rgba(22,27,34,0.92);
+ backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
+ border: 1px solid rgba(88,166,255,0.25);
+ border-radius: 16px; padding: 28px 28px 20px; width: 360px; max-width: 88%;
+ box-shadow: 0 12px 48px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
+ animation: glassSlideUp 0.3s ease;
+ text-align: center;
+ }
+ .glass-alert-card .alert-icon { font-size: 36px; margin-bottom: 12px; }
+ .glass-alert-card .alert-msg { font-size: 14px; color: #e6edf3; line-height: 1.6; margin-bottom: 20px; word-break: break-word; }
+ .glass-alert-card .alert-btns { display: flex; gap: 10px; justify-content: center; }
+ .glass-alert-card .alert-btns button {
+ padding: 8px 24px; border: none; border-radius: 8px; font-size: 13px; font-weight: 600;
+ cursor: pointer; transition: all 0.2s;
+ }
+ .glass-alert-card .alert-btns .ag-ok { background: #58a6ff; color: #fff; }
+ .glass-alert-card .alert-btns .ag-cancel { background: rgba(255,255,255,0.08); color: #8b949e; }
+ .glass-alert-card .alert-btns button:hover { opacity: 0.85; transform: scale(1.03); }
@media (max-width: 768px) {
.sidebar { display: none; }
.grid { grid-template-columns: 1fr; }
@@ -240,8 +268,9 @@ if ($currentVersion !== $BUILD_VERSION) {
+
+
+
@@ -655,7 +695,7 @@ if ($currentVersion !== $BUILD_VERSION) {
if (data.data.need_password_change === 1) {
document.getElementById('playerInfo').textContent = '已登录: ' + currentPlayer + ' ⚠️ 请使用临时密码登录后立即修改密码';
setTimeout(() => {
- alert('您正在使用临时密码登录。为了您的账号安全,请在登录后立即修改密码!');
+ glassAlert('您正在使用临时密码登录。为了您的账号安全,请在登录后立即修改密码!', '🔐');
}, 500);
} else {
document.getElementById('playerInfo').textContent = '已登录: ' + currentPlayer;
@@ -980,6 +1020,7 @@ if ($currentVersion !== $BUILD_VERSION) {
if (!c) return;
if (page === 'dashboard') renderDashboard(c);
else if (page === 'shop') renderShop(c);
+ else if (page === 'lands') renderLands(c);
else if (page === 'cdk') renderCDK(c);
else if (page === 'balance') renderBalance(c);
else if (page === 'account') renderAccount(c);
@@ -1366,6 +1407,31 @@ if ($currentVersion !== $BUILD_VERSION) {
setTimeout(() => t.remove(), 3000);
}
+ // ★ 毛玻璃Alert/Confirm弹窗(替代原生alert/confirm)
+ let _glassAlertResolve = null;
+ function glassAlertResolve(val) {
+ document.getElementById('glassAlertOverlay').classList.remove('show');
+ if (_glassAlertResolve) { _glassAlertResolve(val); _glassAlertResolve = null; }
+ }
+ function glassAlert(msg, icon = '⚠️') {
+ return new Promise(resolve => {
+ _glassAlertResolve = resolve;
+ document.getElementById('glassAlertIcon').textContent = icon;
+ document.getElementById('glassAlertMsg').textContent = msg;
+ document.getElementById('glassAlertBtns').innerHTML = '
确定 ';
+ document.getElementById('glassAlertOverlay').classList.add('show');
+ });
+ }
+ function glassConfirm(msg, icon = '❓') {
+ return new Promise(resolve => {
+ _glassAlertResolve = resolve;
+ document.getElementById('glassAlertIcon').textContent = icon;
+ document.getElementById('glassAlertMsg').textContent = msg;
+ document.getElementById('glassAlertBtns').innerHTML = '
取消 确定 ';
+ document.getElementById('glassAlertOverlay').classList.add('show');
+ });
+ }
+
// ===== 主题切换 =====
function toggleLightTheme() {
document.body.classList.toggle('light-theme');
@@ -1830,7 +1896,7 @@ if ($currentVersion !== $BUILD_VERSION) {
}
async function withdrawTicket(id) {
- if (!confirm('确定要撤销此工单吗?')) return;
+ if (!await glassConfirm('确定要撤销此工单吗?')) return;
try {
const url = new URL(API + 'ticket.php', location.href);
url.searchParams.set('action', 'withdraw');
@@ -1842,10 +1908,10 @@ if ($currentVersion !== $BUILD_VERSION) {
ticketState.view = 'list';
renderTicket(document.getElementById('content'));
} else {
- alert(data.message);
+ glassAlert(data.message);
}
} catch (e) {
- alert('撤销失败: ' + e.message);
+ glassAlert('撤销失败: ' + e.message);
}
}
@@ -1994,6 +2060,250 @@ if ($currentVersion !== $BUILD_VERSION) {
} catch(e) { console.error('[MD_RENDER] Error:', e); return escHtml(text).replace(/\n/g, '
'); }
}
+// ==================== 领地管理 ====================
+
+let landsState = { view: 'list', currentLand: null };
+
+async function renderLands(el) {
+ if (!TOKEN) {
+ el.innerHTML = `
+
🏡
+
领地管理
+
请先登录以管理你的领地
+
🔑 登录
+
`;
+ return;
+ }
+
+ if (landsState.view === 'detail' && landsState.currentLand) {
+ await renderLandDetail(el, landsState.currentLand);
+ return;
+ }
+
+ // 加载我的领地列表
+ el.innerHTML = `
`;
+ try {
+ const url = new URL(API + 'land_api.php', location.href);
+ url.searchParams.set('action', 'my_lands');
+ url.searchParams.set('token', TOKEN);
+ const res = await fetch(url);
+ const data = await res.json();
+
+ if (data.needLogin) {
+ el.innerHTML = `
+
🏡
+
领地管理
+
请先登录以管理你的领地
+
🔑 登录
+
`;
+ return;
+ }
+
+ if (!data.success) {
+ el.innerHTML = `
`;
+ return;
+ }
+
+ const lands = data.lands || [];
+ let html = `
+
+
🏡 我的领地 (${lands.length})
+ 玩家: ${escHtml(data.player)}
+ `;
+
+ if (lands.length === 0) {
+ html += `
+
🏗️
+
你还没有领地
+
在游戏中输入 /protect create <领地名> 来创建领地
+
`;
+ } else {
+ html += `
`;
+ for (const land of lands) {
+ const areaSize = land.area_size || 0;
+ const createdAt = land.created_at ? new Date(land.created_at * 1000).toLocaleDateString('zh-CN') : '未知';
+ const coordStr = `(${land.x1},${land.z1}) → (${land.x2},${land.z2})`;
+ html += `
+
+
📍 ${escHtml(land.name)}
+ ✦ 所有者
+
+
🌍 ${escHtml(land.world || '未知')}
+
📐 ${coordStr}
+
+ 📏 ${areaSize} 格²
+ 📅 ${createdAt}
+
+
`;
+ }
+ html += `
`;
+ }
+ html += `
`;
+
+ // 领地商店
+ html += `
+
🛍️ 领地权限商店 `;
+ try {
+ const shopUrl = new URL(API + 'land_api.php', location.href);
+ shopUrl.searchParams.set('action', 'land_shop');
+ const shopRes = await fetch(shopUrl);
+ const shopData = await shopRes.json();
+ if (shopData.success && shopData.items && shopData.items.length > 0) {
+ html += `
+ 领地 卖家 权限 价格(债券) 时长 操作
+ `;
+ for (const item of shopData.items) {
+ const dur = item.duration >= 86400 ? Math.floor(item.duration/86400)+'天' : Math.floor(item.duration/3600)+'小时';
+ html += `
+ ${escHtml(item.land_name)}
+ ${escHtml(item.seller)}
+ ${escHtml(item.permission)}
+ ${item.price}
+ ${dur}
+ 购买
+ `;
+ }
+ html += `
`;
+ } else {
+ html += `
暂无在售的领地权限
`;
+ }
+ } catch (e) {
+ html += `
商店加载失败
`;
+ }
+ html += `
`;
+
+ el.innerHTML = html;
+ } catch (e) {
+ el.innerHTML = `
`;
+ }
+}
+
+async function renderLandDetail(el, landName) {
+ el.innerHTML = `
`;
+ try {
+ const url = new URL(API + 'land_api.php', location.href);
+ url.searchParams.set('action', 'land_detail');
+ url.searchParams.set('token', TOKEN);
+ url.searchParams.set('name', landName);
+ const res = await fetch(url);
+ const data = await res.json();
+ if (!data.success) {
+ el.innerHTML = `
`;
+ return;
+ }
+
+ const land = data.land;
+ const visitors = data.visitors || [];
+ const coordStr = `(${land.x1},${land.z1}) → (${land.x2},${land.z2})`;
+
+ let html = `
+
← 返回我的领地
+
+
📍 ${escHtml(land.name)}
+
+
+
+
🌍 世界
+
${escHtml(land.world || '未知')}
+
+
+
+
📏 面积
+
${land.area_size || 0} 格²
+
+
+
📅 创建时间
+
${land.created_at ? new Date(land.created_at*1000).toLocaleDateString('zh-CN') : '未知'}
+
+
`;
+
+ // 访客管理
+ html += `
+
+
👥 访客管理 (${visitors.length})
+
+
+
+ 添加访客
+
`;
+
+ if (visitors.length === 0) {
+ html += `
暂无访客
`;
+ } else {
+ html += `
玩家名 角色 授权时间 操作 `;
+ for (const v of visitors) {
+ const grantDate = v.granted_at ? new Date(v.granted_at * 1000).toLocaleString('zh-CN') : '未知';
+ html += `
+ ${escHtml(v.player_name)}
+ ${escHtml(v.role)}
+ ${grantDate}
+ 移除
+ `;
+ }
+ html += `
`;
+ }
+ html += `
`;
+ el.innerHTML = html;
+ } catch (e) {
+ el.innerHTML = `
`;
+ }
+}
+
+async function addLandVisitor(landName) {
+ const input = document.getElementById('newVisitorName');
+ if (!input) return;
+ const visitor = input.value.trim();
+ if (!visitor) return;
+
+ try {
+ const url = new URL(API + 'land_api.php', location.href);
+ url.searchParams.set('action', 'add_visitor');
+ url.searchParams.set('token', TOKEN);
+ url.searchParams.set('name', landName);
+ url.searchParams.set('visitor', visitor);
+ const res = await fetch(url);
+ const data = await res.json();
+ if (data.success) {
+ landsState.currentLand = landName;
+ await renderLandDetail(document.getElementById('content'), landName);
+ } else {
+ glassAlert(data.error || '操作失败');
+ }
+ } catch (e) {
+ glassAlert('操作失败: ' + e.message);
+ }
+}
+
+async function removeLandVisitor(landName, player) {
+ if (!await glassConfirm(`确定要移除访客 ${player} 吗?`)) return;
+ try {
+ const url = new URL(API + 'land_api.php', location.href);
+ url.searchParams.set('action', 'remove_visitor');
+ url.searchParams.set('token', TOKEN);
+ url.searchParams.set('name', landName);
+ url.searchParams.set('visitor', player);
+ const res = await fetch(url);
+ const data = await res.json();
+ if (data.success) {
+ landsState.currentLand = landName;
+ await renderLandDetail(document.getElementById('content'), landName);
+ } else {
+ glassAlert(data.error || '操作失败');
+ }
+ } catch (e) {
+ glassAlert('操作失败: ' + e.message);
+ }
+}
+
+function buyLandPermission(landName, itemId) {
+ glassAlert('请在游戏中使用 /protect shop 购买领地权限', '🎮');
+}
+
function escHtml(s) { return (s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }
diff --git a/self_update.php b/self_update.php
deleted file mode 100644
index 2c7240f..0000000
--- a/self_update.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
\ No newline at end of file
diff --git a/test_sync.php b/test_sync.php
deleted file mode 100644
index c6d1e0b..0000000
--- a/test_sync.php
+++ /dev/null
@@ -1,102 +0,0 @@
- $player, 'login_time' => time()]
- ];
-
- $db->exec("CREATE TABLE IF NOT EXISTS online_players (player_name TEXT PRIMARY KEY, login_time INTEGER NOT NULL)");
- $db->exec("CREATE TABLE IF NOT EXISTS online_player_hb (id INTEGER PRIMARY KEY DEFAULT 1, last_seen INTEGER DEFAULT 0)");
-
- $db->exec("BEGIN IMMEDIATE");
- try {
- $db->exec("DELETE FROM online_players");
- $stmt = $db->prepare("INSERT OR REPLACE INTO online_players (player_name, login_time) VALUES (:name, :time)");
- $stmt->bindValue(':name', $player, SQLITE3_TEXT);
- $stmt->bindValue(':time', time(), SQLITE3_INTEGER);
- $stmt->execute();
-
- $hbSql = $db->prepare("INSERT OR REPLACE INTO online_player_hb (id, last_seen) VALUES (1, :time)");
- $hbSql->bindValue(':time', time(), SQLITE3_INTEGER);
- $hbSql->execute();
-
- $db->exec("COMMIT");
- } catch (Exception $e) {
- $db->exec("ROLLBACK");
- echo json_encode(['success' => false, 'error' => $e->getMessage()]);
- exit;
- }
-
- // 验证
- $stmt = $db->query("SELECT * FROM online_players");
- $rows = [];
- while ($row = $stmt->fetchArray(SQLITE3_ASSOC)) {
- $rows[] = $row;
- }
-
- echo json_encode([
- 'success' => true,
- 'message' => "模拟推送玩家 $player 成功",
- 'online_players' => $rows,
- 'timestamp' => time()
- ], JSON_UNESCAPED_UNICODE);
- exit;
-}
-
-if ($action === 'check_token') {
- // 检查指定玩家的token是否存在
- $player = $_GET['player'] ?? 'yupaishidifu';
- $stmt = $db->prepare("SELECT * FROM weblogin_tokens WHERE player_name = :player ORDER BY created_at DESC LIMIT 1");
- $stmt->bindValue(':player', $player, SQLITE3_TEXT);
- $result = $stmt->execute();
- $row = $result->fetchArray(SQLITE3_ASSOC);
-
- echo json_encode([
- 'player' => $player,
- 'token_found' => $row !== false,
- 'token_data' => $row,
- 'timestamp' => time()
- ], JSON_UNESCAPED_UNICODE);
- exit;
-}
-
-if ($action === 'validate') {
- // 模拟validateWebAccess调用
- $player = $_GET['player'] ?? 'yupaishidifu';
- $token = $_GET['token'] ?? '';
-
- if (!$token) {
- // 查找最新token
- $stmt = $db->prepare("SELECT web_token FROM weblogin_tokens WHERE player_name = :player ORDER BY created_at DESC LIMIT 1");
- $stmt->bindValue(':player', $player, SQLITE3_TEXT);
- $result = $stmt->execute();
- $row = $result->fetchArray(SQLITE3_ASSOC);
- $token = $row ? $row['web_token'] : '';
- }
-
- $result = validateWebAccess($token, 'view');
- echo json_encode($result, JSON_UNESCAPED_UNICODE);
- exit;
-}
-
-echo json_encode([
- 'error' => '未知操作',
- 'usage' => [
- 'test_sync.php?action=simulate_push&player=yupaishidifu',
- 'test_sync.php?action=check_token&player=yupaishidifu',
- 'test_sync.php?action=validate&player=yupaishidifu'
- ]
-], JSON_UNESCAPED_UNICODE);
diff --git a/upload_debug.php b/upload_debug.php
deleted file mode 100644
index 58c7f36..0000000
--- a/upload_debug.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
\ No newline at end of file
diff --git a/upload_debug.ps1 b/upload_debug.ps1
deleted file mode 100644
index c6ba9d5..0000000
--- a/upload_debug.ps1
+++ /dev/null
@@ -1,30 +0,0 @@
-$ftpServer = "43.153.203.46"
-$ftpPort = 61868
-$ftpUser = "qwed"
-$ftpPass = "z22K2Ws8r38a"
-
-$localFile = "D:\桌面\源码\mc_plugn\Web\debug_region_search.php"
-$remoteDir = "/caoyuan.ypshidifu.cn/test1/"
-$remoteFile = $remoteDir + "debug_region_search.php"
-
-Write-Host "Connecting to FTP..."
-$ftpRequest = [System.Net.FtpWebRequest]::Create("ftp://$ftpServer`:$ftpPort$remoteFile")
-$ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
-$ftpRequest.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPass)
-$ftpRequest.UseBinary = $true
-$ftpRequest.UsePassive = $true
-
-$fileContents = [System.IO.File]::ReadAllBytes($localFile)
-$ftpRequest.ContentLength = $fileContents.Length
-
-try {
- $requestStream = $ftpRequest.GetRequestStream()
- $requestStream.Write($fileContents, 0, $fileContents.Length)
- $requestStream.Close()
- $requestStream.Dispose()
- Write-Host "Upload successful"
-} catch {
- Write-Host "Upload failed: $_"
-}
-
-Write-Host "Done"
\ No newline at end of file
diff --git a/upload_via_http.php b/upload_via_http.php
deleted file mode 100644
index 05fb00d..0000000
--- a/upload_via_http.php
+++ /dev/null
@@ -1,295 +0,0 @@
-enableExceptions(false);
-
- echo "\n=== Table Row Counts ===\n";
- $tables = ['users', 'player_ip_changes', 'player_ip_locations', 'web_session_log', 'online_players', 'player_daily_logins', 'player_checkins'];
- foreach ($tables as $t) {
- try {
- $cnt = $db->querySingle("SELECT COUNT(*) FROM $t");
- echo "$t: $cnt rows\n";
- } catch (\Throwable $e) {
- echo "$t: ERROR - " . $e->getMessage() . "\n";
- }
- }
-
- // 检查 player_ip_locations 中的格式
- echo "\n=== IP Location Samples (first 10) ===\n";
- try {
- $r = $db->query("SELECT ip_address, location, player_name FROM player_ip_locations LIMIT 10");
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- echo $row['ip_address'] . " => " . $row['location'] . " (player: " . $row['player_name'] . ")\n";
- }
- } catch (\Throwable $e) {
- echo "ERROR: " . $e->getMessage() . "\n";
- }
-
- // 检查 player_ip_changes 中的玩家IP
- echo "\n=== Player IP Changes Samples (first 10) ===\n";
- try {
- $r = $db->query("SELECT player_name, new_ip, changed_at FROM player_ip_changes ORDER BY changed_at DESC LIMIT 10");
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- echo $row['player_name'] . " => " . $row['new_ip'] . " (at: " . date('Y-m-d H:i:s', $row['changed_at']) . ")\n";
- }
- } catch (\Throwable $e) {
- echo "ERROR: " . $e->getMessage() . "\n";
- }
-
- // 检查 web_session_log 中的玩家IP
- echo "\n=== Web Session Log Samples (first 10) ===\n";
- try {
- $r = $db->query("SELECT player_name, ip_address, login_time FROM web_session_log ORDER BY login_time DESC LIMIT 10");
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- echo $row['player_name'] . " => " . $row['ip_address'] . " (at: " . date('Y-m-d H:i:s', $row['login_time']) . ")\n";
- }
- } catch (\Throwable $e) {
- echo "ERROR: " . $e->getMessage() . "\n";
- }
-
- // 检查 region 搜索模拟
- echo "\n=== Region Search Simulation for '长沙' ===\n";
- echo "Step 1: player_ip_locations with '长沙':\n";
- try {
- $r = $db->query("SELECT ip_address, location FROM player_ip_locations WHERE location LIKE '%长沙%'");
- $cnt = 0;
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- echo " " . $row['ip_address'] . " => " . $row['location'] . "\n";
- $cnt++;
- }
- echo " Found: $cnt IPs\n";
- } catch (\Throwable $e) {
- echo " ERROR: " . $e->getMessage() . "\n";
- }
-
- // 查找有长沙IP的玩家
- echo "\nStep 2: Players with 长沙 IPs:\n";
- try {
- $r = $db->query("
- SELECT DISTINCT c.player_name
- FROM player_ip_changes c
- INNER JOIN player_ip_locations l ON c.new_ip = l.ip_address
- WHERE l.location LIKE '%长沙%'
- UNION
- SELECT DISTINCT w.player_name
- FROM web_session_log w
- INNER JOIN player_ip_locations l ON w.ip_address = l.ip_address
- WHERE l.location LIKE '%长沙%'
- ");
- $cnt = 0;
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- echo " " . $row['player_name'] . "\n";
- $cnt++;
- }
- echo " Found: $cnt players\n";
- } catch (\Throwable $e) {
- echo " ERROR: " . $e->getMessage() . "\n";
- }
-
- $db->close();
- } else {
- echo "Database not found!\n";
- }
-
- echo "\n=== admin.php Version ===\n";
- $adminFile = __DIR__ . '/api/admin.php';
- if (file_exists($adminFile)) {
- $content = file_get_contents($adminFile);
- echo "File size: " . filesize($adminFile) . " bytes\n";
- echo "Has _region_debug: " . (strpos($content, '_region_debug') !== false ? 'YES' : 'NO') . "\n";
- echo "Has regionDebugEnabled: " . (strpos($content, 'regionDebugEnabled') !== false ? 'YES' : 'NO') . "\n";
- echo "Has batchGetPlayerIps: " . (strpos($content, 'function batchGetPlayerIps') !== false ? 'YES' : 'NO') . "\n";
- } else {
- echo "admin.php not found!\n";
- }
-
- // ★ 模拟 admin 的精确 region search 逻辑
- echo "\n=== Simulate Admin Region Search Logic ===\n";
- $db = new SQLite3(DB_PATH);
- $db->enableExceptions(false);
-
- $regionKey = '长沙';
-
- // Step 1: Get all player names
- $allPlayerNames = [];
- $r = $db->query("SELECT player_name FROM users");
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- $allPlayerNames[] = $row['player_name'];
- }
- echo "All players: " . count($allPlayerNames) . "\n";
-
- // Step 2: Build playerIpMap from player_ip_changes
- $playerIpMap = [];
- $stmt = $db->prepare("SELECT player_name, new_ip FROM player_ip_changes WHERE new_ip != '' AND new_ip != '-' AND new_ip NOT LIKE '10.%' AND new_ip NOT LIKE '192.168.%' AND new_ip != '127.0.0.1'");
- $r = $stmt->execute();
- $changesCount = 0;
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- $changesCount++;
- $pn = $row['player_name'];
- $ip = trim($row['new_ip']);
- if (!isset($playerIpMap[$pn])) $playerIpMap[$pn] = [];
- if (!in_array($ip, $playerIpMap[$pn])) $playerIpMap[$pn][] = $ip;
- }
- echo "player_ip_changes rows: $changesCount\n";
- echo "Players with IPs from changes: " . count($playerIpMap) . "\n";
-
- // web_session_log is empty, skip
- echo "web_session_log rows: 0 (skipped)\n";
-
- // Step 3: Get IP locations
- $ipLocMap = [];
- $r = $db->query("SELECT ip_address, location FROM player_ip_locations");
- while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
- $loc = trim($row['location'] ?? '');
- $ip = trim($row['ip_address'] ?? '');
- if ($loc && $ip) {
- $ipLocMap[$ip] = $loc;
- }
- }
- echo "IP locations cached: " . count($ipLocMap) . "\n";
-
- // Show some sample IP from playerIpMap vs ipLocMap
- echo "\n--- IP Comparison ---\n";
- $samplePlayers = array_slice(array_keys($playerIpMap), 0, 5);
- foreach ($samplePlayers as $pn) {
- $ips = $playerIpMap[$pn];
- echo "Player: $pn => IPs: " . implode(', ', $ips) . "\n";
- foreach ($ips as $ip) {
- if (isset($ipLocMap[$ip])) {
- echo " FOUND in cache: $ip => " . $ipLocMap[$ip] . "\n";
- } else {
- echo " NOT in cache: $ip\n";
- }
- }
- }
-
- // Step 4: PHP matching
- $matchedPlayers = [];
- $matchDebug = [];
- foreach ($playerIpMap as $pn => $ips) {
- foreach ($ips as $ip) {
- if (isset($ipLocMap[$ip])) {
- $loc = $ipLocMap[$ip];
- if (mb_stripos($loc, $regionKey) !== false) {
- if (!in_array($pn, $matchedPlayers)) $matchedPlayers[] = $pn;
- $matchDebug[] = "$ip=>$loc ($pn)";
- break;
- }
- }
- }
- }
- echo "\n=== PHP Match Result ===\n";
- echo "Matched players: " . count($matchedPlayers) . "\n";
- foreach ($matchedPlayers as $pn) {
- echo " $pn\n";
- }
-
- // Check if any 长沙 IPs are NOT in the playerIpMap
- echo "\n--- All 长沙 IPs in cache ---\n";
- foreach ($ipLocMap as $ip => $loc) {
- if (mb_stripos($loc, $regionKey) !== false) {
- echo " $ip => $loc\n";
- // Check which player has this IP
- $foundPlayer = false;
- foreach ($playerIpMap as $pn => $ips) {
- if (in_array($ip, $ips)) {
- echo " Has player: $pn\n";
- $foundPlayer = true;
- break;
- }
- }
- if (!$foundPlayer) {
- echo " NO player found with this IP!\n";
- // Try partial match
- foreach ($playerIpMap as $pn => $ips) {
- foreach ($ips as $pip) {
- if (strpos($ip, $pip) !== false || strpos($pip, $ip) !== false) {
- echo " Partial match: $pip (player: $pn)\n";
- }
- }
- }
- }
- }
- }
-
- $db->close();
-
-} elseif ($action === 'upload' || (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK)) {
- // 通过 POST 上传文件
- // 支持 target= 和 path= 两种参数名
- $target = $_POST['target'] ?? $_POST['path'] ?? '';
- $allowed = ['api/admin.php', 'api/sync.php', 'api/cdk.php', 'api/ticket.php', 'api/register.php', 'api/shop.php', 'api/balance.php', 'core.php', 'config.php', 'player.php', 'admin.php', 'service.php', 'upload_via_http.php'];
-
- if (!in_array($target, $allowed)) {
- echo "ERROR: Target not allowed: $target\nAllowed: " . implode(', ', $allowed) . "\n";
- exit;
- }
-
- $targetPath = __DIR__ . '/' . $target;
-
- if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
- $content = file_get_contents($_FILES['file']['tmp_name']);
- } elseif (isset($_POST['content'])) {
- $content = base64_decode($_POST['content']);
- } else {
- $content = file_get_contents('php://input');
- }
-
- if (empty($content)) {
- echo "ERROR: No content\n";
- exit;
- }
-
- if (file_exists($targetPath)) {
- copy($targetPath, $targetPath . '.bak');
- }
-
- $bytes = file_put_contents($targetPath, $content);
- echo "Wrote $bytes bytes to $target\n";
- echo "MD5: " . md5_file($targetPath) . "\n";
-
- if (function_exists('opcache_invalidate')) {
- @opcache_invalidate($targetPath, true);
- }
- if (function_exists('opcache_reset')) {
- @opcache_reset();
- }
- echo "OPCache cleared\n";
-}
-
-echo "\nDone.\n";
-@ob_end_flush();
-?>
\ No newline at end of file
diff --git a/version_check.php b/version_check.php
deleted file mode 100644
index d01ed4d..0000000
--- a/version_check.php
+++ /dev/null
@@ -1,8 +0,0 @@
-
\ No newline at end of file