docs(memory): 第三批修复与三个新踩到的坑
- 2026-10-29.md:第三批 7 项改动与验证数据 - MEMORY.md:三条长期结论 —— 「用户主动删除 = 真删」(模型全带 DeletedAt,默认 Delete 是软删)、 「配额必须与内容同事务」(incrementQuotaTx 是唯一实现,且投票口径是票数不是个数)、 「错误页不套模板」(f.NotFound 不经过组级中间件,注入 template.Data 会 panic) - topics/quality.md:标注第三批范围 新踩到的三个坑: 1. gofmt 会把注释列表 `// 1) ...` 改写成 `// 1. ...`,被标为未格式化时先排除这个 2. Select(...) 漏列 = 字段静默为空(摘要永远是空串,只有一项测试红) 3. CDP 验证纯前端:chrome 必须作为独立后台进程启动,跟在别的命令后面 `&` 会随 shell 退出被回收;headless=new 的空标签页 type 常是 other
This commit is contained in:
@@ -91,19 +91,57 @@ gzip 实现质量高、后台移动端短标签方案做得细。
|
||||
另:本项目 `core.autocrlf=true`,仓库里存 LF、工作区 checkout 成 CRLF,
|
||||
所以「工作区看到 CRLF」不等于「仓库里是 CRLF」,判断基准要看 `git show`。
|
||||
|
||||
### 待办
|
||||
---
|
||||
|
||||
## 第三批(数据安全 + 产品缺口)
|
||||
|
||||
1. **删除投票 / 删除提问改成一个事务 + 真删(`Unscoped`)**。
|
||||
原来四步独立写入、前几步失败只 Warn → 中途失败留下「选项没了票还在」这类
|
||||
界面修不好的状态,重试时投票已不存在、只能报 not exist。
|
||||
更关键的是:所有模型都带 `dbutil.Model` 的软删除,原来全是软删 ——
|
||||
提交记录里的参与者文本、`voter_ip`、`voter_token`、提问里的 `FromIP`
|
||||
只是「界面上看不见」,库里永久留着。用户点删除的预期是删掉。
|
||||
2. **配额与内容同事务**:抽出 `incrementQuotaTx(tx, userID, feature, delta)`,
|
||||
提问 `Questions.Create`、私聊 `CreateWithFirstMessage`、投票提交
|
||||
`VoteSubmissions.Create`(新增 `QuotaUserID` 字段)都在自己的事务里 +1。
|
||||
原来 handler 里独立写入且失败只 Warn,`quota_count` 会永久偏低。
|
||||
3. **追问改回答不再重复发邮件**:`UpdateAnswerByID` 返回 `wasFirst`(事务内读旧值判定),
|
||||
handler 只在首次回答时发。原来改三遍措辞就发三封。
|
||||
4. `isQuestioner` 的 token 比较改 `subtle.ConstantTimeCompare`(与 Pager 中间件同口径);
|
||||
CSV 导出的**表头与选项列**补 `sanitizeCSVCell`(原来只有数据列走它)。
|
||||
5. 写路径功能开关改 **fail-closed**:新增 `db.FeatureStateOfStrict`,
|
||||
读配置失败时拒绝而不是回退默认「开启」。
|
||||
6. **真 404 + HTML 500**:新增 `internal/route/errorpage.go`,`f.NotFound` 不再 302 到首页,
|
||||
改 `writeErrorPage(ctx, 404, ...)`;`flamego.Recovery()` 换成 `recoveryMiddleware()`。
|
||||
7. **提问者找回自己的提问**:成功页写 localStorage,箱主页显示提示条,
|
||||
新增 `POST /api/v1/ask-status`(批量问「哪些已被回答」)。
|
||||
|
||||
### 这批踩到 / 学到的
|
||||
|
||||
- **flamego 的 `f.NotFound` 拿不到组级中间件**:`template.Templater` 与 `Contexter`
|
||||
(含 session、CSRF)都挂在 `f.Group("", ...)` 上,NotFound 不经过它们。
|
||||
所以在 NotFound 里注入 `template.Data` 会直接 panic:
|
||||
`unable to invoke the 8th handler: value not found for type template.Data`。
|
||||
错误页最终用**内联 HTML + i18n.T**(`pickErrorPageLang` 从 Accept-Language 猜语言),
|
||||
不依赖模板系统 —— 错误页本来是最后一道防线,不该依赖可能出错的链路。
|
||||
- **`gofmt` 会把注释里的列表规范化**:`// 1) ...` 会被改写成 `// 1. ...`。
|
||||
自己写的中文注释列表如果用了 `1) 2) 3)`,`gofmt -l` 会标为未格式化 —— 不是行尾问题。
|
||||
- **`Select(...)` 漏列 = 字段静默为空**:`ListByIDsAndUser` 一开始只选了
|
||||
id/token/answer/created_at,摘要永远是空串,接口 12 项里就这一项红。
|
||||
- **配额口径要按功能分别确认**:投票的定量收集是「累计收到的票数」(在提交成功时 +1),
|
||||
不是「创建了几个投票」。所以我一度在删除投票里加了 -1,那是错的 —— 删投票不代表票没发生过。
|
||||
- **CDP 验证纯前端功能的套路**(`verify_ask_history_ui.mjs`):
|
||||
`chrome --headless=new --remote-debugging-port=9222`(必须作为独立后台进程跑,
|
||||
跟在别的命令后面 `&` 会随 shell 退出被回收)→ node 用内置 `WebSocket` 连
|
||||
`/json/list` 里任一 target → `Runtime.evaluate` 注入 localStorage → 重新 navigate →
|
||||
断言 DOM → `Page.captureScreenshot`。注意 headless=new 的空标签页 `type` 常是 `other`,
|
||||
不能只找 `type === "page"`。
|
||||
- **控制台错误要区分来源**:页面里 R2 图片的 CORS 报错是既有问题,
|
||||
把它算进「本次改动失败」只会得到假阳性。判据要按 URL/关键字过滤。
|
||||
|
||||
### 待办(仍未做)
|
||||
|
||||
本批改动已 commit。剩余清单在 `outputs/review-2026-10-29.md` 第四、五节,尚未做的:
|
||||
- 删除投票的级联 4 步无事务(`route/user/vote.go:1048-1076`)
|
||||
- 配额递增失败只 Warn → `quota_count` 与真实行数永久漂移(3 处调用点)
|
||||
- 改写追问回答会重复发「您的提问有回复」邮件
|
||||
- `isQuestioner` 用裸 `==` 比 token(与中间件口径不一致)
|
||||
- CSV 导出选项内容未做公式注入转义
|
||||
- `FeatureStateOf` 查询异常时 fail-open
|
||||
- AutoMigrate 无版本化(改列名会静默丢数据)
|
||||
- 无备份脚本(用户图片全在对象存储,只备份 DB 恢复后全是裂图)
|
||||
- 无备份脚本(图片全在对象存储,只备份 DB 恢复后全是裂图)
|
||||
- 32 个 `<label>` 无 `for=`、语义标签对比度 1.9:1
|
||||
- 真 404 页(现在全 302 到首页)、500 纯文本页
|
||||
|
||||
产品向(`review-2026-10-29.md` 第六节,按 ROI):提问者用 localStorage 记住自己的提问、
|
||||
箱主数据面板(全站无 page view 数据)、Telegram/Webhook 通知、JSON-LD + RSS、图片前端压缩。
|
||||
- 箱主数据面板(全站无 page view 数据)、Telegram/Webhook 通知、JSON-LD + RSS、图片前端压缩
|
||||
|
||||
Reference in New Issue
Block a user