- 2026-10-29.md:第二批 9 项改动与验证数据(含投票结果 2 万条 0.211s→0.023s), 以及剩余待办清单(级联删除事务、配额漂移、JSON-LD/RSS、箱主数据面板等) - MEMORY.md:构建测试段补 write_text 行尾陷阱与排查手法(看 git diff --stat / git show), 其它约定段补 /healthz 与 /readyz 的语义边界(存活探针不查依赖) - topics/quality.md:标注第二批已完成的范围 附带说明:本轮我自己也踩了 write_text 的坑,把 14 个 LF 文件写成了 CRLF, 靠 git diff --stat 的「整文件重写」发现、再用 git show HEAD:<file> 的行尾比对恢复。
5.3 KiB
5.3 KiB
业务正确性 / 运维 / 前端体验(2026-10-29 第二轮复查)
安全向的结论在 sec.md。这里是非安全向的四类,完整清单见 outputs/review-2026-10-29.md。
状态:下列「已实测确认的缺陷」「处置覆盖面」「Redis」三块已于同日修复并端到端验证。 紧接着做了第二批(P2 + 高价值 P3,共 9 项):投票结果 stale-while-revalidate、 三处幂等、私聊首条事务、
/healthz/readyz、验证码失败保正文、tbBeginSubmit、 waline 按页加载 / lazy / safe-area / i18n、5 处裸断言。 条目保留是为了记住为什么会踩、以及验证该怎么做。
已修:两个 P0(都是实测出来的)
- 私聊 50 条截断(P0,已修):
internal/db/message.go的GetByConversationID是Order("created_at ASC") + Limit(50),而唯一调用方chat_handler.go:97永远传nilcursor。 会话上限是 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)
- 提问者找不回自己的提问:不填邮箱时唯一手段是成功页那条链接,关页即失联。
全仓
localStorage零命中。最小解:前端记{domain, id, token},箱主页显示「你有 N 条提问已被回答」。 - 箱主没有自己的数据:
SiteAnalytics是全站聚合(只有COUNT(DISTINCT user_id)), 全站没有 page view 表。箱主不知道自己被看了多少次、回答率多少。 NotifyType只有email/none,无 Telegram / Webhook。- 模板零
application/ld+json(SEO 富摘要),无 RSS。 - 图片原图直传,无前端压缩。
这一轮确认「不用动」的
四语言字典对齐、深色模式正文对比度 4.8–6.8:1、gzip 实现(204/304/Content-Length/Vary/Flusher 都处理对了)、 后台移动端短标签方案。这几块质量高于平均,别为了统一风格去改。