# 2026-10-19 ## 部署后复测(强制严格档那批) - 规则页返回首页已居中:线上 `
` ✔ - 验证码弹窗按钮已缩短:线上 `>送信<` ✔(旧的「認証して送信」已消失) - 箱主面板 `/user/questions`:限制期条幅 + 「查看社区规则」链接都在 ✔ - 严格度锁定(admin6 当前 L2): - `/user/profile` 出现「处置期间审核严格度已锁定为最高档,解除后自动恢复你的原设置」✔ - `select[name=strictness]` 与「保存审核严格度」按钮均带 `disabled` ✔ - 直接 POST `/user/moderation-strictness/update?tab=security` + `strictness=lax` → 302 回 profile, flash 仍是「已锁定」,且选中档仍为 `strict`(说明未写入)✔ - 踩坑:表单 action 是 `/user/moderation-strictness/update`(**没有** `/profile` 段);用错路径返回 302 到 `/`, 容易误判成「被中间件拦」。另外每次 POST 都要重新取 `_csrf`,用上一页(已跳首页)的 token 会 400。 ## 记忆维护 MEMORY.md 一度 19KB 被注入截断,已重写压缩到约 4.9k 字符(8.1KB),保留构建/行尾/embed/审核/处置/CLI 等硬约束, 删去已在日志里的过程细节。 ## CLI 文档 + 全量代码审阅(同日) 新增 `docs/cli.md`:命令总览、reports 三种用法与口径、restrict 级别语义/副作用、unrestrict、 hide-public、ban/restore、典型处置流程、排错表、明确不做的事。 审阅中发现并修复(全部本地临时库实测): 1. **`hide-public` 举报闭环静默失效**(真 bug):`build()` 带 `is_private=false`,在 Update 之后拿它当子查询恒为空集 → 隐藏成功但 `content_reports` 仍 pending。改为 Update 前 `Pluck("id")` 快照,再按 ID 更新/标记。 实测:3 条 pending → accepted,未波及其他箱主。 2. **整箱分享卡未拦截**:`ShareCard` 直接走缓存,L1–L3 仍能生成整箱图。已在缓存查询前加拦截(fail-closed)。 3. **L2 通知邮件误导**:强制私密后仍给提问者发「你的提问有回答」,链接点开 302。改为 `!forcedPrivate` 才发。 4. **L2 写入顺序**:`SetPrivate` + `AnswerByID` 两条语句有「公开+已作答」窗口,第二条失败还会留下「私密+无回答」。 新增 `Questions.AnswerByIDPrivate`(单条 UPDATE 同时写 answer/is_read/is_private),并补测试 `TestQuestions/AnswerByIDPrivate`。 5. **`ListAPI` 绕过 Pager**:L4 停用与页面访问密码只挡 HTML,`/api/v1/user/{domain}/questions` 仍能整页拉。 已补两道校验(均返回「用户不存在」,不暴露原因)。**页面密码可被 API 绕过是升级前就存在的问题。** 6. **L1 曝光残留**:`GetShowInHome` 的 Count 未过滤(分页总数虚高、末页空白)、热门标签查询未过滤。已补同一子查询。 7. **性能**:`ActiveLevel` 由两次查询改一次(取最高 rank);新增 `moderation.EffectiveStrictnessFor(level, raw)`, 发布路径上不再重复查库(原先一次发布最多 4 次处置查询)。 8. **证据包口径**:分母是「累计公开回答」不是窗口产出 → 文案加「累计」;新增 `待处理 N` / `已处理` 标记, 避免把已闭合的举报误读成还挂着;补 `q.deleted_at IS NULL`;`--stats` 说明最长回溯 30 天;CLI 对 `active` 做 nil 守卫。 验证:`go build ./...` EXIT=0;db/i18n/templates 测试全绿;CLI 全流程(restrict L3 → 证据包 → unrestrict)实测通过; 临时库已 DROP。 遗留(未改,已向用户说明):L4 不覆盖 `/vote/{token}`;`restrict` 没有 `--days` 上限;处置不发邮件给箱主(只有登录后条幅); `abuseAlertCooldowns` 进程内 map 无清理(重启即失效)。 ## 第二轮:处置通知邮件 + 超长确认 + 冷却表清理 - 新增 `templates/mail/restriction-notice.html` + `mail.SendRestrictionNoticeMail(RestrictionNotice)`: 文案由调用方按**箱主自己的语言**(`users.language`,空则 `conf.App.DefaultLang`)预翻译后传入, 模板本身单语言。**不含 `--reason` 原文**,只有级别名/到期/规则链接/申诉渠道。 i18n 新增 10 个键 ×4 语言:`mail.restriction.*`(6)+ `restriction.level.L1..L4`。 - `restrict`:新增 `--force`(`--days > 90` 必填,否则直接报错)与 `--no-notify`;交互模式下超长处置 还要再输一次级别名(`--force --yes` 视为两次确认)。发信失败只 warn + 终端提示,**不回滚处置**; 发信加了 20s 超时(gomail 无 Timeout 字段,裸 Dial 会等系统 TCP 超时)。 - `abuseAlertCooldowns` 加惰性清理(>1024 条时删除已过 24h 的项)。 - 顺带:`/user/questions` 限制期条幅改用 `restriction.level.*` 的名称(「收窄公开」)而不是裸 `L2`。 验证:真实 SMTP sink 实收两封(ja 与 zh-CN),标题/级别名/到期/规则链接均正确、`--reason` 未出现、 链接未被 `ZgotmplZ` 转义;`--days 120` 无 `--force` 被拒;黑洞 SMTP 下 23s 内返回(未阻塞两分钟)。 `go build ./...`、`go vet`、db/i18n/templates 测试、checktpl 全绿。 遗留:`unrestrict` 不发通知(只解禁);`restrict` 对 `--days` 无上限(设计如此)。 ## 第三轮:解除处置也发通知 - 复用同一封模板 `mail/restriction-notice.html`:`appeal` 段改为 `{{if .appeal}}` 可选, 解除通知不传该字段就整段省略。 - 新增 i18n 键 ×4:`mail.unrestrict.heading/body/note`(body 带域与级别名; note 明确写「处置记录保留用于审计、私密内容不会自动恢复」)。 - `unrestrict` 新增 `--no-notify`;撤销前先 `ActiveLevel` 取级别(撤销后读不到), 撤销后按箱主语言发解除通知。`affected==0` 直接返回、不发信。 - 抽出公共件:`sendNotice`(20s 超时)、`reportNoticeResult`(统一成功/失败输出)。 - 文档补充:`unrestrict` 的 `--no-notify`、解除通知内容、以及**到期自动解除不会发信**(无调度器)。 实测(本地 SMTP sink,2 封):处置信含级别名/到期/申诉段;解除信含「已解除」+ 私密内容提示、 **无申诉段**;两封都不含 `--reason`。`--no-notify` 两处都不发信;无生效处置时不发信。 `go build ./...`、db/i18n/templates 测试、checktpl 全绿。 ## 上线前最终验收(部署后实测) - 公开页:`/` `/rules` `/restricted` `/deactivated` 全部 200;`/rules` 四语言标题正确。 - 箱主面板(admin6,L2):条幅显示「收窄公开」+ 规则链接;严格度面板锁定提示 1 处、下拉 disabled。 - **L2 写入路径实测**(关键,因为改成了 `AnswerByIDPrivate` 单条 UPDATE): 对未回答题 q95 作答 → 302 回题目页 → flash「已保存为私密:当前处于平台处置期,回答不会公开」→ 匿名访问 302。 - 公开列表 API `/api/v1/user/{domain}/questions` 正常返回 `{code,data:{data,next_cursor,has_more}}`(新增的 L4/密码校验没有打断正常路径)。 - 整箱分享卡:受处置箱主 302(拦截生效);未处置箱主 500,原因是**测试站未配置字体**(`分享图生成失败:字体文件未配置或无法加载`),与本次改动无关。 这一点反而证明拦截位置在生成之前。 - 未能自测:CLI 发信(restrict/unrestrict 通知)需要在服务器上跑命令,只能由用户执行。 注意:admin6 现有测试题 73/88/89/90/94/95;其中 95 已被我回答并存为私密(内容带 `restrict self-check`)。 admin6 的 L2 于 2026-10-19 21:29 自动到期。 ## 邮件模板统一(用户反馈:页脚与其它邮件不一致) - 原因:`restriction-notice.html` 是我新写的,页脚用了自造的 `© 2026 TamaBox`,与项目其它 11 封邮件的 「此邮件由系统自动发送,请勿直接回复。
{{ .year }} 狼的提问箱」不一致。 - 处理:整封模板按 `question-reported.html` 的结构重写(24px 标题 + `{{.time}}` 副行、同样的卡片/页脚/外间距), `time` 由 `mail.SendRestrictionNoticeMail` 统一注入(调用方不用各自拼时间)。 - 实测(本地 SMTP sink):页脚文本与 question-reported 完全一致,`© ... TamaBox` 已消失,头部时间行存在。 - 注意:站点名在所有邮件模板里都是硬编码「狼的提问箱」,不是配置项;改站名要一起改 12 个模板。 ## 页脚精简(移动端一行放不下) - 去掉「盘」链接(`https://share.shiroko.one`,zh 文案就叫「盘」),并删除已无引用的 `site.footer.drive` 键(四语言同步删,保证 `TestDictionaryComplete` 词条数一致)。 - `site.footer.rules`:zh-CN「社区规则」→「规则」,zh-TW「社群規則」→「規則」(en/ja 本就短)。 - 现在页脚一行:规则 | 找站长 | 公告 | 聊天 | 状态 | 主页。 - 注意:`templates/base/footer.html` 用 `|` 分隔,行首行尾各带一个,删整行时不会留下悬空分隔符(已核对)。 ## 页脚改两行(用户改主意:盘恢复) - 恢复 `https://share.shiroko.one`「盘」链接与 `site.footer.drive` 键(zh-CN 盘 / zh-TW 盤 / en Drive / ja ドライブ)。 - `site.footer.rules` 文案恢复:社区规则 / 社群規則(en/ja 不变)。 - 布局两行(靠 `
` 断行,不用额外 CSS): 第一行 `社区规则 | 找站长`,第二行 `盘 | 公告板 | 聊天室 | 监控板 | 导航页`。 - 键插在 `site.footer.rules` 之后(组末),空格数按同长度键复制,gofmt 对齐不变;dict 仍 CRLF。 - 验证:build / checktpl / i18n / templates 测试全绿。 ## 页脚改为「从少到多」四行 版权行与存活计时移到最后两行(原来在最上面),整块按文字量递增: `社区规则 | 找站长` → `盘 | 公告板 | 聊天室 | 监控板 | 导航页` → `Powered by NekoBox | ICP` → `提问箱已成功存活:…`。 实现只调整了 `
` 的位置,仍共 4 个 `
`,所以块高与顶部空行跟原来一致(未新增占位)。 注意:`box_runtime` 的脚本按 id 取元素,移动位置不影响;但 `
` 数量别随意增删,否则页脚高度会变。 ## 前端样式体检(只出结论,未改代码) 量化事实(脚本统计 templates/**/*.html): - 全站 **0 个 .css 文件**,样式全部内联:`base/header.html` 内含 **17.5KB CSS + 10.6KB JS**(模板总 42.5KB), footer 3.0KB JS;全模板共 **24 个 `