Files
tamabox/.workbuddy/memory/topics/quality.md
T
tamakyi d4e6da76f1 docs(memory): 记录第二轮复查结论与六项修复的验证方式
- 新增 topics/quality.md:业务正确性 / 运维 / 前端体验四类结论,含
  「私聊取最早 50 条」「Classic() 的 Logger 记 RequestURI」这两个坑的成因,
  以及处置覆盖面、Redis 两种故障模式、产品缺口(无 page view / 提问者找不回自己的提问)
- 2026-10-29.md:本轮六项修复的改动与验证结果,另记两个实现坑
  (limit 兜底不该抬大用户配置;Python bytes 字面量不支持 \uXXXX)
- topics/sec.md / 2026-10-27.md:补上一轮遗漏的字体自动发现与 CSP enforce 结论
- MEMORY.md:索引加 quality,处置覆盖面一条标注投票链路已补齐
2026-10-29 15:53:57 +08:00

5.0 KiB
Raw Blame History

业务正确性 / 运维 / 前端体验(2026-10-29 第二轮复查)

安全向的结论在 sec.md。这里是非安全向的四类,完整清单见 outputs/review-2026-10-29.md。

状态:下列「已实测确认的缺陷」「处置覆盖面」「Redis」三块已于同日修复并端到端验证, 条目保留是为了记住为什么会踩、以及验证该怎么做。P2/P3 仍未动。

已修:两个 P0(都是实测出来的)

  • 私聊 50 条截断(P0,已修):internal/db/message.go 的 GetByConversationID 是 Order("created_at ASC") + Limit(50),而唯一调用方 chat_handler.go:97 永远传 nil cursor。 会话上限是 200 → 第 51 条起写库成功、message_count 在涨、页面显示 51/200,但列表永远只有最早 50 条。 实测造 55 条,页面只渲染 #1–#50。取最新 N 条要 Order("id DESC") 再内存反转; 游标 id < 也必须配 DESC(配升序会反复返回同一页)。
  • 访问日志泄露 token(P0,已修):flamego.Classic() = Use(Logger(), Recovery(), Static()), Logger() 记的是 ctx.Request().RequestURI(含 query)。实测出现 path=/_/tblocal/4?t=SECRETTOKEN123。?t= 是私密题钥匙,?code= 是改密凭证。 tracing 中间件早就为同一风险避开 RequestURI 并写了注释,框架默认 Logger 漏了。 改用 flamego.New() + 自写访问日志,只记 URL.Path 与白名单 query key。

处置(restrict)的覆盖面(投票已补齐)

  • 提问链路 7 处 ActiveLevel(page L4 / question L3 / setPublic L2 / followUpAnswer L2 / shareCard 全级 / ListAPI L4 / moderation 锁定)。
  • 投票链路 0 处:vote.Page、vote.Submit、VotePublish 都不查。L4 处置后 /_/domain 已 302 到 /restricted,但 /vote/{token} 照常可投、可发新投票、探索页照常曝光。
  • 教训:新增任何「用户发起」的写入口,都要问一句「这里要不要查 ActiveLevel」。

运维面

  • 无优雅关闭(已修):全仓 signal.Notify / server.Shutdown 零命中,SIGTERM 直接杀进程。 web.go 里 ListenAndServe 一把梭。每次部署都掐断在途 POST。
  • Redis 两种故障(已修):session 走 Redis 时 load 失败会 panic(flamego/session),被 Recovery 兜成 全站 500; cache.Cacher 没写 if conf.Redis.Addr != "",空 Addr 被 go-redis 回退 localhost:6379 → 「不配 Redis」的部署里找回密码静默坏掉、私信邮件节流失效。启动时该 PING 一次并 fail fast。
  • ReadTimeout: 30s 与 8MB 单图上限冲突(手机 4G 上行 1–2 Mbps → 8MB 必超时),正文也一起丢。
  • 无 /healthz:route.Home 不碰 DB,DB 挂了 / 仍 200。
  • Dockerfile 用 golang:1.20 而 go.mod 是 1.27.3 → 构建必失败;-X 路径还是旧仓库名 github.com/NekoWheel/NekoBox,BuildCommit 永远是 dev。
  • AutoMigrate 无版本化:GORM 只加列不删列,改列名会静默留下旧列 + 新列全 NULL,比迁移失败更难发现。
  • 备份只有 docs/cli.md 一句 pg_dump;用户图片全在对象存储,只恢复 DB = 全站裂图。

前端

  • templates/question/history-template.html 有硬编码中文(查看回答 / 加载更多 / 无更多留言),en/ja 用户直接看到。 vote/page.html:140 中英混排。字典本身 917 条 × 4 语言完全对齐(有测试守着),问题只在模板层。
  • 提问页验证码失败走 SetErrorFlash + Redirect → 整段提问内容丢弃;其他分支都是 SetError + Success 保留内容。最常见的失败反而最不该丢内容。
  • form.submit() 不触发 submit 事件 → 验证码流(默认 captcha_type)完全没有防重复点击。
  • waline.js 全站加载但只有 home.html / item.html 用;UIkit CSS 走第三方 CDN 且阻塞首屏。
  • 32 个 <label> 无 for=;语义标签白字对比度约 1.9:1;全仓零 loading="lazy"。
  • 移动端 body{padding-bottom:56px} 与 env(safe-area-inset-bottom) 冲突,iPhone 上遮住约 32px。

产品缺口(按 ROI)

  1. 提问者找不回自己的提问:不填邮箱时唯一手段是成功页那条链接,关页即失联。 全仓 localStorage 零命中。最小解:前端记 {domain, id, token},箱主页显示「你有 N 条提问已被回答」。
  2. 箱主没有自己的数据:SiteAnalytics 是全站聚合(只有 COUNT(DISTINCT user_id)), 全站没有 page view 表。箱主不知道自己被看了多少次、回答率多少。
  3. NotifyType 只有 email / none,无 Telegram / Webhook。
  4. 模板零 application/ld+json(SEO 富摘要),无 RSS。
  5. 图片原图直传,无前端压缩。

这一轮确认「不用动」的

四语言字典对齐、深色模式正文对比度 4.8–6.8:1、gzip 实现(204/304/Content-Length/Vary/Flusher 都处理对了)、 后台移动端短标签方案。这几块质量高于平均,别为了统一风格去改。