Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
5.7 KiB
TamaBox-v4 评审修复落地报告(2026-08-27)
基于 outputs/TamaBox-v4-code-review-2026-08-27.md 的 P2/P3 findings,落实 4 项修复。
改动遵循「最小、可读、可回滚」原则;每项均给出根因、改法、验证状态。
已落地(4 项)
P3-1 autoban 404 探测失效 ✅
文件:internal/route/autoban.go
根因:autobanMiddleware 内 defer 在该 handler 返回时触发,而 Flamego run() 会在 handler 返回后自动推进链路,因此 defer 执行时下游路由尚未运行,rw.Status() 恒为 0 → KindNotFound 分支永远进不去。敏感路径封禁因 kind 在 body 内同步置位,不受影响。
修复:在 defer 之后、函数返回前加 ctx.Next()。经核对 [email protected]:Next() 即 c.index++; c.run(),会把下游全部跑完并让 c.index 越过末尾,外层循环随后只「超调」一次即退出,不会重复执行下游。
验证:internal/security/autoban 单测通过(安全核心逻辑);gofmt 语法校验通过。
P3-4 草稿/归档提交提示误导 ✅
文件:route/vote/vote.go(Submit,约 647 行)
根因:!isVoteActive 仅二分「已结束 / 尚未开始」。草稿(draft)与归档(archived)状态均 Status != published 且通常无 EndAt,双双落入「尚未开始」,而重定向目标 /vote/{LinkToken} 本身会拦截草稿/归档 → 死循环式跳转。
修复:按 vote.Status 精确分支:
draft→ 「该投票尚未发布」archived→ 「该投票已归档,无法参与」- 其余:
isVoteEnded→ 「该投票已结束」/ 否则「该投票尚未开始」 验证:gofmt语法校验通过;状态常量db.VoteStatusDraft/Archived已核实存在。
P2-2 匿名投票强制验证码 ✅
方向(用户拍板):匿名投票始终要求验证码(或行为验证码)。
文件:route/vote/vote.go(Submit)、route/user/vote.go(parseVoteMeta / renderVoteFormWithInput)、templates/user/vote-form.html
根因:原逻辑 if vote.RequireCaptcha 仅在创建者开启验证码时才校验匿名提交;关闭后匿名用户清除 vote_fp Cookie 即可换签名 token 重复投票,仅剩 limit*10 的 IP 兜底。
修复:
Submit匿名分支门槛由if vote.RequireCaptcha改为if conf.Recaptcha.CaptchaType != ""——只要全局配置了验证码,匿名提交一律校验;未配置(无密钥部署)时不阻断,避免整崩。parseVoteMeta(草稿创建/编辑、发布中编辑三路共用的唯一require_captcha读取点)与renderVoteFormWithInput强制requireCaptcha := true,使存储值与策略一致。- 模板复选框置为
checked disabled,对 owner 明确「不可关闭」。 对齐既有决策:db.Votes.RequireCaptchagorm 默认true,votes_test.go:63断言「匿名验证码默认开启(决策 #3)」,i18n 即「匿名投票需通过验证码」。本修复使该决策不可被 UI 关闭。 验证:gofmt语法校验通过;conf.Recaptcha.CaptchaType字段在internal/context/context.go:269已使用,类型无误。
P2-1 结果缓存写穿失效 ✅(务实版)
文件:internal/cache/cache.go、route/vote/vote.go(Submit)
根因:LoadResults 对 multiple/rating/rank/form 类型需 ListByVoteID 全量加载并在 Go 层逐条 json.Unmarshal 聚合(O(N));resultsCache(30s TTL)仅在过期时重算,新提交后结果页最多陈旧 30s,且每次窗口到期都要重算一次全量。
修复:
- 给
TTLCache新增Delete(key)方法。 Submit成功Create提交后调用resultsCache.Delete(strconv.FormatUint(vote.ID,10))——写穿式失效:新投票后立即清除该投票结果缓存,结果页随即反映,且把 O(N) 重算成本收敛到「每次新投票一次」。 未做(需独立迁移):真正的 O(1) SQL 下推受限于选项选择结果存于 JSONFormData大字段(跨 MySQL/PG 做 JSON 聚合既脆弱又依赖具体版本)。彻底消除 O(N) 需把选择项正态化到独立表(如vote_submission_selections(vote_id, submission_id, option_id)),以GROUP BY option_id聚合。建议作为后续独立 schema 迁移评估,不在本次改动内。 验证:internal/cache单测通过(含新Delete);gofmt语法校验通过。
本次未改(留作独立决策)
- P3-3 死配置
MaxVotesPerFingerprint:全链路写死为 1、提交侧从不读取,属历史遗留。鉴于设计本意即「一人一票」,建议整体移除该字段与表单接线;涉及 DB schema 迁移,需你确认后再做。 - P3-2 进程内缓存跨实例陈旧:
TTLCache为进程本地,多实例下清缓存不跨实例。可接受最终一致 / 缩短 TTL / 迁 Redis——架构决策,不急。
构建与验证状态
- ✅ 语法:
gofmt -e -l对 4 个改动的 .go 文件均无报错。 - ✅ 单元:
go test ./internal/security/autoban/... ./internal/cache/...全绿。 - ⚠️ 全量构建受阻(既有环境问题,非本次改动):
github.com/chai2010/webp依赖 libwebp 本机 C 库(CGO),当前沙箱缺该库,报undefined: webpGetInfo等。该依赖经传递链被route/vote、route/user、internal/route引入,故这几个包在本环境无法完整go build。在带 libwebp 的 CI/Docker 环境中可正常构建。建议你在本地/CI 跑一次全量go build ./...与go test ./...做最终确认。
改动文件清单
internal/route/autoban.gointernal/cache/cache.goroute/vote/vote.goroute/user/vote.gotemplates/user/vote-form.html