fix: SN回执幂等防止重复补发,后台命令下发后轮询回执直到出结果

This commit is contained in:
ypsdf1
2026-09-29 18:03:32 +08:00
parent 345e1e9a9b
commit 52e07ec9de
2 changed files with 42 additions and 1 deletions
+35
View File
@@ -4258,11 +4258,46 @@ async function snAdmCmd(cmd) {
if (!r.success) { toast(r.message || '下发失败', 'err'); return; }
toast(r.message || '命令已下发', 'ok');
setTimeout(() => renderSnAdmin(document.getElementById('C')), 1200);
// ★ 游戏服回执是异步的(原先要等下一轮 Timer C,10~2 分钟不等)。
// 原实现只在 1.2 秒后刷新一次,那时 status 还是 pending、result 为空,
// 看起来就像"没动作 / 结果没返回"。这里持续轮询该命令回执直到出结果。
const cmdId = (r.data && r.data.id) || 0;
if (cmdId) snAdmPollCmd(cmdId, sn, 0);
} catch (e) {
toast('下发失败: ' + e.message, 'err');
}
}
/**
* 轮询 SN 命令回执(3 秒一次,最长 150 秒)。
* 游戏服执行完会把 web_sn_commands.status 置为 done 并写入 result;
* 3 分钟未回执则被后端标记为 timeout。
*/
function snAdmPollCmd(cmdId, sn, tries) {
const MAX_TRIES = 50; // 50 * 3s = 150 秒
setTimeout(async () => {
if (snAdm.tab !== 'detail' || snAdm.sn !== sn) return; // 用户已离开该详情页
let c = null;
try {
const r = await snAdmApi('admin_detail', { sn: sn });
const list = (r.success && r.data && r.data.commands) || [];
c = list.find(x => Number(x.id) === Number(cmdId)) || null;
} catch (e) { /* 网络抖动,下一轮再试 */ }
if (c && (c.status === 'done' || c.status === 'timeout')) {
if (c.status === 'timeout') toast('游戏服 3 分钟内未回执,命令已超时', 'err');
else toast('游戏服已执行:' + (c.result || '(无结果)'), 'ok');
renderSnAdmin(document.getElementById('C'));
return;
}
if (tries < MAX_TRIES) {
snAdmPollCmd(cmdId, sn, tries + 1);
} else {
toast('游戏服仍未回执,可稍后刷新查看', 'err');
renderSnAdmin(document.getElementById('C'));
}
}, 3000);
}
async function renderSnLost(el) {
const r = await snAdmApi('admin_lost_list', { page: 1, size: 100 });
if (!r.success) { el.innerHTML = '<div class="card"><p style="color:var(--red)">' + escAdmHtml(r.message || '加载失败') + '</p></div>'; return; }
+7 -1
View File
@@ -493,7 +493,8 @@ function snAckCommands() {
$itemType = '';
$linkType = '';
$linkId = 0;
$res = $db->prepare("SELECT cmd, sn, player, item_type, link_type, link_id
$alreadyDone = false;
$res = $db->prepare("SELECT cmd, sn, player, item_type, link_type, link_id, status
FROM web_sn_commands WHERE id = :id");
$res->bindValue(':id', $id, SQLITE3_INTEGER);
$rr = $res->execute();
@@ -504,8 +505,13 @@ function snAckCommands() {
$itemType = (string)$row['item_type'];
$linkType = (string)$row['link_type'];
$linkId = (int)$row['link_id'];
$alreadyDone = ((string)$row['status'] === 'done');
}
// ★ 幂等:Java 侧只有确认收到才从本地回执缓冲移除,网络抖动会原样重发。
// 已处理过的直接跳过,否则报失核查通过会再入队一次补发、注销会重置冷静期。
if ($alreadyDone) continue;
$u = $db->prepare("UPDATE web_sn_commands SET status='done', result=:r, done_at=:t
WHERE id=:id");
$u->bindValue(':r', $result, SQLITE3_TEXT);