feat: 成员独立权限管理 + PHP→Java变更同步
This commit is contained in:
+491
-5
@@ -33,8 +33,14 @@ try {
|
||||
$adminActions = ['list_lands', 'list_shop', 'get_config', 'update_config', 'delete_land', 'update_land_owner', 'delete_shop_item'];
|
||||
// 玩家端action:需要token
|
||||
$playerActions = ['my_lands', 'land_detail', 'add_visitor', 'remove_visitor', 'list_visitors', 'land_shop', 'buy_permission'];
|
||||
// ★ 玩家端权限操作action
|
||||
$playerPermActions = ['update_visitor_perm', 'get_visitor_perm'];
|
||||
// ★ 成员独立权限操作action(领地所有者编辑成员权限)
|
||||
$memberPermActions = ['get_member_perms', 'update_member_perm', 'clear_member_perm'];
|
||||
// ★ Java端轮询PHP管理员变更
|
||||
$syncFromPhpActions = ['poll_admin_changes', 'ack_admin_changes'];
|
||||
|
||||
if (in_array($action, $syncActions)) {
|
||||
if (in_array($action, $syncActions) || in_array($action, $syncFromPhpActions)) {
|
||||
// 同步类:必须有secret
|
||||
if (!validateSecret($secret)) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid secret']);
|
||||
@@ -56,7 +62,7 @@ try {
|
||||
echo json_encode(['success' => false, 'error' => '需要管理员认证']);
|
||||
exit;
|
||||
}
|
||||
} elseif (in_array($action, $playerActions)) {
|
||||
} elseif (in_array($action, $playerActions) || in_array($action, $playerPermActions) || in_array($action, $memberPermActions)) {
|
||||
// 玩家端:需要token
|
||||
$token = $_GET['token'] ?? '';
|
||||
if (empty($token)) {
|
||||
@@ -181,6 +187,53 @@ try {
|
||||
handleLandShop($db, $_GET['name'] ?? '');
|
||||
break;
|
||||
|
||||
// ===== 玩家端:获取访客权限 =====
|
||||
case 'get_visitor_perm':
|
||||
handleGetVisitorPerm($db, $playerName, $_GET['land'] ?? '');
|
||||
break;
|
||||
|
||||
// ===== 玩家端:更新访客权限 =====
|
||||
case 'update_visitor_perm':
|
||||
if ($method !== 'POST') {
|
||||
echo json_encode(['success' => false, 'error' => 'POST only']);
|
||||
exit;
|
||||
}
|
||||
handleUpdateVisitorPerm($db, $playerName, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 成员独立权限:获取成员权限列表 =====
|
||||
case 'get_member_perms':
|
||||
handleGetMemberPerms($db, $playerName, $_GET['land'] ?? '');
|
||||
break;
|
||||
|
||||
// ===== 成员独立权限:更新单个权限 =====
|
||||
case 'update_member_perm':
|
||||
if ($method !== 'POST') {
|
||||
echo json_encode(['success' => false, 'error' => 'POST only']);
|
||||
exit;
|
||||
}
|
||||
handleUpdateMemberPerm($db, $playerName, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 成员独立权限:清除所有自定义权限 =====
|
||||
case 'clear_member_perm':
|
||||
if ($method !== 'POST') {
|
||||
echo json_encode(['success' => false, 'error' => 'POST only']);
|
||||
exit;
|
||||
}
|
||||
handleClearMemberPerm($db, $playerName, $_POST);
|
||||
break;
|
||||
|
||||
// ===== Java端轮询PHP管理员变更 =====
|
||||
case 'poll_admin_changes':
|
||||
handlePollAdminChanges($db, $_GET);
|
||||
break;
|
||||
|
||||
// ===== Java端确认已处理变更 =====
|
||||
case 'ack_admin_changes':
|
||||
handleAckAdminChanges($db, $_POST);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success' => false, 'error' => 'unknown action']);
|
||||
}
|
||||
@@ -237,6 +290,29 @@ function initLandTables($db) {
|
||||
synced_at INTEGER DEFAULT 0,
|
||||
UNIQUE(land_id, player_name)
|
||||
)");
|
||||
|
||||
// ★ 管理员变更队列表(PHP→Java同步)
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_admin_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
change_type TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
target_name TEXT DEFAULT '',
|
||||
change_data TEXT DEFAULT '',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
acknowledged INTEGER DEFAULT 0,
|
||||
acked_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
// ★ 领地所有者变更表(记录PHP端修改的所有者变更)
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_land_owner_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
land_id INTEGER NOT NULL,
|
||||
land_name TEXT NOT NULL,
|
||||
old_owner TEXT DEFAULT '',
|
||||
new_owner TEXT NOT NULL,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
synced INTEGER DEFAULT 0
|
||||
)");
|
||||
}
|
||||
|
||||
function handleSyncLands($db, $post) {
|
||||
@@ -367,10 +443,36 @@ function handleUpdateLandOwner($db, $post) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
|
||||
$stmt->bindValue(':owner', $owner, SQLITE3_TEXT);
|
||||
// 获取旧所有者
|
||||
$stmt = $db->prepare("SELECT owner FROM web_area_lands WHERE name = :name");
|
||||
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
$oldOwner = $row ? ($row['owner'] ?? '') : '';
|
||||
|
||||
// 更新所有者
|
||||
$stmt2 = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
|
||||
$stmt2->bindValue(':owner', $owner, SQLITE3_TEXT);
|
||||
$stmt2->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt2->execute();
|
||||
|
||||
// 记录变更(用于同步到Java)
|
||||
if ($oldOwner !== $owner) {
|
||||
$stmt3 = $db->prepare("INSERT INTO web_land_owner_changes (land_name, old_owner, new_owner, created_at) VALUES (:name, :old, :new, :now)");
|
||||
$stmt3->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':old', $oldOwner, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':new', $owner, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt3->execute();
|
||||
|
||||
// 同时添加到变更队列
|
||||
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('owner_change', :id, :name, :data, :now)");
|
||||
$stmt4->bindValue(':id', (int)($post['id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt4->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt4->bindValue(':data', json_encode(['old_owner' => $oldOwner, 'new_owner' => $owner]), SQLITE3_TEXT);
|
||||
$stmt4->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt4->execute();
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
@@ -482,6 +584,18 @@ function handleAddVisitor($db, $playerName, $req) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 校验玩家名格式(至少3字符,只含字母数字下划线)
|
||||
if (!preg_match('/^[a-zA-Z0-9_]{3,16}$/', $visitor)) {
|
||||
echo json_encode(['success' => false, 'error' => '无效的玩家名: ' . $visitor]);
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 校验玩家是否存在于数据库(login.db或web用户表)
|
||||
if (!playerExists($db, $visitor)) {
|
||||
echo json_encode(['success' => false, 'error' => '玩家不存在: ' . $visitor . ',请确认玩家已注册']);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取领地
|
||||
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
|
||||
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
|
||||
@@ -511,6 +625,46 @@ function handleAddVisitor($db, $playerName, $req) {
|
||||
echo json_encode(['success' => true, 'message' => "已添加访客: $visitor"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 检查玩家是否存在于数据库
|
||||
*/
|
||||
function playerExists($db, $playerName) {
|
||||
// 检查web用户表
|
||||
try {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM weblogin_tokens WHERE player = :player");
|
||||
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($row && $row['cnt'] > 0) return true;
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能不存在,忽略
|
||||
}
|
||||
|
||||
// 检查users表
|
||||
try {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM users WHERE player_name = :player");
|
||||
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($row && $row['cnt'] > 0) return true;
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能不存在,忽略
|
||||
}
|
||||
|
||||
// 检查tokens表
|
||||
try {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM tokens WHERE player = :player");
|
||||
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($row && $row['cnt'] > 0) return true;
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能不存在,忽略
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== 玩家端:移除访客 ====================
|
||||
|
||||
function handleRemoveVisitor($db, $playerName, $req) {
|
||||
@@ -602,6 +756,338 @@ function handleLandShop($db, $landName) {
|
||||
echo json_encode(['success' => true, 'items' => $items]);
|
||||
}
|
||||
|
||||
// ==================== 玩家端:获取访客权限 ====================
|
||||
|
||||
function handleGetVisitorPerm($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;
|
||||
}
|
||||
|
||||
// 获取该玩家的权限
|
||||
$stmt2 = $db->prepare("SELECT * 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', $playerName, SQLITE3_TEXT);
|
||||
$result2 = $stmt2->execute();
|
||||
$perm = $result2->fetchArray(SQLITE3_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'permission' => $perm]);
|
||||
}
|
||||
|
||||
// ==================== 玩家端:更新访客权限 ====================
|
||||
|
||||
function handleUpdateVisitorPerm($db, $playerName, $post) {
|
||||
$landName = $post['land'] ?? '';
|
||||
$visitor = $post['visitor'] ?? '';
|
||||
$permissions = $post['permissions'] ?? '';
|
||||
$role = $post['role'] ?? 'visitor';
|
||||
|
||||
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, :role, :perms, :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(':role', $role, SQLITE3_TEXT);
|
||||
$stmt2->bindValue(':perms', $permissions, SQLITE3_TEXT);
|
||||
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt2->execute();
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "已更新 $visitor 的权限"]);
|
||||
}
|
||||
|
||||
// ==================== 成员独立权限操作 ====================
|
||||
|
||||
/**
|
||||
* 29种权限类型定义
|
||||
*/
|
||||
$PERM_TYPES = [
|
||||
'denyMove' => '移动',
|
||||
'denyBlockPlace' => '放置方块',
|
||||
'denyBlockBreak' => '破坏方块',
|
||||
'denyPVP' => 'PVP战斗',
|
||||
'denyMount' => '骑乘',
|
||||
'denyEnderPearl' => '末影珍珠',
|
||||
'denyThrownProjectiles' => '投掷物',
|
||||
'denyRaid' => '袭击',
|
||||
'denyBow' => '弓箭',
|
||||
'denyPotion' => '药水',
|
||||
'denyFire' => '点火',
|
||||
'denyFireSpread' => '火焰蔓延',
|
||||
'denyPickup' => '拾取物品',
|
||||
'denyDrop' => '丢弃物品',
|
||||
'denyExplosion' => '爆炸',
|
||||
'denyFallDamage' => '摔落伤害',
|
||||
'denyHunger' => '饥饿',
|
||||
'denyAllDamage' => '所有伤害',
|
||||
'denyAllEffects' => '所有效果',
|
||||
'denyItemFrame' => '物品展示框',
|
||||
'denyRedstoneInteraction' => '红石交互',
|
||||
'denyDoorInteraction' => '门禁交互',
|
||||
'denyNoteblockJukebox' => '音符盒/唱片机',
|
||||
'denyLead' => '拴绳',
|
||||
'denyCropHarvest' => '收割作物',
|
||||
'denyWoolShear' => '剪羊毛',
|
||||
'denyAnimalFeeding' => '投喂动物',
|
||||
'denyGlowing' => '发光效果',
|
||||
'denyPeaceMode' => '和平模式'
|
||||
];
|
||||
|
||||
function handleGetMemberPerms($db, $playerName, $landName) {
|
||||
global $PERM_TYPES;
|
||||
|
||||
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 player_name");
|
||||
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$result2 = $stmt2->execute();
|
||||
|
||||
$members = [];
|
||||
while ($row = $result2->fetchArray(SQLITE3_ASSOC)) {
|
||||
// 解析权限JSON
|
||||
$permJson = $row['permissions'] ?? '';
|
||||
$perms = [];
|
||||
if (!empty($permJson)) {
|
||||
$decoded = json_decode($permJson, true);
|
||||
if (is_array($decoded)) {
|
||||
$perms = $decoded;
|
||||
}
|
||||
}
|
||||
$row['perm_map'] = $perms;
|
||||
$members[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'land' => $land,
|
||||
'members' => $members,
|
||||
'perm_types' => $PERM_TYPES
|
||||
]);
|
||||
}
|
||||
|
||||
function handleUpdateMemberPerm($db, $playerName, $post) {
|
||||
$landName = $post['land'] ?? '';
|
||||
$targetPlayer = $post['player'] ?? '';
|
||||
$permKey = $post['perm'] ?? '';
|
||||
$enabled = isset($post['enabled']) ? (bool)$post['enabled'] : false;
|
||||
|
||||
if (empty($landName) || empty($targetPlayer) || empty($permKey)) {
|
||||
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 AND player_name = :player");
|
||||
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
||||
$result2 = $stmt2->execute();
|
||||
$perm = $result2->fetchArray(SQLITE3_ASSOC);
|
||||
|
||||
if (!$perm) {
|
||||
echo json_encode(['success' => false, 'error' => '该玩家不是领地成员']);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析现有权限
|
||||
$permMap = [];
|
||||
$permJson = $perm['permissions'] ?? '';
|
||||
if (!empty($permJson)) {
|
||||
$decoded = json_decode($permJson, true);
|
||||
if (is_array($decoded)) {
|
||||
$permMap = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新权限
|
||||
if ($enabled) {
|
||||
$permMap[$permKey] = true;
|
||||
} else {
|
||||
unset($permMap[$permKey]);
|
||||
}
|
||||
|
||||
// 写回数据库
|
||||
$newJson = json_encode($permMap, JSON_UNESCAPED_UNICODE);
|
||||
$stmt3 = $db->prepare("UPDATE web_area_permissions SET permissions = :perms, synced_at = :now WHERE land_id = :land_id AND player_name = :player");
|
||||
$stmt3->bindValue(':perms', $newJson, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt3->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$stmt3->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
||||
$stmt3->execute();
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "已更新 $targetPlayer 的 $permKey 权限"]);
|
||||
}
|
||||
|
||||
function handleClearMemberPerm($db, $playerName, $post) {
|
||||
$landName = $post['land'] ?? '';
|
||||
$targetPlayer = $post['player'] ?? '';
|
||||
|
||||
if (empty($landName) || empty($targetPlayer)) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 获取旧权限
|
||||
$stmt_old = $db->prepare("SELECT permissions FROM web_area_permissions WHERE land_id = :land_id AND player_name = :player");
|
||||
$stmt_old->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$stmt_old->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
||||
$result_old = $stmt_old->execute();
|
||||
$old_perm = $result_old->fetchArray(SQLITE3_ASSOC);
|
||||
$old_perms = $old_perm ? ($old_perm['permissions'] ?? '') : '';
|
||||
|
||||
// 清除权限
|
||||
$stmt2 = $db->prepare("UPDATE web_area_permissions SET permissions = '', synced_at = :now WHERE land_id = :land_id AND player_name = :player");
|
||||
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
||||
$stmt2->execute();
|
||||
|
||||
// 记录变更
|
||||
if (!empty($old_perms)) {
|
||||
$stmt3 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_clear', :id, :name, :data, :now)");
|
||||
$stmt3->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||
$stmt3->bindValue(':name', $targetPlayer, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':data', json_encode(['land_name' => $landName, 'old_permissions' => $old_perms]), SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt3->execute();
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "已清除 $targetPlayer 的所有自定义权限"]);
|
||||
}
|
||||
|
||||
// ==================== Java端轮询PHP管理员变更 ====================
|
||||
|
||||
function handlePollAdminChanges($db, $get) {
|
||||
$lastId = (int)($get['last_id'] ?? 0);
|
||||
$limit = min((int)($get['limit'] ?? 50), 100);
|
||||
|
||||
// 获取未确认的变更
|
||||
$stmt = $db->prepare("SELECT * FROM web_admin_changes WHERE id > :last_id AND acknowledged = 0 ORDER BY id ASC LIMIT :limit");
|
||||
$stmt->bindValue(':last_id', $lastId, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':limit', $limit, SQLITE3_INTEGER);
|
||||
$result = $stmt->execute();
|
||||
|
||||
$changes = [];
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$changes[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'changes' => $changes, 'count' => count($changes)]);
|
||||
}
|
||||
|
||||
function handleAckAdminChanges($db, $post) {
|
||||
$ids = $post['ids'] ?? '';
|
||||
if (empty($ids)) {
|
||||
echo json_encode(['success' => false, 'error' => 'missing ids']);
|
||||
return;
|
||||
}
|
||||
|
||||
$idList = array_filter(array_map('intval', explode(',', $ids)));
|
||||
if (empty($idList)) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid ids']);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$placeholders = implode(',', array_fill(0, count($idList), '?'));
|
||||
$stmt = $db->prepare("UPDATE web_admin_changes SET acknowledged = 1, acked_at = :now WHERE id IN ($placeholders)");
|
||||
$stmt->bindValue(':now', $now, SQLITE3_INTEGER);
|
||||
$i = 1;
|
||||
foreach ($idList as $id) {
|
||||
$stmt->bindValue($i++, $id, SQLITE3_INTEGER);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
echo json_encode(['success' => true, 'acked' => count($idList)]);
|
||||
}
|
||||
|
||||
function validateSecret($secret) {
|
||||
if (empty($secret)) return false;
|
||||
$validSecrets = [
|
||||
|
||||
+197
-1
@@ -2062,7 +2062,7 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
|
||||
// ==================== 领地管理 ====================
|
||||
|
||||
let landsState = { view: 'list', currentLand: null };
|
||||
let landsState = { view: 'list', currentLand: null, currentMember: null };
|
||||
|
||||
async function renderLands(el) {
|
||||
if (!TOKEN) {
|
||||
@@ -2080,6 +2080,16 @@ async function renderLands(el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (landsState.view === 'member_perms' && landsState.currentLand) {
|
||||
await renderMemberPerms(el, landsState.currentLand);
|
||||
return;
|
||||
}
|
||||
|
||||
if (landsState.view === 'member_perm_detail' && landsState.currentLand && landsState.currentMember) {
|
||||
await renderMemberPermDetail(el, landsState.currentLand, landsState.currentMember);
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载我的领地列表
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载领地中...</p></div>`;
|
||||
try {
|
||||
@@ -2226,6 +2236,7 @@ async function renderLandDetail(el, landName) {
|
||||
html += `<div style="margin-bottom:16px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
||||
<h3 style="margin:0;color:var(--fg)">👥 访客管理 (${visitors.length})</h3>
|
||||
<button class="btn" style="font-size:12px;padding:4px 12px" onclick="landsState.view='member_perms';landsState.currentLand='${escHtml(land.name)}';renderLands(document.getElementById('content'))">🎯 成员权限</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin-bottom:12px">
|
||||
<input type="text" id="newVisitorName" placeholder="输入玩家名" style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px">
|
||||
@@ -2254,6 +2265,59 @@ async function renderLandDetail(el, landName) {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderMemberPerms(el, landName) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载成员权限...</p></div>`;
|
||||
try {
|
||||
const url = new URL(API + 'land_api.php', location.href);
|
||||
url.searchParams.set('action', 'get_member_perms');
|
||||
url.searchParams.set('token', TOKEN);
|
||||
url.searchParams.set('land', landName);
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (!data.success) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--red)">${data.error}</p><button class="btn" onclick="landsState.view='detail';renderLands(document.getElementById('content'))">← 返回</button></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const land = data.land;
|
||||
const members = data.members || [];
|
||||
const permTypes = data.perm_types || {};
|
||||
|
||||
let html = `<div class="card">
|
||||
<button class="btn" onclick="landsState.view='detail';landsState.currentLand='${escHtml(landName)}';landsState.currentMember=null;renderLands(document.getElementById('content'))" style="margin-bottom:12px;font-size:13px">← 返回领地详情</button>
|
||||
<h2 style="margin:0 0 8px;color:var(--fg)">🎯 ${escHtml(landName)} 成员权限</h2>
|
||||
<p style="color:var(--dim);font-size:13px;margin-bottom:16px">为每个成员设置独立的权限覆盖。启用=覆盖默认值,禁用=使用领地默认值。</p>`;
|
||||
|
||||
if (members.length === 0) {
|
||||
html += `<div style="text-align:center;padding:32px;color:var(--dim)">
|
||||
<div style="font-size:40px;margin-bottom:12px">👥</div>
|
||||
<p>暂无成员</p>
|
||||
<p style="font-size:12px;margin-top:8px">请先在访客管理中添加成员</p>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px">`;
|
||||
for (const member of members) {
|
||||
const permCount = member.perm_map ? Object.keys(member.perm_map).length : 0;
|
||||
const hasCustom = permCount > 0;
|
||||
html += `<div onclick="landsState.view='member_perm_detail';landsState.currentMember='${escHtml(member.player_name)}';renderLands(document.getElementById('content'))"
|
||||
style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:16px;cursor:pointer;transition:all 0.2s"
|
||||
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||
<h3 style="margin:0;color:var(--fg);font-size:15px">👤 ${escHtml(member.player_name)}</h3>
|
||||
${hasCustom ? '<span style="background:var(--accent);color:#fff;padding:2px 8px;border-radius:12px;font-size:11px">★ 自定义</span>' : '<span style="color:var(--dim);font-size:12px">默认权限</span>'}
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--dim)">${permCount} 项自定义权限</div>
|
||||
</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
el.innerHTML = html;
|
||||
} catch (e) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--red)">加载失败: ${e.message}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function addLandVisitor(landName) {
|
||||
const input = document.getElementById('newVisitorName');
|
||||
if (!input) return;
|
||||
@@ -2304,6 +2368,138 @@ function buyLandPermission(landName, itemId) {
|
||||
glassAlert('请在游戏中使用 /protect shop 购买领地权限', '🎮');
|
||||
}
|
||||
|
||||
async function renderMemberPermDetail(el, landName, targetPlayer) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载权限详情...</p></div>`;
|
||||
try {
|
||||
const url = new URL(API + 'land_api.php', location.href);
|
||||
url.searchParams.set('action', 'get_member_perms');
|
||||
url.searchParams.set('token', TOKEN);
|
||||
url.searchParams.set('land', landName);
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (!data.success) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--red)">${data.error}</p><button class="btn" onclick="landsState.view='member_perms';renderLands(document.getElementById('content'))">← 返回</button></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const land = data.land;
|
||||
const permTypes = data.perm_types || {};
|
||||
const members = data.members || [];
|
||||
const member = members.find(m => m.player_name === targetPlayer);
|
||||
|
||||
if (!member) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--red)">成员不存在</p><button class="btn" onclick="landsState.view='member_perms';renderLands(document.getElementById('content'))">← 返回</button></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const permMap = member.perm_map || {};
|
||||
const permEntries = Object.entries(permTypes);
|
||||
|
||||
let html = `<div class="card">
|
||||
<button class="btn" onclick="landsState.view='member_perms';landsState.currentMember=null;renderLands(document.getElementById('content'))" style="margin-bottom:12px;font-size:13px">← 返回成员列表</button>
|
||||
<h2 style="margin:0 0 8px;color:var(--fg)">👤 ${escHtml(targetPlayer)} 权限</h2>
|
||||
<p style="color:var(--dim);font-size:13px;margin-bottom:16px">在 ${escHtml(landName)} 中的独立权限设置</p>
|
||||
<div style="display:flex;gap:8px;margin-bottom:16px">
|
||||
<button class="btn" onclick="clearAllMemberPerms('${escHtml(landName)}','${escHtml(targetPlayer)}')" style="color:var(--red)">🗑️ 清除所有自定义</button>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px">`;
|
||||
|
||||
for (const [key, label] of permEntries) {
|
||||
const isCustom = permMap.hasOwnProperty(key);
|
||||
const enabled = isCustom ? permMap[key] : false;
|
||||
const defaultVal = land[key] || false;
|
||||
|
||||
html += `<div onclick="toggleMemberPerm('${escHtml(landName)}','${escHtml(targetPlayer)}','${key}',this)"
|
||||
style="background:${isCustom ? (enabled ? 'rgba(34,197,94,0.1)' : 'rgba(239,68,68,0.1)') : 'var(--bg)'};
|
||||
border:1px solid ${isCustom ? (enabled ? 'var(--green)' : 'var(--red)') : 'var(--border)'};
|
||||
border-radius:8px;padding:12px;cursor:pointer;transition:all 0.2s">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
|
||||
<span style="color:var(--fg);font-size:13px;font-weight:500">${label}</span>
|
||||
<span style="font-size:11px;color:${isCustom ? (enabled ? 'var(--green)' : 'var(--red)') : 'var(--dim)'}">
|
||||
${isCustom ? (enabled ? '✓ 启用' : '✗ 禁用') : '默认'}
|
||||
</span>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--dim)">
|
||||
${isCustom ? '★ 自定义' : `领地默认: ${defaultVal ? '禁止' : '允许'}`}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
el.innerHTML = html;
|
||||
} catch (e) {
|
||||
el.innerHTML = `<div class="card"><p style="color:var(--red)">加载失败: ${e.message}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMemberPerm(landName, targetPlayer, permKey, el) {
|
||||
try {
|
||||
const url = new URL(API + 'land_api.php', location.href);
|
||||
url.searchParams.set('action', 'update_member_perm');
|
||||
url.searchParams.set('token', TOKEN);
|
||||
|
||||
const body = new URLSearchParams();
|
||||
body.set('land', landName);
|
||||
body.set('player', targetPlayer);
|
||||
body.set('perm', permKey);
|
||||
|
||||
// 从元素中获取当前状态
|
||||
const statusSpan = el.querySelector('span:last-child');
|
||||
const isCustom = statusSpan.textContent.includes('自定义');
|
||||
const isEnabled = statusSpan.textContent.includes('启用');
|
||||
|
||||
if (isCustom) {
|
||||
// 已有自定义:切换启用/禁用
|
||||
body.set('enabled', (!isEnabled).toString());
|
||||
} else {
|
||||
// 无自定义:设置为启用
|
||||
body.set('enabled', 'true');
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString()
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
// 刷新页面
|
||||
await renderMemberPermDetail(document.getElementById('content'), landName, targetPlayer);
|
||||
} else {
|
||||
glassAlert(data.error || '操作失败');
|
||||
}
|
||||
} catch (e) {
|
||||
glassAlert('操作失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAllMemberPerms(landName, targetPlayer) {
|
||||
if (!await glassConfirm(`确定要清除 ${targetPlayer} 的所有自定义权限吗?`)) return;
|
||||
try {
|
||||
const url = new URL(API + 'land_api.php', location.href);
|
||||
url.searchParams.set('action', 'clear_member_perm');
|
||||
url.searchParams.set('token', TOKEN);
|
||||
|
||||
const body = new URLSearchParams();
|
||||
body.set('land', landName);
|
||||
body.set('player', targetPlayer);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString()
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
glassAlert('已清除所有自定义权限');
|
||||
await renderMemberPermDetail(document.getElementById('content'), landName, targetPlayer);
|
||||
} else {
|
||||
glassAlert(data.error || '操作失败');
|
||||
}
|
||||
} catch (e) {
|
||||
glassAlert('操作失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function escHtml(s) { return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user