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.
66 lines
5.7 KiB
Markdown
66 lines
5.7 KiB
Markdown
# 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 兜底。
|
||
**修复**:
|
||
1. `Submit` 匿名分支门槛由 `if vote.RequireCaptcha` 改为 `if conf.Recaptcha.CaptchaType != ""`——**只要全局配置了验证码,匿名提交一律校验**;未配置(无密钥部署)时不阻断,避免整崩。
|
||
2. `parseVoteMeta`(草稿创建/编辑、发布中编辑三路共用的唯一 `require_captcha` 读取点)与 `renderVoteFormWithInput` 强制 `requireCaptcha := true`,使存储值与策略一致。
|
||
3. 模板复选框置为 `checked disabled`,对 owner 明确「不可关闭」。
|
||
**对齐既有决策**:`db.Votes.RequireCaptcha` gorm 默认 `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,且每次窗口到期都要重算一次全量。
|
||
**修复**:
|
||
1. 给 `TTLCache` 新增 `Delete(key)` 方法。
|
||
2. `Submit` 成功 `Create` 提交后调用 `resultsCache.Delete(strconv.FormatUint(vote.ID,10))`——**写穿式失效**:新投票后立即清除该投票结果缓存,结果页随即反映,且把 O(N) 重算成本收敛到「每次新投票一次」。
|
||
**未做(需独立迁移)**:真正的 O(1) SQL 下推受限于选项选择结果存于 JSON `FormData` 大字段(跨 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.go`
|
||
- `internal/cache/cache.go`
|
||
- `route/vote/vote.go`
|
||
- `route/user/vote.go`
|
||
- `templates/user/vote-form.html`
|