修复过户面板:移除独立过户按钮改为主改主、adminTransferLand/deleteLand函数修复、改主流程改为pending等待Java验证、添加owner_change_callback回调

This commit is contained in:
ypsdf1
2026-06-29 15:24:04 +08:00
parent 866b5abe15
commit e9bd63a1e4
2 changed files with 124 additions and 74 deletions
+32 -51
View File
@@ -2330,8 +2330,7 @@ async function loadLands(el) {
<td style="font-size:11px">${l.x1},${l.z1} → ${l.x2},${l.z2}</td>
<td>${size} 格²</td>
<td>
<button onclick="changeLandOwner('${escAdmHtml(l.name)}','${escAdmHtml(l.owner)}')" style="padding:2px 8px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">改主</button>
<button onclick="adminTransferLand('${escAdmHtml(l.name)}','${escAdmHtml(l.owner)}')" style="padding:2px 8px;background:#ff9800;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">过户</button>
<button onclick="adminTransferLand('${escAdmHtml(l.name)}','${escAdmHtml(l.owner)}')" style="padding:2px 8px;background:#ff9800;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">改主</button>
<button onclick="deleteLand('${escAdmHtml(l.name)}')" style="padding:2px 8px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">删除</button>
</td>
</tr>`;
@@ -2414,55 +2413,6 @@ async function saveLandConfig() {
}
}
async function changeLandOwner(name, currentOwner) {
const newOwner = await glassPrompt('领地 [' + name + '] 新所有者', currentOwner||'', '仅允许英文字母、数字和下划线,3-16位');
if (newOwner === null || newOwner === false || newOwner === undefined) return;
const trimmed = newOwner.trim();
if (trimmed === '' || trimmed === currentOwner) return;
// 前端验证:Minecraft玩家名格式
if (!/^[a-zA-Z0-9_]{3,16}$/.test(trimmed)) {
glassAlert('玩家名格式无效:仅允许英文字母、数字和下划线,3-16位');
return;
}
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(trimmed) + '&secret=sdf1_web_comm_2026_ypshidifu'
}).then(r => r.json()).then(d => {
if (d.success) {
glassAlert(d.message || '改主成功');
loadLands(document.getElementById('C'));
} else {
glassAlert('失败: ' + (d.error||''));
}
});
}
// ★ 管理面板过户(带冷却机制)
async function adminTransferLand(name, currentOwner) {
const newOwner = await glassPrompt('过户领地 [' + name + ']\n新所有者(过户后有1分钟冷却,期间可取消)', currentOwner||'', '仅允许英文字母、数字和下划线,3-16位');
if (newOwner === null || newOwner === false || newOwner === undefined) return;
const trimmed = newOwner.trim();
if (trimmed === '' || trimmed === currentOwner) return;
if (!/^[a-zA-Z0-9_]{3,16}$/.test(trimmed)) {
glassAlert('玩家名格式无效:仅允许英文字母、数字和下划线,3-16位');
return;
}
if (!await glassConfirm('确认将领地 [' + name + '] 过户给 ' + trimmed + ' ?\n\n过户后1分钟内可以取消')) return;
fetch('api/land_api.php?action=transfer_land', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'land=' + encodeURIComponent(name) + '&new_owner=' + encodeURIComponent(trimmed) + '&secret=sdf1_web_comm_2026_ypshidifu'
}).then(r => r.json()).then(d => {
if (d.success) {
glassAlert(d.message || '过户成功');
loadLands(document.getElementById('C'));
} else {
glassAlert('失败: ' + (d.error||''));
}
});
}
async function deleteLand(name) {
if (!await glassConfirm('确定删除领地 [' + name + '] ?')) return;
fetch('api/land_api.php?action=delete_land', {
@@ -2475,6 +2425,37 @@ async function deleteLand(name) {
});
}
async function adminTransferLand(name, currentOwner) {
const newOwner = prompt('将领地 [' + name + '] 改主给谁?\n当前所有者: ' + (currentOwner||'无'), '');
if (!newOwner || !newOwner.trim()) return;
const trimmed = newOwner.trim();
if (!/^[a-zA-Z0-9_]{3,16}$/.test(trimmed)) {
glassAlert('玩家名格式不正确,仅支持英文字母、数字和下划线(3-16位)');
return;
}
if (trimmed === currentOwner) {
glassAlert('新旧所有者相同,无需更改');
return;
}
if (!await glassConfirm('确定将领地 [' + name + '] 改主为 [' + trimmed + '] ?\n(将写入待验证队列,等Java端确认后生效)')) return;
try {
const res = await 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(trimmed) + '&admin_token=' + (admToken||'')
});
const d = await res.json();
if (d.success) {
glassAlert(d.message || '已提交改主请求,等待Java端验证');
loadLands(document.getElementById('C'));
} else {
glassAlert('失败: ' + (d.error || ''));
}
} catch(e) {
glassAlert('请求失败: ' + e.message);
}
}
async function deleteShopItem(id) {
if (!await glassConfirm('确定下架商品 #' + id + ' ?')) return;
fetch('api/land_api.php?action=delete_shop_item', {
+92 -23
View File
@@ -92,7 +92,7 @@ try {
// 同步类action只接受secret验证(Java端推送)
$syncActions = ['sync_lands', 'sync_shop', 'sync_permissions', 'get_pending_validations', 'validation_callback'];
// 管理面板action:支持admin_token或secret
$adminActions = ['list_lands', 'list_shop', 'get_config', 'update_config', 'delete_land', 'update_land_owner', 'delete_shop_item', 'list_user_groups', 'get_user_group', 'update_user_group', 'delete_user_group', 'list_group_members', 'add_group_member', 'remove_group_member', 'get_player_groups'];
$adminActions = ['list_lands', 'list_shop', 'get_config', 'update_config', 'delete_land', 'update_land_owner', 'delete_shop_item', 'list_user_groups', 'get_user_group', 'update_user_group', 'delete_user_group', 'list_group_members', 'add_group_member', 'remove_group_member', 'get_player_groups', 'transfer_land_admin'];
// 玩家端action:需要token
$playerActions = ['my_lands', 'land_detail', 'add_visitor', 'remove_visitor', 'list_visitors', 'land_shop', 'buy_permission', 'transfer_land', 'cancel_transfer', 'transfer_status'];
// ★ 玩家端领地字段更新(效果管理、开关等)
@@ -102,7 +102,7 @@ try {
// ★ 成员独立权限操作action(领地所有者编辑成员权限)
$memberPermActions = ['get_member_perms', 'update_member_perm', 'clear_member_perm'];
// ★ Java端轮询PHP管理员变更 + Java回调过户结果
$syncFromPhpActions = ['poll_admin_changes', 'ack_admin_changes', 'transfer_callback'];
$syncFromPhpActions = ['poll_admin_changes', 'ack_admin_changes', 'transfer_callback', 'owner_change_callback'];
if (in_array($action, $syncActions) || in_array($action, $syncFromPhpActions)) {
// 同步类:必须有secret
@@ -201,11 +201,16 @@ try {
handleDeleteLand($db, $_POST);
break;
// ===== 管理面板:更新领地所有者 =====
// ===== 管理面板:更新领地所有者(含过户冷却机制)=====
case 'update_land_owner':
handleUpdateLandOwner($db, $_POST);
break;
// ===== 管理面板:领地过户(管理员专用,带冷却机制)=====
case 'transfer_land_admin':
handleTransferLandAdmin($db, $_POST);
break;
// ===== 管理面板:删除权限商品 =====
case 'delete_shop_item':
handleDeleteShopItem($db, $_POST);
@@ -354,6 +359,11 @@ try {
handleTransferCallback($db, $_POST + $_GET);
break;
// ===== Java端:管理面板改主结果回调 =====
case 'owner_change_callback':
handleOwnerChangeCallback($db, $_POST + $_GET);
break;
// ===== 用户组管理 =====
case 'list_user_groups':
handleListUserGroups($db);
@@ -525,9 +535,15 @@ function initLandTables($db) {
change_data TEXT DEFAULT '',
created_at INTEGER DEFAULT 0,
acknowledged INTEGER DEFAULT 0,
acked_at INTEGER DEFAULT 0
acked_at INTEGER DEFAULT 0,
status TEXT DEFAULT 'done'
)");
// ★ 为已有表添加status字段(如果不存在)
try {
$db->exec("ALTER TABLE web_admin_changes ADD COLUMN status TEXT DEFAULT 'done'");
} catch (\Throwable $e) { /* 字段已存在 */ }
// ★ 领地所有者变更表(记录PHP端修改的所有者变更)
$db->exec("CREATE TABLE IF NOT EXISTS web_land_owner_changes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -791,7 +807,7 @@ function handleUpdateLandOwner($db, $post) {
return;
}
// ★ 管理面板改主:不走异步验证,直接写入变更队列让Java端执行
// ★ 查询领地信息
$stmt = $db->prepare("SELECT id, owner FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
$result = $stmt->execute();
@@ -808,31 +824,26 @@ function handleUpdateLandOwner($db, $post) {
return;
}
// 更新PHP本地副本
$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();
// ★ 检查是否已有pending的owner_change(防止重复提交)
$stmtCheck = $db->prepare("SELECT id FROM web_admin_changes WHERE change_type = 'owner_change' AND target_name = :name AND status = 'pending'");
$stmtCheck->bindValue(':name', $name, SQLITE3_TEXT);
$checkResult = $stmtCheck->execute();
if ($checkResult->fetchArray(SQLITE3_ASSOC)) {
echo json_encode(['success' => false, 'error' => '该领地已有待验证的改主请求,请等待Java端处理']);
return;
}
// 记录变更
$stmt3 = $db->prepare("INSERT INTO web_land_owner_changes (land_id, land_name, old_owner, new_owner, created_at) VALUES (:lid, :name, :old, :new, :now)");
$stmt3->bindValue(':lid', $landId, SQLITE3_INTEGER);
$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();
// ★ 写入web_admin_changes让Java端pollAdminChanges拉取并执行
$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)");
// ★ 不再直接更新PHP本地副本!等Java端验证并回调后再更新
// 写入web_admin_changes让Java端pollAdminChanges拉取并验证执行
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at, status) VALUES ('owner_change', :id, :name, :data, :now, 'pending')");
$stmt4->bindValue(':id', $landId, SQLITE3_INTEGER);
$stmt4->bindValue(':name', $name, SQLITE3_TEXT);
$stmt4->bindValue(':data', json_encode(['old_owner' => $oldOwner, 'new_owner' => $owner, 'source' => 'admin_panel']), SQLITE3_TEXT);
$stmt4->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt4->execute();
debugLog("handleUpdateLandOwner: 管理面板改主 {$name}: {$oldOwner} → {$owner}");
echo json_encode(['success' => true, 'message' => "领地 [{$name}] 所有者已从 {$oldOwner} 变更为 {$owner}"]);
debugLog("handleUpdateLandOwner: 管理面板改主 {$name}: {$oldOwner} → {$owner} (已写入pending队列,等待Java验证)");
echo json_encode(['success' => true, 'pending' => true, 'message' => "改主请求已提交: [{$name}] {$oldOwner} → {$owner},等待Java端验证后生效"]);
}
function handleDeleteShopItem($db, $post) {
@@ -2141,6 +2152,64 @@ function handleTransferCallback($db, $post) {
}
}
/**
* ★ Java端管理面板改主结果回调
* 验证通过→更新PHP本地副本+记录变更;验证失败→标记失败
*/
function handleOwnerChangeCallback($db, $post) {
$changeId = (int)($post['change_id'] ?? 0);
$success = (int)($post['success'] ?? 0);
$reason = $post['reason'] ?? '';
if ($changeId <= 0) {
echo json_encode(['success' => false, 'error' => '缺少change_id']);
return;
}
// 查找pending的owner_change记录
$stmt = $db->prepare("SELECT * FROM web_admin_changes WHERE id = :id AND change_type = 'owner_change' AND (status = 'pending' OR status IS NULL OR status = 'done')");
$stmt->bindValue(':id', $changeId, SQLITE3_INTEGER);
$rs = $stmt->execute();
$row = $rs->fetchArray(SQLITE3_ASSOC);
if (!$row) {
echo json_encode(['success' => false, 'error' => '变更记录不存在或已处理']);
return;
}
$targetName = $row['target_name'];
$changeData = json_decode($row['change_data'] ?? '{}', true);
$newOwner = $changeData['new_owner'] ?? '';
$oldOwner = $changeData['old_owner'] ?? '';
if ($success) {
// ★ Java验证通过:更新PHP本地副本
$stmtUpd = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
$stmtUpd->bindValue(':owner', $newOwner, SQLITE3_TEXT);
$stmtUpd->bindValue(':name', $targetName, SQLITE3_TEXT);
$stmtUpd->execute();
// 记录变更历史
$landId = (int)($row['target_id'] ?? 0);
$stmtChg = $db->prepare("INSERT INTO web_land_owner_changes (land_id, land_name, old_owner, new_owner, created_at) VALUES (:lid, :name, :old, :new, :now)");
$stmtChg->bindValue(':lid', $landId, SQLITE3_INTEGER);
$stmtChg->bindValue(':name', $targetName, SQLITE3_TEXT);
$stmtChg->bindValue(':old', $oldOwner, SQLITE3_TEXT);
$stmtChg->bindValue(':new', $newOwner, SQLITE3_TEXT);
$stmtChg->bindValue(':now', time(), SQLITE3_INTEGER);
$stmtChg->execute();
// 标记处理完成
$db->query("UPDATE web_admin_changes SET status = 'completed', acknowledged = 1, acked_at = " . time() . " WHERE id = " . $changeId);
debugLog("handleOwnerChangeCallback: 改主 {$targetName} {$oldOwner}→{$newOwner} Java验证通过,已更新PHP副本");
echo json_encode(['success' => true, 'message' => '改主验证通过,已更新']);
} else {
// ★ Java验证失败
$db->query("UPDATE web_admin_changes SET status = 'failed', acknowledged = 1, acked_at = " . time() . " WHERE id = " . $changeId);
debugLog("handleOwnerChangeCallback: 改主 {$targetName} {$oldOwner}→{$newOwner} Java验证失败: {$reason}");
echo json_encode(['success' => true, 'message' => "改主验证失败: {$reason}"]);
}
}
/**
* 查询过户状态
*/