[开发]SEO 优化(seo)插件说明

👑Lv.11 元老 🌞 骨干会员
2026-10-25 01:08:53

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/seo/说明.md。

SEO 优化(seo)插件说明

版本:1.5.0 | 作者:SoSite | 状态:✅ 已启用

功能:搜索引擎优化插件,支持自定义 Meta 标签、robots 指令、canonical、Open Graph、Twitter Card、JSON-LD 结构化数据(含面包屑)、文章元数据、站点地图、robots.txt、llms.txt(AI 爬虫索引)


一、功能简介

  • 后台设置页:自定义站点描述/关键词、站点 URL、OG 默认图、自定义 head 代码,以及四个输出开关(robots 指令 / Open Graph / sitemap / llms.txt)
  • 全局 head 注入(layout_head_end):按页面类型输出
  • <meta name="robots">(低价值页面 noindex;404/410/429 用 noindex,nofollow)
  • <meta name="description">(详情页取正文摘要,列表页/首页取站点描述)
  • <meta name="keywords">(后台配置非空时输出)
  • <link rel="canonical">(自指,分页带 ?page=N)
  • Open Graph:og:type / og:title / og:description / og:url / og:site_name / og:locale / og:image
  • Twitter Card:twitter:card / twitter:title / twitter:description / twitter:image(随 enable_og 门控;页面自带图时用 summary_large_image,仅站点图标兜底时用 summary)
  • JSON-LD 结构化数据(每页最多一个块,主节点与面包屑合并进 @graph):
  • 帖子页 DiscussionForumPosting(含 author / publisher / datePublished / dateModified / commentCount / interactionStatistic)
  • 博客页 Article(含 articleSection / commentCount)
  • 首页 WebSite(刻意不输出 potentialAction/SearchAction——本站 /search 未登录会 302,声明不可用的动作会被判错)
  • 详情页追加 BreadcrumbList(帖子:首页→版块→标题;博客:首页→博客→分类→标题)
  • 文章元数据(仅详情页):article:published_time / article:modified_time / article:author、citation_title / citation_author / citation_publication_date
  • 作者权威信号(B4):JSON-LD 的 author 带主页 URL(/u/{id},需 user_profile 插件启用)与等级署名(Lv.11 · 61 帖;博客行无帖数字段时降级为 Lv.11)
  • 详情页数据复用(controller_view_before):帖子/博客详情页把控制器已加载的行暂存,供 head 注入复用,省掉一次重查询
  • 站点地图:/sitemap.xml 自动生成(首页/论坛/博客/版块/帖子/博客条目,含 lastmod)
  • robots.txt:/robots.txt 输出爬虫规则(屏蔽后台/API/登录等敏感路径 + Sitemap 指向)
  • llms.txt(GEO):/llms.txt 输出给大模型/AI 爬虫的站点内容索引,Markdown 格式
  • 结构:# 站点名 + > 站点描述(引述块)+ ## 帖子 / ## 博客 两个分节,每条为 - 标题: 摘要
  • 候选池:帖子/博客各取 getLatest(100),PHP 侧按「活跃时间」(帖子取 last_reply_at、博客取 updated_at)统一重排,再全局截断到 100 条——保证是「最近活跃的 100 条」而非「100 帖 + 100 博客」
  • 条目文案走 Plugin::oneLine() 净化:先去 HTML 与实体、折叠空白,再剔除 [ ] ( ) \ 五个会破坏 Markdown 列表语法的字符(标题含 ] 会提前闭合 标题`,导致整份文件解析错位)
  • 结果渲染成文本后缓存到 plugins/seo/data/llms.txt,TTL 60 分钟;命中缓存时 0 查询
  • 开关:seo_enable_llms(默认开);关闭时输出 llms.txt disabled. 且不读写缓存文件
  • 发现入口:robots.txt 末尾以注释形式声明 # llms.txt: https://站点/llms.txt(紧跟 Sitemap: 行)。用 # 注释而非 Allow: 指令 —— 注释对不认识的爬虫无害、对认识的爬虫有用,且与 Sitemap: 声明对称
  • 设置存 settings 表(键前缀 seo_),无独立数据表

二、目录结构

plugins/seo/
├── plugin.json              # 插件清单(hooks: init_after / route_register / admin_route_register / controller_view_before / layout_head_end)
├── Plugin.php               # 生命周期 + 设置读写 + SEO 上下文解析(resolveContext/stash/esc)
├── AdminController.php      # 后台设置页(index/save,继承 Admin\BaseController)
├── FrontController.php      # 前台输出(sitemap/robots/llms)
├── hook/
│   ├── init_after.php       # 初始化(历史性能坑:勿每请求写默认值)
│   ├── route_register.php   # 前台路由(/sitemap.xml /robots.txt /llms.txt)
│   ├── admin_route_register.php  # 后台路由(/seo /seo/save)
│   ├── controller_view_before.php # 暂存 thread/blog 行数据(供 head 注入复用)
│   └── layout_head_end.php  # 全局 head 注入(robots / meta / canonical / OG)
├── lang/                    # 语言包(zh / en / zh_tw)
├── data/                    # 插件生成数据(llms.txt 缓存)+ 目录防护文件
│   ├── .htaccess            # Apache:deny all(防缓存文件被 HTTP 直接下载)
│   └── web.config           # IIS:hiddenSegments 屏蔽 data 段
├── assets/style.css         # 后台设置页样式
└── views/admin.php          # 后台设置视图

data/ 目录里的两个防护文件随包发布(照 plugins/medal/data/ 模板)。Nginx 侧由站点 nginx-server.conf 的全局正则统一覆盖,不需要单独文件。Plugin::dataDir() 在目录/防护文件被误删时会幂等重建,属于自愈兜底,不是唯一防线。

后台入口不在本插件内:由 plugin.json 的 admin_url + 核心后台侧边栏渲染,没有 admin_sidebar_links 钩子文件。

三、数据表

无独立表(配置存 settings 表,键前缀 seo_):

键说明
seo_meta_description站点默认描述(列表页/首页兜底,详情页提取不到正文时也用)
seo_meta_keywords关键词
seo_site_urlcanonical / sitemap / og:url 的绝对地址前缀;留空回落核心 SITE_URL
seo_custom_head自定义 head 代码(原样输出,值经 Content::decode)
seo_enable_robots是否输出 <meta name="robots"> 指令(默认开)
seo_enable_og是否输出 Open Graph 标签(默认开)
seo_enable_sitemap/sitemap.xml 是否可用(默认开)
seo_enable_llms/llms.txt 是否可用(默认开)
seo_og_default_imageOG 兜底图;留空用 /assets/pwa/icon-512.png

四、Hooks

Hook用途
init_after初始化(注意性能:勿每请求写默认配置)
route_register前台路由(/sitemap.xml、/robots.txt、/llms.txt)
admin_route_register后台路由(/seo、/seo/save)
controller_view_before详情页暂存 thread / blog 行数据(命中时 head 注入不再重查)
layout_head_end全站 head 注入(robots / description / keywords / canonical / OG / Twitter Card / JSON-LD / 文章元数据)

五、路由

方法路径说明
GET/sitemap.xmlXML 站点地图(最近 1000 帖子 + 500 博客 + 版块,含 lastmod W3C 格式)
GET/robots.txt爬虫规则(Disallow 敏感路径 + Sitemap: 声明 + # llms.txt: 注释声明)
GET/llms.txtAI 爬虫内容索引(Markdown,最近活跃 100 条,文件缓存 TTL 60min)
GET/seo后台设置页
POST/seo/save保存设置(CSRF,保存后清游客静态页缓存 + llms.txt 缓存)

六、注意事项

  • 性能坑(历史事故):init_after 曾每请求调用 Plugin::activate() 写 4 个空字符串默认值 → 每页 4 次 Settings::update → 全表扫描 + 写缓存文件,导致全站性能回退。修复:默认配置在插件激活时写入 DB,init_after 勿再重复写默认值
  • 性能红线:单插件每请求所有 hook 查询总和 ≤ 5 次。og:image 只做正文正则取首图,不查附件表;详情页经 controller_view_before 暂存复用,不额外查库
  • 布尔开关必须归一:复选框未勾选时浏览器不提交该字段,AdminController::save() 对 BOOLEAN_KEYS 显式写 '0';若存成空串,isEnabled() 会当成「未配置」而回落默认值(=开启),开关形同失效
  • 游客静态页缓存:首页/版块列表页的 head 会被烘焙进 data/runtime/pages/*.html,保存 SEO 设置后已自动 PageCache::invalidate();改本插件 head 输出逻辑后如需立即见效,也需清一次该缓存
  • noindex 与 canonical 互斥:命中 noindex 的页面不再输出 canonical(不给「不该被收录的页面」做自指声明)
  • JSON-LD 输出约束:必须以 JSON_UNESCAPED_UNICODE | JSON_HEX_TAG 编码(JSON_HEX_TAG 把 < > 转成 \u003C \u003E,防标题/正文里的 </script> 提前闭合标签);每页只注入一个 ld+json 块,多个节点合并进 @graph
  • B4 作者 URL 降级:user_profile 插件未启用(或行内 user_id 为 0)时,author 只出 name,不出指向 404 的 url
  • twitter:card 只对位图声明大卡片:has_own_image 判的是「页面自带图且是位图」(扩展名限 .png / .jpg / .jpeg / .webp / .gif)。博客封面可以是 SVG(实测 /blog/16 就是),而 Twitter/X 与 Facebook 都不渲染 SVG 分享图——若照样声明 summary_large_image,会变成「声明了大卡片却拿不到图」。故 SVG 与其他非位图一律退回 summary
  • llms.txt 缓存失效链路(明确到「谁清、谁不清」):
触发是否清缓存理由
后台保存 SEO 设置清(AdminController::save() 调 Plugin::invalidateLlms())站点名/描述/开关变了,缓存文本必须重建
发新帖不清见下
删帖 / 编辑不清见下

发帖/删帖不清不是因为清理开销大(单次 unlink + 一次钩子 include 约 0.1ms 量级,可忽略),而是命中率问题:发帖/回帖是站内最高频写操作,按「每次写就清一次」的节奏,TTL 60min 的缓存几乎永不命中,等于白建缓存;而每次 AI 爬虫来抓 /llms.txt 仍要付冷缓存成本(Thread::getLatest(100) 含 decorateList 的批量取用户/分类 + Blog::getLatest(100) ≈ 4 次查询)。llms.txt 只是「AI 爬虫的发现入口」,晚 60 分钟收录没有实际损失。另外删帖路径在核心 ThreadController::delete() 里没有任何钩子,要做到「删帖也清」必须往核心加钩子——越线

  • plugins/seo/data/ 必须带防护文件:缓存文件是纯文本、可被 HTTP 直接下载,故目录内随包附 .htaccess(Apache deny all)+ web.config(IIS hiddenSegments),照 plugins/medal/data/ 模板
  • 站点地图条目数有限制(帖子 1000 / 博客 500),超大站需扩展
  • robots.txt 屏蔽路径覆盖:admin/api/login/register/logout/找回密码/消息/个人中心等
  • 卸载后 /sitemap.xml、/robots.txt、/llms.txt 路由失效(404),meta 注入消失
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 2 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录