129 lines
11 KiB
Markdown
129 lines
11 KiB
Markdown
# TamaBox-v4 长期笔记
|
||
|
||
## 协作
|
||
|
||
- 结论分级 P0/P1/P2;给建议前先根因取证 + 实测复现,别只静态推断。
|
||
- 改代码前先出变更清单获批准;用户常授权自主推进,但重大问题仍要显式指出。
|
||
- 用户是运营者也是圈内人:处置功能必须可解释、可留痕、可申诉,不做自动级联封号。
|
||
|
||
## 构建与测试
|
||
|
||
- **无 libwebp/GCC**,cgo 的 `internal/storage/s3.go` 挡编译 → 用 overlay 换 `dev/s3stub/s3_stub.go`:
|
||
`-overlay="C:/Users/Tamakyi Gekka/AppData/Local/Temp/tb-webpcheck/overlay.json"`。
|
||
但 overlay 会把它当桩,**检查 s3.go 本身要 tar 复制项目 + `go mod edit -replace`**(overlay 不能替换 GOMODCACHE 文件)。
|
||
- **「编译卡死」= `proxy.golang.org` 不可达**。每条 go 命令前:
|
||
`export GOPROXY=https://goproxy.cn,direct GOSUMDB=off GOFLAGS=-mod=mod`。`go env -w` 在本沙箱失败。
|
||
1C1G 是正式生产环境,**别用「沙箱慢」解释超时**。
|
||
- DB 测试:`DB_TYPE=postgres DB_USER=postgres DB_PASSWORD=15207786347hXPm DB_HOST=127.0.0.1 DB_PORT=5432`
|
||
(DSN 不带库名,`NewTestDB` 自建临时库后 DROP)。带 DB 的 `go test ./...` 目前全绿。
|
||
- store 方法内用 `NewXStore(db.DB)`,别引用包级单例(测试下 nil panic;`FeatureStateOf` 例外,有守卫)。
|
||
- ⚠️ **本沙箱起不了 `TamaBox web`**(session 固定走 redis,6379 未开,启动即 panic)→ HTTP 端到端只能部署后做。
|
||
探测端口 200 可能是别的进程,必须看日志确认 `Starting web server` 且无 panic。
|
||
- ⚠️ **本机 python urllib 不稳定**(代理下偶发读超时,curl 正常),曾误判「接口卡死」。线上验证优先 curl;
|
||
python 别用 `/tmp`(Windows 解析成 `C:\tmp`,与 MSYS `/tmp` 不通)。
|
||
|
||
## 行尾 / 模板
|
||
|
||
- 行尾混合不可推断(`internal/db/*`、`mail.go`、`route/user/profile.go` 是 LF;`internal/route/route.go`、
|
||
`users.go`、`i18n/dict_*.go`、`route/question/*.go`、`question-list.html` 是 CRLF)。改前用 Python 数 `b'\r\n'`;
|
||
`gofmt -l` 会把 CRLF 误报为未格式化。格式化走 `dev/gofmt_keep_crlf.py`。
|
||
- 往 gofmt 对齐组中间插键会污染 diff,新键插组末;注释别用 `''`(gofmt 会改成 `”`)。
|
||
- ⚠️ **`templates/embed.go` 是显式清单**:新增**顶层**模板必须加进 `//go:embed` 那行,否则运行时
|
||
`html/template: "xxx" is undefined` → 500,编译期无提示(`checktpl` 也只查语法)。子目录已被目录项覆盖。
|
||
- `templates/user/moderation-strictness.html` 只用 shell/python 改(Edit/Write 会注入 `data-page-node-id`)。
|
||
|
||
## 审核
|
||
|
||
- 线上通道 = 七牛(`ai.qiniuapi.com/v3/text/censor`,antispam);`[security] text_censor_source = qiniu|aliyun|auto`。
|
||
七牛只硬拦涉政/暴恐/色情,`review` 放行 → 裁决在 `censor/policy.go` + `moderation.Check`。
|
||
- **新增审核调用点必须走 `moderation.Check`**(直调 `censor.Text` 会绕过档位)。不可用时 **fail-closed**。
|
||
- ⚠️ **圈内黑话(代称+群体标签+避雷叙述)任何通用审核都拦不住**,别指望词库;靠处置 + 举报 + 人工判断。
|
||
- 严格度三档 lax/standard/strict,存 `users.moderation_strictness`。**受处置用户强制 strict**:
|
||
`moderation.EffectiveStrictness(ctx, userID, raw)`(7 个入口已全部改用它);**查询失败时回退用户设置**
|
||
(否则 DB 抖动会让全站突然加严)。处置期内 `UpdateModerationStrictness` 拒绝保存 + 设置页 disabled。
|
||
|
||
## 提示(Flash)
|
||
|
||
- ⚠️ **同请求渲染只能用 `ctx.SetError` / `SetInternalError`,不能用 `*Flash`**:flash 写 session,要下一次请求才读 →
|
||
提示静默丢失(「点了没反应」)。2026-10-18 已清理 9 处;`Set*Flash + Redirect` 是正常模式。
|
||
扫描法:找 flash 调用 → 向下第一个 `Success(`/`Redirect(` → 前者即缺陷。
|
||
|
||
## 处置与举报
|
||
|
||
- L1 停止分发(禁分享卡 + 首页/探索/标签/随机位摘除)/L2 收窄公开(新回答强制私密、禁止 set-public)/
|
||
L3 冻结发布(仍可收信)/L4 整页停用(`/restricted`)。到期靠 `expires_at` 自动失效,**无 cron**;
|
||
`restrict` 是**替换语义**;级别比较用 `RestrictionAtLeast`(显式权重)。
|
||
- ⚠️ L2 强制私密必须在**审核通过之后**,且与写回答**同一条 UPDATE**(`AnswerByIDPrivate`):
|
||
拆成「先 SetPrivate 再 Answer」会留下「公开+已作答」窗口,第二条失败还会变成「私密+无回答」。
|
||
- ⚠️ **`ListAPI`(`/api/v1/user/{domain}/questions`)不经过 Pager 中间件**,L4 与页面密码必须自己校验
|
||
(已在 2026-10-19 补;否则「整页停用」和访问密码都能被 API 绕过)。
|
||
- ⚠️ **整箱分享卡 `ShareCard` 与单题分享卡都要拦**(拦截必须在缓存查询之前,否则 30 分钟内还能拿到旧图)。
|
||
- 举报 `POST /_/{domain}/{qid}/report`:公开免登录 + 验证码 + 限流;理由固定枚举;同 IP 同题唯一索引 →
|
||
**幂等且提示与首次一致**(否则成了内容存在性探测器)。箱主只看「被举报 N 次」,**不给 IP**。
|
||
- 闭环:箱主删除/设私密或 `hide-public` → `MarkAcceptedByQuestion`;待处理超 30 天视为过期。
|
||
⚠️ `hide-public` 里必须**先快照目标 ID 再更新**:更新后再用带 `is_private=false` 的子查询恒为空集(曾静默失效)。
|
||
- 告警:24h≥8 或 7d≥20 → 邮件到 `[security] security_alert_email`(24h 冷却,进程内);单条恰好 3 人举报 → 提醒箱主。
|
||
⚠️ 该邮箱为空时静默跳过,无日志;改配置必须重启。
|
||
- `/rules` 是处置依据页(四语言);注册页必须勾选 `agree_rules`(服务端同样校验),改 e2e 脚本要同步。
|
||
- `restrict` 默认给箱主发处置通知邮件(按 `users.language`,模板 `mail/restriction-notice.html`),
|
||
**只含级别名/到期/规则链接,不含 `--reason`**;`--no-notify` 可跳过(L1 静默止血用)。
|
||
`--days > 90` 必须加 `--force`(防手误),交互模式还要再输一次级别名。
|
||
`unrestrict` 也会发解除通知(同模板、无申诉段),双方都有 `--no-notify`。
|
||
⚠️ **到期自动解除不发信**(读表比较时间,无调度器)。
|
||
|
||
## GORM
|
||
|
||
- 默认值必须带引号;`default:` 会把零值替换,空串有语义的字段别加。
|
||
- ⚠️ 事务里复用同一 `tx` 会串条件,每句 `tx.WithContext(ctx).<链式>`。
|
||
- ⚠️ 软删除 + 唯一索引 = 槽位永久占用:停用(可恢复)= 物理删内容 + 软删用户行,注册侧 `Unscoped` 给
|
||
`ErrDeactivatedEmail/Domain`;封禁 = 物理删用户行,邮箱释放。级联删除一律 `Unscoped()`。
|
||
- ⚠️ GORM 链式对象 Count/Find 后带状态,统计/预览/更新必须各自重建 query。
|
||
|
||
## 前端样式
|
||
|
||
- 基础样式在 `static/css/base.css`(2026-10-19 从 header 内联块抽出,`/static/css/base.css?v=YYYYMMDDx`,
|
||
**改完必须把 header 里的 v 号 +1**,静态缓存 1 天)。设计令牌是文件开头的 `:root`(亮)+ `html[data-theme="dark"]`(暗)。
|
||
新样式一律用 `var(--tb-*)`,别再写死色值/圆角/阴影,也别为深色另写一套分支。
|
||
- 页面级样式仍内联在各自模板的 `<style>` 里(variance 大、复用少),但同样只能用令牌。
|
||
- 静态文件是**显式 embed 清单**(`static/fs.go`),新增 css/js 必须手动加进 `//go:embed` 那行,否则运行期 404。
|
||
- 主题属性在 head 内联脚本里打(cookie `theme`),**不能挪回 body**,否则深色用户白闪。
|
||
- 页面级样式尽量用类,不用 `style=`(深色下内联写死色值会露白,`.tb-img-uploader` 踩过)。
|
||
- ⚠️ **UIkit 3.3.3 有 `.uk-flex::before/::after{display:none}`**:给带 `uk-flex` 的元素加伪元素遮罩必须写
|
||
`.tb-cover.uk-flex::after` 这类复合选择器,否则静默失效(不报错)。
|
||
- 视觉自检:`TEST_PREVIEW=1 go test ./templates -run TestGenPreview` → `outputs/preview-*.html`;
|
||
截图必须 `chrome --headless=old`(新版 headless 在本机挂住),外部资源先落到 `outputs/preview-assets/`。
|
||
改 CSS 的正确验证法是**像素比对**(PIL 在 `~/.workbuddy/binaries/python/envs/default`),不是目测。
|
||
|
||
## 其它约定
|
||
|
||
- 校验靠 `valid:"required;maxlen:N"` 标签(`internal/form/*.go`),别在 handler 手写。
|
||
- 终止性操作(停用)落独立公开页 `/deactivated`(注册在 `reqUserSignIn` 外);改密码后重登才用 flash + `/login`。
|
||
- 只有箱主 `/user` 面板,**无管理员角色、无站方后台,且用户要求不要加**:站方动作只走 CLI / 邮件 / 改配置重启。
|
||
要开管理页先问:能不能用 CLI 表达?
|
||
- git 仓库已损坏,**不要动 git**;备份用 tar.gz 排除 `.git`/`.workbuddy`,放项目同级。
|
||
|
||
## 测试环境 box-dev-api.337845818.best
|
||
|
||
- 专属测试账号:`[email protected]` / `adminadmin`,公开页 `/_/admin6`(用户 2026-10-19 指定)。
|
||
登录要过算数验证码:`GET /login` 取 `_csrf`+cookie → `GET /api/captcha/generate` → `POST /api/captcha/verify`
|
||
(头 `X-CSRF-Token`)→ `POST /login` 带 `g-recaptcha-response="<id>:<答案>"`。
|
||
- 跑本工作区构建,但**部署有延迟**;模板是编译期 embed,**只重启不重编不会变**。用户说「已部署但没变化」→
|
||
先抓线上 HTML 比对标记,别先怀疑缓存。
|
||
- 默认语言 **ja**;四语言文案要一起加(`TestDictionaryComplete` 查齐全)。验证码是数学题:
|
||
`GET /api/captcha/generate` → 算 `a op b` → `g-recaptcha-response="<id>:<答案>"`,并带页面 `_csrf`。
|
||
- 表单字段:提问 `content`,回答 `answer`。`GET /user/votes` 故意 302;匿名无邮箱提问成功直接 200 渲染。
|
||
- 自检:`dev/devcheck-e2e.py`(28 项)、`dev/devcheck-restrict.sh`(**基线非 200 就别往下走**;`RUN=1` 才真跑;
|
||
`trap` 保证撤销)。
|
||
- 图片:编码三档(头像 512/q78、背景 1920/q80、通用 1600/q80),**EXIF 方向必须先归一**(转 WebP 后会丢),
|
||
比例校验用同一套方向(头像 1:1 测不出,只有背景 16:9 触发)。
|
||
|
||
## CLI(`TamaBox <cmd>`)
|
||
|
||
- ⚠️ **两个构建入口必须同步**:根 `go build .`(根 `tamabox.go`)与 Dockerfile 的 `./cmd/`(`cmd/tamabox.go`),
|
||
否则 `No help topic for '<cmd>'`。
|
||
- 现有:`web` `censor` `uid` `ban` `restore` `reports` `restrict` `unrestrict` `hide-public`。
|
||
- `restrict --domain|--email --level L1..L4 --days N --reason` / `unrestrict`;
|
||
`reports --domain|--email [--days]`(证据包)、`--pending`、`--stats --days N`(Top 被举报箱主 + Top 举报 IP);
|
||
`hide-public [--only-reported] [--since N]`。
|
||
- 本地冒烟:`NEKOBOX_CONFIG_PATH=<ini>`;`conf.Init` 强制 `app.external_url`、`server.salt`、`server.xsrf_key` 非空。
|