Files
tamabox/.workbuddy/memory/2026-09-06.md
T
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

137 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-09-06
## 移除 vercount 第三方页脚统计(用户确认"不用就去掉")
- 【诊断】cn.vercount.one 实际 = Vercel 托管 + CF 前置(104.21.1.96),cf-cache-status DYNAMIC 每次回源跨洋;南宁实测 js ttfb ~1.9s(DNS 0.72s 高);脚本内部 DOMContentLoaded 后才 fetch events.vercount.one(ttfb ~3s,才是页脚 spinner 真瓶颈);且慢 defer 会推迟 DOMContentLoaded ~1.4s。页脚仅展示 site_uv/page_pv 两徽标(busuanzi 兼容命名)。
- 【决策】第三方装饰统计非必须 → 用户选彻底移除(非动态注入方案)。备份 .workbuddy/backup/remove-vercount-20260906/。
- 【改动 7 文件】
1. templates/base/header.html:删 dns-prefetch cn.vercount.one;删 busuanzi 注释行与 <script defer vercount>;删专用 .tb-spinner CSS 块(含 dark 覆盖)
2. templates/base/footer.html:删整行 visitor/pageview 统计(busuanzi_value_site_uv/page_pv)
3-6. internal/i18n/dict_{zh_cn,zh_tw,en,ja}.go:删 site.footer.visitor/pageview 两个 key
7. internal/i18n/i18n_test.go:visitor 参数填充断言改指 auth.recover.greeting(保 %s 覆盖)
- 【验证】残留 grep 全空;gofmt clean;go test ./internal/i18n/ + ./templates/ 全绿。
- 【未动】static/tamabox.changelogs.json 为 2024 停更归档;根 CHANGELOG.md 是否追加条目待用户确认。
## umami 数据展示放弃 + tamami.js 加载优化(方案A load 注入)
- 【umami 取数可行性】mami.tama.guru = Vercel 托管 Next.js(CNAME vercel-dns-017.com);umami v2 API 全需登录(/api/websites 401、login 空体 zod 400);新版 umami 无 Share 功能 → 用户放弃 umami 数据恢复页脚统计,footer 维持无统计徽标(vercount 移除后现状)。
- 【mami 慢根因】Vercel 无国内节点,白天/冷访问 1-2s+;tamami.js 4.6KB 缓存头好(public max-age=86400, Age=303024 靠 CDN 兜底)凌晨命中 0.3s;用户体感慢主因=umami 后台(Next.js 动态回源)+ 白天边缘波动。defer 在 head 会推迟 DOMContentLoaded(同 vercount 问题)。
- 【R2 方案排除】实测 R2 直链 0.80-1.56s 比 Vercel 缓存命中 0.28-0.35s 更慢(凌晨南宁),印证 UIkit 当初迁 cdnjs.snrat.com 的原因。
- 【脚本能力确认】tamami.js 内部:host = data-host-url 优先否则脚本同源,拼 /api/send 上报 → 静态搬迁可行但无快托管点故弃。
- 【改动】templates/base/header.html(备份 .workbuddy/backup/tamami-load-inject-20260906/):638 行 <script defer tamami.js> → window load 后 createElement 注入 + setAttribute data-website-id。效果:mami 再慢不阻塞页面关键事件;PV 统计损失≈0(load 后立即上报当前页)。
- 【验证】go test ./templates/ 通过。
- 【未做】mami 接入 EdgeOne(方案C)未实施——用户暂选 A;上报 /api/send 跨洋延迟/丢失为 Vercel 托管固有限制。
## 全站外部资源加载体检(南宁凌晨实测 2 次取快值)
- 【健康】snrat 全家(uikit css/js/icons/dayjs/alpine/waline) 0.14-0.32s;www.recaptcha.net 0.17s;cf-turnstile 0.61s;bytecdntp 0.065s(注释未启用);cdn.github.red 0.15s;sentry-cdn 0.32s(仅 SentryDSN 非空才加载)。
- 【🔴唯一慢域=R2】time.box.js 0.7-1.7s(header 全站 defer 1.6KB,纯装饰 runtime 倒计时,硬编码 2023-06-01 起算,无外部依赖);neko-pixel umd 0.87s(pixel页);waline R2 源/home.html bytecdntp 均注释残留未启用(实际 waline 走 snrat)。图片类(头像/social icons)同 R2 慢但异步非阻塞,缓存头已 immutable。
- 【发现】header 的 vConsole 有 .VConsole 开关;sentry script 无 defer/async 且 tracesSampleRate=1.0——若生产 SentryDSN 启用则同步阻塞+高采样开销(需用户确认是否启用)。
- 【未决】time.box.js 优化方案待用户选(内联 footer / load 注入 / 不动);SentryDSN 生产是否配置待确认。
## time.box.js 内联到 footer(用户选"内联",备份 .workbuddy/backup/timebox-inline-20260906/)
- 【背景】加载体检确认 R2 是全站唯一慢域(0.71-1.68s),公共路径只剩 time.box.js(header 全站 defer 1.6KB 存活倒计时,无外部依赖)拖慢 DOMContentLoaded。
- 【改动】① header.html 删 <script defer r2 cdn/time.box.js>;② footer.html box_runtime 后内联紧凑版 IIFE:功能等价 + 修 2 个原版 bug(先赋值后判空会抛错;DOMContentLoaded 后首个 1s 才显示)。
- 【验证】time.box.js 引用清零(仅注释提及);go test ./templates/ 通过。
- 【待确认】SentryDSN 生产是否配置(若启用 sentry bundle 250KB 同步阻塞 + tracesSampleRate=1.0);R2 旧对象 time.box.js 可留作兼容不动。
## 测试环境部署验证通过(box-dev-api,01:19-01:21)
- 【tamami load 注入】主页+admin6:旧 defer src=mami.tama.guru 标签残留 0;addEventListener("load") 注入监听器 1;内联代码含 createElement+data-website-id 与源码一致。
- 【time.box.js 内联】主页+admin6:time.box.js 外链 0;id="box_runtime" 元素 1;footer 内联脚本(Date.UTC(2023 + setInterval(tick))就位。
- 【说明】未做浏览器 JS 行为验证(box-dev 境外站 agent-browser 有卡死史);SSR HTML 结构与源码逐字一致 + 标准 API 逻辑,判定部署生效。文案「存活」未出现因 curl 无语言 cookie 回退 en。
## EO(生产 box.tama.guru) vs CF(测试 box-dev) 速度实测对比(南宁,凌晨 01:36,http1.1)
- 【归属】prod: Server TencentEdgeOne IP 43.174.246/247.43(EO 新加坡);dev: Server cloudflare 104.21.47.52/172.67.144.141,但 CF-RAY 后缀 AMS(阿姆斯特丹)与实测 conn ~0.24s 矛盾(疑 Ray 字段不准/边缘非 AMS)。
- 【数据】①动态首页 8 轮 TTFB:prod 0.86-1.69s 均~1.22 vs dev 1.06-1.81s 均~1.49 → EO 快 ~0.27s;②连接阶段 8 轮:prod conn 0.20/TLS 中位 0.43(2次>0.9s 尖峰)vs dev conn 0.25/TLS 0.53(平稳无尖峰)→ EO 网络层快 50-100ms 但稳定性差;③favicon 静态 6 轮:prod 0.60-1.41 均0.84(有1.4s尖峰)vs dev 0.70-0.85 均0.75 → CF 反而更稳更快。
- 【结论】EO vs CF 对南宁同一量级互有胜负,无碾压差异;动态页 EO 略快,静态/稳定性 CF 优。差异更多源于回源链路+边缘抖动而非 CDN 标签本身。局限:凌晨单点、http1.1、生产/测试后端实例不同(变量未隔离);白天高峰需复测。
## EO vs CF 正式 A/B(01:40-01:47,南宁,http1.1,后端不同服务器-残余变量已注明)
- 【方法】robots.txt(静态文本近零后端耗时)30 轮交替 + 随机 query 强制回源 15 轮 + 此前 TCP/TLS/首页/favicon 数据汇总。
- 【robots 30轮】EO med 0.61s/mean 0.74/p90 1.16/>1s×7;CF med 1.16/mean 1.34/p90 2.23/全部>1s(min 1.03)。
- 【强制回源 15轮】EO med 0.83/mean 0.97;CF med 1.56/mean 1.68(CF min 1.39)→ EO 快 ~0.7s 近 2 倍。
- 【综合】TCP 0.20vs0.25、TLS 0.43vs0.53、首页 1.22vs1.49:大样本下 EO 全面领先;favicon 6轮小样本 CF 略快(缓存状态差异)不改变结论。
- 【坑】本机 Windows curl(Schannel) 不支持 --http2,勿用于 A/B;上轮 --http2 全失败 999。Bash 同名 /tmp 文件曾出现残留污染→用 $$ 文件名。
- 【说明】CF 回源走 tunnel(cloudflared)、EO 回源直连;源站不同为残余变量,strict A/B 需同源(把 dev 挂 EO 或 prod 挂 CF)。
## autoban 敏感特征补强:sensitiveFragments 片段匹配(用户选"补强 WP 特征表",备份 .workbuddy/backup/wp-fragments-20260906/)
- 【背景】生产日志见 wlwmanifest 扫描://wp/wp-includes/wlwmanifest.xml、//news/...、//2018/...(枚举前缀变体)全部 302 放行。根因:① 所有未注册路径走 f.NotFound→ctx.Redirect("/") 得 302(非 404,注释"先写404"与实际不符,flamego Redirect 覆盖状态);② TrackNotFound=false 后 404 不计数;③ IsSensitivePath 前缀匹配抓不到中部敏感段 + 特征表无 wp-includes/xmlrpc/wlwmanifest。
- 【改动 autoban.go】新增 sensitiveFragments 数组(子串匹配强指纹,仅 WP 特征业务必无):/wp-includes/、/wp-json/、/wp-cron.php、/wp-trackback.php、/wp-admin、/wp-content、/wp-login.php、/wlwmanifest.xml、/xmlrpc.php、/wordpress;IsSensitivePath 增加 Contains 第二循环(大小写不敏感)。前缀表 sensitivePaths 保留不动(防 /administratorx 类误伤语义不变)。
- 【测试】TestIsSensitivePath 补 9 正例(含日志真实形态 //wp/wp-includes/wlwmanifest.xml 等)+ 6 负例(/_/tamakyi、/_/admin6、/api/v1/question/xxx、/explore、/i18n 不误伤)。
- 【验证】gofmt clean;autoban 全测 PASS;route 带 overlay 编译+测试通过(IsSensitivePath 由 internal/route/autoban.go:35 中间件调用,改匹配器全链生效)。
## 域名迁移方案:box.tama.guru → box.shiroko.one(方案阶段,未动代码)
- 【用户决策】tama.guru 不续费(2026-12-24 到期,RDAP 实证,注册商 DNSPod)→ 硬过渡;新域 box.shiroko.one;Bing+GSC 全套;umami 随迁 mami.shiroko.one。方案文档 outputs/domain-migration-box-shiroko-2026-09-06.md。
- 【取证要点】shiroko.one 未配置(NS=spaceship,http 200 为注册商 Parking Page);tama.guru apex=导航页 Hugo/CF Pages,box 走 DNSPod 三线路(境内 EO/境外+默认 CF SaaS);mami=Vercel。
- 【SEO 现状】box sitemap 仅 8 条(/ /explore /register /login /_/tamakyi /change-logs),explore 是唯一内容页;robots Disallow /_/ 与 sitemap 提交 /_/tamakyi 矛盾;页面无 canonical;og:url/邮件链接均依赖必填配置 external_url;static/robots.txt+sitemap.xml go:embed 硬编码旧域;register.go:64 Bloglink 硬编码;censor TrustedHosts 含 tama.guru/337845818.best;R2 CORS 白名单需加新域。
- 【核心机制】301 逐 URL 是唯一可靠保排名路径;Bing Site Move 工具可用性不稳定(2021 传闻下线)仅加速器;Site Move 后新域 6 个月禁再迁;通行 301 保持 6-12 月 vs 本方案仅 3.5 月窗口=R1 主风险,靠索引面小+早切换(≤10/01)+IndexNow 缓解。
- 【301 载体推荐】旧域 DNS 切换日统一 CNAME → CF Pages 跳转项目(_redirects: /* https://box.shiroko.one/:splat 301!),与源站/旧 CDN 解耦;弃"源站 nginx 按 Host 301"(回源 Host=旧域会成环)。
- 【开放项待用户】站群 blog/status/share/chat 是否迁(影响 footer/home 内链);/_/tamakyi 是否保(决定 robots/sitemap 取舍);cloudflared ingress 是否按 hostname。
## 域名迁移批1落地:全项目 tama.guru → shiroko.one(本地代码,备份 .workbuddy/backup/domain-shiroko-20260906/)
- 【用户决策】其他 blog 之类子域一并调整到 shiroko.one;/_/tamakyi 不保(sitemap 移除,robots 维持 Disallow /_/);先做本地操作。
- 【替换规则】*.tama.guru → *.shiroko.one(box/blog/chat/share/status/mami),apex tama.guru → shiroko.one。
- 【改 11 文件】static/robots.txt(Sitemap行);static/sitemap.xml(5条换域+删/_/tamakyi+去重旧域无斜杠重复项+lastmod全刷2026-09-06);static/llms.txt(6处);static/llms-full.txt(7处);templates/base/footer.html(6条链接);templates/base/header.html(umami注释同步+验证token加迁移提醒注释);templates/home.html(站长箱/投票/Markdown/4条blog链/explore);route/auth/register.go:64 Bloglink默认值;internal/security/censor/text.go(TrustedHosts 新增 shiroko.one,**保留** tama.guru 过渡期);conf/app.sample.ini(补齐缺失的必填项 external_url,conf.go:36 强制校验,原模板缺失→照抄即启动失败);dev/eo-*.py(3脚本 --host 默认值)。
- 【关键发现】① header 的 mami src 此前已先行改为 mami.shiroko.one(仅注释待同步);② google-site-verification(636行) 与 msvalidate.01(7行+637行重复) 是域名绑定 token,新域验证后必须替换 content,否则 GSC/Bing 无法验证新站→Site Move/Change of Address 无法提交;③ app.sample.ini 缺 external_url 必填键(模板缺陷)。
- 【验证】sitemap XML 解析通过(5条无重复);go test ./templates/ + ./internal/security/censor/ 通过;route/auth、static、internal/conf 带 overlay 编译通过。
- 【方案文档同步】outputs/domain-migration-box-shiroko-2026-09-06.md:§0 补两条决策;§11 开放项标 1/2 已答并新增 token 项;新增 §12 批1实施记录;附批次标批1完成。
- 【待用户操作】blog/chat/share/status 各子域实际服务搬迁(否则新域链接成死链);censor 旧域条目待旧域到期后删。
## IndexNow key 文件 302 修复(static/index-now/<key>.txt 无法访问)
- 【现象】用户新建 static/index-now/66e5e0911b0941f2ba7dbfbbd961e9be.txt 并改 fs.go 的 //go:embed,但访问 302 跳首页。
- 【根因】internal/route/route.go:138 的 flamego.Static 中间件 **Prefix="/static"** 只接管 /static/... 前缀;而 robots.txt/sitemap.xml/llms*.txt/favicon.ico 等根目录静态文件**每个都是单独注册的 f.Get handler**。新文件无对应路由 → 落 f.NotFound → ctx.Redirect("/") 得 302。embed 本身没问题(实测 FS.Open 可读,内容正确)。
- 【修复】route.go:① import 加 regexp;② 包级 var indexNowKeyPattern = `^[0-9a-f]{32}\.txt$`(防路径穿越);③ 在 llms-full.txt 路由后加 f.Get("/index-now/{key}"),校验白名单后 static.FS.Open("index-now/"+key),Content-Type text/plain + max-age=86400,失败 404。备份 .workbuddy/backup/domain-shiroko-20260906/route.go.bak。
- 【新增回归测试】internal/route/indexnow_route_test.go TestIndexNowKeyFileRoute:正确key→200+内容;路径穿越(..%2f 与 ../../)、合法格式但不存在、后缀不符→全 404。用 flamego.NewWithLogger(io.Discard) 构造最小实例(不依赖 New())。
- 【flamego 关键行为】internal/route/tree.go placeholderTree.match 把整个路径段赋给参数,**不排除点号** → /index-now/{key} 能匹配 <key>.txt。
- 【验证】gofmt clean;带 overlay 编译 ./internal/route/ 通过;route/static/templates 三包测试 ok。
- 【部署提醒】需重新编译部署(embed+路由均在二进制内);IndexNow 提交时 keyLocation 填完整 URL https://box.shiroko.one/index-now/66e5e0911b0941f2ba7dbfbbd961e9be.txt;旧域 301 后该路径会被重定向,搜索引擎校验需 200 直出,故切换后应用新域 keyLocation 重新提交。
## 域名变更全员邮件通知脚本(dev/notify_domain_change.py,不碰项目源码)
- 【需求】用户改了站点域名,想向全体未注销用户发邮件通知;要求复用生产 conf/app.ini 的 DB+SMTP,不动项目源码。
- 【依据】① users 表软删除用 gorm.Model,未注销=deleted_at IS NULL;收件=email 列;可顺带取 name/domain 做个性化。② 发信对齐 internal/mail/mail.go:发件人"TamaBox <mail.account>",465=隐式SSL 否则 STARTTLS,支持 mail.skip_tls_verify。③ DSN 拼装见 internal/conf/db.go(postgres sslmode=disable+search_path=schema)。
- 【实现】独立 Python 脚本:读 NEKOBOX_CONFIG_PATH 或 -c,configparser 设 strict=False(容忍示例 [upload] 重复键 image_endpoint)、去引号、去行内注释;fetch_recipients 查未注销有邮箱用户;smtp_login 复用生产配置;模板用 {{占位符}} 避免与 HTML 大括号冲突。
- 【安全开关】默认 --dry-run;--send 才真发;--to 单测地址;--limit N 小批量;单条失败隔离+汇总;每封 --delay 默认 0.3s 防限流。
- 【验证】py_compile OK;对 app.sample.ini 实测 load_config 解析正确(external_url 去引号、重复键容忍);render 模板无残留占位符。依赖 psycopg2-binary(Postgres)/pymysql(MySQL)。
- 【增强·四语言】TamaBox 为四语言站点,给脚本加 LANG_TEMPLATES(zh-CN/zh-TW/en/ja),fetch_recipients 多取 language 列;发送时按 users.language 选模板,回退 用户语言→[app] default_lang→zh-CN;--subject/--html/--plain 为全局覆盖。effective_templates() 选模板,build_message() 内统一 render 后才拼装(曾一度漏 render 已在自检发现并修正)。去掉默认模板 emoji。复测:四语言 text/plain+text/html 双部、box_link/site 就位、无残留占位符(正文 base64 传输编码属正常,解码核验通过)。
## 用户问"前端还有什么要改造的" + "box.tama.guru 接 CF 自定义主机名如何配 301"
- 【诊断残留】grep 全项目:tama.guru 残留仅在 sitemap.xml 注释、header.html 验证 token 提醒注释、censor/text.go(过渡期有意保留)、outputs/(文档)——即迁移批1已完成,无业务代码残留旧域。
- 【R2 硬编码仍散落】`r2.337845818.best` 约 14 处:header.html(preconnect/waline css/favicon/OG)、pixel.html(neko-pixel css+js)、question-not-found.html & list.html(social icons + easy.qrcode.min.js)、user/profile.html(qrcode js + pickr css/js)、route/question/share.go(social icons);waline serverURL `waline.337845818.best` 在 home.html/item.html。建议抽 `CdnBase`/`WalineURL` 模板变量集中管理(为同源化/换域铺路)。
- 【301 指引(CF 自定义主机名架构)】规则建在承载该自定义主机名的 zone 上;迁移未完成前**不要**整站 301 到 shiroko.one(会断线上)。①现在做:SSL/TLS→Always Use HTTPS(http→https 自动301)+ www 规范化 Redirect Rule;②切换时做:Bulk Redirects(box.tama.guru→https://box.shiroko.one, 301, include subpaths+query)或 Single Redirect Dynamic `concat("https://box.shiroko.one", http.request.uri.path, http.request.uri.query!=""?concat("?",http.request.uri.query):"")`;③SEO 配套:sitemap 已换域重提交 GSC/Bing、旧域 DNS 保 6-12 月、header 638-639 行验证 token + 第7行 msvalidate.01 新域重新验证替换、canonical/OG 指向新域。
- 【架构提醒】A/B 实测 EO 国内比 CF 快约 2 倍(EO 0.61 vs CF 1.16,CF 走 tunnel 回源);box.tama.guru 接 CF 后国内(南宁电信)失 EO 提速优势,规划 box.shiroko.one CDN 时建议继续用 EO 或 CF China 保国内速。
## 通知脚本三要求落地(dev/notify_domain_change.py,不碰项目源码)
- 【需求】①发送前打印 DB 总用户/已注销/未注销/未注销有邮箱数,须人工 yes 确认才发;②不一口气发完留间隔;③正文明确换域原因+用户动作:新域 box.shiroko.one、旧域 2026-12-24 到期、期间 301 转发、到期后旧域停用;Why=.guru 连年涨价一年三百出头遭不住;Do=记新域+替换此前分享的二维码。
- 【改动】抽 connect_db() 供 fetch_recipients/fetch_counts 复用;新增 fetch_counts() 打统计;main 发送前打印五类统计;--send 路径加 input("yes") 人工确认(EOFError→中止,--yes 跳过);--delay 默认 1.0s,新增 --pause-every/--pause-for 分批长暂停;四语言模板全重写,加 {{old_domain}}/{{expire_date}} 占位符与「为什么/该做什么」问答段;--old-domain 默认 box.tama.guru、--expire-date 默认 2026-12-24(均可覆盖)。
- 【验证】py_compile OK;四语言 build_message 解码核验:新域名/旧域名/2026-12-24/301 均就位、无残留 {{占位符}}、text/plain+text/html 双部。
## 通知脚本二次微调(硬编码域名 + 价格模糊 + 无语言默认简中)
- 【改动】① 新旧域名+到期日改为模块级硬编码常量 NEW_DOMAIN=box.shiroko.one / OLD_DOMAIN=box.tama.guru / EXPIRE_DATE=2026-12-24,删除 --old-domain/--expire-date 参数;site 与 box_link 改用硬编码新域(不再依赖 app.external_url,故移除其空值中止判断)。② 四语言「为什么换」文案去掉具体金额与「遭不住」,改为「.guru 连续涨价/续费成本越来越高」把原因写清。③ 语言回退:空语言直接→zh-CN(不再先落 default_lang);未知语言→default_lang→zh-CN。
- 【验证】py_compile OK;源码 grep 无「三百/over 300/年間300/遭不住」;四语言渲染含 box.shiroko.one/box.tama.guru/2026-12-24/301、无占位符;回退逻辑 空/未知→zh-CN、en→en。
## 通知脚本加「断点续发」+「按语言分群」
- 【改动】① 断点续发:新增 load_sent_state/record_sent/reset_state(JSONL 逐行追加,崩溃安全);默认状态文件 DEFAULT_STATE=脚本同目录 notify_state.json(--state 可改);发送前 load 已发邮箱集合并过滤,打印「已跳过 N 封 / 剩余待发送 N 封」;每成功一封 record 一行;--reset-state(仅 --send 时)清空后全量重发;测试 --to 模式不写状态避免污染。② 按语言分群:resolve_lang() 统一语言解析(空→zh-CN、未知→default_lang→zh-CN);发送前打印「按语言分布」统计;main 发送循环改为按 LANG_ORDER=[zh-CN,zh-TW,en,ja]+其他 分组,每组打印「[语言分组 gi/gn] 语言(标签) : 本组 N 封」,组间 --group-pause 默认 3.0s(0 关);组内仍走 --delay/--pause-every。
- 【验证】py_compile OK;monkeypatch DB+SMTP 实测:RUN1(--limit 3)→发3、state 3 行且分组正确;RUN2(同 state 续发)→跳过3、发剩余3(zh-CN 组2+ja 组1)、state 6 行;RUN3(再续发)→跳过6、发0;RUN4(--reset-state)→清空并全量重发2。空语言/未知语言均归 zh-CN。
- 【增强】捕获 send_message 返回字典(RCPT TO 阶段被拒收的 {addr:(code,err)}),非空时计为「[SMTP 拒收]」失败(不计入 sent、不写断点状态、可续发重试),避免误把"已被拒收"算成成功。验证:2 收件人第 2 封返回 550 拒收→成功1/失败1、state 仅1行。
- 【用户现象】实测运行时显示"成功 N 封"但收不到信;定位:SMTP 250 OK 仅代表 RCPT TO 接收,不等于投递成功;截图收件人全是 *@admin.com(含 admin3x/admin4 等大概率不存在账号),最可能是收件不存在→退信回到 From(mail_account) 而未被监控,或发件域 SPF/DKIM 未对齐被静默丢信。建议用 `--to 真实外部邮箱 --send` 隔离验证。
- 【增强】notify_domain_change.py 加「监控报告」+「测试模式命中数据库」:① 每次运行结束写 notify_report.json(JSON,含 db_stats/lang_distribution/sent/failed/smtp_rejected/skipped_already_sent/failed_list/config(无密码)/test_mode),`--report PATH` 改路径、`--no-report` 关闭,dry-run 与 send 都会写;② `--to 邮箱` 先查库,命中则套用该用户姓名/域名/语言,未命中按 zh-CN。验证:en 命中→英文主题、未命中→中文主题、报告字段与 SMTP 拒收单独计数均正确。
## 通知脚本模板修正(box_link 路径 + 头部致谢/群发 + 底部版权)
- 【用户反馈】实测邮件内容两点问题:① 提问箱个性域名实际为 `box.shiroko.one/_/xxx`(项目 templates/profile.html 与 base/footer.html 均为 `/_/{domain}` 路径),脚本写成 `box.shiroko.one/xxx` 不对;② 邮件头部应先感谢用户使用提问箱并说明是群发站内信;③ 底部加 `© 2026 狼的提问箱`。
- 【改动】发送循环 box_link 由 `https://{NEW_DOMAIN}/{domain}` 改为 `https://{NEW_DOMAIN}/_/{domain}`;四语言 HTML+纯文本模板(zh-CN/zh-TW/en/ja)头部问候后新增「首先感谢使用(狼的提问箱)」+「这是群发的站内通知邮件,请放心查阅」两段;底部系统通知行追加 `© 2026 狼的提问箱`(英 Wolf's Question Box / 日 狼の質問箱 / 繁 狼的提問箱)。
- 【验证】py_compile OK;四语言解码核验:box_link 含 `/_/`、致谢+群发+版权关键词均就位、无残留 {{占位符}}。
## 通知脚本去外部依赖(零 pip 安装)
- 【用户痛点】当前脚本依赖 psycopg2-binary(Postgres)/ pymysql(MySQL),但环境 pip install 基本不可用,装不了第三方包。
- 【改动】DB 层改为「零依赖优先」:默认 auto 驱动优先调用系统自带命令行客户端 psql(Postgres)/mysql(MySQL),纯标准库 subprocess,无需 pip;仅当客户端都不存在时才回退 psycopg2/pymysql(可选 pip 安装)。新增 _build_pg_uri(密码 URL 编码)、_choose_driver、_psql_query(-X -t -A -F '\x1f' -P null=\N -P footer=off)、_mysql_query(临时 cnf 传密码避免进程参数泄露)、_lib_query、run_query 统一入口;fetch_recipients/fetch_counts 改为接收 args 并调 run_query;新增 --db-driver {auto,psql,mysql,psycopg2,pymysql};load_config 增加 db_sslmode(默认 disable,与旧 psycopg2 行为一致);PG 统计用 COUNT FILTER(WHERE...) ,MySQL 用 SUM(CASE...);main 打印 [DB 驱动]。
- 【验证】py_compile OK;monkeypatch 实测:驱动选择(pg有psql→psql / mysql有mysql→mysql / 无客户端但可import psycopg2→lib / 全无→psql报错路径);psql 路径解析(含空语言/空名/语言NULL→空);mysql 路径解析;E2E 经 psql 路径 dry-run 写报告、send 6 封(按 zh-CN3/zh-TW1/en1/ja1 分组、空语言+未知fr回退zh-CN)、RESUME 跳过6发0、--to 命中库用户发往其邮箱。
- 【结论】现在只需 Python 标准库 + 服务器上的 psql/mysql 客户端即可运行,彻底不依赖 pip。
## 通知脚本 bug 修复:language 列不存在导致 FATAL
- 【现象】用户用 `python3 notify_domain_change.py -c ./auto/conf/app.ini --to [email protected] --send` 报 `ERROR: column "language" does not exist`。
- 【根因】GORM 模型 `User.Language` 默认 column=language,但线上 `nekobox` 库 schema 较旧,users 表无 language 列(可能叫 lang 或根本没有该列)。脚本此前在 fetch_recipients 里硬编码 `SELECT ..., language FROM users`。
- 【修复】新增 resolve_lang_column(cfg, args):默认 auto 探测 information_schema.columns 的 candidates(language/lang),找到即用、都不存在返回 None(调用处用 `NULL AS language` 兜底,全员回退默认语言);新增 `--lang-column` 参数可手动指定(如 --lang-column=lang)。fetch_recipients 改用探测到的列名拼 SQL;main 打印 `[语言列]` 行。mock 验证 4 场景(lang 命中/lang 缺失→NULL/显式指定/language 命中)均正确。
- 【给用户建议】直接重跑即可;若库里列叫 lang 会自动识别,若真无该列则全员按 [app] default_lang(ja) 模板发送,不影响发信。
## 通知脚本「依旧报错」二次排查:版本标记 + 改名验证法
- 【现象】首轮修完用户报「依旧不行,报错一致」。初判为用户在 Linux 服务器跑的是仓库根 `/home/tamakyi/Github/Tamabox_v4/notify_domain_change.py`(state_file 路径证伪),而工作区仅 `dev/notify_domain_change.py` 一份;用户坚称没用错文件。
- 【加版本标记】SCRIPT_VERSION = "2026-09-06.langdetect.v2",main 配置头打印 `[版本] SCRIPT_VERSION = ...`。作用:运行输出若无此行即证明跑的不是最新副本,把"是否用错文件"变成客观判定(不再靠争论)。
- 【改名验证】建议用户在服务器 `mv notify_domain_change.py notify_mail_v2.py` + `find . -name __pycache__ -exec rm -rf` 后跑新名,强制重编译以排除陈旧 .pyc / 同名模块作祟(用户自己提出的怀疑点)。
- 【结论】修复后版本不可能再出现 `column "language" does not exist`(查询已无 language 字面量);若改名+清缓存后仍报该错且带 [版本] 行,则需查 information_schema 探测是否异常。