- server/:node:http+node:sqlite 零依赖后端(users/sessions/user_stats/codex_unlocks/runs 五表) - 安全:scrypt密码哈希/服务端session/全prepared statement/输入白名单/限流/CSP等安全头/静态白名单防穿越 - 前端:o-auth登陆注册 + o-home飞行员大厅(排行榜Top100三排序+图鉴展柜背景:随机3主3副2僚机+刷怪巡游) - 图鉴改为收集点亮制(未收集=剪影+???),服务端codex_unlocks与本地并集同步 - 修复:回标题/大厅后背景卡在上场残影——render按battleUI门控+离场G=null;空闲=星空 - 结算上报:endRun/rush胜利两处submitRun,Boss击杀计数入排行榜 - playwright全流程验证通过(注册/登陆/游戏/结算/图鉴/安全注入/限流);游戏视觉未验收(用户亲自)
64 lines
2.1 KiB
JavaScript
64 lines
2.1 KiB
JavaScript
"use strict";
|
||
/* ================= SQLite(node:sqlite 内置驱动,零依赖) ================= */
|
||
const { DatabaseSync } = require("node:sqlite");
|
||
const path = require("node:path");
|
||
const fs = require("node:fs");
|
||
|
||
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data");
|
||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||
|
||
const db = new DatabaseSync(path.join(DATA_DIR, "starraid.db"));
|
||
db.exec("PRAGMA journal_mode=WAL;");
|
||
db.exec("PRAGMA foreign_keys=ON;");
|
||
|
||
db.exec(`
|
||
CREATE TABLE IF NOT EXISTS users(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||
email TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||
pass_hash TEXT NOT NULL,
|
||
created_at INTEGER NOT NULL
|
||
);
|
||
CREATE TABLE IF NOT EXISTS sessions(
|
||
token TEXT PRIMARY KEY,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
created_at INTEGER NOT NULL,
|
||
expires_at INTEGER NOT NULL
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
||
CREATE TABLE IF NOT EXISTS user_stats(
|
||
user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||
boss_kills INTEGER NOT NULL DEFAULT 0,
|
||
endless_high INTEGER NOT NULL DEFAULT 0,
|
||
runs INTEGER NOT NULL DEFAULT 0,
|
||
updated_at INTEGER NOT NULL DEFAULT 0
|
||
);
|
||
CREATE TABLE IF NOT EXISTS codex_unlocks(
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
entry_key TEXT NOT NULL,
|
||
unlocked_at INTEGER NOT NULL,
|
||
PRIMARY KEY(user_id, entry_key)
|
||
);
|
||
CREATE TABLE IF NOT EXISTS runs(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
mode TEXT NOT NULL,
|
||
diff INTEGER NOT NULL,
|
||
score INTEGER NOT NULL,
|
||
wave INTEGER NOT NULL,
|
||
stage INTEGER NOT NULL,
|
||
time_sec INTEGER NOT NULL,
|
||
boss_kills INTEGER NOT NULL,
|
||
win INTEGER NOT NULL,
|
||
created_at INTEGER NOT NULL
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_runs_user ON runs(user_id);
|
||
`);
|
||
|
||
/* 定期清理过期 session */
|
||
setInterval(() => {
|
||
try { db.prepare("DELETE FROM sessions WHERE expires_at<?").run(Date.now()); } catch (e) { /* 忽略 */ }
|
||
}, 3600000).unref();
|
||
|
||
module.exports = db;
|