会话持久化到DB+有效期延至7天: 部署重启不再全员登出(之前会话在内存,每次发布都把用户踢下线)
This commit is contained in:
@@ -94,12 +94,15 @@ let rdb = new Database(SNAP_PATH, { readonly: true });
|
|||||||
// 注意: makeSnapshot 用 rename 覆盖快照文件后, 已打开的连接仍指向旧 inode(读到陈旧数据),
|
// 注意: makeSnapshot 用 rename 覆盖快照文件后, 已打开的连接仍指向旧 inode(读到陈旧数据),
|
||||||
// 必须在每次快照后重开连接(rdb 为 let 就是为此)。
|
// 必须在每次快照后重开连接(rdb 为 let 就是为此)。
|
||||||
|
|
||||||
// ---------- 会话(内存态,重启需重登) ----------
|
// ---------- 会话(持久化到users.db, 部署重启不再全员登出) ----------
|
||||||
const sessions = new Map(); // sid -> { uid, role, expires }
|
const sessions = new Map(); // sid -> { uid, role, expires } (内存缓存,源在DB)
|
||||||
const SESSION_HOURS = 12;
|
const SESSION_HOURS = 168; // 7天
|
||||||
|
udb.exec(`CREATE TABLE IF NOT EXISTS sessions (sid TEXT PRIMARY KEY, uid INTEGER NOT NULL, expires INTEGER NOT NULL)`);
|
||||||
function newSession(uid, role) {
|
function newSession(uid, role) {
|
||||||
const sid = crypto.randomBytes(24).toString('hex');
|
const sid = crypto.randomBytes(24).toString('hex');
|
||||||
sessions.set(sid, { uid, role, expires: Date.now() + SESSION_HOURS * 3600e3 });
|
const expires = Date.now() + SESSION_HOURS * 3600e3;
|
||||||
|
sessions.set(sid, { uid, role, expires });
|
||||||
|
try { udb.prepare('INSERT OR REPLACE INTO sessions (sid, uid, expires) VALUES (?,?,?)').run(sid, uid, expires); } catch (_) {}
|
||||||
return sid;
|
return sid;
|
||||||
}
|
}
|
||||||
function signSid(sid) { return sid + '.' + crypto.createHmac('sha256', SECRET).update(sid).digest('hex').slice(0, 16); }
|
function signSid(sid) { return sid + '.' + crypto.createHmac('sha256', SECRET).update(sid).digest('hex').slice(0, 16); }
|
||||||
@@ -113,8 +116,18 @@ function parseSid(cookie) {
|
|||||||
function authUser(req) {
|
function authUser(req) {
|
||||||
const sid = parseSid(req.headers.cookie);
|
const sid = parseSid(req.headers.cookie);
|
||||||
if (!sid) return null;
|
if (!sid) return null;
|
||||||
const s = sessions.get(sid);
|
let s = sessions.get(sid);
|
||||||
if (!s || s.expires < Date.now()) { sessions.delete(sid); return null; }
|
if (!s) { // 容器重启后从DB恢复
|
||||||
|
const row = udb.prepare('SELECT uid, expires FROM sessions WHERE sid = ?').get(sid);
|
||||||
|
if (row) {
|
||||||
|
if (row.expires < Date.now()) { udb.prepare('DELETE FROM sessions WHERE sid = ?').run(sid); return null; }
|
||||||
|
const u = udb.prepare('SELECT id, role FROM users WHERE id = ? AND disabled = 0').get(row.uid);
|
||||||
|
if (!u) return null;
|
||||||
|
s = { uid: u.id, role: u.role, expires: row.expires };
|
||||||
|
sessions.set(sid, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!s || s.expires < Date.now()) { sessions.delete(sid); try { udb.prepare('DELETE FROM sessions WHERE sid = ?').run(sid); } catch (_) {} return null; }
|
||||||
return udb.prepare('SELECT id, username, role FROM users WHERE id = ? AND disabled = 0').get(s.uid) || null;
|
return udb.prepare('SELECT id, username, role FROM users WHERE id = ? AND disabled = 0').get(s.uid) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +164,10 @@ app.post('/api/logout', (req, res) => {
|
|||||||
const sid = parseSid(req.headers.cookie);
|
const sid = parseSid(req.headers.cookie);
|
||||||
const me = authUser(req);
|
const me = authUser(req);
|
||||||
if (me) logAccess(req, me.username, 'logout');
|
if (me) logAccess(req, me.username, 'logout');
|
||||||
if (sid) sessions.delete(sid);
|
if (sid) {
|
||||||
|
sessions.delete(sid);
|
||||||
|
try { udb.prepare('DELETE FROM sessions WHERE sid = ?').run(sid); } catch (_) {}
|
||||||
|
}
|
||||||
res.setHeader('Set-Cookie', 'session=; HttpOnly; Path=/; Max-Age=0');
|
res.setHeader('Set-Cookie', 'session=; HttpOnly; Path=/; Max-Age=0');
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user