69 lines
2.3 KiB
JavaScript
69 lines
2.3 KiB
JavaScript
/**
|
|
* 状态修订号(CAS)。
|
|
*
|
|
* 场景:设置页每 5 秒轮询一次 `/dsh-purge/status`。两次轮询之间用户可能点了
|
|
* 「应用」,另一个窗口(Web + 桌面同时开着)也可能点了。旧代码里 apply / revert
|
|
* 是无条件执行的——后到的那个会覆盖先到的结果,而界面以为自己看到的是最新状态。
|
|
*
|
|
* 这里给状态机一个单调递增的 `rev`:读状态时带出、写动作时带上。服务端比对
|
|
* 不一致就返回 409 和当前 rev,由调用方决定重新拉一次还是放弃,而不是盲写。
|
|
*
|
|
* 不持久化:rev 只描述「本进程内的状态演进」,重启后归零是正确语义——
|
|
* 重启本来就会让所有客户端重新拉状态。
|
|
*/
|
|
|
|
let rev = 0;
|
|
let lastAction = "";
|
|
let lastActionAt = 0;
|
|
|
|
export function currentRev() {
|
|
return rev;
|
|
}
|
|
|
|
export function bumpRev(action) {
|
|
rev += 1;
|
|
lastAction = String(action || "");
|
|
lastActionAt = Date.now();
|
|
return rev;
|
|
}
|
|
|
|
export function lastActionInfo() {
|
|
return { action: lastAction, at: lastActionAt, rev };
|
|
}
|
|
|
|
/**
|
|
* 校验调用方期望的修订号。
|
|
*
|
|
* - 不传(undefined / null / ""):放行,并把当前 rev 一起返回,兼容旧客户端。
|
|
* - 传了且相等:放行。
|
|
* - 传了但不相等:拒绝,附上当前 rev 与最近一次动作,便于界面提示。
|
|
*
|
|
* 解析失败按「没传」处理:宁可放行也不要把一个手误的参数变成硬错误,
|
|
* 真正的保护来自相等判断本身,而不是参数格式校验。
|
|
*/
|
|
export function checkRev(expected) {
|
|
if (expected === undefined || expected === null || expected === "") {
|
|
return { ok: true, rev, expected: null };
|
|
}
|
|
const wanted = Number(expected);
|
|
if (!Number.isFinite(wanted)) return { ok: true, rev, expected: null };
|
|
if (wanted === rev) return { ok: true, rev, expected: wanted };
|
|
return {
|
|
ok: false,
|
|
rev,
|
|
expected: wanted,
|
|
stale: wanted < rev,
|
|
last: lastActionInfo(),
|
|
error: wanted < rev
|
|
? `stale revision: client had rev ${wanted}, current is ${rev}`
|
|
: `unknown revision: client had rev ${wanted}, current is ${rev}`,
|
|
};
|
|
}
|
|
|
|
/** 测试用:把状态复位。生产代码不要调用。 */
|
|
export function resetRevForTests() {
|
|
rev = 0;
|
|
lastAction = "";
|
|
lastActionAt = 0;
|
|
}
|