17 KiB
17 KiB
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 当桩,要检查 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 与页面密码必须自己校验 (否则「整页停用」和访问密码都能被 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(/static/css/base.css?v=YYYYMMDDx,改完必须 +1,静态缓存 1 天)。 设计令牌在文件开头:root(亮)+html[data-theme="dark"](暗)。新样式一律用var(--tb-*),别写死色值,也别为深色另写分支。 - 页面级样式仍内联在各自模板
<style>里,同样只用令牌;尽量用类不用style=(深色下内联写死色值会露白)。 - 静态文件是显式 embed 清单(
static/fs.go),新增 css/js 必须手动加进//go:embed,否则运行期 404。 - 主题属性在 head 内联脚本里打(cookie
theme),不能挪回 body,否则深色用户白闪。 - 移动端(≤640px)语言切换与主题按钮不在顶栏:
.tb-nav-utils被隐藏,改由#tb-mobile-utils固定底栏承载 (bottom:0;z-index:1000,body 留padding-bottom:56px)。用户曾误以为"按钮不见了",先解释位置再排查。 ⚠️/_/{domain}箱主页IsUserPage = true(route/question/page.go:133),语言切换包在{{ if not .IsUserPage }}里, 所以箱主页没有语言切换入口——有意设计(用户 2026-10-19 明确:箱主页以箱主配置为准),别当 bug 修。 - ⚠️ UIkit 3.3.3 有
.uk-flex::before/::after{display:none}:给带uk-flex的元素加伪元素必须写.tb-cover.uk-flex::after这类复合选择器,否则静默失效。 - 分类/题型标签一律
.tb-chip;彩色uk-label只留给语义状态(置顶/私密/被举报/已读、投票四态)。 - 视觉自检:
TEST_PREVIEW=1 go test ./templates -run TestGenPreview→outputs/preview-*.html; 截图必须chrome --headless=old --force-device-scale-factor=1(新版 headless 本机挂住)。 ⚠️--window-size≠ CSS 视口(1080→985)。验证 CSS 靠像素比对(PIL 在~/.workbuddy/binaries/python/envs/default) 而非目测;扫描循环上界不能与物体边界重合(曾把边界截断读成"方角")。
主题(箱主个性化)
- 三个注入点,别再漏:
/_/{domain}/*→question.Pager按pageUser.Theme;/user/*→internal/context的 Contexter 按c.User.Theme(isThemeScope()只认/user与/user/);/vote/{token}→route/vote.Page按发布者owner.Theme。都看箱主/发布者,不看访客。全站页(/、/explore、/login)不主题化。 - 投票页语言也跟发布者:
applyOwnerLocale(data map[string]interface{}, owner)(与主题同一个函数), 必须在任何 i18n 取词之前调用;箱主未配语言才回退访客/站点默认。签名收 map 是为了能纯内存单测。 - 按页面作用域:
internal/context.pageScope()输出<html data-page-scope>,取值box/question/admin(/user/questions,/user/votes*)/profile/vote。主题用html[data-theme-style="x"][data-page-scope="y"]覆盖整页背景与装饰。⚠️ 深色覆盖必须写 三重选择器(加[data-theme="dark"]),否则被浅色作用域块盖掉(同特异性、写在后面)。 没写覆盖块的页面自动回退全局。首页//explore不加载主题 css。 - ⚠️ 装饰层固定 10 槽(base.css 12 层 = pattern1 + deco10 + image1),
--tb-deco/-size/-position三组必须等长。TestDecoSlotCount逐文件逐声明检查;少写一个不报错, 只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角)。 - 位置一律写两值
calc(0%|50%|100% ± Npx)(百分比相对「容器 − 图片」)。别用 left/bottom 关键字混搭:「水平居中 + 垂直贴底」是三值语法,非法时整条声明作废、图层静默回左上。 - 可视化编辑走
dev/theme-builder/(serve.py起服务 + iframe 加载真实页面快照)。 file:// 下父页面读不到 iframe 的 DOM,所以必须走 HTTP;预览快照由TEST_PREVIEW=1 go test ./templates -run TestGenPreview生成。 - 一套主题 =
static/css/themes/<slug>.css,只覆盖令牌,不碰布局。static/fs.go已嵌整个themes/目录, 新增主题只需放文件 + 登记[theme] available(底稿_scaffold.css)。 - ⚠️
[theme] available是硬白名单,启动时读一次:css 发布了但没登记,设置页就是看不到,现象和「部署没生效」一样。 排查分两类:curl /static/css/themes/<slug>.css200 = 文件在(那就是没登记);404 = 旧二进制。 启动日志有主题注册表已加载 enabled=...与[theme] available 未登记以下主题。 - ⚠️ 主题 css 必须成对写:
html[data-theme-style="x"](0,1,1)+html[data-theme="dark"][data-theme-style="x"](0,2,1)。 只写浅色块会让深色模式被浅色令牌污染。TestThemeCSSComplete查两个选择器 + 10 个颜色令牌。 - ⚠️ UIkit 3.3.3 的主色是编译期常量
#1e87f0,令牌注入不进去。base.css 末尾按组件映射(a/uk-link、 uk-button-primary、checkbox/radio、input:focus、uk-tab active、.uk-progress、.uk-label:not(danger|warning|success|secondary))。 新增会显示主色的组件先确认已映射,否则换主题后「页面变粉、按钮还蓝」。 - 令牌
--tb-navbar:顶栏底色,默认var(--tb-surface)。页面底色偏彩时主题必须覆盖,否则顶栏是纯白带压在彩底上割裂。 - 静态资源版本号统一
conf.StaticVersion(base.css 与主题 css 共用);图片不走这个缓存键(max-age=86400),换图要给 URL 带?v=。 - 整页背景
--tb-page-image(cover)/--tb-page-pattern(平铺,--tb-page-pattern-size控尺寸),图案叠在图片之上。 底色用纯色别用整页渐变;图案 ≥24px、透明度 ≤0.06。 - ⚠️ 多背景层不能塞变量列表:变量自身是逗号列表会让层数与 size/position 错位(曾整页重复同一张插画)。
多张装饰用固定槽位:
--tb-deco恒 6 个,缺的填none,size/position 同步补齐。 - ⚠️
background-position四值语法必须先是边关键字:left calc(...) bottom 0合法,max(...) bottom 0非法 → 整条声明作废、人物跑到左上。 - 装饰摆位:贴边、成组、避开页脚;四角各摆一个会读成贴纸墙纸;页脚文案右对齐 → 右下角不能放人物。
--tb-deco是 fixed 视口层,贴边插画别放进cover背景图(必裁边)。 - 主题视觉的结构比摆位重要:二次元站点通行做法是「整屏场景插画 + UI 悬浮其上」(蔚蓝档案 JP)。
白底留白里摆人物,无论多大都会读成贴纸;卡片/顶栏配半透明白 +
backdrop-filter才有"浮在画面上"的关系。 - 素材放
static/images/themes/<slug>/(embed 已含images/themes,子目录自动覆盖;新增其它顶层目录才改清单)。 整页图用 16:9 + 竖版两张,靠@media切换。 - 人物素材:图生图 + 抠白底(
image1给立绘、input_fidelity: high、提示词强调flat vector illustration, no outlines), 角色一致性远好于手绘 SVG;抠图用四角+四边中点 floodfill(容差 16)+ alpha MinFilter(3) 收边。 ⚠️ 正面立绘直接贴进空场景像贴纸:改用侧后方视角(不直视访客),并把 RGB 压白 12~16% 与压白 38% 的场景对齐雾度。 - ⚠️
ruleBody取令牌前要剥/* */:注释会和后面的令牌粘成一个键,色块预览静默失效。
其它约定
- 校验靠
valid:"required;maxlen:N"标签(internal/form/*.go),别在 handler 手写。 - 终止性操作(停用)落独立公开页
/deactivated(注册在reqUserSignIn外);改密码后重登才用 flash +/login。 - 只有箱主
/user面板,无管理员角色、无站方后台,且用户要求不要加:站方动作只走 CLI / 邮件 / 改配置重启。 - git 仓库
https://git.shiroko.one/tamakyi/tamabox(main)。git push能直接推(本机 Git for Windows 已认证); 「Failed to authenticate user」是临时态,别据此断言「无凭据、只能打包」。推前git log --oneline -3确认已提交。 改代码时只做只读git status/log,别动历史。outputs/未纳入版本控制(untracked),别整目录 add。 - 打包交付用 tar.gz(排除
.git/.workbuddy/outputs),放项目同级。
测试环境 box-dev-api.337845818.best
- 账号
[email protected]/adminadmin,公开页/_/admin6(用户 2026-10-19 指定)。 - 登录要过算数验证码:
GET /login取_csrf+cookie →GET /api/captcha/generate→ 算a op b→POST /login带g-recaptcha-response="<id>:<答案>"。默认语言 ja,四语言文案要一起加(TestDictionaryComplete)。 - 跑本工作区构建但部署有延迟;模板是编译期 embed,只重启不重编不会变。用户说「已部署但没变化」→ 先抓线上 HTML 比对标记,别先怀疑缓存。
- 表单字段:提问
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>'。 - 现有:
webcensoruidbanrestorereportsrestrictunrestricthide-public。 restrict --domain|--email --level L1..L4 --days N --reason/unrestrict;reports --domain|--email [--days](证据包)、--pending、--stats --days N;hide-public [--only-reported] [--since N]。- 本地冒烟:
NEKOBOX_CONFIG_PATH=<ini>;conf.Init强制app.external_url、server.salt、server.xsrf_key非空。