fix: PHP数据库锁修复 + 用户组添加/移除错误处理
1. initTables()统一创建所有核心表(web_user_groups, web_user_group_members等) 2. handleAddGroupMember改用playerExists()本地验证替代异步validatePlayerViaJava() 3. handleRemoveGroupMember增加错误处理和affected行数检查 4. 移除validation_callback/visitor中的CREATE TABLE IF NOT EXISTS
This commit is contained in:
@@ -2450,10 +2450,10 @@ async function adminTransferLand(name, currentOwner) {
|
||||
const d = await res.json();
|
||||
if (d.success && d.pending) {
|
||||
glassAlert(d.message || '已提交改主请求,等待Java端验证');
|
||||
// 启动轮询,每5秒查询状态,最多等待1.05分钟
|
||||
// 启动轮询,每3秒查询状态,最多等待15秒
|
||||
let elapsed = 0;
|
||||
const maxWait = 65000; // 1.05分钟 = 65秒
|
||||
const interval = 5000; // 5秒轮询一次
|
||||
const maxWait = 15000; // 15秒
|
||||
const interval = 3000; // 3秒轮询一次
|
||||
const poll = async () => {
|
||||
try {
|
||||
const pollRes = await fetch('api/land_api.php?action=get_owner_change_status&name=' + encodeURIComponent(name) + '&secret=sdf1_web_comm_2026_ypshidifu');
|
||||
|
||||
+115
-51
@@ -948,7 +948,7 @@ function handleGetOwnerChangeStatus($db, $get) {
|
||||
if ($rowTime && $status === 'pending') {
|
||||
$createdAt = (int)($rowTime['created_at'] ?? 0);
|
||||
$now = time();
|
||||
if ($now - $createdAt > 65) {
|
||||
if ($now - $createdAt > 15) {
|
||||
// 超时,自动回滚
|
||||
$stmtRollback = $db->prepare("UPDATE web_admin_changes SET status = 'failed', acknowledged = 1, acked_at = :now WHERE change_type = 'owner_change' AND target_name = :name AND status = 'pending'");
|
||||
$stmtRollback->bindValue(':now', $now, SQLITE3_INTEGER);
|
||||
@@ -1226,20 +1226,93 @@ function handleValidationCallback($db, $data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 先读取完整记录(含request_type和request_data),再更新状态
|
||||
$stmt0 = $db->prepare("SELECT player_name, request_type, request_data FROM pending_player_validations WHERE id = :id");
|
||||
$stmt0->bindValue(':id', $id, SQLITE3_INTEGER);
|
||||
$rs0 = $stmt0->execute();
|
||||
$row = $rs0->fetchArray(SQLITE3_ASSOC);
|
||||
$player = $row ? $row['player_name'] : '?';
|
||||
$reqType = $row ? $row['request_type'] : '';
|
||||
$reqData = $row ? json_decode($row['request_data'] ?? '{}', true) : [];
|
||||
|
||||
$stmt = $db->prepare("UPDATE pending_player_validations SET status = :status, validated_at = :now WHERE id = :id");
|
||||
$stmt->bindValue(':status', $status, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
|
||||
// 获取玩家名用于日志
|
||||
$stmt2 = $db->prepare("SELECT player_name FROM pending_player_validations WHERE id = :id");
|
||||
$stmt2->bindValue(':id', $id, SQLITE3_INTEGER);
|
||||
$rs = $stmt2->execute();
|
||||
$row = $rs->fetchArray(SQLITE3_ASSOC);
|
||||
$player = $row ? $row['player_name'] : '?';
|
||||
debugLog("validation_callback: 玩家 {$player} 验证结果={$status}, type={$reqType}");
|
||||
|
||||
// ★ 验证通过时,自动执行挂起的操作
|
||||
if ($status === 'valid') {
|
||||
// --- add_group_member: 自动将玩家加入用户组 ---
|
||||
if ($reqType === 'add_group_member' && !empty($reqData['group'])) {
|
||||
$group = $reqData['group'];
|
||||
$now = time();
|
||||
$expiry = (int)($reqData['expiry_time'] ?? 0);
|
||||
|
||||
// ★ 如果没指定expiry_time,根据用户组的duration_minutes计算
|
||||
if ($expiry <= 0) {
|
||||
try {
|
||||
$stmtCfg = $db->prepare("SELECT duration_minutes FROM web_user_groups WHERE group_name = :group");
|
||||
$stmtCfg->bindValue(':group', $group, SQLITE3_TEXT);
|
||||
$rsCfg = $stmtCfg->execute();
|
||||
$cfgRow = $rsCfg->fetchArray(SQLITE3_ASSOC);
|
||||
if ($cfgRow && (int)$cfgRow['duration_minutes'] > 0) {
|
||||
$expiry = $now + (int)$cfgRow['duration_minutes'] * 60;
|
||||
}
|
||||
} catch (\Throwable $e) { /* 表可能不存在 */ }
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt3 = $db->prepare("INSERT OR REPLACE INTO web_user_group_members
|
||||
(player_name, group_name, added_by, added_time, expiry_time)
|
||||
VALUES (:player, :group, 'admin', :added, :expiry)");
|
||||
$stmt3->bindValue(':player', $player, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':group', $group, SQLITE3_TEXT);
|
||||
$stmt3->bindValue(':added', $now, SQLITE3_INTEGER);
|
||||
$stmt3->bindValue(':expiry', $expiry, SQLITE3_INTEGER);
|
||||
$stmt3->execute();
|
||||
|
||||
// 写入变更队列,通知Java端同步
|
||||
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at)
|
||||
VALUES ('group_change', :id, :name, :data, :time)");
|
||||
$stmt4->bindValue(':id', $group, SQLITE3_TEXT);
|
||||
$stmt4->bindValue(':name', $group, SQLITE3_TEXT);
|
||||
$stmt4->bindValue(':data', json_encode(['action' => 'add_member', 'group_name' => $group, 'player' => $player]), SQLITE3_TEXT);
|
||||
$stmt4->bindValue(':time', $now, SQLITE3_INTEGER);
|
||||
$stmt4->execute();
|
||||
} catch (\Throwable $e) { /* 静默 */ }
|
||||
|
||||
debugLog("validation_callback: 自动执行add_group_member", ['player' => $player, 'group' => $group]);
|
||||
}
|
||||
|
||||
// --- add_visitor: 自动将玩家加入领地访客列表 ---
|
||||
if ($reqType === 'add_visitor' && !empty($reqData['land'])) {
|
||||
$landName = $reqData['land'];
|
||||
$now = time();
|
||||
try {
|
||||
$stmt5 = $db->prepare("INSERT OR IGNORE INTO web_area_visitors (land_name, player_name, added_by, added_time)
|
||||
VALUES (:land, :player, 'admin', :time)");
|
||||
$stmt5->bindValue(':land', $landName, SQLITE3_TEXT);
|
||||
$stmt5->bindValue(':player', $player, SQLITE3_TEXT);
|
||||
$stmt5->bindValue(':time', $now, SQLITE3_INTEGER);
|
||||
$stmt5->execute();
|
||||
|
||||
// 写入变更队列,通知Java端
|
||||
$stmt6 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at)
|
||||
VALUES ('visitor_change', :id, :name, :data, :time)");
|
||||
$stmt6->bindValue(':id', $landName, SQLITE3_TEXT);
|
||||
$stmt6->bindValue(':name', $landName, SQLITE3_TEXT);
|
||||
$stmt6->bindValue(':data', json_encode(['action' => 'add_visitor', 'player' => $player]), SQLITE3_TEXT);
|
||||
$stmt6->bindValue(':time', $now, SQLITE3_INTEGER);
|
||||
$stmt6->execute();
|
||||
} catch (\Throwable $e) { /* 静默 */ }
|
||||
|
||||
debugLog("validation_callback: 自动执行add_visitor", ['player' => $player, 'land' => $landName]);
|
||||
}
|
||||
}
|
||||
|
||||
debugLog("validation_callback: 玩家 {$player} 验证结果={$status}");
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
|
||||
@@ -1952,14 +2025,7 @@ function handleDeleteUserGroup($db, $name) {
|
||||
|
||||
function handleListGroupMembers($db, $group) {
|
||||
if (empty($group)) { echo json_encode(['success' => false, 'error' => 'missing group']); return; }
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_user_group_members (
|
||||
player_name TEXT NOT NULL,
|
||||
group_name TEXT NOT NULL,
|
||||
added_by TEXT DEFAULT 'system',
|
||||
added_time INTEGER DEFAULT 0,
|
||||
expiry_time INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(player_name, group_name)
|
||||
)");
|
||||
try {
|
||||
$stmt = $db->prepare("SELECT * FROM web_user_group_members WHERE group_name = :group");
|
||||
$stmt->bindValue(':group', $group, SQLITE3_TEXT);
|
||||
$rs = $stmt->execute();
|
||||
@@ -1968,6 +2034,9 @@ function handleListGroupMembers($db, $group) {
|
||||
$members[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'members' => $members]);
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => '数据库错误: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddGroupMember($db, $data) {
|
||||
@@ -1982,29 +2051,30 @@ function handleAddGroupMember($db, $data) {
|
||||
echo json_encode(['success' => false, 'error' => "玩家名格式不正确,仅支持英文字母、数字和下划线(3-16位)"]);
|
||||
return;
|
||||
}
|
||||
// ★ 校验玩家是否存在
|
||||
$playerValid = validatePlayerViaJava($db, $player, 'add_group_member', ['group' => $group]);
|
||||
if ($playerValid === null) {
|
||||
// 异步验证中 → 稍后重试(不直接写DB,等Java验证)
|
||||
echo json_encode(['success' => true, 'pending' => true, 'message' => "玩家 {$player} 的验证请求已提交,系统正在验证,请稍候再试"]);
|
||||
return;
|
||||
}
|
||||
// ★ 管理面板操作:直接用本地playerExists验证(无需异步等待Java)
|
||||
$playerValid = playerExists($db, $player);
|
||||
if (!$playerValid) {
|
||||
echo json_encode(['success' => false, 'error' => "玩家 {$player} 尚未注册,请确认玩家名是否正确"]);
|
||||
return;
|
||||
}
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_user_group_members (
|
||||
player_name TEXT NOT NULL,
|
||||
group_name TEXT NOT NULL,
|
||||
added_by TEXT DEFAULT 'system',
|
||||
added_time INTEGER DEFAULT 0,
|
||||
expiry_time INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(player_name, group_name)
|
||||
)");
|
||||
$addedBy = $data['added_by'] ?? 'admin';
|
||||
$now = time();
|
||||
$expiry = (int)($data['expiry_time'] ?? 0);
|
||||
|
||||
// ★ 如果没指定expiry_time,根据用户组的duration_minutes计算
|
||||
if ($expiry <= 0) {
|
||||
try {
|
||||
$stmtCfg = $db->prepare("SELECT duration_minutes FROM web_user_groups WHERE group_name = :group");
|
||||
$stmtCfg->bindValue(':group', $group, SQLITE3_TEXT);
|
||||
$rsCfg = $stmtCfg->execute();
|
||||
$cfgRow = $rsCfg->fetchArray(SQLITE3_ASSOC);
|
||||
if ($cfgRow && (int)$cfgRow['duration_minutes'] > 0) {
|
||||
$expiry = $now + (int)$cfgRow['duration_minutes'] * 60;
|
||||
}
|
||||
} catch (\Throwable $e) { /* 表可能不存在 */ }
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_user_group_members
|
||||
(player_name, group_name, added_by, added_time, expiry_time)
|
||||
VALUES (:player, :group, :addedby, :added, :expiry)");
|
||||
@@ -2014,19 +2084,13 @@ function handleAddGroupMember($db, $data) {
|
||||
$stmt->bindValue(':added', $now, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':expiry', $expiry, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => '数据库错误: ' . $e->getMessage()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 写入变更队列,通知Java端同步成员
|
||||
try {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_admin_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
change_type TEXT NOT NULL,
|
||||
target_id TEXT DEFAULT '',
|
||||
target_name TEXT DEFAULT '',
|
||||
change_data TEXT DEFAULT '{}',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
acknowledged INTEGER DEFAULT 0,
|
||||
acked_at INTEGER DEFAULT 0
|
||||
)");
|
||||
$stmt2 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at)
|
||||
VALUES ('group_change', :id, :name, :data, :time)");
|
||||
$stmt2->bindValue(':id', $group, SQLITE3_TEXT);
|
||||
@@ -2046,23 +2110,19 @@ function handleRemoveGroupMember($db, $data) {
|
||||
echo json_encode(['success' => false, 'error' => 'missing player or group']);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$stmt = $db->prepare("DELETE FROM web_user_group_members WHERE player_name = :player AND group_name = :group");
|
||||
$stmt->bindValue(':player', $player, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':group', $group, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
$affected = $db->changes();
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => '数据库错误: ' . $e->getMessage()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 写入变更队列,通知Java端同步成员
|
||||
try {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_admin_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
change_type TEXT NOT NULL,
|
||||
target_id TEXT DEFAULT '',
|
||||
target_name TEXT DEFAULT '',
|
||||
change_data TEXT DEFAULT '{}',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
acknowledged INTEGER DEFAULT 0,
|
||||
acked_at INTEGER DEFAULT 0
|
||||
)");
|
||||
$stmt2 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at)
|
||||
VALUES ('group_change', :id, :name, :data, :time)");
|
||||
$stmt2->bindValue(':id', $group, SQLITE3_TEXT);
|
||||
@@ -2072,7 +2132,11 @@ function handleRemoveGroupMember($db, $data) {
|
||||
$stmt2->execute();
|
||||
} catch (\Throwable $e) { /* 静默 */ }
|
||||
|
||||
if ($affected > 0) {
|
||||
echo json_encode(['success' => true, 'message' => "{$player} 已移出用户组 {$group}"]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => "玩家 {$player} 不在用户组 {$group} 中"]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGetPlayerGroups($db, $player) {
|
||||
|
||||
@@ -469,6 +469,122 @@ function initTables(SQLite3 $db) {
|
||||
$db->exec("CREATE INDEX IF NOT EXISTS idx_web_tickets_assigned ON web_tickets(assigned_to)");
|
||||
$db->exec("CREATE INDEX IF NOT EXISTS idx_web_ticket_replies_ticket ON web_ticket_replies(ticket_id)");
|
||||
|
||||
// ★ 用户组相关表(从handler函数中提取到这里统一创建,避免并发请求锁竞争)
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_user_groups (
|
||||
group_name TEXT PRIMARY KEY,
|
||||
display_name TEXT DEFAULT '',
|
||||
display_color TEXT DEFAULT '§f',
|
||||
display_emoji TEXT DEFAULT '',
|
||||
priority INTEGER DEFAULT 0,
|
||||
land_price_per_sqm INTEGER DEFAULT -1,
|
||||
max_lands INTEGER DEFAULT -1,
|
||||
home_limit INTEGER DEFAULT 0,
|
||||
join_price INTEGER DEFAULT 0,
|
||||
auto_renew INTEGER DEFAULT 0,
|
||||
renew_price INTEGER DEFAULT 0,
|
||||
duration_minutes INTEGER DEFAULT 0,
|
||||
default_perms TEXT DEFAULT '{}',
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
// 用户组成员表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_user_group_members (
|
||||
player_name TEXT NOT NULL,
|
||||
group_name TEXT NOT NULL,
|
||||
added_by TEXT DEFAULT 'system',
|
||||
added_time INTEGER DEFAULT 0,
|
||||
expiry_time INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(player_name, group_name)
|
||||
)");
|
||||
|
||||
// 管理员变更队列
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_admin_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
change_type TEXT NOT NULL,
|
||||
target_id TEXT DEFAULT '',
|
||||
target_name TEXT DEFAULT '',
|
||||
change_data TEXT DEFAULT '{}',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
acknowledged INTEGER DEFAULT 0,
|
||||
acked_at INTEGER DEFAULT 0,
|
||||
status TEXT DEFAULT 'pending'
|
||||
)");
|
||||
|
||||
// 待验证玩家表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS pending_player_validations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_name TEXT NOT NULL,
|
||||
request_type TEXT DEFAULT 'general',
|
||||
request_data TEXT DEFAULT '{}',
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
validated_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
// 续费请求表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_group_renew (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_name TEXT NOT NULL,
|
||||
group_name TEXT NOT NULL,
|
||||
renew_price INTEGER DEFAULT 0,
|
||||
duration_minutes INTEGER DEFAULT 0,
|
||||
req_id TEXT DEFAULT '',
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
processed_at INTEGER DEFAULT 0,
|
||||
remark TEXT DEFAULT ''
|
||||
)");
|
||||
|
||||
// 过户请求表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_land_transfers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
land_name TEXT NOT NULL,
|
||||
old_owner TEXT NOT NULL,
|
||||
new_owner TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
completed_at INTEGER DEFAULT 0,
|
||||
expires_at INTEGER DEFAULT 0,
|
||||
cooldown_until INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
// 领地表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_lands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
owner TEXT DEFAULT '',
|
||||
world TEXT DEFAULT '',
|
||||
x1 INTEGER DEFAULT 0,
|
||||
z1 INTEGER DEFAULT 0,
|
||||
x2 INTEGER DEFAULT 0,
|
||||
z2 INTEGER DEFAULT 0,
|
||||
y1 INTEGER DEFAULT 0,
|
||||
y2 INTEGER DEFAULT 0,
|
||||
area_size INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
admin_changed INTEGER DEFAULT 0,
|
||||
deny_pvp INTEGER DEFAULT 0,
|
||||
deny_fall_damage INTEGER DEFAULT 0,
|
||||
deny_hunger INTEGER DEFAULT 0,
|
||||
deny_all_damage INTEGER DEFAULT 0,
|
||||
deny_fire_spread INTEGER DEFAULT 0,
|
||||
deny_explosion INTEGER DEFAULT 0,
|
||||
deny_mob_grief INTEGER DEFAULT 0,
|
||||
deny_block_place INTEGER DEFAULT 0,
|
||||
deny_block_break INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
// 领地权限表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
land_id INTEGER NOT NULL,
|
||||
player_name TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'visitor',
|
||||
permissions TEXT DEFAULT '',
|
||||
granted_at INTEGER DEFAULT 0,
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
$db->exec('COMMIT');
|
||||
} catch (Exception $e) {
|
||||
$db->exec('ROLLBACK');
|
||||
|
||||
Reference in New Issue
Block a user