227 lines
6.0 KiB
PHP
227 lines
6.0 KiB
PHP
<?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;
|
||
}
|
||
}
|
||
|
||
|
||
}
|