面试知识库

BP16 · 账户体系与会话归属#

定位:面试官追问持久化、鉴权、数据归属设计时的拷问预备。


核心口述(30 秒)#

“把「数据存住了」变成「数据是你的」。数据一直是持久的(Redis AOF + 磁盘卷),缺的是归属:历史清单只活在 localStorage、按 thread_id 寻址的接口从不验属主、所有访客共用一个 demo-user。M16 做三件事——给用户身份(注册登录)、给会话主人(归属表 + 一次性认领)、给每个接口一道门(属主校验)。用 SQLite + ORM,不加 Postgres(单进程无并发写需求),换 Postgres 只改一行连接串。“


拷问链#

Q: 为什么用 SQLite 而不是 Postgres?#

单进程单 worker 是既定约束(任务表、并发队列、幂等窗口全是进程内状态,多 worker 会让取消和去重静默失效)。不存在多进程并发写,Postgres 的核心能力(高并发写、连接池)在这里一条都用不上,换来的是一个容器 + 几百 MB 内存。SQLite 零容器零运维。代价:多进程时锁表,但用了 ORM 不用手写 SQL,换 Postgres 只改连接串。

Q: 会话认领为什么是一次性的?#

“谁最后发言归谁” 有洞——攻击者拿别人的 thread_id 发一句话就能过户。所以认领是一次性的:会话首次出现时绑定 user_id,之后永不更改。拿着别人的 id 来发消息直接拒。读要校验属主,写更要。

Q: 登录失败为什么不区分”没这个人”和”密码错”?#

区分开会变成用户名探测器——攻击者先枚举存在的账号,再集中撞库。一律回”用户名或密码错误”。同理 user_id 用随机串不用自增,防猜。

Q: WS 的 token 只能挂 URL 上,安全怎么办?#

浏览器原生 WebSocket 接口不允许设自定义请求头。token 挂 query 参数,代价是 URL 更容易进访问日志。缓解:token 有过期时间(24h),窗口有限。彻底干净要一次性连接票据,诚实标注未做。

Q: 库文件为什么不能放 data/ 下?#

生产编排里 data/ 是只读挂载(语料),SQLite 要写,放进去直接炸。单独放 var/ 并挂命名卷。漏了这步,容器每次重建所有账号全没——本地怎么跑都对,上生产静默丢数据。


压力题#

Q: 单进程 SQLite 扛不住了怎么升级?#

ORM 层全覆盖,换 Postgres 只改一行 DATABASE_URL 连接串,表结构和查询零改动。同时要把进程内状态(任务表、幂等窗口)挪 Redis,否则多 worker 会让取消和去重失效。

Q: 那个 vite.config.js 幽灵文件的教训?#

目录里躺着一个 TypeScript 编译产物 vite.config.js(gitignore 不可见),Vite 优先读 .js 而非 .ts——改 .ts 永远不生效,完全静默。教训:「改了配置不生效」先怀疑「文件根本没被读」,而非反复怀疑改错了值。