适用环境:PHP + PDO SQLite 自建 BBS(本站架构),伪静态已开启,密钥已生成。 效果:后台保存文章(新增或修改)后自动向 IndexNow 提交该文章 URL,无需手动跑脚本。 涉及文件:
indexnow_push.php(新建)、indexnow_worker.php(新建)、admin/indexnow_log.php(新建)、config.php(加 1 行)、admin/edit.php(加 3 行)。
一、为什么原来的手动脚本不够用
你现有的 push_indexnow.php 是访问一次推一批的模式,存在三个问题:
- 要手动打开浏览器访问,发完文章容易忘
- 推的是最新 100 条,包含大量早就提交过的旧 URL,属于无效请求
- 没有留痕,推成功还是失败、哪条失败,事后无从查证
IndexNow 的机制是"URL 有变化时通知一次",所以最合理的触发点就是文章保存的那一刻——新增时提交新 URL,修改时重新提交触发更新索引。
二、方案设计
2.1 整体思路
website/edit.php 保存成功
↓
indexNowAutoPush($db, $artId)
↓
环境判断 → 去重检查 → POST api.indexnow.org → 写库留痕 + 文本日志
2.2 五个关键设计
| 设计 | 解决的问题 |
|---|---|
| 自动去重(同一 URL 3600 秒内不重推) | 连续点保存、微调正文反复提交,避免被 IndexNow 限流 |
推送留痕(写入 indexnow_push 表) |
后台可查每篇推没推、HTTP 状态码是什么 |
| 环境判断(localhost 自动跳过) | 本地调试不污染线上,也不会把 localhost 的 URL 提交上去 |
| 失败降级(curl 不可用回退 file_get_contents) | 服务器没装 curl 也能工作;请求失败不影响文章保存 |
可异步(INDEXNOW_ASYNC) |
开启后编辑页零等待,推送交给后台子进程 |
2.3 数据表
首次调用自动建表,无需手动执行:
CREATE TABLE IF NOT EXISTS indexnow_push (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
art_id INTEGER,
status INTEGER DEFAULT 0,
http_code TEXT,
note TEXT,
push_count INTEGER DEFAULT 1,
push_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
三、文件一:indexnow_push.php(根目录)
放到 bbs/ 根目录,与 config.php 平级。
<?php
/**
* indexnow_push.php —— 发布/修改文章时自动推送 IndexNow
* ------------------------------------------------------------
* 部署:放到 bbs 根目录(与 config.php 平级)
* 接入:admin/edit.php 保存成功后调用 indexNowAutoPush($db, $artId);
*
* 特性:
* 1. 自动去重:同一 URL 在 INDEXNOW_MIN_INTERVAL 秒内不重复推送,避免被限流
* 2. 推送留痕:写入 indexnow_push 表,后台 admin/indexnow_log.php 可查看
* 3. 环境判断:localhost / 127.0.0.1 自动跳过,本地调试不污染线上
* 4. 失败降级:curl 不可用时自动回退 file_get_contents,失败不影响文章保存
* 5. 可异步:设 INDEXNOW_ASYNC=true 后台静默推送,编辑页零等待
*/
if (!defined('IN_BBS')) exit;
/* ====================== 配置区 ====================== */
define('INDEXNOW_HOST', 'bbs.website.com');
define('INDEXNOW_KEY', 'b5125678633f0224f4d034576818ahgjgthfc');
define('INDEXNOW_KEY_LOCATION', 'https://bbs.website.com/b5125678633f0224f4d034576818ahgjgthfc.txt');
define('INDEXNOW_ENABLED', true); // 总开关:临时关闭改 false
define('INDEXNOW_MIN_INTERVAL', 3600); // 同一URL最小推送间隔(秒),0=每次都推
define('INDEXNOW_TIMEOUT', 5); // 单次请求超时(秒)
define('INDEXNOW_ASYNC', false); // true=后台异步推送(Windows XAMPP 需 php.exe 在 PATH)
define('INDEXNOW_LOG_FILE', __DIR__ . '/indexnow.log'); // 文本日志,排查用
/* ==================================================== */
/** 文章 URL */
if (!function_exists('indexNowArticleUrl')) {
function indexNowArticleUrl(int $id): string
{
return 'https://' . INDEXNOW_HOST . '/article-' . $id . '.html';
}
}
/** 是否允许推送(总开关 + 排除本地环境) */
if (!function_exists('indexNowCanPush')) {
function indexNowCanPush(): bool
{
if (!INDEXNOW_ENABLED) return false;
$host = strtolower($_SERVER['HTTP_HOST'] ?? '');
if ($host === '') return true; // CLI 环境,放行
$host = preg_replace('/:\d+$/', '', $host);
if (in_array($host, ['localhost', '127.0.0.1', '::1'], true)) return false;
return true;
}
}
/** 建表(重复运行无害) */
if (!function_exists('indexNowEnsureTable')) {
function indexNowEnsureTable(PDO $db): void
{
$db->exec("
CREATE TABLE IF NOT EXISTS indexnow_push (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
art_id INTEGER,
status INTEGER DEFAULT 0,
http_code TEXT,
note TEXT,
push_count INTEGER DEFAULT 1,
push_time DATETIME DEFAULT CURRENT_TIMESTAMP
)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_inx_url ON indexnow_push(url)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_inx_time ON indexnow_push(push_time)");
}
}
/** 查询该 URL 上次推送记录 */
if (!function_exists('indexNowLastPush')) {
function indexNowLastPush(PDO $db, string $url): ?array
{
$st = $db->prepare("SELECT * FROM indexnow_push WHERE url = :u ORDER BY id DESC LIMIT 1");
$st->bindValue(':u', $url);
$st->execute();
$row = $st->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
}
/** 记录推送结果 */
if (!function_exists('indexNowSaveLog')) {
function indexNowSaveLog(PDO $db, string $url, ?int $artId, int $status, string $httpCode, string $note): void
{
$last = indexNowLastPush($db, $url);
try {
if ($last) {
$st = $db->prepare("UPDATE indexnow_push
SET status=:s, http_code=:c, note=:n, push_time=CURRENT_TIMESTAMP,
push_count = push_count + 1, art_id=:a
WHERE id=:id");
$st->execute([':s'=>$status, ':c'=>$httpCode, ':n'=>$note, ':a'=>$artId, ':id'=>$last['id']]);
} else {
$st = $db->prepare("INSERT INTO indexnow_push (url, art_id, status, http_code, note)
VALUES (:u, :a, :s, :c, :n)");
$st->execute([':u'=>$url, ':a'=>$artId, ':s'=>$status, ':c'=>$httpCode, ':n'=>$note]);
}
} catch (Exception $e) {
// 日志写失败不影响主流程
}
}
}
/** 文本日志 */
if (!function_exists('indexNowFileLog')) {
function indexNowFileLog(string $line): void
{
@file_put_contents(INDEXNOW_LOG_FILE,
'[' . date('Y-m-d H:i:s') . '] ' . $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}
}
/**
* 真正发送 POST 到 IndexNow
* @return array ['ok'=>bool,'code'=>int,'msg'=>string]
*/
if (!function_exists('indexNowSend')) {
function indexNowSend(array $urls): array
{
$urls = array_values(array_unique(array_filter($urls)));
if (empty($urls)) return ['ok'=>false, 'code'=>0, 'msg'=>'URL 列表为空'];
$data = json_encode([
'host' => INDEXNOW_HOST,
'key' => INDEXNOW_KEY,
'keyLocation' => INDEXNOW_KEY_LOCATION,
'urlList' => $urls,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// 优先 curl:能拿到精确状态码
if (function_exists('curl_init')) {
$ch = curl_init('https://api.indexnow.org/indexnow');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_HTTPHEADER => ['Content-Type: application/json; charset=utf-8'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => INDEXNOW_TIMEOUT,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => true,
]);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err !== '') {
return ['ok'=>false, 'code'=>$code, 'msg'=>'curl 错误:' . $err];
}
return ['ok'=>($code >= 200 && $code < 300), 'code'=>$code, 'msg'=>'HTTP ' . $code];
}
// 回退 file_get_contents
$opts = ['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json; charset=utf-8\r\n",
'content' => $data,
'timeout' => INDEXNOW_TIMEOUT,
'ignore_errors' => true,
]];
$ctx = stream_context_create($opts);
$body = @file_get_contents('https://api.indexnow.org/indexnow', false, $ctx);
$line = isset($http_response_header[0]) ? $http_response_header[0] : '';
$code = 0;
if (preg_match('/\s(\d{3})\s/', $line, $m)) $code = (int)$m[1];
return ['ok'=>($code >= 200 && $code < 300), 'code'=>$code, 'msg'=>$line ?: '无响应'];
}
}
/**
* 【主入口】发布或修改文章后调用
*
* @param PDO $db
* @param int $artId 文章ID
* @param array $extra 额外要推的URL(如首页、分页)
* @param bool $force true=忽略最小间隔,强制推送
* @return array ['ok'=>bool,'skipped'=>bool,'msg'=>string]
*/
if (!function_exists('indexNowAutoPush')) {
function indexNowAutoPush(PDO $db, int $artId, array $extra = [], bool $force = false): array
{
if (!indexNowCanPush()) {
return ['ok'=>false, 'skipped'=>true, 'msg'=>'当前环境未启用推送(本地或总开关关闭)'];
}
if ($artId <= 0) {
return ['ok'=>false, 'skipped'=>true, 'msg'=>'文章ID无效'];
}
indexNowEnsureTable($db);
$url = indexNowArticleUrl($artId);
// 去重:距上次推送太近就跳过(修改文章连点保存时很有用)
if (!$force && INDEXNOW_MIN_INTERVAL > 0) {
$last = indexNowLastPush($db, $url);
if ($last && !empty($last['push_time'])) {
$gap = time() - strtotime((string)$last['push_time']);
if ($gap >= 0 && $gap < INDEXNOW_MIN_INTERVAL) {
return ['ok'=>false, 'skipped'=>true,
'msg'=>'距上次推送仅 ' . $gap . ' 秒,小于间隔 ' . INDEXNOW_MIN_INTERVAL . ' 秒,已跳过'];
}
}
}
$urls = array_merge([$url], $extra);
// 异步模式:Windows 下用 start /B 拉起子进程,编辑页零等待
if (INDEXNOW_ASYNC) {
$worker = __DIR__ . '/indexnow_worker.php';
if (is_file($worker)) {
$cmd = 'start /B php "' . $worker . '" ' . (int)$artId . ' > NUL 2>&1';
@pclose(@popen($cmd, 'r'));
return ['ok'=>true, 'skipped'=>false, 'msg'=>'已提交后台异步推送'];
}
}
$res = indexNowSend($urls);
indexNowSaveLog($db, $url, $artId, $res['ok'] ? 1 : 0, (string)$res['code'], $res['msg']);
indexNowFileLog(($res['ok'] ? 'OK ' : 'FAIL') . " art={$artId} code={$res['code']} {$res['msg']} url={$url}");
return ['ok'=>$res['ok'], 'skipped'=>false, 'msg'=>$res['msg']];
}
}
配置区就在这个文件顶部,六个开关按需调整:
define('INDEXNOW_ENABLED', true); // 总开关,临时停用改 false
define('INDEXNOW_MIN_INTERVAL', 3600); // 同一URL最小推送间隔(秒),0=每次都推
define('INDEXNOW_TIMEOUT', 5); // 请求超时
define('INDEXNOW_ASYNC', false); // true=后台异步推送
四、文件二:indexnow_worker.php(根目录)
异步模式下的工作进程,命令行调用,无需手动访问。
<?php
/**
* indexnow_worker.php —— 异步推送工作进程
* 用法(命令行):php indexnow_worker.php 文章ID
* 由 indexnow_push.php 在 INDEXNOW_ASYNC=true 时后台拉起,无需手动访问。
*/
if (PHP_SAPI !== 'cli') { exit; }
define('IN_BBS', true);
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/indexnow_push.php';
$artId = isset($argv[1]) ? (int)$argv[1] : 0;
if ($artId <= 0) exit;
$res = indexNowAutoPush($db, $artId, [], true); // 异步模式下强制推送
indexNowFileLog('ASYNC art=' . $artId . ' ' . ($res['ok'] ? 'OK' : 'SKIP/FAIL') . ' ' . $res['msg']);
只有把
INDEXNOW_ASYNC改成true时才需要它。Windows XAMPP 需要php.exe在系统 PATH 里,否则异步会静默失败(不影响文章保存,只是没推)。
五、文件三:website/indexnow_log.php(后台查看页)
放到 bbs/website/,登录后访问 /website/indexnow_log.php。
功能:
- 统计:累计推送数、成功数
- 记录:最近 200 条,含时间、URL、文章ID、状态、HTTP码、推送次数、说明
- 手动补推单篇(按文章ID,强制推送,忽略间隔)
- 批量补推最新 N 条(默认 100,最大 10000)
<?php
/**
* website/indexnow_log.php —— IndexNow 推送记录查看 + 手动补推
* 部署:放到 bbs/website/ 目录
* 访问:登录后打开 /website/indexnow_log.php
*
* 注意:本页依赖你后台的登录校验文件 _auth.php;
* 如果登录校验方式不同,把下面 require 那行改成你后台统一的校验写法。
*/
require_once __DIR__ . '/_auth.php';
require_once dirname(__DIR__) . '/config.php';
require_once dirname(__DIR__) . '/indexnow_push.php';
indexNowEnsureTable($db);
$msg = '';
$msgType = 'info';
// 手动操作
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$act = $_POST['act'] ?? '';
if ($act === 'push_one') {
$artId = (int)($_POST['art_id'] ?? 0);
if ($artId > 0) {
$res = indexNowAutoPush($db, $artId, [], true); // 手动=强制
$msg = '文章 #' . $artId . ':' . ($res['ok'] ? '✅ 推送成功' : '⚠️ ' . $res['msg']);
$msgType = $res['ok'] ? 'success' : 'warning';
}
}
if ($act === 'push_latest') {
$limit = (int)($_POST['limit'] ?? 100);
$limit = max(1, min(10000, $limit));
$rows = $db->query("SELECT id FROM article ORDER BY id DESC LIMIT " . $limit)->fetchAll(PDO::FETCH_ASSOC);
$urls = [];
foreach ($rows as $r) $urls[] = indexNowArticleUrl((int)$r['id']);
$res = indexNowSend($urls);
indexNowFileLog(($res['ok'] ? 'OK ' : 'FAIL') . " batch={$limit} code={$res['code']} {$res['msg']}");
foreach ($urls as $u) {
indexNowSaveLog($db, $u, null, $res['ok'] ? 1 : 0, (string)$res['code'], '批量补推 ' . $res['msg']);
}
$msg = '批量推送 ' . count($urls) . ' 条:' . ($res['ok'] ? '✅ 成功' : '❌ 失败') . '(' . $res['msg'] . ')';
$msgType = $res['ok'] ? 'success' : 'danger';
}
}
// 读取记录
$logs = $db->query("SELECT * FROM indexnow_push ORDER BY push_time DESC, id DESC LIMIT 200")
->fetchAll(PDO::FETCH_ASSOC);
$stat = $db->query("SELECT
COUNT(*) AS total,
SUM(CASE WHEN status=1 THEN 1 ELSE 0 END) AS ok_cnt
FROM indexnow_push")->fetch(PDO::FETCH_ASSOC);
$h = function ($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); };
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>IndexNow 推送记录 - 511遇见BBS后台</title>
<meta name="robots" content="noindex,nofollow">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css">
<style>body{padding:24px;background:#f5f7fb;} table{background:#fff;} .mono{font-family:Consolas,Monaco,monospace;font-size:12px;}</style>
</head>
<body>
<div class="container" style="max-width:1100px;">
<div class="d-flex align-items-center justify-content-between mb-3">
<h4 class="mb-0">📡 IndexNow 推送记录</h4>
<div>
<a href="index.php" class="btn btn-sm btn-outline-secondary">← 后台首页</a>
<a href="../index.php" target="_blank" class="btn btn-sm btn-outline-primary">前台首页</a>
</div>
</div>
<?php if ($msg): ?>
<div class="alert alert-<?php echo $h($msgType); ?>"><?php echo $h($msg); ?></div>
<?php endif; ?>
<div class="card mb-3">
<div class="card-body">
<div class="row text-center">
<div class="col-4">
<div class="text-muted" style="font-size:13px;">累计推送</div>
<div style="font-size:24px;font-weight:600;"><?php echo (int)$stat['total']; ?></div>
</div>
<div class="col-4">
<div class="text-muted" style="font-size:13px;">成功</div>
<div style="font-size:24px;font-weight:600;color:#28a745;"><?php echo (int)$stat['ok_cnt']; ?></div>
</div>
<div class="col-4">
<div class="text-muted" style="font-size:13px;">密钥</div>
<div class="mono" style="font-size:11px;word-break:break-all;"><?php echo $h(substr(INDEXNOW_KEY, 0, 12) . '…'); ?></div>
</div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<form method="post" class="form-inline mb-0">
<label class="mr-2" style="font-size:14px;">手动补推文章ID</label>
<input type="number" name="art_id" class="form-control form-control-sm mr-2" placeholder="如 12" min="1" required>
<input type="hidden" name="act" value="push_one">
<button class="btn btn-sm btn-primary mr-4">推送这篇</button>
</form>
<hr class="my-3">
<form method="post" class="form-inline mb-0" onsubmit="return confirm('确认批量推送最新文章?');">
<label class="mr-2" style="font-size:14px;">批量补推最新</label>
<input type="number" name="limit" class="form-control form-control-sm mr-2" value="100" min="1" max="10000">
<span class="mr-2" style="font-size:14px;">条</span>
<input type="hidden" name="act" value="push_latest">
<button class="btn btn-sm btn-warning">批量推送</button>
</form>
</div>
</div>
<div class="card">
<div class="card-body p-0">
<table class="table table-sm table-hover mb-0">
<thead class="thead-light">
<tr>
<th style="width:120px;">时间</th>
<th>URL</th>
<th style="width:70px;">文章</th>
<th style="width:60px;">状态</th>
<th style="width:70px;">HTTP</th>
<th style="width:60px;">次数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr><td colspan="7" class="text-center text-muted py-4">暂无推送记录,去发布或修改一篇文章试试</td></tr>
<?php else: ?>
<?php foreach ($logs as $l): ?>
<tr>
<td class="mono"><?php echo $h($l['push_time']); ?></td>
<td class="mono"><a href="<?php echo $h($l['url']); ?>" target="_blank"><?php echo $h($l['url']); ?></a></td>
<td><?php echo $l['art_id'] ? '#' . (int)$l['art_id'] : '-'; ?></td>
<td>
<?php if ((int)$l['status'] === 1): ?>
<span class="badge badge-success">成功</span>
<?php else: ?>
<span class="badge badge-danger">失败</span>
<?php endif; ?>
</td>
<td class="mono"><?php echo $h($l['http_code']); ?></td>
<td><?php echo (int)$l['push_count']; ?></td>
<td class="mono text-muted"><?php echo $h($l['note']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<p class="text-muted mt-3" style="font-size:12px;">
说明:发布/修改文章时会自动推送。同一 URL 默认 <?php echo (int)INDEXNOW_MIN_INTERVAL; ?> 秒内不重复推送,手动补推与批量推送不受此限制。
</p>
</div>
</body>
</html>
注意:本页
require_once __DIR__ . '/_auth.php';依赖你后台的登录校验文件。如果你的后台校验方式不同(比如写在_header.php里),把这行改成你后台统一的校验写法,切勿跳过校验直接开放。
六、接线:两处改动
6.1 config.php 加一行
在 require_once __DIR__ . '/functions.php'; 之后加:
require_once __DIR__ . '/indexnow_push.php';
这一步让全站(含后台)都能调用推送函数,后面 edit.php 就不用再 require。
6.2 admin/edit.php 的三处改动(以本站实际代码为例)
你的 admin/edit.php 里原本已经有一版推送:
// 推送 IndexNow(函数在 functions.php 里)
if (function_exists('indexNowPushSingle')) {
try {
$pushUrl = getStaticUrl('article', $pushId, 1, [], true);
indexNowPushSingle($pushUrl);
} catch (Exception $e) {}
}
这版能用,但没有去重、没有留痕。改法如下:
改动 1:顶部引入推送模块(紧跟 require_once __DIR__ . '/_auth.php';)
// ===== IndexNow 自动推送模块(根目录 indexnow_push.php)=====
// 提供去重、留痕、本地环境跳过;文件不存在时自动回退到旧的 indexNowPushSingle()
if (!defined('IN_BBS')) define('IN_BBS', true); // 保险:_auth.php 若未间接引入 config.php
$indexNowPath = dirname(__DIR__) . '/indexnow_push.php';
if (is_file($indexNowPath)) {
require_once $indexNowPath;
}
注意用
dirname(__DIR__)而非__DIR__:edit.php 在website/子目录,推送文件在根目录。is_file()判断保证即使文件没上传,编辑页也不会报错。
改动 2:替换原来的推送块
// ===== 自动推送 IndexNow(新增/修改都会触发)=====
unset($_SESSION['indexnow_msg']);
if (function_exists('indexNowAutoPush')) {
// 去重逻辑:同一 URL 默认 3600 秒内不重复推送,避免连点保存被限流
try {
$pushRes = indexNowAutoPush($db, (int)$pushId);
if (!$pushRes['ok'] && !$pushRes['skipped']) {
$_SESSION['indexnow_msg'] = '⚠️ IndexNow 推送失败:' . $pushRes['msg'];
} elseif ($pushRes['ok']) {
$_SESSION['indexnow_msg'] = '✅ IndexNow 已推送';
} else {
$_SESSION['indexnow_msg'] = 'ℹ️ IndexNow 本次跳过:' . $pushRes['msg'];
}
} catch (Exception $e) {
// 推送异常绝不影响文章保存
$_SESSION['indexnow_msg'] = '⚠️ IndexNow 推送异常:' . $e->getMessage();
}
} elseif (function_exists('indexNowPushSingle')) {
// 回退:旧的单条推送函数(functions.php 里)
try {
$pushUrl = getStaticUrl('article', $pushId, 1, [], true);
indexNowPushSingle($pushUrl);
} catch (Exception $e) {}
}
改动 3:保存后展示推送结果(原第 61 行 if (isset($_GET['ok'])) $msg = '✅保存成功';)
if (isset($_GET['ok'])) {
$msg = '✅保存成功';
if (!empty($_SESSION['indexnow_msg'])) {
$msg .= ' ' . $_SESSION['indexnow_msg'];
unset($_SESSION['indexnow_msg']);
}
}
之所以绕一圈 session:保存后会 header() 跳走,原来的 $msg 变量丢失,只有跨请求的 session 能把结果带到跳转后的页面。
三种常见变量写法对照(如果你的 edit.php 结构与示例不同):
| edit.php 的写法 | 对应取值 |
|---|---|
新增用 lastInsertId() |
(int)$db->lastInsertId() |
更新时 id 来自 $_POST['aid'] |
(int)$_POST['aid'] |
更新时 id 来自 $_GET['id'] |
(int)$_GET['id'] |
本站 edit.php 的隐藏字段叫
aid(不是id),并且新增/修改共用$pushId变量,所以直接indexNowAutoPush($db, (int)$pushId)即可。
七、上线验证
- [ ] 上传
indexnow_push.php、indexnow_worker.php到website/根目录 - [ ] 上传
indexnow_log.php到bbs/website/ - [ ]
config.php加一行 require - [ ]
website/edit.php保存逻辑后加 3 行 - [ ] 重启 Apache(清 OPcache)
- [ ] 新建一篇测试文章,保存后打开
/website/indexnow_log.php,应看到状态"成功"、HTTP 200 - [ ] 再改一次同一篇(间隔内),应看到记录被跳过或次数+1
- [ ] 点"手动补推"输入该文章ID,应强制推送成功
- [ ] 确认
indexnow.log文本文件有记录
常见状态码
| 状态码 | 含义 |
|---|---|
| 200 | 成功接收 |
| 202 | 已接受,后台排队处理(也算成功) |
| 400 | JSON 格式错误,检查 urlList |
| 403 | 密钥无效,检查 key 与 keyLocation 是否匹配 |
| 422 | URL 不属于该 host |
| 429 | 请求过频,需降低推送频率(调大 INDEXNOW_MIN_INTERVAL) |
八、几个实用建议
1. **密钥文件必须外网可访问**:`https://bbs.website.com/aiodsfjh9837490q823jrfokAE456f8ff418aaefc.txt` 要能直接打开且内容为密钥本身。这是 403 报错的头号原因。
-
不要每次保存都推:默认 3600 秒间隔已经够用。IndexNow 本身就是"通知变化",一天推一次同一 URL 足矣。
-
批量脚本可以留着:
push_indexnow.php保留作为"全站补推"的兜底手段,但加访问密码,别裸奔在公网。或者直接用新的admin/indexnow_log.php里的批量按钮(需登录,更安全)。 -
发布新文章时可顺便推首页:如果你的首页/分页也想让搜索引擎尽快更新,调用时传第二个参数:
indexNowAutoPush($db, $artId, [ 'https://bbs.website.com/', 'https://bbs.website.com/index-website-1.html', ]); -
确认服务器能出站:部分虚拟机会屏蔽出站请求。临时在服务器上执行
curl -I https://api.indexnow.org验证,连不通就只能在有外网的机器跑批量脚本。
九、改动文件对照表
| 文件 | 操作 | 说明 |
|---|---|---|
indexnow_push.php |
新建 | 核心:环境判断、去重、发送、留痕 |
indexnow_worker.php |
新建 | 异步模式工作进程(可选) |
website/indexnow_log.php |
新建 | 推送记录查看 + 手动补推 |
config.php |
加 1 行 | require_once indexnow_push.php |
website/edit.php |
改 3 处 | 引入模块 + 替换推送块 + session 回显结果 |
functions.php |
无需改动 | 原有 indexNowPushSingle() 保留,不影响 |
备份提示:改前请备份 config.php、admin/edit.php 与数据库 site.db。推送失败不会影响文章保存,可放心上线。98