docs(seo): record recovery findings and add seo copy tools

This commit is contained in:
root
2026-04-17 13:02:13 +08:00
parent 56644e7f50
commit 0c534d2903
6 changed files with 1178 additions and 0 deletions

View File

@@ -91,6 +91,234 @@
5. 是否存在非 default `play/*.json`
6. 回灌后是否命中 `guide-detail / guide-play`
### 2026-04-16 晚间补充结论GPT 模板“已做优化突然像没了”的真实根因
这段是给后续 Codex 专门避坑用的。
当出现下面这些现象时:
- 详情页 / 播放页的引导文像突然没了
- 播放页 canonical 又指回详情页
- 详情页切集链接退化成 `/bf-76359-/douban-1`
- 你感觉“之前已经测试通过的 GPT 模板优化好像被覆盖了”
不要先判断成:
- `seo_copy` 数据丢了
- `seotkd` 数据被删了
- 只是数据库被覆盖了
本轮已经确认过,至少在 `videoGpt1` 这条线上,更常见的真实根因是:
1. 活跃模板链路里重新混入了旧模板写法
2. 旧写法继续使用 `{site:vpurl ...}`,没有稳定走 `VideoService::getVideoPlayUrl(...)`
3. 结果就是:
- 详情页切集列表可能退回旧格式
- 播放页内部切集入口可能退回旧格式
- 播放页 canonical / og:url / JSON-LD url 可能重新指错
4. 所以最终表现会让人误以为“之前做过的 SEO/UI 优化全部消失了”
### 这次已经确认修过的活跃入口
如果后面又出现类似问题,优先检查这些文件,而不是先去怀疑数据库:
- `code/app/home/view/videoGpt1/video/getVideoPlayUrl.html`
- `code/app/home/view/videoGpt1/module/playline/layout/layout_A.html`
- `code/app/home/view/videoGpt1/module/playline/layout/layout_B.html`
- `code/app/home/view/videoGpt1/module/playline/layout/layout_C.html`
- `code/app/home/view/videoGpt1/module/play/play_01.html`
- `code/app/home/view/videoGpt1/module/play/play_02.html`
- `code/app/home/view/videoGpt1/module/play/play_03.html`
- `code/app/home/view/videoGpt1/module/play/play_04.html`
- `code/app/home/view/videoGpt1/module/play/play_05.html`
- `code/app/home/view/videoGpt1/module/detail_main/action.html`
- `code/app/home/view/videoGpt1/module/detail_main/cover.html`
### 这次已经确认过的验证标准
后续 Codex 不要只看页面“能打开”,而要按下面标准验:
1. 详情页包含 `guide-detail`
2. 播放页包含 `guide-play`
3. 详情页切集链接必须带 slug例如
- `/bf-76359-san-fen-zhi-yi-qing-ren/douban-1`
4. 不能再出现:
- `/bf-76359-/douban-1`
5. 播放页 canonical 必须指向播放页自己,而不是详情页
6. `og:url` 与 JSON-LD `url` 也必须同步指向播放页自己
### 模板表达式的额外坑
本轮还确认了一个模板层坑:
- 在模板 `{: ... }` 表达式里直接写 `app(\app\services\VideoService::class)`,某些场景下会被模板解析吞掉命名空间
- 报错形式通常像:
- `类不存在: appservicesVideoService`
因此模板内联调用更稳的写法是:
- `app('app\\services\\VideoService')->getVideoPlayUrl(...)`
如果后续又出现“明明逻辑没问题,但模板页直接系统错误”,优先先查这一条。
### DPlayer 那条旧问题的当前结论
之前出现过:
- `TypeError: Cannot read properties of null (reading 'classList')`
本轮复核后,当前正式生效的编译脚本是:
- `code/public/static/js/compiled/48dd19fee8.js`
其中与 DPlayer fullscreen 相关的 `classList` 访问已经带容器判空保护,因此:
- 当前仓库主链并没有重新把这条空指针直接引回来
- 如果后续线上再次出现这类报错,优先怀疑:
- 老旧静态资源缓存未清
- 旧模板 JS 被覆盖回线上
- 不是本轮这批 `videoGpt1` 模板修复本身造成的
### 2026-04-16 GPT 模板快速验收清单
这份清单给后续所有 Codex 共用。
目标不是“页面能打开就算过”,而是快速确认:
- `seo_copy` 引导块还在
- TKD 还在
- URL 没退化
- detail / play 主链没被旧模板重新接管
#### 验收前提
默认以某个已验证 host 为样本,例如:
- `jpjdxs.com`
本轮验证样本里常用的页面有:
- 首页:`/`
- 榜单首页:`/phb-index`
- 一级分类页:`/videotype/1-dian-ying`
- 二级分类页:`/videotype/dian-ying/shao-shi-dian-ying-1`
- 搜索结果页:`/get-index?keyword=test`
- 详情页:`/neirong-76359-san-fen-zhi-yi-qing-ren`
- 播放页:`/bf-76359-san-fen-zhi-yi-qing-ren/douban-1`
#### 页面级验收标准
1. 首页
- 必须命中:`guide-collection`
- 必须能看到:首页导览、自定义首页引导块
- 必须有正常 `title / keywords / description`
2. 榜单首页
- 必须命中:`guide-collection`
- 必须有正常榜单页 TKD
3. 一级分类页
- 必须命中:`guide-collection`
- 必须有正常分类页 TKD
4. 二级分类列表页
- 必须命中:`guide-collection`
- 必须有正常列表页 TKD
5. 搜索结果页
- 必须命中:`guide-collection`
- `canonical` 必须指向当前真实搜索页
- `og:url` 必须与 `canonical` 一致
- `CollectionPage.url` 必须与 `canonical` 一致
6. 详情页
- 必须命中:`guide-detail`
- `canonical / og:url / JSON-LD url` 必须都指向详情页自己
- 切集链接必须带 slug
- 不能出现 `/bf-76359-/douban-1` 这种退化链接
7. 播放页
- 必须命中:`guide-play`
- `canonical / og:url / JSON-LD url` 必须都指向播放页自己
- 播放页内部切集链接不能退化成无 slug 形式
#### 最小命令清单
以下命令是后续 Codex 可直接复用的最小验收方式。
假设当前本地回放入口是:
- `http://127.0.0.1:18081`
- Host 头是:`jpjdxs.com`
1. 首页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/' | rg -n 'guide-collection|首页导览|<title>|<meta name="keywords"|<meta name="description"'
```
2. 榜单首页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/phb-index' | rg -n 'guide-collection|<title>|排行榜|榜单'
```
3. 一级分类页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/videotype/1-dian-ying' | rg -n 'guide-collection|<title>|<meta name="keywords"|<meta name="description"'
```
4. 二级分类页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/videotype/dian-ying/shao-shi-dian-ying-1' | rg -n 'guide-collection|<title>|<meta name="keywords"|<meta name="description"'
```
5. 搜索结果页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/get-index?keyword=test' | rg -n 'guide-collection|canonical|og:url|<title>'
```
6. 详情页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/neirong-76359-san-fen-zhi-yi-qing-ren' | rg -n 'guide-detail|canonical|og:url|/bf-76359-san-fen-zhi-yi-qing-ren/douban-1|/bf-76359-/'
```
7. 播放页
```bash
curl -s -H 'Host: jpjdxs.com' 'http://127.0.0.1:18081/bf-76359-san-fen-zhi-yi-qing-ren/douban-1' | rg -n 'guide-play|canonical|og:url|https://jpjdxs.com/bf-76359-san-fen-zhi-yi-qing-ren/douban-1'
```
#### 通过 / 不通过的最小结论模板
后续 Codex 回报时,尽量只按这个格式,不要写散:
1. 首页:通过 / 不通过
2. 榜单首页:通过 / 不通过
3. 一级分类页:通过 / 不通过
4. 二级分类页:通过 / 不通过
5. 搜索结果页:通过 / 不通过
6. 详情页:通过 / 不通过
7. 播放页:通过 / 不通过
8. 当前唯一阻塞点:
#### 如果搜索页再次异常,优先先查什么
搜索页这条容易和其它主链问题混在一起,后续若异常,先按这个顺序看:
1. `getSearchVideo.html` 是否仍包含 `module/seo_copy/collection`
2. `canonical / og:url / CollectionPage.url` 是否一致
3. 是否又误用了错误类名
- 错误示例:`app\services\UrlBuilder`
- 正确类:`app\common\helper\UrlBuilder`
4. 如果是本地 PHP 内置服务回放异常,再区分:
- 是模板真报错
- 还是仅本地回放对 query 参数存在边角波动
## 目的
这份文档用于让多个 Codex 在 `SEONexus` 新版与旧版本之间长期协作时,保持:
@@ -2605,3 +2833,321 @@ php scripts/seo_copy_front_verify.php <host> play <videoId> <playType> <episode>
1. 深页资产缺失是真实存在的
2. 不是已经生成但前台没命中
## 2026-04-16 `jpjdxs.com` 第一批深页试点已落地
这段不是计划,是本地已经执行过的结果。
### 本次选择的最小样本
详情页:
1. `76318`
2. `76342`
3. `76347`
播放页:
1. `76318-play-douban-1`
2. `76342-play-douban-1`
3. `76347-play-douban-1`
### 已执行动作
1.`seo_copy_domain_bootstrap.php` 分别为这 3 个样本生成 bundle
2. 从 bundle 中抽出真实 `detail/play` page-key JSON
3. 先在临时目录做了 `seo_copy_batch_import.php` 闭环验证
4. 再把这 6 个深页 JSON 回灌到真实:
- `code/data/seo_copy_published/jpjdxs-com/detail/`
- `code/data/seo_copy_published/jpjdxs-com/play/`
### 当前结果
当前 `jpjdxs-com` 已从原来的深页接近 `default-only`,变成:
1. `detail` 目录:
- `default.json`
- `76318.json`
- `76342.json`
- `76347.json`
2. `play` 目录:
- `default.json`
- `76318-play-douban-1.json`
- `76342-play-douban-1.json`
- `76347-play-douban-1.json`
### 审计结果
重新跑 `seo_copy_published_audit.php` 后已确认:
1. `detail_default_only: no`
2. `play_default_only: no`
3. `detail non_default_count: 3`
4. `play non_default_count: 3`
也就是说:
1. 第一批深页资产链已经重新打通
2. 当前不是停留在“理论上可恢复”
3. 而是 `jpjdxs.com` 已经具备最小规模的真实深页 page-key 资产
## 2026-04-16 再确认:当前仓库里的“生成能力”和“导入能力”边界
这段是给后续所有接手的 Codex 看的,避免再把问题判断错方向。
### 1. `SeoCopyAiProviderHelper` 这条通用链,只会发布固定槽位
已再次核对:
- `code/app/common/helper/SeoCopyAiProviderHelper.php`
- `code/app/common/helper/SeoCopyGenerationHelper.php`
当前通用发布链固定只处理:
1. `home/index`
2. `category_index/index`
3. `category_list/default`
4. `search/landing`
5. `detail/default`
6. `play/default`
也就是说:
1. 它不会自动产出 `detail/<videoId>.json`
2. 它不会自动产出 `play/<videoId>-play-<line>-<episode>.json`
3. 所以某个 host 就算“通用 AI 发布成功”,深页也仍然可能是 `default-only`
这一点非常重要,后面不要再把“通用发布成功”误判成“深页差异化一定已经恢复”。
### 2. 真正的深页差异化资产,来自 bootstrap 深页链
已再次核对:
- `code/scripts/seo_copy_domain_bootstrap.php`
这条链会直接生成真实 page-key
1. `detail/<videoId>.json`
2. `forge/<videoId>-forge-<n>.json`
3. `play/<videoId>-play-<line>-<episode>.json`
因此:
1. 你之前记得的那批 GPT 模板详情引导文、播放引导文、差异化块
2. 本质上更接近 bootstrap 深页资产链的结果
3. 不是后台那条“固定 6 槽位”通用发布链的自然产物
### 3. 系统并不是“不能导入深页文件”,而是“缺少深页源文件”
已再次核对:
- `code/scripts/seo_copy_batch_import.php`
- `code/app/common/helper/SeoCopyBatchImportHelper.php`
当前导入器支持的目录结构就是:
1. `<source-dir>/<host-dir>/<scene>/<page_key>.json`
这意味着:
1. 它支持导入任意 `detail/<videoId>.json`
2. 它支持导入任意 `play/<videoId>-play-<line>-<episode>.json`
3. 当前真正缺的不是导入能力
4. 当前真正缺的是:
- 历史深页源文件
- 或新生成出来的深页源文件
### 4. 以后再排查“引导文没了”,统一按这个顺序
1. 先看模板层是不是挂载还在
2. 再看运行时是不是被 suppress / fallback 卡住
3. 再看 `seo_copy_published/<host>/detail``play` 是否只剩 `default.json`
4. 再看 `storage/domain_bootstrap_bundles` / `storage/seo_copy_publish_logs` / `storage/seo_copy_release_runs` 里有没有历史深页成品
5. 确认历史成品确实不存在后,才进入“小批量 bootstrap 深页重建”
## 2026-04-16 深夜补充:不是“模板又没了”,而是 `runtime/home/temp` 权限污染
这次线上又出现了一次很容易误判的现象,必须单独记下来。
### 1. 现象
当时看到的是:
1. 首页 `title/keywords/description` 正常
2. 详情页大多正常
3. 播放页有时正常,有时直接变成 ThinkPHP 错误页
4. 从体感上很像:
- 之前做好的 GPT 模板引导文又没了
- 页面又像被 git 覆盖回旧状态
- SEO 头信息像“忽然空掉”
### 2. 实际根因
实测抓到的不是模板逻辑丢失,而是:
- `code/runtime/home/temp` 里混进了一批 `root:root` 的编译模板文件
- 实际 PHP-FPM 进程是 `www` 用户
- 所以当 ThinkPHP 需要重写这些模板缓存时,会报:
- `file_put_contents(...runtime/home/temp/...php): Failed to open stream: Permission denied`
### 3. 为什么它会表现得像“优化被覆盖了”
因为这个问题不是“所有页面一起死”,而是:
1. 已经命中旧缓存的页面,可能还能正常显示
2. 需要重新编译模板的页面,会直接报错
3. 于是现场看起来就像:
- 一部分 GPT 引导文还在
- 一部分页面像恢复成旧样子
- 一部分页面干脆系统错误
所以这种现象不能第一时间认定成:
1. git 又把模板覆盖了
2. SEO key 又丢了
3. 数据库把引导文清空了
### 4. 这次实际处理方式
已在正式排查中确认:
1. `php-fpm` 运行用户是 `www`
2. `runtime/home/temp` 中存在多份 `root root` 文件
3.`runtime/home` 下属主纠正回 `www:www`
4. 再复测:
- 详情页恢复正常
- 播放页恢复正常
- `guide-detail` / `guide-play` 正常输出
- `canonical` / `og:url` / JSON-LD `url` 恢复到正确页面
### 5. 后续所有 Codex 统一排查顺序
以后再遇到“GPT 模板像突然失忆”时,统一先做这 4 步:
1. 先抓线上实际 HTML不要只凭浏览器体感判断
2. 看是不是 ThinkPHP 错误页,尤其关注 `runtime/home/temp` 写入失败
3. 检查 `runtime/home/temp` 是否混入 `root:root`
4. 只有确认运行时缓存正常后,才继续判断是不是模板 / SEO copy / git 历史问题
## 2026-04-17 凌晨补充:搜索页串页不是模板错,是前端缓存 key 漏了 query string
这也是一个很容易把人带偏的问题,必须单列。
### 1. 实测现象
同一时间分别抓:
1. `/get-index?keyword=爱情`
2. `/get-index?keyword=动作`
结果两次返回:
1. `x-cache-status: HIT`
2. HTML 里的 `title`
3. `canonical`
4. `og:url`
全部都是“爱情”那一页的内容。
也就是说:
1. 不同关键词请求
2. 命中了同一份前端缓存
3. 不是后端模板在实时生成对应 query 的页面
### 2. 结论
这说明当前前端缓存层对搜索页的 cache key 存在高概率配置问题:
1. cache key 没有带上 query string
2. 或搜索页请求被错误归入“只按 URI 缓存”的 location
它会直接造成:
1. 搜索页串页
2. `canonical` / `og:url` / `title` 与真实 query 不一致
3. SEO 表现看起来像“模板或 SEO 数据随机失效”
### 3. 这类问题的判定规则
以后凡是看到:
1. 搜索页关键词 A 打开后像关键词 B
2. `canonical` 看着像随机错乱
3. 源码明明已经修对,但线上输出不稳定
优先先看:
1. 响应头里的 `x-cache-status`
2. 前端缓存 key 是否包含 query string
不要第一时间误判为:
1. 模板回滚
2. seo_copy 丢失
3. 数据库内容被覆盖
### 4. 当前前端 nginx 模板里的直接根因
已对照当前仓库内的前端配置模板:
- [站点伪静态.txt](/www/wwwroot/diff-maccms/前端站群服务器nginx配置/站点伪静态.txt)
问题点在于动态页缓存 key 原来是:
```nginx
proxy_cache_key "$scheme$request_method$host$uri";
```
这意味着:
1. `/get-index?keyword=爱情`
2. `/get-index?keyword=动作`
会共用同一个缓存 key因为它们的 `uri` 都只是 `/get-index`
### 5. 最小修复方案
动态页缓存 key 至少改成:
```nginx
proxy_cache_key "$scheme$request_method$host$uri$is_args$args";
```
这样:
1. 不同 query string 会拆分缓存
2. 搜索页不会再互相串页
3. `canonical` / `og:url` / `title` 不会再被别的关键词页面污染
### 6. 推荐同步修复范围
不要只改搜索页单点 location。
当前建议直接同步到:
1. `location /`
2. `location ~* \.(html|htm)$`
原因是:
1. 搜索页当前命中的是 `location /`
2. 但以后如果某些动态页走 `.html` 风格并带 query也会遇到同类问题
### 7. 上线后复测方法
改完 nginx 并清缓存后,用两组不同关键词直接抓响应头和 HTML
```bash
curl -s -D - 'https://你的域名/get-index?keyword=爱情' -o /tmp/a.html
curl -s -D - 'https://你的域名/get-index?keyword=动作' -o /tmp/b.html
rg '<title>|canonical|og:url' /tmp/a.html
rg '<title>|canonical|og:url' /tmp/b.html
```
通过标准:
1. 两个页面的 `title` 不同
2. `canonical` 分别指向各自关键词
3. `og:url` 分别指向各自关键词
4. 不再出现“动作词页返回爱情 HTML”的现象