223 lines
14 KiB
Markdown
223 lines
14 KiB
Markdown
# 后端 Sitemap 生成对接规则
|
||
|
||
核对时间:2026-10-22 22:53(Asia/Shanghai)。
|
||
|
||
本文根据当前工作区的前端代码(包含尚未提交的修改)整理固定页面、URL、canonical 和 noindex 规则,供后端实现 sitemap 生成。它描述现有页面行为及生成约定,不表示 sitemap、存储或 Nginx 已完成接入。
|
||
|
||
当前内容主地址仅优先使用符合英文/拼音字符规则的 slug;中文、已编码中文或其他无效 slug 回退到 ID。搜索关键词仍支持中文和特殊字符,但所有搜索地址均排除在 sitemap 外。
|
||
|
||
## 1. 域名与通用约定
|
||
|
||
- `D` 表示正式站点的 origin,即协议、域名和可选端口,不带末尾 `/`。
|
||
- 域名优先取 `config.config.foreverUrlAddr`;无有效值时取 `config.newAddressInfo.foreverUrlAddr`。移除配置中的路径、查询参数和锚点,与前端 canonical 保持一致。
|
||
- 不使用 API 域名、CDN 域名、请求 Host、临时访问域名或本地地址作为页面域名。
|
||
- 生成任务没有有效正式域名时,不猜测域名;暂停本次发布并保留上一份成功结果。
|
||
- 页面 URL 以 `/` 结尾;sitemap 中使用绝对地址 `D + 规范路径`。
|
||
- sitemap 只提交正常、允许索引的页面及其 canonical 地址;别名、参数变体和重复地址不单独提交。
|
||
- API、媒体源、静态资源和健康检查不属于本文的页面 sitemap 范围。
|
||
|
||
## 2. 固定页面清单
|
||
|
||
固定地址候选共 12 个。满足条件时,其 canonical 均为 `D + URL`。列表为空或页面异常时,按后续排除规则处理。
|
||
|
||
| 页面 | URL | 加入 sitemap 的条件 |
|
||
| --- | --- | --- |
|
||
| 首页 | `/` | 页面正常 |
|
||
| 短剧列表 | `/shorts/` | 页面正常且列表非空 |
|
||
| 专题列表 | `/topics/` | 页面正常且列表非空 |
|
||
| 热播榜 | `/rank/hot/` | 页面正常且列表非空 |
|
||
| 新剧榜 | `/rank/new/` | 页面正常且列表非空 |
|
||
| 追剧榜 | `/rank/follow/` | 页面正常且列表非空 |
|
||
| 关于我们 | `/info/about-us/` | 页面正常 |
|
||
| 用户协议 | `/info/user-agreement/` | 页面正常 |
|
||
| 隐私政策 | `/info/privacy-policy/` | 页面正常 |
|
||
| 常见问题 | `/info/help-center/` | 页面正常 |
|
||
| 2257 合规声明 | `/info/2257/` | 页面正常 |
|
||
| DMCA 版权声明 | `/info/dmca/` | 页面正常 |
|
||
|
||
这些是页面地址,不是需要前端预建的 sitemap XML 文件。
|
||
|
||
## 3. 动态页面 URL 规则
|
||
|
||
| 类型 | 规范 URL | 加入 sitemap 的条件 |
|
||
| --- | --- | --- |
|
||
| 作品详情 | `/detail/{key}/` | 作品存在,页面正常且未被标记为需登录或禁止索引 |
|
||
| 专题详情 | `/topics/{key}/` | 专题存在,页面正常且列表非空 |
|
||
| 标签列表 | `/tag/{key}/` | 标签存在,页面正常且列表非空 |
|
||
| 更多栏目 | `/config/{key}/` | 首页栏目存在、已启用且类型为 `1` 或 `3`,并具有自己的标题、正文简介和有效内容卡片 |
|
||
| 单集播放 | `/play/{作品key}/{chapterId}/` | 满足下述播放页条件 |
|
||
| 内容列表分页 | `{列表基础路径}page/{n}/` | `n >= 2`,分页真实存在且允许索引 |
|
||
|
||
### 3.1 内容标识
|
||
|
||
- `{key}` 优先使用内容接口返回的有效英文/拼音 `slug`;无有效 slug 时使用正整数 ID。前端不会自动把中文转成拼音。
|
||
- slug 去掉首尾空格后,必须满足 `/^[a-z\d][a-z\d_-]*$/i`:以 ASCII 字母或数字开头,其余只能包含 ASCII 字母、数字、连字符 `-`、下划线 `_`。
|
||
- 使用接口返回的规范大小写,作为一个路径段按 `encodeURIComponent` 的语义编码一次,避免重复编码;不得把编码后的中文当作英文 slug。
|
||
- 中文、中文英文混合、内含空格、斜杠、点号、百分号、问号等不符合正则的 slug 均回退到 ID。`-city` 和 `_city` 也不符合开头规则。
|
||
- 历史中文 slug 地址在能够匹配到真实内容时仍可读取,但 canonical 和新生成链接使用 ID;后端不应继续向 sitemap 写入这些兼容地址。
|
||
- 纯数字路径按 ID 查询,不按 slug 查询。当前生成正则本身未单独排除纯数字 slug,因此后端应避免分配此类 slug;存量数据须核对身份,不能直接当作 slug 发布。
|
||
- 使用数字 ID 时去除前导零;ID 必须是正整数,且处于 JavaScript 安全整数范围内。
|
||
- `{chapterId}` 是真实章节 ID,不是集数,也不支持章节 slug。章节必须属于 URL 中的作品。
|
||
|
||
以作品 `id=9` 为例:
|
||
|
||
| 接口返回的 slug | 生成的详情路径 |
|
||
| --- | --- |
|
||
| `city-story` | `/detail/city-story/` |
|
||
| `city_story-2026` | `/detail/city_story-2026/` |
|
||
| `dushiaiqing` | `/detail/dushiaiqing/` |
|
||
| `City-Story` | `/detail/City-Story/` |
|
||
| `都市爱情` | `/detail/9/` |
|
||
| `city-爱情` | `/detail/9/` |
|
||
| `%E9%83%BD%E5%B8%82` | `/detail/9/` |
|
||
| `city story`、`city/story`、`city.story`、空值 | `/detail/9/` |
|
||
|
||
以上规则同时用于作品、专题、标签和更多栏目;播放页的作品段使用同一规则,章节段仍使用章节 ID。
|
||
|
||
### 3.2 播放页条件
|
||
|
||
播放页同时满足以下条件时才可进入公共 sitemap:
|
||
|
||
1. 作品及章节真实存在,且两者对应。
|
||
2. 有作品名和真实的正整数集数。
|
||
3. 公开抓取身份有权观看当前章节。
|
||
4. 存在有效的当前章节正片地址。
|
||
|
||
共享预告、继承的作品简介、章节 SEO 标题或纯解锁提示不能单独满足条件。收费类型本身不是排除依据,但不能使用管理员、VIP 或已购用户的个人权限生成公共 sitemap。
|
||
|
||
sitemap 提交页面 URL,不写入用户凭证或受限媒体源。
|
||
|
||
### 3.3 列表分页
|
||
|
||
- 普通内容目录当前每页 24 条,后端根据有效总数计算真实页数。
|
||
- 更多栏目当前使用有限首页栏目快照:类型 `1` 取实际作品列表,类型 `3` 取实际专题列表,以实际条目数为 `totalCount`。SEO 层取 `pageSize = max(24, totalCount)`,因此当前只发布栏目基础地址,不生成 `/config/{key}/page/2/` 等额外分页。
|
||
- 第一页只提交基础地址,不额外提交 `/page/1/`。
|
||
- 第 2 页及以后保留自己的 canonical,例如 `/shorts/page/2/`,不统一归并到第一页。
|
||
- 作品详情和播放页的 `/page/{n}/` 是评论分页;`n > 1` 时不进入 sitemap。
|
||
- 搜索、收藏、历史、已购和充值即使有分页,也不进入 sitemap。
|
||
|
||
## 4. Canonical 规则
|
||
|
||
| 场景 | 规范化规则 |
|
||
| --- | --- |
|
||
| 正式域名 | 统一使用 `D`,与前端页面 canonical 的域名一致 |
|
||
| 尾斜杠 | 页面路径以 `/` 结尾 |
|
||
| 查询参数和锚点 | canonical 不含查询参数和锚点;sitemap 同样不提交这些变体 |
|
||
| 第一页 | `/shorts/page/1/` 规范化为 `/shorts/`;其他支持分页的页面同理 |
|
||
| 有效列表分页 | 第 2 页及以后自引用,保留 `/page/{n}/` |
|
||
| 默认榜单 | `/rank/` 规范化为 `/rank/hot/`;`/rank/page/2/` 规范化为 `/rank/hot/page/2/` |
|
||
| 专题别名 | `/theme/{key}/` 规范化为 `/topics/{规范key}/`,保留有效分页 |
|
||
| ID 与 slug | 有符合英文/拼音字符正则的 slug 时,ID 地址指向 slug 主地址;中文或其他无效 slug 回退到规范 ID |
|
||
| 历史中文 slug 地址 | 能匹配到资源时保留兼容读取,canonical 使用该资源的规范 ID 地址 |
|
||
| 数字 ID 前导零 | 无 slug 时,`/detail/009/` 规范化为 `/detail/9/`;其他数字内容 ID 同理 |
|
||
| noindex 页面 | 当前前端不输出 canonical,不进入 sitemap |
|
||
|
||
例如作品 `id=9`、`slug=city-story`,章节 ID 为 `90`:
|
||
|
||
```text
|
||
/detail/9/ -> D/detail/city-story/
|
||
/detail/city-story/ -> D/detail/city-story/
|
||
/play/9/90/ -> D/play/city-story/90/
|
||
/play/city-story/90/ -> D/play/city-story/90/
|
||
```
|
||
|
||
例如专题 `id=7`、`slug=city-collection`:
|
||
|
||
```text
|
||
/theme/7/ -> D/topics/city-collection/
|
||
/topics/7/ -> D/topics/city-collection/
|
||
/theme/7/page/2/ -> D/topics/city-collection/page/2/
|
||
```
|
||
|
||
例如作品 `id=9`、`slug=都市爱情`,章节 ID 为 `90`,中文仅用于展示或兼容读取:
|
||
|
||
```text
|
||
/detail/9/ -> D/detail/9/
|
||
/detail/都市爱情/ -> D/detail/9/
|
||
/play/9/90/ -> D/play/9/90/
|
||
/play/都市爱情/90/ -> D/play/9/90/
|
||
```
|
||
|
||
上述中文路径按可读形式展示,实际请求可使用百分号编码;sitemap 只写入右侧 ID 主地址。
|
||
|
||
别名页面仍可允许索引,canonical 归并不等于 noindex,也不表示已有重定向。sitemap 只提交最终 canonical 地址,并按完整地址去重。
|
||
|
||
`/home`、`/playlet`、`/list`、`/topic`、`/application` 等值只用于后台导航值转换,不是额外公开页面,不应据此生成 sitemap 地址。
|
||
|
||
## 5. 固定 Noindex 页面
|
||
|
||
| 页面范围 | URL | 当前 robots |
|
||
| --- | --- | --- |
|
||
| 搜索和搜索分页 | `/search/`、`/search/{关键词}/`、`/search/{关键词}/page/{n}/`、`/search/?q={关键词}`、`/search/page/{n}/?q={关键词}` | `noindex,follow` |
|
||
| 独立入口 | `/navigation/` | `noindex,follow` |
|
||
| 回家的路 | `/home-road/` | `noindex,follow` |
|
||
| 福利应用 | `/apps/` | `noindex,follow` |
|
||
| 个人中心 | `/member/center/`、`/info/user-center/` | `noindex,nofollow` |
|
||
| 收藏 | `/member/favorites/`,包括分页 | `noindex,nofollow` |
|
||
| 历史 | `/member/history/`,包括分页 | `noindex,nofollow` |
|
||
| 已购 | `/member/purchased/`,包括分页 | `noindex,nofollow` |
|
||
| 账号设置 | `/member/settings/`、`/info/settings/` | `noindex,nofollow` |
|
||
| 充值及 VIP | `/info/deposit/`、`/info/deposit/vip/`,包括支持的分页 | `noindex,nofollow` |
|
||
| 意见反馈 | `/info/feedback/` | `noindex,nofollow` |
|
||
|
||
以上页面及其参数变体均不进入 sitemap。
|
||
|
||
### 5.1 当前搜索地址的排除规则
|
||
|
||
- 新生成的搜索入口统一使用 `/search/{关键词}/`;关键词先去掉首尾空格,再通过 `encodeURIComponent` 整体编码为一个路径段。
|
||
- 关键词可以包含中文、空格、斜杠、问号、百分号等字符。搜索关键词不受内容 slug 的 ASCII 正则限制,也不进行数字 ID 归一。
|
||
- `/search/?q={关键词}` 和 `/search/page/{n}/?q={关键词}` 仍可解析;后续分页链接生成路径形式并移除 `q` 参数,不依赖重定向完成转换。
|
||
- 路径本身已有关键词时优先使用路径关键词;只有路径没有关键词时才从 `q` 读取。
|
||
- 不论入口形式、搜索结果数量或关键词字符,搜索页始终 `noindex,follow`,不输出 canonical,也不进入 sitemap。
|
||
- 无关键词的 `/search/` 可以访问;无关键词且页码大于 1 时返回 404。
|
||
|
||
## 6. 条件性 Noindex 与异常处理
|
||
|
||
| 情况 | 当前前端行为 | 后端生成处理 |
|
||
| --- | --- | --- |
|
||
| 空公开列表 | 接口成功确认 `totalCount=0` 时,第一页 `noindex,follow`,仍返回 200 | 不提交 |
|
||
| 详情或播放页评论分页 | `/detail/{key}/page/{n}/`、`/play/{key}/{chapterId}/page/{n}/`,`n>1` 时 `noindex,follow` | 不提交 |
|
||
| 非默认列表排序 | 除榜单和更多栏目外,公开列表 `sort != 1` 时 noindex | 只生成无参数的默认地址 |
|
||
| 非法列表排序 | 公开列表的 `sort` 不属于 `1、2、3` 时 noindex;未传时默认 `1` | 不生成非法变体 |
|
||
| 非默认评论排序 | 存在 `commentSort` 且值不等于字符串 `2` 时 noindex | 不生成参数变体 |
|
||
| 更多栏目内容不足或无效 | 已存在栏目缺少自己的标题、正文简介或有效卡片时 `noindex,follow`;找不到启用的类型 `1/3` 栏目时按资源缺失处理 | 不提交 |
|
||
| 播放页条件不足 | 预告、纯解锁提示、无有效正片或无观看权限时 `noindex,follow` | 不提交 |
|
||
| 明确禁止索引或需要登录 | `indexable=false` 或 `loginRequired=true` 时 noindex | 不提交 |
|
||
| 无效路由或不存在的资源 | 返回 404,且无 canonical | 不提交 |
|
||
| 已确认越界的分页 | 返回 404,且无 canonical | 不提交 |
|
||
| 临时取数故障 | `failed` 或 `unavailable` 时返回 500,且无 canonical | 不将故障误判为删除;保留上次成功结果,稍后重试 |
|
||
|
||
补充约定:
|
||
|
||
- 榜单和更多栏目可以接受 `sort=2/3`,但 canonical 仍不含参数;sitemap 不重复提交这些参数地址。
|
||
- 作品详情的评论数为 0,不等于作品内容为空,不能按空列表规则排除作品详情。
|
||
- 不存在的内容与临时故障必须区分;无法可靠判断一批内容时,不发布因取数失败而缺项的新结果。
|
||
|
||
## 7. 建议的生成与发布约定
|
||
|
||
1. 合并本文的固定地址与后端数据库枚举的动态内容。
|
||
2. 按公开访问时的页面状态判断是否允许索引。
|
||
3. 按英文/拼音 slug 白名单和 ID 回退规则生成最终 canonical URL,不输出参数、锚点、别名、中文内容 slug 或非页面资源。
|
||
4. 按完整 canonical URL 去重,只发布正常、有效的页面地址。
|
||
5. `lastmod` 有可靠数据时使用页面内容实际更新时间;没有时省略,不统一伪造为生成时间。
|
||
6. 生成文件保存到前端发布目录之外的持久化存储,不写入前端 `public` 或 `.next`。
|
||
7. 完成分片生成和可访问性校验后,再切换 sitemap 索引;失败时保留上一份成功版本。
|
||
8. 建议固定入口为 `/sitemap.xml`,分片通过 `/sitemaps/` 下的通用路径提供访问。现有入口不同时,优先保留已提交的地址。
|
||
9. `robots.txt` 中声明正式站点的 sitemap 索引地址;前后端 URL 或索引规则变更时同步更新生成逻辑。
|
||
|
||
首次接入需要部署访问配置;后续内容和分片变化不应要求前端预留文件、重新构建或重启 H5。
|
||
|
||
## 8. 代码依据
|
||
|
||
- [路由解析、内容路径和分页支持](../src/lib/routing.ts)
|
||
- [域名、canonical、noindex 和页面状态判断](../src/lib/seo.ts)
|
||
- [服务端资源核对与 SEO 元数据输出](../src/lib/seo-server.ts)
|
||
- [专题解析、首页栏目快照和列表分页](../src/lib/client/channel.ts)
|
||
- [章节权限与正片地址判断](../src/lib/client/playback.ts)
|
||
- [固定信息页](../src/lib/info-pages.ts)
|
||
- [现有页面索引策略](indexation-policy.md)(其“非空 slug 优先”描述需结合当前代码的英文/拼音字符白名单理解)
|
||
- [SEO 规则回归用例](../tests/seo.test.mjs)
|
||
- [Slug 路由与 canonical 回归用例](../tests/slug-routing.test.mjs)
|
||
- [路由与搜索参数兼容回归用例](../tests/routing.test.mjs)
|
||
- [分页与搜索路径回归用例](../tests/pagination.test.mjs)
|