fn_b/app/Services/IndexNowService.php
2026-09-11 16:33:27 +08:00

227 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;
class IndexNowService
{
/**
* 检查 URL 是否真实存在,并将存在的 URL 推送到 IndexNow。
*
* 判断逻辑:
*
* 1. URL 格式正确
* 2. URL 属于当前站点
* 3. URL 实际可以访问(HTTP 2xx)
* 4. 只有通过以上检查的 URL 才推送 IndexNow
* 5. IndexNow 返回 200 / 202,认为提交成功
*
* @param array<int, string> $aUrlList
* @return bool
*/
public function pushUrls(array $aUrlList): bool
{
if (empty($aUrlList)) {
return false;
}
$sIndexNowKey = config('key.indexnow');
$sHostDomain = config('app.site_code', 'clashfreenode.com');
// TEST 时可以写死,但正式环境建议删除这一行
// $sHostDomain = 'clashfreenode.com';
if (empty($sIndexNowKey) || empty($sHostDomain)) {
Log::error('IndexNow 配置缺失。');
return false;
}
/**
* ---------------------------------------------------------
* 1. 清理 URL
* ---------------------------------------------------------
*/
$aUrlList = array_values(array_unique(array_filter(
$aUrlList,
static function ($sUrl) {
return is_string($sUrl)
&& filter_var($sUrl, FILTER_VALIDATE_URL);
}
)));
if (empty($aUrlList)) {
return false;
}
/**
* ---------------------------------------------------------
* 2. 检查 URL 是否真实存在
* ---------------------------------------------------------
*/
$aValidUrlList = [];
foreach ($aUrlList as $sUrl) {
// 检查域名
$sUrlHost = parse_url($sUrl, PHP_URL_HOST);
if ($sUrlHost !== $sHostDomain) {
Log::warning('IndexNow URL 域名不匹配', [
'url' => $sUrl,
'host' => $sHostDomain,
]);
continue;
}
// 检查页面是否真实存在
if (!$this->urlExists($sUrl)) {
Log::warning('IndexNow 跳过不存在的 URL', [
'url' => $sUrl,
]);
continue;
}
$aValidUrlList[] = $sUrl;
}
/**
* 一个 URL 都不存在
*/
if (empty($aValidUrlList)) {
Log::warning('IndexNow 没有可推送的有效 URL', [
'count' => count($aUrlList),
]);
return false;
}
/**
* ---------------------------------------------------------
* 3. 构造 IndexNow Payload
* ---------------------------------------------------------
*/
$aPayloadData = [
'host' => $sHostDomain,
'key' => $sIndexNowKey,
'keyLocation' => "https://{$sHostDomain}/{$sIndexNowKey}.txt",
'urlList' => $aValidUrlList,
];
/**
* ---------------------------------------------------------
* 4. 推送 IndexNow
* ---------------------------------------------------------
*/
try {
$oResponse = Http::timeout(10)
->acceptJson()
->withHeaders([
'Content-Type' => 'application/json; charset=utf-8',
])
->post(
'https://api.indexnow.org/indexnow',
$aPayloadData
);
$iStatusCode = $oResponse->status();
/**
* 200 = 成功处理
* 202 = 已接受,等待处理
*/
if (in_array($iStatusCode, [200, 202], true)) {
Log::info('IndexNow 推送成功', [
'status' => $iStatusCode,
'count' => count($aValidUrlList),
'host' => $sHostDomain,
'urls' => $aValidUrlList,
]);
return true;
}
Log::error('IndexNow 推送失败', [
'status' => $iStatusCode,
'body' => $oResponse->body(),
'count' => count($aValidUrlList),
'host' => $sHostDomain,
'urls' => $aValidUrlList,
]);
return false;
} catch (Throwable $e) {
Log::error('IndexNow 请求异常', [
'message' => $e->getMessage(),
'count' => count($aValidUrlList),
'host' => $sHostDomain,
]);
return false;
}
}
/**
* 判断 URL 页面是否真实存在。
*
* 使用 GET 检查,跟搜索引擎实际访问页面的方式更接近。
*
* @param string $sUrl
* @return bool
*/
private function urlExists(string $sUrl): bool
{
try {
$oResponse = Http::timeout(10)
->withOptions([
'allow_redirects' => true,
])
->get($sUrl);
$iStatusCode = $oResponse->status();
Log::info('URL 页面检查', [
'url' => $sUrl,
'status' => $iStatusCode,
]);
/**
* 200 ~ 299
* 认为页面正常存在
*/
if ($oResponse->successful()) {
return true;
}
Log::warning('URL 页面不存在或无法访问', [
'url' => $sUrl,
'status' => $iStatusCode,
]);
return false;
} catch (Throwable $e) {
Log::warning('URL 页面检查异常', [
'url' => $sUrl,
'message' => $e->getMessage(),
]);
return false;
}
}
}