适用环境:PHP + PDO SQLite 的轻量自建 BBS(本站架构),伪静态已开启、后台管理分离。 涉及文件:
sidebar.php(新建)、index.php、article.php、functions.php(各改少量行)。 本文记录问题现象、根因分析、完整代码与上线踩坑,可直接作为备份对照。
一、问题现象
现象 A:文章详情页右侧边栏,「最新文章」和「相关阅读」两个板块的内容一模一样,五条标题逐条相同。
现象 B:首页搜索关键词后,结果超过一页时,点击第 2 页回到全站列表,关键词丢失,搜索结果只剩全站文章。
两个现象看起来不相关,实际都是"兜底逻辑"埋的坑。
二、根因分析
2.1 侧边栏为什么会一模一样
原始代码的相关阅读逻辑分两步:
第一步:从标题抽取关键词
$kwParts = preg_split('/[\s\+\(\)\/\—\-\|\,,。、::]+/u', $curTitle);
这个分隔符只认空格和标点。中文标题几乎不含空格,例如「WordPress自动推送IndexNow设置教程」整条会被当成一个关键词,最终执行的 SQL 变成:
WHERE title LIKE '%WordPress自动推送IndexNow设置教程%'
除非库里存在标题几乎完全相同的文章,否则匹配结果恒为空。
第二步:空结果时用"最新文章"补齐
if (count($relatedList) < 3) {
$fillSql = "SELECT id,title,create_time FROM article
WHERE id NOT IN (...) ORDER BY id DESC LIMIT " . (5 - count($relatedList));
}
结果为 0 时就是 LIMIT 5 + ORDER BY id DESC,排除条件只有当前文章 ID——和上面「最新文章」的查询完全等价。于是两个列表逐条一致。哪怕只命中 1~2 条,前几条同样会被最新文章填满,看起来依然"差不多"。
一句话总结:中文分词失败 → 命中为空 → 兜底复制最新文章。
2.2 搜索翻页为什么会丢关键词
getStaticUrl() 里拼接额外参数的条件是:
if(!$rewriteOn && !empty($query)){ // ← 问题所在
$url .= ... . http_build_query($query);
}
本站 $rewriteOn = true,这个条件恒为 false,q 参数从来没被拼进 URL。
搜索时 buildPagination($currentPage, $totalPages, $q) 虽然传了 ['q' => '大漠'],生成的链接却是干净的 index-page-2.html,点进去 $_GET['q'] 为空,自然回落全站列表。
一句话总结:伪静态开启时,额外参数被无条件丢弃。
2.3 关于"是不是缓存"的排查结论
这套代码没有任何应用层缓存(无 APCu、无文件缓存、无 ob 复用)。第 1 页和第 2 页是不同 URL,浏览器/CDN 缓存也无法解释参数丢失。
唯一可能造成"改完没立刻生效"的是 OPcache 字节码缓存(XAMPP 默认开启,opcache.revalidate_freq 默认 2 秒)。改完文件最多延迟 2 秒生效,看起来像缓存,实际不是。
彻底排除二选一:
- 改完文件重启一次 Apache
- php.ini 设
opcache.validate_timestamps=1+opcache.revalidate_freq=0(开发环境推荐)
另外务必确认改的是站点实际加载的那份文件,不是副本——这比缓存更容易踩。
三、修复一:新建 sidebar.php(侧边栏统一文件)
把侧边栏的数据获取与 HTML 渲染抽成独立文件,index.php 和 article.php 共用同一份,避免两套逻辑各自走样。
核心改动点:
- 中文标题用停用词剔除 + 2 字滑窗分词,英文/数字词单独提取
- 候选集按命中词数打分,再用 Dice 系数重排(抵消"标题越长命中越多"的偏差)
- 兜底填充时排除「最新文章」已出现的 ID,两个板块永不再雷同
- 真实相关为 0 时,板块标题自动降级为「猜你喜欢」,不再伪装成"相关阅读"
- 若
article表存在tags字段,自动启用标签加权,无需改代码
在站点根目录新建 sidebar.php,内容如下:
<?php
/**
* sidebar.php —— 侧边栏「最新文章 / 相关阅读」数据与渲染
* ------------------------------------------------------------
* 用法(article.php 与 index.php 共用同一份,避免两套逻辑走样):
* 1) config.php 里在 functions.php 之后加一行:
* require_once __DIR__ . '/sidebar.php';
* 2) 文章页:
* $side = getSidebarData($db, $id); // $id 为当前文章ID
* renderSidebar($side, $db); // 输出整个 <div class="art-side">
*
* 设计要点:
* - 相关阅读使用「标题分词 + 命中打分 + Dice 相似度重排」,不再整条标题 LIKE;
* - 兜底填充时排除「最新文章」已出现的 ID,两个板块永不再一模一样;
* - 真实相关不足 2 条时,板块标题自动降级为「猜你喜欢」,不再伪装成相关阅读;
* - 若 article 表存在 tags 字段,自动启用标签加权(无需改代码,加了字段就生效)。
*/
if (!defined('IN_BBS')) exit;
/* ========== 小工具:判断表里有没有某列(SQLite) ========== */
if (!function_exists('sbHasColumn')) {
function sbHasColumn(PDO $db, string $table, string $col): bool
{
static $cache = [];
$key = $table . '.' . $col;
if (isset($cache[$key])) return $cache[$key];
try {
$st = $db->query("PRAGMA table_info({$table})");
$found = false;
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
if (strcasecmp($r['name'], $col) === 0) { $found = true; break; }
}
return $cache[$key] = $found;
} catch (Exception $e) {
return $cache[$key] = false;
}
}
}
/* ========== 中文通用词表(2字),命中即丢弃,避免"教程/方法"把全站文章串起来 ========== */
if (!function_exists('sbStopZh')) {
function sbStopZh(): array
{
static $list = null;
if ($list === null) {
$words = ['教程','方法','使用','实现','问题','解决','分享','详解','入门','总结','介绍',
'以及','如何','怎么','什么','步骤','完整','最新','简单','常用','常见','技巧',
'注意','说明','分析','实战','系列','案例','基础','高级','相关','配置','安装',
'下载','代码','示例','笔记','记录','汇总','大全','视频','源码','免费','推荐',
'一篇','几个','那些','这些','怎样','需要','关于','中的','之一','之二','新手',
'必看','史上','最强','整理','收藏','合集','详解','图解','超简','详细','常见',
'常见','写法','用法','区别','对比','介绍','说明','学习','初级','进阶','快速'];
$list = array_fill_keys($words, true);
}
return $list;
}
}
/* ========== 从标题抽取关键词:英文/数字词 + 中文2字滑窗 ========== */
if (!function_exists('sbExtractKeywords')) {
function sbExtractKeywords(string $title, int $max = 10): array
{
$title = trim(preg_replace('/\s+/u', ' ', $title));
if ($title === '') return [];
$stopZh = sbStopZh();
$stopEn = array_fill_keys(['the','and','for','with','you','www','com','http','https','html','net','org'], true);
$kw = [];
// 英文/数字词,长度>=3,避免 v1、x2 这类噪声
if (preg_match_all('/[A-Za-z][A-Za-z0-9_.+#]{2,}/u', $title, $m)) {
foreach ($m[0] as $w) {
$w = strtolower(trim($w, "._-"));
if (strlen($w) < 3) continue;
if (isset($stopEn[$w])) continue;
$kw[] = $w;
}
}
// 中文片段:先剔除通用词("设置教程"→"设置"),<=3字整体保留,>3字切2字滑窗
$segs = preg_split('/[^\x{4e00}-\x{9fa5}]+/u', $title);
if ($segs !== false) {
foreach ($segs as $seg) {
if ($seg === '') continue;
$seg = str_replace(array_keys($stopZh), ' ', $seg);
foreach (preg_split('/\s+/u', trim($seg)) as $piece) {
if ($piece === '') continue;
$len = mb_strlen($piece, 'UTF-8');
if ($len < 2) continue;
if ($len <= 3) { $kw[] = $piece; continue; }
for ($i = 0; $i + 2 <= $len; $i++) {
$kw[] = mb_substr($piece, $i, 2, 'UTF-8');
}
}
}
}
$kw = array_values(array_unique($kw));
// 长词区分度更高,优先送进 SQL(同时限制关键词数量,防止 LIKE 条件爆炸)
usort($kw, function ($a, $b) {
$la = mb_strlen($a, 'UTF-8'); $lb = mb_strlen($b, 'UTF-8');
if ($la === $lb) return 0;
return ($la > $lb) ? -1 : 1;
});
return array_slice($kw, 0, $max);
}
}
/* ========== 标题特征集:中文2字gram + 英文词,用于 Dice 相似度 ========== */
if (!function_exists('sbFeatureSet')) {
function sbFeatureSet(string $s): array
{
$set = [];
$segs = preg_split('/[^\x{4e00}-\x{9fa5}]+/u', $s);
if ($segs !== false) {
foreach ($segs as $seg) {
$len = mb_strlen($seg, 'UTF-8');
if ($len === 0) continue;
if ($len === 1) { $set[$seg] = 1; continue; }
for ($i = 0; $i + 2 <= $len; $i++) $set[mb_substr($seg, $i, 2, 'UTF-8')] = 1;
}
}
$ens = preg_split('/[^A-Za-z0-9+#]+/u', $s);
if ($ens !== false) {
foreach ($ens as $w) {
$w = strtolower(trim($w));
if (strlen($w) >= 2) $set[$w] = 1;
}
}
return $set;
}
}
if (!function_exists('sbDice')) {
function sbDice(array $a, array $b): float
{
$inter = 0;
foreach ($a as $k => $v) if (isset($b[$k])) $inter++;
$total = count($a) + count($b);
return $total > 0 ? (2.0 * $inter / $total) : 0.0;
}
}
/**
* 取侧边栏数据
* @return array ['latest'=>[], 'related'=>[], 'relatedTitle'=>'相关阅读'|'猜你喜欢']
*/
if (!function_exists('getSidebarData')) {
function getSidebarData(PDO $db, int $artId, int $limit = 5): array
{
$limit = max(1, min(10, $limit));
$latestList = [];
$relatedList = [];
$relTitle = '相关阅读';
$hasTags = sbHasColumn($db, 'article', 'tags');
/* 1. 当前文章 */
$cur = null;
if ($artId > 0) {
$cols = $hasTags ? 'id,title,tags,create_time' : 'id,title,create_time';
$st = $db->prepare("SELECT {$cols} FROM article WHERE id=:id");
$st->bindValue(':id', $artId, PDO::PARAM_INT);
$st->execute();
$cur = $st->fetch(PDO::FETCH_ASSOC);
}
/* 2. 最新文章(基准集,相关阅读要避开它) */
$latestStmt = $db->prepare("SELECT id,title,create_time FROM article WHERE id != :id ORDER BY id DESC LIMIT " . (int)$limit);
$latestStmt->bindValue(':id', $artId, PDO::PARAM_INT);
$latestStmt->execute();
$latestList = $latestStmt->fetchAll(PDO::FETCH_ASSOC);
$latestIds = array_map('intval', array_column($latestList, 'id'));
if (!$cur) {
return ['latest' => $latestList, 'related' => [], 'relatedTitle' => $relTitle];
}
/* 3. 关键词(标签优先、权重更高) */
$tagTerms = [];
if ($hasTags && !empty($cur['tags'])) {
$parts = preg_split('/[,,;;|、\s]+/u', (string)$cur['tags']);
if ($parts !== false) {
foreach ($parts as $t) {
$t = trim($t);
if ($t !== '') $tagTerms[] = $t;
}
}
}
$terms = array_values(array_unique(array_merge($tagTerms, sbExtractKeywords($cur['title']))));
$termWeight = [];
foreach ($terms as $t) $termWeight[$t] = in_array($t, $tagTerms, true) ? 3 : 1;
/* 4. 候选集:标题命中任一关键词(tags 列存在时一并匹配标签) */
$cand = [];
if ($terms) {
$where = []; $score = []; $params = [':currentId' => $artId];
foreach ($terms as $i => $t) {
$w = $termWeight[$t];
$where[] = $hasTags
? ("title LIKE :kw{$i} OR tags LIKE :kw{$i}")
: ("title LIKE :kw{$i}");
$score[] = "(CASE WHEN title LIKE :kw{$i} THEN {$w} ELSE 0 END)"
. ($hasTags ? " + (CASE WHEN tags LIKE :kw{$i} THEN {$w} ELSE 0 END)" : '');
$params[":kw{$i}"] = '%' . $t . '%';
}
$sql = "SELECT id,title,create_time, (" . implode(' + ', $score) . ") AS score
FROM article
WHERE id != :currentId AND (" . implode(' OR ', $where) . ")
ORDER BY score DESC, id DESC LIMIT 30";
$relStmt = $db->prepare($sql);
foreach ($params as $k => $v) $relStmt->bindValue($k, $v);
$relStmt->execute();
$cand = $relStmt->fetchAll(PDO::FETCH_ASSOC);
}
/* 5. 用 Dice 相似度重排(抵消"标题越长命中越多"的偏差) */
$curSet = sbFeatureSet($cur['title'] . ($hasTags ? ' ' . (string)$cur['tags'] : ''));
foreach ($cand as $k => $row) {
$cand[$k]['score'] = (int)$row['score']; // PDO_SQLite 不同版本可能返回字符串,统一转 int
$cand[$k]['dice'] = sbDice($curSet, sbFeatureSet($row['title']));
}
usort($cand, function ($a, $b) {
if (abs($a['dice'] - $b['dice']) > 0.0001) return ($a['dice'] > $b['dice']) ? -1 : 1;
if ($a['score'] !== $b['score']) return ($a['score'] > $b['score']) ? -1 : 1;
return ($a['id'] > $b['id']) ? -1 : 1;
});
/* 6. 相似度达标的算"真相关";一条都没有才降级为「猜你喜欢」。
* 填充时始终排除「最新文章」已出现的 ID,两个板块不会再看不出区别。 */
$real = [];
foreach ($cand as $row) {
if (count($real) >= $limit) break;
if ($row['dice'] >= 0.12) $real[] = $row;
}
$month = substr((string)$cur['create_time'], 0, 7);
if (!empty($real)) {
$relatedList = $real;
$need = min(3, $limit) - count($relatedList); // 至少凑够 3 条,板块不至于太空
if ($need > 0) {
$exclude = array_merge([$artId], $latestIds, array_column($relatedList, 'id'));
$relatedList = array_merge($relatedList, sbFillArticles($db, $need, $exclude, $month));
}
} else {
$relTitle = '猜你喜欢';
$relatedList = sbFillArticles($db, $limit, array_merge([$artId], $latestIds), $month);
}
return ['latest' => $latestList, 'related' => $relatedList, 'relatedTitle' => $relTitle];
}
}
/* ========== 兜底填充:同月优先 + 随机,视觉上和「最新文章」区分开 ========== */
if (!function_exists('sbFillArticles')) {
function sbFillArticles(PDO $db, int $need, array $excludeIds, string $month = ''): array
{
if ($need <= 0) return [];
$excludeIds = array_values(array_unique(array_filter(array_map('intval', $excludeIds))));
if (empty($excludeIds)) $excludeIds = [0];
$in = implode(',', $excludeIds);
$sql = "SELECT id,title,create_time FROM article
WHERE id NOT IN ({$in})
ORDER BY CASE WHEN substr(create_time,1,7) = :m THEN 0 ELSE 1 END, RANDOM()
LIMIT " . (int)$need;
$st = $db->prepare($sql);
$st->bindValue(':m', $month);
$st->execute();
return $st->fetchAll(PDO::FETCH_ASSOC);
}
}
/* ========== 输出侧边栏 HTML ========== */
if (!function_exists('renderSidebar')) {
function renderSidebar(array $side, PDO $db): void
{
$latestList = isset($side['latest']) ? $side['latest'] : [];
$relatedList = isset($side['related']) ? $side['related'] : [];
$relTitle = isset($side['relatedTitle']) ? $side['relatedTitle'] : '相关阅读';
$h = function ($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); };
echo '<div class="art-side">';
// 关于本站
echo '<div class="card mb-3"><div class="card-body">';
echo '<h5 class="side-title">关于本站</h5>';
echo '<p style="font-size:14px;line-height:1.7;color:#555;">511遇见BBS,专注易语言、大漠插件、游戏脚本、多线程编程、C#、Lua等技术分享,提供实战源码与视频教程。</p>';
echo '<a href="http://www.511yj.com/" target="_blank" rel="noopener" class="btn btn-primary btn-sm"><i class="fa fa-cloud-upload mr-1"></i>511遇见官网</a>';
echo '</div></div>';
// 本站统计
$totalCount = (int)$db->query("SELECT COUNT(*) AS c FROM article")->fetch(PDO::FETCH_ASSOC)['c'];
echo '<div class="card mb-3"><div class="card-body">';
echo '<h5 class="side-title">本站统计</h5>';
echo '<ul class="list-unstyled mb-0" style="font-size:14px;">';
echo '<li class="d-flex justify-content-between py-1" style="border-bottom:1px dashed #eee;"><span>文章总数</span><b>' . $totalCount . '</b></li>';
echo '<li class="d-flex justify-content-between py-1" style="border-bottom:1px dashed #eee;"><span>文章归档</span><a href="index.php?act=archive">查看</a></li>';
echo '<li class="d-flex justify-content-between py-1"><span>站点地图</span><a href="sitemap.xml" target="_blank">查看</a></li>';
echo '</ul></div></div>';
// 最新文章
if (!empty($latestList)) {
echo '<div class="card mb-3"><div class="card-body">';
echo '<h5 class="side-title">最新文章</h5><ul class="list-unstyled mb-0">';
foreach ($latestList as $lat) {
echo '<li class="mb-2 pb-2" style="border-bottom:1px dashed #eee;">';
echo '<a href="' . $h(getStaticUrl('article', (int)$lat['id'])) . '" style="font-size:14px;line-height:1.6;color:#2362d3;">' . $h($lat['title']) . '</a>';
echo '<div class="text-muted" style="font-size:12px;margin-top:2px;">' . $h($lat['create_time']) . '</div>';
echo '</li>';
}
echo '</ul></div></div>';
}
// 相关阅读 / 猜你喜欢
if (!empty($relatedList)) {
echo '<div class="card mb-3"><div class="card-body">';
echo '<h5 class="side-title">' . $h($relTitle) . '</h5><ul class="list-unstyled mb-0">';
foreach ($relatedList as $rel) {
echo '<li class="mb-2 pb-2" style="border-bottom:1px dashed #eee;">';
echo '<a href="' . $h(getStaticUrl('article', (int)$rel['id'])) . '" style="font-size:14px;line-height:1.6;color:#2362d3;">' . $h($rel['title']) . '</a>';
echo '<div class="text-muted" style="font-size:12px;margin-top:2px;">' . $h($rel['create_time']) . '</div>';
echo '</li>';
}
echo '</ul></div></div>';
}
echo '</div>';
}
}
四、修复二:改造 index.php / article.php 调用点
4.1 必须同步改的 3 行
新版 getSidebarData() 返回的是关联数组,不是列表,调用方式要跟着变:
// ❌ 旧写法(会报 Undefined array key 0 / 1)
list($latestList, $relatedList) = getSidebarData($db, $artId);
renderSidebar($latestList, $relatedList, $db);
// ✅ 新写法
$side = getSidebarData($db, $artId);
renderSidebar($side, $db);
同时在文件顶部、require_once 'config.php'; 之后加一句:
require_once __DIR__ . '/sidebar.php';
4.2 为什么会报 TypeError
关联数组没有 0、1 这两个键,PHP 先抛:
Warning: Undefined array key 0 in index.php on line 44
Warning: Undefined array key 1 in index.php on line 44
解包结果 $latestList 变成 null,接着传给声明为 array $side 的参数:
Fatal error: Uncaught TypeError: renderSidebar(): Argument #1 ($side) must be of type array, null given
两个报错是同一处根因的连锁反应,改掉 list() 解包即可一起消失。
4.3 index.php 具体位置
<?php
require_once 'config.php';
require_once __DIR__ . '/sidebar.php'; // ← 新增
...
// 文章详情分支内,原 list() 那一行(约 44 行)
$side = getSidebarData($db, $artId); // ← 改
// 模板里右侧边栏位置(约 124 行)
<?php renderSidebar($side, $db); ?> // ← 改
article.php 同样处理,两个入口行为保持一致。
提示:原
index.php里内联定义的getSidebarData()/renderSidebar()两个函数请整段删除,否则会与sidebar.php冲突(PHP 不允许重复声明函数)。
五、修复三:functions.php 改一行(搜索分页)
5.1 核心修复
// ❌ 旧:伪静态开启时额外参数被丢弃
if(!$rewriteOn && !empty($query)){
$url .= (strpos($url,'?') === false ? '?' : '&') . http_build_query($query);
}
// ✅ 新:只要传了参数就拼接
if(!empty($query)){
$url .= (strpos($url,'?') === false ? '?' : '&') . http_build_query($query);
}
改后链接效果:
index-page-2.html?q=%E5%A4%A7%E6%BC%A0%E6%8F%92%E4%BB%B6+%E5%A4%9A%E7%BA%BF%E7%A8%8B
安全性说明:绝大多数调用点 $query 都是空数组(文章页、首页分页、canonical、编辑页),行为与原来完全一致,不会被波及。
5.2 建议同时补两处
(1)分页 href 做 HTML 转义,防止关键词里出现 &、" 时破坏结构:
$mk = function($p) use ($param) {
return htmlspecialchars(getStaticUrl('index_page', 0, $p, $param), ENT_QUOTES, 'UTF-8');
};
(2)内置动态 URL 开关,rewrite 规则留不住 query 时直接绕开:
$useDynamic = false; // 改成 true:搜索分页输出 index.php?q=xxx&page=2
$mk = function($p) use ($param, $useDynamic) {
$url = $useDynamic && !empty($param)
? 'index.php?' . http_build_query(array_merge($param, ['page' => $p]))
: getStaticUrl('index_page', 0, $p, $param);
return htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
};
5.3 rewrite 规则必须能保留 query
Nginx:replacement 结尾不能有 ?(有 ? 表示丢弃原参数)
rewrite ^/index-page-([0-9]+)\.html$ /index.php?page=$1 last;
Apache:加 QSA 显式追加
RewriteRule ^index-page-([0-9]+)\.html$ index.php?page=$1 [L,QSA]
六、SEO 补充:搜索结果页不该被收录
不带 q 的 /index-page-2.html 仍会回落全站,这是正常行为,但这类 URL 不应参与收录。在 head.php 的 canonical 后面加:
<?php if (isset($_GET['q']) && $_GET['q'] !== ''): ?>
<meta name="robots" content="noindex,follow">
<?php endif; ?>
同时建议搜索时把 canonical 固定指向 https://bbs.511yj.com/index.php,避免带 q 的 URL 分散权重。
七、上线验证清单
- [ ] 上传
sidebar.php到站点根目录(与config.php平级) - [ ]
index.php/article.php删除内联的旧函数,改完 3 行调用 - [ ]
functions.php改拼接条件 + href 转义 - [ ] 重启 Apache(清 OPcache)
- [ ] 打开任意文章页,确认「最新文章」与「相关阅读」不再完全一致
- [ ] 标题区分度低的文章,确认板块标题自动变成「猜你喜欢」
- [ ] 搜索一个有多页结果的关键词,点第 2 页,确认关键词仍在且高亮提示还在
- [ ] 检查文章页
article-1.html与动态article.php?id=1表现一致
注意:若侧边栏仍显示「猜你喜欢」而非「相关阅读」,说明相似度全部低于阈值走了随机兜底——这是预期行为,不是 bug。想提升命中率见下一节。
八、进阶建议(可选)
-
给 article 表加
tags字段:标题分词再怎么优化也只是"猜"。同标签匹配的相关性远高于 LIKE 标题,且命中率稳定,是治本方案。sidebar.php已内置支持,加了字段自动生效:ALTER TABLE article ADD COLUMN tags TEXT DEFAULT ''; -
数据量大时考虑 FTS5:文章上千后,多个
LIKE '%xx%'全表扫描会拖慢详情页。SQLite 可用 FTS5 虚拟表 +MATCH查询替代 LIKE。 -
侧边栏加缓存:最新文章与相关阅读变化很慢,可按"当前 ID + 文章总数"做 key,缓存 10~30 分钟,详情页少 3~4 次查询。
-
相关阅读加摘要或分类色块:两个板块样式完全一致时,即使有 1~2 条重合也显得违和。加个 60 字摘要或标签色块即可明显区分。
九、改动文件对照表
| 文件 | 操作 | 关键改动 |
|---|---|---|
sidebar.php |
新建 | 分词 + Dice 相似度重排 + 兜底排除最新文章 |
index.php |
改 3 行 | 删内联旧函数、改 $side = getSidebarData()、renderSidebar($side, $db) |
article.php |
改 3 行 | 同上,与伪静态版保持一致 |
functions.php |
改 1 行 | if(!empty($query)) + href 转义 |
head.php |
加 3 行 | 搜索页 noindex(可选) |
备份提示:修改前请完整备份 index.php、article.php、functions.php,以及数据库文件 site.db。本文所有代码均在本地模拟环境验证通过。