账号与每日在线原型后端#
2026-09-14 · 修复验收报告中已实现链路的业务与安全缺陷。Node.js 24.4+;密码使用 Argon2id;业务数据暂存 SQLite,Redis 仅用于每日榜单缓存和官方录像。此轮没有迁移数据库,也没有修改 SQLite DDL。
实现边界#
可用链路:游客 → 注册升级/登录/退出 → 服务器今日三档 → HTTP/WSS 权威对局 → 无伤成绩候选 → 重演验证与压缩证据 → Redis 持久化确认 → 指定单榜/总榜发布。网页入口 /daily-online.html,主游戏页脚有「每日在线 · 原型」。各榜只接受玩家明确提交的组合,不自动替其他账号或其他榜提交。
网页支持游客升级、恢复码展示、刷新续局、断线后重新连接、放弃、独立四榜提交、历史榜日期选择。日期采用服务器时间;普通单人游戏和本机录像继续独立使用。
尚未实现:私人同步/离线经验合账、成长与每日奖励结算、恢复码找回/密码修改、跨设备接管现有对局、观看授权/曝光屏障/播放块与播放器、正式游戏内嵌在线模式。PVP、直播不在本版;SSO 留待后续。历史榜可查询,不代表录像观看已完成。
本轮修复#
- 正式地图不再引用随客户端分发的研究题库。服务器用私有随机数生成并保存地图;独立 Worker 最多工作 5 秒、每档最多 256 个候选,失败不发布,重试间隔 60 秒。初级始终无猜;中高级在 UTC 每月 8/18/28 日不限无猜,其余日期实际执行推理解题验证。生产 API 不临时同步出题,也不为缺失历史日期编造榜单。
daily-rules-v2区分私有地图规则。旧公开地图的未冻结榜会标记 invalid,不接收新正式成绩;不静默换图或修改历史名次。旧原型库当天可能显示不可用,需要新的独立测试库或等待下一日私有题;不得清空已有库来伪装兼容。- 普通/排位通关均不立即保存 Redis 录像。先提交成绩并同意公开,符合候选后才从服务器持久化命令重演生成 MSO1,1MiB 压缩/8MiB 展开/20,000 事件限制继续有效。取消旗帜使用规范
unflag事件。 - 重演与编码在有资源上限的 Worker 中执行(最多 2 个,每个 5 秒,忙时 503)。正文写 Redis、同连接 WAITAOF 确认、读取核对后才发布;缓存/正文缺失不能假报 available。正常客户端重试沿用同 submission 和内容 key,固定 TTL 不续命。
- 发布仅修改玩家明确提交且通过验证的那个榜,其他榜复制上一代合法行,四榜同 generation 原子可见。总榜独立提交三档段,不要求单项先入 Top10;时间相同时按完成时间、入场序号排序。false 公开同意、账号撤权、观看锁、跨日、未验证段均不能发榜。
- WS 每条消息重新验证会话,退出/到期/禁用/auth_version 变化后原连接拒绝操作;控制设备与 epoch 也必须匹配。HTTP 涉及等待的敏感写入在提交前复查身份。
- 有伤排位通关正常结束和解锁,不写无伤 best。操作和终局事务失败后丢弃内存 runner,从持久化状态恢复;重演采用原始服务器时间与 game_seq。更慢新成绩不覆盖旧 best。
- 定时处理全部到期榜;超过正式局总时限/日期截止的活动局结束为 expired 并释放正式占用。命令、投影、开局和恢复查询同样检查截止,不等待定时器才生效。
配置与启动#
cd apps/server
npm ci按 .env.example 配置 apps/server/.env,至少填入 MS_DATABASE(SQLite 文件绝对路径)和 MS_ORIGIN(例如 http://localhost:8787)。npm start 与 npm run init-db 自动读取 .env;进程环境变量优先。.env 不进 Git。
MS_DATABASE=/absolute/path/daily.sqlite
MS_ORIGIN=http://localhost:8787
MS_PORT=8787
MS_REDIS_URL=redis://127.0.0.1:6380/6
MS_REDIS_PREFIX=minesweeper:dev
MS_REPLAY_DAYS=30MS_AUTH_KEY 需配置为 64 位十六进制随机值(32 字节),用于会话 CSRF、预认证签名和重试回执加密;不配置时每次重启会换密钥,旧登录不能可靠恢复。勿公开或提交密钥。
首次仅对新的空库运行 npm run init-db;已有库不重复初始化。仓库根目录运行 node apps/web/build.cjs,然后在 apps/server 运行 npm start。访问 http://localhost:8787/ 或 /daily-online.html。默认绑定本机地址;部署需 HTTPS 同源反向代理。默认 Cookie 为 HttpOnly/Secure/__Host;非 localhost 的纯 HTTP 需要显式启用下述内部测试模式。
内部 IP HTTP 测试#
无需 Nginx 时,可设置 MS_ALLOW_INSECURE_HTTP=true、MS_HOST=服务器具体 IP、MS_PORT=60080,并将 MS_ORIGIN 设为浏览器实际使用的完整地址,例如 http://173.231.17.218:60080。该开关仅接受 HTTP origin;未设置或为 false 时仍使用默认 HTTPS Cookie。
测试模式使用带端口后缀的独立 Cookie 名,保留 HttpOnly、SameSite、Origin 和 CSRF 校验,HTTP 与 WebSocket 使用相同会话设置。页面在不提供 crypto.randomUUID() 的 HTTP 环境中使用 crypto.getRandomValues() 生成 UUID。HTTP 不加密账号密码,仅使用测试账号;切回 HTTPS 时关闭此开关,已有 HTTP 测试 Cookie 不作为正式会话接收。
Redis 逻辑 DB 为 URL 中的 /6。未配置 URL 时回退 redis://127.0.0.1:6379/6,显式 URL 请保留 /6。key 统一为 ${MS_REDIS_PREFIX}:v1:lb:… 和 ${MS_REDIS_PREFIX}:v1:replay:…;生产可用 minesweeper:prod。前缀与 DB 是命名隔离,权限仍需 Redis ACL。
录像正文不进入 DB;仅元数据登记在 restricted_segments 等表,服务器裁决命令/锚点作为业务审计记录存 DB。Redis 要求 7.2+、持久卷、appendonly yes、appendfsync everysec、maxmemory-policy noeviction。写入 WAITAOF 等待 2000ms,单次 Redis 操作上限 5000ms。
公开期在建榜时固定为 ends_at + MS_REPLAY_DAYS(允许 1–30,默认沿用已有 30 天方案,未擅自把讨论中的 7 天当作确认值)。元数据与正文使用同一 public_until;改配置只影响新建榜。掉榜/跨榜复用不续期,孤儿由 Redis TTL 回收,无录像到期清理 job。容量按所有已保存候选/曾入榜的不同正文核算,不能只按最终 Top10 数量估算。
验证#
npm test
npm run test:redis
# 仓库根目录
node --test tests/web-build.test.cjs模块测试包含内存 SQLite 和模拟存储;账号/对局 API 使用临时 HTTP/WS Unix socket。test:redis 另启独立临时 Redis,不连接用户实例。新增回归覆盖地图隔离、未经提交玩家不入榜、公开同意、缺正文/Redis拒写、取消旗帜、重启恢复、有伤结算、事务失败、日切与会话撤销。
未实现的产品需求使用真正的 skip 报告,不计作通过。测试通过不是 70 项完整产品验收、真实弱网/AOF 崩溃恢复或生产负载认证。
tools/stress-daily.cjs 是注册账号种子驱动的竞榜压测,不覆盖密码注册负载;只从工具自己的临时库获取测试解题图,无公开答案 API。要运行压测请提供专用测试 Redis/socket 和前缀,不混用生产数据。