# TamaBox-v4 长期笔记(索引版) 明细已拆分到 `topics/*.md`(按需 Read 全文): `coop` 协作 / `build` 构建测试 / `embed` 行尾·模板·embed / `review` 审核处置举报 / `gorm` GORM / `style` 前端样式 / `theme` 主题 / `builder` 主题生成器 / `share` 分享卡 / `honeypot` 蜜罐多家族伪装 / `stats` 全站数据页 / `sec` 安全评估·客户端 IP 信任 / `quality` 业务正确性·运维·前端(2026-10-29) / `misc` 其它约定。 新增(2026-10-29 数据面板批次):`insights` 箱主数据面板(box_views 表 + 异步记录器 + `/user/insights` 页面 + 图片前端压缩)—— 详见 topics 可暂并入 quality,或另起 topic。 ## 协作 - 结论分级 P0/P1/P2;先根因取证 + 实测复现,别只静态推断。 - 改代码前先出变更清单;重大问题显式指出。用户常授权自主推进。 - 用户是运营者也是圈内人:处置必须可解释、可留痕、可申诉,不做自动级联封号。 ## 构建与测试(详见 topics/build.md) - 无 libwebp/GCC:`internal/storage/s3.go` 挡编译 → overlay 换桩 `dev/s3stub/s3_stub.go`。 - 「编译卡死」= `proxy.golang.org` 不可达。每条 go 命令前 `export GOPROXY=https://goproxy.cn,direct GOSUMDB=off GOFLAGS=-mod=mod`。 - DB 测试:`DB_TYPE=postgres DB_USER=postgres DB_PASSWORD=15207786347hXPm DB_HOST=127.0.0.1 DB_PORT=5432`(DSN 不带库名)。 - store 方法内用 `NewXStore(db.DB)`,别引用包级单例。 - ~~本沙箱起不了 `TamaBox web`~~ **2026-10-27 起可在 WSL2 Debian 上真跑**(PG+Redis+systemd,端口 5877): 详见 topics/build.md「本地部署」小节。要点:`env -u GOROOT`(bashrc 指着 1.26.3)、 `[recaptcha] captcha_type = math`、Windows 侧 127.0.0.1:5877 被占要用 WSL eth0 IP。 HTTP 端到端与 P1-3 的双路径 A/B 都能在这里做(回环=可信、跨主机=不可信)。 - python urllib 不稳定(用 curl);python 别用 `/tmp`;PIL 在 `~/.workbuddy/binaries/python/envs/default`。 - **Windows 上 `Path.write_text()` 默认把 `\n` 写成 `\r\n`** —— 改 LF 文件必须传 `newline="\n"` 或走 bytes,否则整个文件行尾翻车。症状:`gofmt -l` 突然把整文件标为未格式化。 **排查看 `git diff --stat`**:真实改动应是几十行,整文件重写就是行尾变了; 恢复用 `git show HEAD:` 取其行尾再统一替换。 另:本项目 `core.autocrlf=true`,仓库存 LF、工作区 checkout 成 CRLF —— 判断基准要看 `git show`。 ## 行尾 / 模板 / embed(topics/embed.md) - 行尾混合不可推断,改前用 Python 数 `b'\r\n'`;gofmt 走 `dev/gofmt_keep_crlf.py`。 - `templates/embed.go` 是显式清单:新增**顶层**模板必须加 `//go:embed`。 - `//go:embed` 的 `*` 不匹配 `_`/`.` 开头文件。 ## 审核 / 处置 / 举报(topics/review.md) - 线上通道 = 七牛;裁决在 `censor/policy.go` + `moderation.Check`,新增调用点必须走 `moderation.Check`,不可用时 fail-closed。 - 严格度存 `users.moderation_strictness`;受处置用户强制 strict(`moderation.EffectiveStrictness`),查询失败回退用户设置。 - 同请求渲染只能用 `ctx.SetError`/`SetInternalError`,不能 `*Flash`。 - 写路径查功能开关用 `db.FeatureStateOfStrict`(失败即拒);`FeatureStateOf` 的 fail-open 只给读展示用 —— 它的默认值对提问/投票是「开启」,等于让一次配置读失败 绕过箱主的关闭与定量上限。 - 处置四级 L1–L4;到期靠 `expires_at` 无 cron;`restrict` 是替换语义;比较用 `RestrictionAtLeast`。 - L2 强制私密须在审核通过后、与写回答同一条 UPDATE(`AnswerByIDPrivate`)。 - `ListAPI` 不经 Pager 中间件,L4 与页面密码须自校验;分享卡拦截须在缓存查询之前。 - 举报:免登录+验证码+限流;同 IP 同题唯一索引 → 幂等;箱主只看次数不给 IP。 - 告警 24h≥8 或 7d≥20 → 邮件;`[security] security_alert_email` 为空则静默跳过。 - **处置覆盖面:新增任何「用户发起」的写入口都要问一句「这里要不要查 `ActiveLevel`」**。 提问链路 7 处齐全;投票链路曾 0 处(L4 后 `/vote/{token}` 照常可投、可发新投票、探索页照常曝光), 2026-10-29 已补齐:`vote.Page`/`vote.Submit` 拦 L4、`VotePublish` 拦 L2、探索页按 `ActiveLevels` 批量过滤。 ## GORM(topics/gorm.md) - 默认值必须带引号;事务里每句 `tx.WithContext(ctx)`;链式对象带状态,统计/更新各自重建 query。 - 软删除+唯一索引 = 槽位永久占用;级联删除一律 `Unscoped()`。 - **「用户主动删除」= 真删**:模型全带 `dbutil.Model` 的 `DeletedAt`,默认 Delete 是软删 —— 提交记录里的 `voter_ip`/文本、提问里的 `FromIP` 会永久留在库里。删投票/删提问已改 `Unscoped` 并收进一个事务(`Votes.DeleteWithCascade`、`Questions.DeleteByID`)。 - **配额必须与内容同事务**:`incrementQuotaTx(tx,...)` 是唯一实现;别在 handler 里 再补一次 `IncrementQuota`(那是独立写入,失败只 Warn → `quota_count` 永久偏低)。 注意口径按功能不同:提问/私聊是「条数」,投票是「累计收到的票数」。 ## 前端样式(topics/style.md) - `static/css/base.css`(`?v=` 取 `conf.StaticVersion`,改完 +1)。令牌在 `:root` + `html[data-theme="dark"]`,新样式一律 `var(--tb-*)`。 - 移动端(≤640px)语言/主题按钮在固定底栏 `#tb-mobile-utils`;箱主页**有意**无语言切换。 - **错误页不套模板**:`f.NotFound` 不经过组级中间件(`template.Templater`/`Contexter` 连同 session、CSRF 都在 `f.Group` 里),在里面注入 `template.Data` 会 panic。 404/500 统一走 `internal/route/errorpage.go` 的内联 HTML + `i18n.T`, 语言从 `Accept-Language` 猜(`pickErrorPageLang`)。Recovery 也是自写的。 - **提问者找回自己的提问**:`templates/question/{success,list}.html` + `POST /api/v1/ask-status`。 记录只在本机 localStorage;接口必须同时给对 token 才返回信息,三种失败情形响应完全一致。 - UIkit 3.3.3:`.uk-flex::before/::after{display:none}`;主色是编译期常量,需在 base.css 末尾映射。 - 视觉自检:`TEST_PREVIEW=1 go test ./templates -run TestGenPreview`;截图用 `chrome --headless=old`。 ## 主题(topics/theme.md、topics/builder.md) - 三个注入点按箱主/发布者的 `Theme`;投票页语言也跟发布者(`applyOwnerLocale`)。 - 一套主题 = `static/css/themes/.css`,只覆盖令牌;显示名用 `{{ themeName ... }}`,别直接 `printf "theme.name.%s"`。 - `[theme] available` 是硬白名单(启动时读一次);css 必须成对写亮/暗两个选择器。 - 装饰层固定 10 槽,三组 `--tb-deco/-size/-position` 必须等长;位置用两值 `calc(0%|50%|100% ± N)`。 - 主题生成器 `dev/theme-builder/`:非破坏性图片处理、`layerSize`/`unitGeom` 必须共用同一函数、 `hideDeco` 会把 @media 10 槽全写 none(窄屏素材消失的头号来源)。 ## 安全(topics/sec.md) - 权威头 `CF-Connecting-IP` / `EO-Client-IP` 只在 TCP 对端可信(回环 或 `trusted_proxies` 命中) 才采信,公网对端一律忽略;唯一读取点 `internal/shared/ip.go`,别在别处另读 header。 - 生产回源 = cloudflared Tunnel 回本机(对端 127.0.0.1)→ 零配置;`trust_proxy` 两套环境都 true。 - **EdgeOne 已取消**(境内速度差)。重新启用前必须把 EO 回源网段写进 `trusted_proxies`, 否则 EO 线退化成按边缘 IP 聚合(限流互挤、自动封禁误伤整片)。`app.sample.ini` 已注明。 - 跳转参数只走 `shared.SafeRedirectTarget`;`url.Parse` 会解 `%0d%0a`,必须逐 rune 查 `u.Path`。 - **同一 handler 里 GET 有所有权校验 ≠ POST 有**(私聊 `SendMessage` 曾漏,可跨箱骚扰)。逐分支核对。 - CSP 默认 **enforce**(逃生口 `csp = report|off` 后重启);`script-src` 必须含 `mami.shiroko.one` (umami 动态注入,grep 抓不到)与按 `sentry_dsn` 条件放行的 `browser.sentry-cdn.com`。 改 CSP 白名单后必须跑 `csp_sweep.py`(真浏览器巡检),静态检查不算数。 - 注册防枚举:邮箱重复/停用与正常注册共用同一个成功出口;代价是忘密只能走找回。 - **`flamego.Classic()` 自带 `Logger()` 会记 `RequestURI`(含 query)** —— `?t=` 私密题钥匙、 `?code=` 改密凭证都会明文进日志(已实测)。要自写访问日志,只记 `URL.Path`。 - 报告 `outputs/security-audit-2026-10-25.md` + 复现脚本 `outputs/sec-audit-2026-10-25/`; 非安全向的复盘见 `outputs/review-2026-10-29.md`。 ## 其它约定(topics/misc.md) - 健康检查:`/healthz` 永远 200(不碰任何依赖,只看进程活着)+ `/readyz` 查 DB(失败 503)。 别把 DB 检查并进 healthz —— 数据库抖动会让进程被反复重启,对恢复毫无帮助。 - 校验用 `valid:"required;maxlen:N"` 标签。 - 只有箱主 `/user` 面板,**无管理员角色、无站方后台**(用户要求不要加):站方动作只走 CLI / 邮件 / 改配置重启。 - CLI 有两个构建入口必须同步:根 `go build .` 与 Dockerfile 的 `./cmd/`。 - 现有 CLI:`web censor uid ban restore reports restrict unrestrict hide-public`。 - git `https://git.shiroko.one/tamakyi/tamabox`(main),`git push` 可直推;`outputs/` 已 gitignore。 - **ImageGen 的 `background: "transparent"` 至今不生效**(返回 RGB、无 alpha 通道)。要透明素材: 提示词把背景写成一个**具体纯色**(不要写 white,白衬衫/光环会被一起吃), 再按该色值算 alpha:`alpha = clip((maxChannelDiff - 6) / 12)`,最后 `MinFilter(3)` 收 1px。 闭合图形(如光环圆环)内部的白/底色用 floodfill 填不到,按色值抠才干净。 - 生成图的右下角有 "AI生成 WORKBUDDY" 水印,做素材时统一裁掉底部约 10%(切口平直,正好贴页面下沿)。