debug
This commit is contained in:
1
code/.gitignore
vendored
1
code/.gitignore
vendored
@@ -21,4 +21,5 @@ public/static/favicon/generated/*
|
||||
/app/public/_admin_templates/video-metadata-missing-task-pool/*
|
||||
/app/public/_admin_templates/video-metadata-missing-workbench/*
|
||||
/data/seo_copy_published/*
|
||||
/data/seo_resource/keyword_feedback/*
|
||||
/storage/*
|
||||
|
||||
@@ -7,6 +7,7 @@ use app\admin\controller\Index;
|
||||
use app\admin\controller\Domain;
|
||||
use app\admin\controller\SeoSupply;
|
||||
use app\admin\controller\Novel;
|
||||
use app\admin\controller\ServerNode;
|
||||
use app\admin\controller\Site;
|
||||
use app\admin\controller\SystemConfig;
|
||||
use app\admin\controller\Video;
|
||||
@@ -32,6 +33,9 @@ Route::group("/system", function () {
|
||||
Route::post("/config/save", [SystemConfig::class, "saveSystemConfig"])->name("SystemConfig@saveSystemConfig");
|
||||
Route::post("/config/seo-ai-rules/reset", [SystemConfig::class, "resetSeoAiRules"])->name("SystemConfig@resetSeoAiRules");
|
||||
Route::get("/storage/file", [SystemConfig::class, "previewStorageFile"])->name("SystemConfig@previewStorageFile");
|
||||
Route::get("/node/list", [ServerNode::class, "getServerNodeList"])->name("ServerNode@getServerNodeList");
|
||||
Route::post("/node/save", [ServerNode::class, "saveServerNode"])->name("ServerNode@saveServerNode");
|
||||
Route::post("/node/del", [ServerNode::class, "delServerNode"])->name("ServerNode@delServerNode");
|
||||
|
||||
Route::get("/cjpz/list", [SystemConfig::class, "getCaiJiPeiZhiList"])->name("SystemConfig@getCaiJiPeiZhiList");
|
||||
Route::post("/cjpz/save", [SystemConfig::class, "saveCaiJiPeiZhi"])->name("SystemConfig@saveCaiJiPeiZhi");
|
||||
|
||||
210
code/app/admin/controller/ServerNode.php
Normal file
210
code/app/admin/controller/ServerNode.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\BaseController;
|
||||
use app\model\AdminUserModel;
|
||||
use app\model\ServerNodeModel;
|
||||
use app\Request;
|
||||
use think\Response;
|
||||
|
||||
class ServerNode extends BaseController
|
||||
{
|
||||
/**
|
||||
* 前端服务器节点列表
|
||||
*/
|
||||
public function getServerNodeList(Request $Request, ?AdminUserModel $AdminUserModel): Response
|
||||
{
|
||||
$arrData = [
|
||||
'page' => (int)$Request->get('page', 1),
|
||||
'limit' => (int)$Request->get('limit', 10),
|
||||
'key' => trim((string)$Request->get('key', '')),
|
||||
];
|
||||
|
||||
$this->validate($arrData, [
|
||||
'page' => 'require|number',
|
||||
'limit' => 'require|number',
|
||||
]);
|
||||
|
||||
$ServerNodeModel = ServerNodeModel::alias('sn')
|
||||
->order('sn.sn_status', 'desc')
|
||||
->order('sn.updated_at', 'desc')
|
||||
->order('sn.sn_id', 'desc');
|
||||
|
||||
if ($arrData['key'] !== '') {
|
||||
$strKey = $arrData['key'];
|
||||
$ServerNodeModel->where(function ($query) use ($strKey) {
|
||||
$query->whereOr([
|
||||
['sn.sn_name', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_code', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_host', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_username', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_region', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_purpose', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_project_dir', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_log_dir', 'like', '%' . $strKey . '%'],
|
||||
['sn.sn_nginx_dir', 'like', '%' . $strKey . '%'],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
$Paginate = $ServerNodeModel->paginate([
|
||||
'list_rows' => $arrData['limit'],
|
||||
'page' => $arrData['page'],
|
||||
]);
|
||||
|
||||
$arrItems = [];
|
||||
foreach ($Paginate->items() as $arrItem) {
|
||||
$arrItems[] = $this->serializeServerNodeRow($arrItem);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'items' => $arrItems,
|
||||
'total' => $Paginate->total(),
|
||||
'current_page' => $Paginate->currentPage(),
|
||||
'total_pages' => $Paginate->lastPage(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前端服务器节点
|
||||
*/
|
||||
public function saveServerNode(Request $Request, ?AdminUserModel $AdminUserModel): Response
|
||||
{
|
||||
$arrData = [
|
||||
'sn_id' => $Request->post('sn_id'),
|
||||
'sn_name' => trim((string)$Request->post('sn_name', '')),
|
||||
'sn_code' => $this->normalizeNullableString($Request->post('sn_code')),
|
||||
'sn_host' => trim((string)$Request->post('sn_host', '')),
|
||||
'sn_port' => (int)$Request->post('sn_port', 22),
|
||||
'sn_username' => trim((string)$Request->post('sn_username', '')),
|
||||
'sn_auth_type' => trim((string)$Request->post('sn_auth_type', 'password')),
|
||||
'sn_password' => trim((string)$Request->post('sn_password', '')),
|
||||
'sn_private_key' => trim((string)$Request->post('sn_private_key', '')),
|
||||
'sn_passphrase' => trim((string)$Request->post('sn_passphrase', '')),
|
||||
'sn_region' => $this->normalizeNullableString($Request->post('sn_region')),
|
||||
'sn_purpose' => $this->normalizeNullableString($Request->post('sn_purpose')),
|
||||
'sn_project_dir' => $this->normalizeNullableString($Request->post('sn_project_dir')),
|
||||
'sn_log_dir' => $this->normalizeNullableString($Request->post('sn_log_dir')),
|
||||
'sn_nginx_dir' => $this->normalizeNullableString($Request->post('sn_nginx_dir')),
|
||||
'sn_note' => $this->normalizeNullableString($Request->post('sn_note')),
|
||||
'sn_status' => (int)$Request->post('sn_status', 1),
|
||||
];
|
||||
|
||||
$this->validate($arrData, [
|
||||
'sn_name' => 'require|max:255',
|
||||
'sn_host' => 'require|max:255',
|
||||
'sn_port' => 'require|number|between:1,65535',
|
||||
'sn_username' => 'require|max:100',
|
||||
'sn_auth_type' => 'require|in:password,private_key',
|
||||
'sn_status' => 'require|in:0,1',
|
||||
]);
|
||||
|
||||
$boolIsCreate = empty($arrData['sn_id']);
|
||||
if ($boolIsCreate) {
|
||||
$ServerNodeModel = new ServerNodeModel();
|
||||
} else {
|
||||
$ServerNodeModel = ServerNodeModel::where('sn_id', (int)$arrData['sn_id'])->find();
|
||||
if (empty($ServerNodeModel)) {
|
||||
return $this->error('9993', '服务器节点不存在');
|
||||
}
|
||||
}
|
||||
|
||||
if ($arrData['sn_code'] !== null) {
|
||||
$DuplicateModel = ServerNodeModel::where('sn_code', $arrData['sn_code']);
|
||||
if (!$boolIsCreate) {
|
||||
$DuplicateModel->where('sn_id', '<>', (int)$arrData['sn_id']);
|
||||
}
|
||||
if ($DuplicateModel->find() !== null) {
|
||||
return $this->error('9993', '节点编码已存在,请更换后重试');
|
||||
}
|
||||
}
|
||||
|
||||
$strCurrentAuthType = trim((string)($ServerNodeModel->sn_auth_type ?? ''));
|
||||
$boolHasExistingPassword = trim((string)($ServerNodeModel->sn_password ?? '')) !== '';
|
||||
$boolHasExistingPrivateKey = trim((string)($ServerNodeModel->sn_private_key ?? '')) !== '';
|
||||
|
||||
if ($arrData['sn_auth_type'] === 'password') {
|
||||
if ($arrData['sn_password'] === '') {
|
||||
if ($boolIsCreate || $strCurrentAuthType !== 'password' || !$boolHasExistingPassword) {
|
||||
return $this->error('9993', '当前登录方式为密码,请填写 SSH 密码');
|
||||
}
|
||||
unset($arrData['sn_password']);
|
||||
}
|
||||
|
||||
$arrData['sn_private_key'] = '';
|
||||
$arrData['sn_passphrase'] = '';
|
||||
} else {
|
||||
if ($arrData['sn_private_key'] === '') {
|
||||
if ($boolIsCreate || $strCurrentAuthType !== 'private_key' || !$boolHasExistingPrivateKey) {
|
||||
return $this->error('9993', '当前登录方式为私钥,请填写 SSH 私钥');
|
||||
}
|
||||
unset($arrData['sn_private_key']);
|
||||
if ($arrData['sn_passphrase'] === '') {
|
||||
unset($arrData['sn_passphrase']);
|
||||
}
|
||||
}
|
||||
|
||||
$arrData['sn_password'] = '';
|
||||
}
|
||||
|
||||
$ServerNodeModel->fill($arrData);
|
||||
$ServerNodeModel->save();
|
||||
|
||||
return $this->success([
|
||||
'sn_id' => (int)$ServerNodeModel->sn_id,
|
||||
], $boolIsCreate ? '服务器节点新增成功' : '服务器节点保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除前端服务器节点
|
||||
*/
|
||||
public function delServerNode(Request $Request, ?AdminUserModel $AdminUserModel): Response
|
||||
{
|
||||
$arrData = [
|
||||
'sn_id' => trim((string)$Request->post('sn_id', '')),
|
||||
];
|
||||
|
||||
$this->validate($arrData, [
|
||||
'sn_id' => 'require',
|
||||
]);
|
||||
|
||||
$arrIds = array_values(array_unique(array_filter(array_map('intval', explode(',', $arrData['sn_id'])), static function (int $intId): bool {
|
||||
return $intId > 0;
|
||||
})));
|
||||
|
||||
if ($arrIds === []) {
|
||||
return $this->error('9993', '未识别到可删除的服务器节点');
|
||||
}
|
||||
|
||||
ServerNodeModel::whereIn('sn_id', $arrIds)->delete();
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 屏蔽列表接口里的敏感 SSH 凭证,只返回配置状态。
|
||||
*/
|
||||
protected function serializeServerNodeRow($arrItem): array
|
||||
{
|
||||
$arrRow = is_array($arrItem) ? $arrItem : $arrItem->toArray();
|
||||
$arrRow['sn_port'] = (int)($arrRow['sn_port'] ?? 22);
|
||||
$arrRow['sn_status'] = (int)($arrRow['sn_status'] ?? 1);
|
||||
$arrRow['sn_password_configured'] = trim((string)($arrRow['sn_password'] ?? '')) !== '';
|
||||
$arrRow['sn_private_key_configured'] = trim((string)($arrRow['sn_private_key'] ?? '')) !== '';
|
||||
$arrRow['sn_passphrase_configured'] = trim((string)($arrRow['sn_passphrase'] ?? '')) !== '';
|
||||
|
||||
unset($arrRow['sn_password'], $arrRow['sn_private_key'], $arrRow['sn_passphrase']);
|
||||
|
||||
return $arrRow;
|
||||
}
|
||||
|
||||
protected function normalizeNullableString($value): ?string
|
||||
{
|
||||
$strValue = trim((string)$value);
|
||||
|
||||
return $strValue === '' ? null : $strValue;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
|
||||
$arrProviders = [];
|
||||
$arrHosts = [];
|
||||
$arrLatestIndexByHost = [];
|
||||
$arrLatestSignalsByHost = [];
|
||||
$intKeywordSnapshots = (int)SeoExternalSnapshotModel::where('metric_date', '>=', $strStartDate)
|
||||
->where('scope', 'keyword')
|
||||
->count();
|
||||
@@ -28,9 +28,10 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
self::eachSnapshotRow(
|
||||
SeoExternalSnapshotModel::where('metric_date', '>=', $strStartDate)
|
||||
->where('scope', 'host'),
|
||||
['id', 'provider', 'host', 'scope', 'metric_date', 'status', 'indexed_status', 'baidu_pc_ip_range', 'baidu_mobile_ip_range', 'pc_keyword_count', 'mobile_keyword_count', 'queried_at'],
|
||||
static function (array $arrItem) use (&$arrProviders, &$arrHosts, &$arrLatestIndexByHost, &$strLatestMetricDate): void {
|
||||
['id', 'provider', 'snapshot_type', 'host', 'scope', 'metric_date', 'status', 'indexed_status', 'result_count_text', 'baidu_pc_ip_range', 'baidu_mobile_ip_range', 'pc_keyword_count', 'mobile_keyword_count', 'queried_at'],
|
||||
static function (array $arrItem) use (&$arrProviders, &$arrHosts, &$arrLatestSignalsByHost, &$strLatestMetricDate): void {
|
||||
$strProvider = trim((string)($arrItem['provider'] ?? ''));
|
||||
$strSnapshotType = trim((string)($arrItem['snapshot_type'] ?? ''));
|
||||
$strHost = trim((string)($arrItem['host'] ?? ''));
|
||||
$strMetricDate = trim((string)($arrItem['metric_date'] ?? ''));
|
||||
$strStatus = trim((string)($arrItem['status'] ?? ''));
|
||||
@@ -48,34 +49,33 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
}
|
||||
|
||||
if ($strHost !== '') {
|
||||
$strHostKey = $strHost;
|
||||
$arrCurrent = $arrLatestIndexByHost[$strHostKey] ?? null;
|
||||
$boolShouldReplace = false;
|
||||
if (!$arrCurrent) {
|
||||
$boolShouldReplace = true;
|
||||
} else {
|
||||
$strCurrentDate = (string)($arrCurrent['metric_date'] ?? '');
|
||||
$intCurrentQueriedAt = (int)($arrCurrent['queried_at'] ?? 0);
|
||||
if (strcmp($strCurrentDate, $strMetricDate) < 0) {
|
||||
$boolShouldReplace = true;
|
||||
} elseif ($strCurrentDate === $strMetricDate && $intCurrentQueriedAt < $intQueriedAt) {
|
||||
$boolShouldReplace = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($boolShouldReplace) {
|
||||
$arrLatestIndexByHost[$strHostKey] = [
|
||||
$arrCandidate = [
|
||||
'host' => $strHost,
|
||||
'provider' => $strProvider,
|
||||
'snapshot_type' => $strSnapshotType,
|
||||
'metric_date' => $strMetricDate,
|
||||
'queried_at' => $intQueriedAt,
|
||||
'status' => $strStatus,
|
||||
'indexed_status' => $strIndexedStatus,
|
||||
'result_count_text' => trim((string)($arrItem['result_count_text'] ?? '')),
|
||||
'baidu_pc_ip_range' => trim((string)($arrItem['baidu_pc_ip_range'] ?? '')),
|
||||
'baidu_mobile_ip_range' => trim((string)($arrItem['baidu_mobile_ip_range'] ?? '')),
|
||||
'pc_keyword_count' => (int)($arrItem['pc_keyword_count'] ?? 0),
|
||||
'mobile_keyword_count' => (int)($arrItem['mobile_keyword_count'] ?? 0),
|
||||
];
|
||||
|
||||
if (self::shouldReplaceSnapshot($arrLatestSignalsByHost[$strHost]['display'] ?? null, $strMetricDate, $intQueriedAt)) {
|
||||
$arrLatestSignalsByHost[$strHost]['display'] = $arrCandidate;
|
||||
}
|
||||
|
||||
if ($strSnapshotType === 'site_query'
|
||||
&& self::shouldReplaceSnapshot($arrLatestSignalsByHost[$strHost]['index'] ?? null, $strMetricDate, $intQueriedAt)) {
|
||||
$arrLatestSignalsByHost[$strHost]['index'] = $arrCandidate;
|
||||
}
|
||||
|
||||
if ($strSnapshotType === 'aizhan_summary'
|
||||
&& self::shouldReplaceSnapshot($arrLatestSignalsByHost[$strHost]['keyword'] ?? null, $strMetricDate, $intQueriedAt)) {
|
||||
$arrLatestSignalsByHost[$strHost]['keyword'] = $arrCandidate;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -85,13 +85,44 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
$intIndexedLikeCount = 0;
|
||||
$intKeywordReadyCount = 0;
|
||||
$intTrafficReadyCount = 0;
|
||||
$arrHostRows = array_values($arrLatestIndexByHost);
|
||||
$arrHostRows = [];
|
||||
|
||||
foreach ($arrLatestSignalsByHost as $strHost => $arrSignals) {
|
||||
$arrDisplay = (array)($arrSignals['display'] ?? []);
|
||||
$arrIndex = (array)($arrSignals['index'] ?? []);
|
||||
$arrKeyword = (array)($arrSignals['keyword'] ?? []);
|
||||
$arrProvidersForHost = [];
|
||||
|
||||
foreach ([$arrIndex, $arrKeyword, $arrDisplay] as $arrSignalRow) {
|
||||
$strSignalProvider = trim((string)($arrSignalRow['provider'] ?? ''));
|
||||
if ($strSignalProvider !== '') {
|
||||
$arrProvidersForHost[$strSignalProvider] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$arrHostRows[] = [
|
||||
'host' => $strHost,
|
||||
'provider' => implode(',', array_keys($arrProvidersForHost)),
|
||||
'index_provider' => (string)($arrIndex['provider'] ?? ''),
|
||||
'keyword_provider' => (string)($arrKeyword['provider'] ?? ''),
|
||||
'metric_date' => max((string)($arrDisplay['metric_date'] ?? ''), (string)($arrIndex['metric_date'] ?? ''), (string)($arrKeyword['metric_date'] ?? '')),
|
||||
'queried_at' => max((int)($arrDisplay['queried_at'] ?? 0), (int)($arrIndex['queried_at'] ?? 0), (int)($arrKeyword['queried_at'] ?? 0)),
|
||||
'status' => (string)($arrDisplay['status'] ?? ''),
|
||||
'indexed_status' => (string)($arrIndex['indexed_status'] ?? ''),
|
||||
'result_count_text' => (string)($arrIndex['result_count_text'] ?? ''),
|
||||
'baidu_pc_ip_range' => (string)($arrKeyword['baidu_pc_ip_range'] ?? ''),
|
||||
'baidu_mobile_ip_range' => (string)($arrKeyword['baidu_mobile_ip_range'] ?? ''),
|
||||
'pc_keyword_count' => (int)($arrKeyword['pc_keyword_count'] ?? 0),
|
||||
'mobile_keyword_count' => (int)($arrKeyword['mobile_keyword_count'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($arrHostRows as &$arrRow) {
|
||||
$boolIndexedLike = (string)($arrRow['indexed_status'] ?? '') === 'indexed_like';
|
||||
$boolIndexedLike = self::normalizeIndexState($arrRow) === 'indexed_like';
|
||||
$boolKeywordReady = (int)($arrRow['pc_keyword_count'] ?? 0) > 0 || (int)($arrRow['mobile_keyword_count'] ?? 0) > 0;
|
||||
$boolTrafficReady = !self::isZeroRange((string)($arrRow['baidu_pc_ip_range'] ?? ''))
|
||||
|| !self::isZeroRange((string)($arrRow['baidu_mobile_ip_range'] ?? ''));
|
||||
$arrRow['indexed_like'] = $boolIndexedLike;
|
||||
$arrRow['keyword_ready'] = $boolKeywordReady;
|
||||
$arrRow['traffic_ready'] = $boolTrafficReady;
|
||||
|
||||
@@ -113,6 +144,11 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
if ($intLeftKeyword !== $intRightKeyword) {
|
||||
return $intRightKeyword <=> $intLeftKeyword;
|
||||
}
|
||||
$intLeftIndexed = !empty($arrLeft['indexed_like']) ? 1 : 0;
|
||||
$intRightIndexed = !empty($arrRight['indexed_like']) ? 1 : 0;
|
||||
if ($intLeftIndexed !== $intRightIndexed) {
|
||||
return $intRightIndexed <=> $intLeftIndexed;
|
||||
}
|
||||
return strcmp((string)($arrLeft['host'] ?? ''), (string)($arrRight['host'] ?? ''));
|
||||
});
|
||||
|
||||
@@ -402,6 +438,19 @@ class DomainExternalSeoSnapshotAnalysisHelper
|
||||
return $strRange === '' || $strRange === '0 ~ 0' || $strRange === '0~0';
|
||||
}
|
||||
|
||||
protected static function shouldReplaceSnapshot(?array $arrCurrent, string $strMetricDate, int $intQueriedAt): bool
|
||||
{
|
||||
if (!$arrCurrent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$strCurrentDate = (string)($arrCurrent['metric_date'] ?? '');
|
||||
$intCurrentQueriedAt = (int)($arrCurrent['queried_at'] ?? 0);
|
||||
|
||||
return strcmp($strCurrentDate, $strMetricDate) < 0
|
||||
|| ($strCurrentDate === $strMetricDate && $intCurrentQueriedAt < $intQueriedAt);
|
||||
}
|
||||
|
||||
protected static function normalizeIndexState(array $arrRow): string
|
||||
{
|
||||
$strIndexedStatus = trim((string)($arrRow['indexed_status'] ?? ''));
|
||||
|
||||
@@ -27,14 +27,6 @@ class JsBuilder
|
||||
@mkdir($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
// 输出文件名(域名专属缓存)
|
||||
$targetFile = $targetDir . "{$staticHash}.js";
|
||||
|
||||
// 如果已经生成过,直接返回
|
||||
if (file_exists($targetFile)) {
|
||||
return "/static/js/compiled/{$staticHash}.js";
|
||||
}
|
||||
|
||||
// 你可以像 CSS 一样在这里挂一个“全站基础 JS”
|
||||
// 注意:如果没有这个文件,就别写入,否则会被 skip
|
||||
$listJs = [
|
||||
@@ -46,9 +38,46 @@ class JsBuilder
|
||||
$finalJsFiles = array_merge($listJs, $jsFiles);
|
||||
$finalJsFiles = array_values(array_unique(array_filter($finalJsFiles)));
|
||||
|
||||
$signatureParts = [
|
||||
'static_hash' => $staticHash,
|
||||
'dom_prefix' => $domPrefix,
|
||||
'builder' => is_file(__FILE__) ? sha1_file(__FILE__) : (string)@filemtime(__FILE__),
|
||||
'files' => [],
|
||||
];
|
||||
|
||||
// 如果 compiled 已存在且比所有源文件都新,直接返回;否则自动重建。
|
||||
$intLatestSourceTime = @filemtime(__FILE__) ?: 0;
|
||||
foreach ($finalJsFiles as $file) {
|
||||
$file = ltrim((string)$file, '/');
|
||||
$path = $baseDir . $file;
|
||||
if (is_file($path)) {
|
||||
$intLatestSourceTime = max($intLatestSourceTime, (int)@filemtime($path));
|
||||
$signatureParts['files'][] = [
|
||||
'file' => $file,
|
||||
'mtime' => (int)@filemtime($path),
|
||||
'size' => (int)@filesize($path),
|
||||
'sha1' => sha1_file($path),
|
||||
];
|
||||
} else {
|
||||
$signatureParts['files'][] = [
|
||||
'file' => $file,
|
||||
'missing' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 前端代理的静态缓存按 path 命中,query version 无法兜底;文件名必须跟随源码变化。
|
||||
$sourceHash = substr(sha1((string)json_encode($signatureParts, JSON_UNESCAPED_SLASHES)), 0, 10);
|
||||
$targetName = "{$staticHash}-{$sourceHash}.js";
|
||||
$targetFile = $targetDir . $targetName;
|
||||
|
||||
if (file_exists($targetFile) && (int)@filemtime($targetFile) >= $intLatestSourceTime) {
|
||||
return "/static/js/compiled/{$targetName}";
|
||||
}
|
||||
|
||||
// 合并 JS 文件内容
|
||||
$allJs = "";
|
||||
$allJs .= "/*! compiled: {$staticHash}.js */\n";
|
||||
$allJs .= "/*! compiled: {$targetName} */\n";
|
||||
$allJs .= "(function(){\n'use strict';\n";
|
||||
|
||||
foreach ($finalJsFiles as $file) {
|
||||
@@ -82,6 +111,6 @@ class JsBuilder
|
||||
file_put_contents($targetFile, $allJs);
|
||||
|
||||
// 返回前端可访问路径
|
||||
return "/static/js/compiled/{$staticHash}.js";
|
||||
return "/static/js/compiled/{$targetName}";
|
||||
}
|
||||
}
|
||||
|
||||
18
code/app/model/ServerNodeModel.php
Normal file
18
code/app/model/ServerNodeModel.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\model;
|
||||
|
||||
/**
|
||||
* 前端服务器节点模型
|
||||
*
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class ServerNodeModel extends BaseModel
|
||||
{
|
||||
protected $name = 'server_node';
|
||||
protected $pk = 'sn_id';
|
||||
protected $createTime = 'created_at';
|
||||
protected $updateTime = 'updated_at';
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
use think\migration\Migrator;
|
||||
use think\migration\db\Column;
|
||||
|
||||
class CreateServerNodeTable extends Migrator
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!$this->hasTable('server_node')) {
|
||||
$this->table('server_node', ['id' => false])
|
||||
->addColumn(Column::integer('sn_id')->setUnsigned()->setIdentity(true)->setComment('节点ID'))
|
||||
->addColumn(Column::string('sn_name', 255)->setDefault('')->setComment('节点名称'))
|
||||
->addColumn(Column::string('sn_code', 100)->setNullable(true)->setComment('节点编码'))
|
||||
->addColumn(Column::string('sn_host', 255)->setDefault('')->setComment('SSH 主机/IP'))
|
||||
->addColumn(Column::smallInteger('sn_port')->setUnsigned()->setDefault(22)->setComment('SSH 端口'))
|
||||
->addColumn(Column::string('sn_username', 100)->setDefault('')->setComment('SSH 用户名'))
|
||||
->addColumn(Column::string('sn_auth_type', 20)->setDefault('password')->setComment('认证方式: password/private_key'))
|
||||
->addColumn(Column::text('sn_password')->setNullable(true)->setComment('SSH 密码'))
|
||||
->addColumn(Column::mediumText('sn_private_key')->setNullable(true)->setComment('SSH 私钥'))
|
||||
->addColumn(Column::text('sn_passphrase')->setNullable(true)->setComment('私钥口令'))
|
||||
->addColumn(Column::string('sn_region', 100)->setNullable(true)->setComment('区域/环境'))
|
||||
->addColumn(Column::string('sn_purpose', 255)->setNullable(true)->setComment('机器用途'))
|
||||
->addColumn(Column::string('sn_project_dir', 500)->setNullable(true)->setComment('项目目录'))
|
||||
->addColumn(Column::string('sn_log_dir', 500)->setNullable(true)->setComment('日志目录'))
|
||||
->addColumn(Column::string('sn_nginx_dir', 500)->setNullable(true)->setComment('Nginx 配置目录'))
|
||||
->addColumn(Column::text('sn_note')->setNullable(true)->setComment('备注'))
|
||||
->addColumn(Column::tinyInteger('sn_status')->setUnsigned()->setDefault(1)->setComment('状态:1启用,0禁用'))
|
||||
->addTimestamps('created_at', 'updated_at')
|
||||
->addIndex(['sn_id'], ['type' => 'primary'])
|
||||
->addIndex(['sn_code'], ['unique' => true])
|
||||
->addIndex(['sn_status'])
|
||||
->addIndex(['sn_host'])
|
||||
->setComment('前端服务器节点')
|
||||
->create();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$Table = $this->table('server_node');
|
||||
|
||||
$this->addColumnIfMissing($Table, 'sn_name', Column::string('sn_name', 255)->setDefault('')->setComment('节点名称'));
|
||||
$this->addColumnIfMissing($Table, 'sn_code', Column::string('sn_code', 100)->setNullable(true)->setComment('节点编码'));
|
||||
$this->addColumnIfMissing($Table, 'sn_host', Column::string('sn_host', 255)->setDefault('')->setComment('SSH 主机/IP'));
|
||||
$this->addColumnIfMissing($Table, 'sn_port', Column::smallInteger('sn_port')->setUnsigned()->setDefault(22)->setComment('SSH 端口'));
|
||||
$this->addColumnIfMissing($Table, 'sn_username', Column::string('sn_username', 100)->setDefault('')->setComment('SSH 用户名'));
|
||||
$this->addColumnIfMissing($Table, 'sn_auth_type', Column::string('sn_auth_type', 20)->setDefault('password')->setComment('认证方式: password/private_key'));
|
||||
$this->addColumnIfMissing($Table, 'sn_password', Column::text('sn_password')->setNullable(true)->setComment('SSH 密码'));
|
||||
$this->addColumnIfMissing($Table, 'sn_private_key', Column::mediumText('sn_private_key')->setNullable(true)->setComment('SSH 私钥'));
|
||||
$this->addColumnIfMissing($Table, 'sn_passphrase', Column::text('sn_passphrase')->setNullable(true)->setComment('私钥口令'));
|
||||
$this->addColumnIfMissing($Table, 'sn_region', Column::string('sn_region', 100)->setNullable(true)->setComment('区域/环境'));
|
||||
$this->addColumnIfMissing($Table, 'sn_purpose', Column::string('sn_purpose', 255)->setNullable(true)->setComment('机器用途'));
|
||||
$this->addColumnIfMissing($Table, 'sn_project_dir', Column::string('sn_project_dir', 500)->setNullable(true)->setComment('项目目录'));
|
||||
$this->addColumnIfMissing($Table, 'sn_log_dir', Column::string('sn_log_dir', 500)->setNullable(true)->setComment('日志目录'));
|
||||
$this->addColumnIfMissing($Table, 'sn_nginx_dir', Column::string('sn_nginx_dir', 500)->setNullable(true)->setComment('Nginx 配置目录'));
|
||||
$this->addColumnIfMissing($Table, 'sn_note', Column::text('sn_note')->setNullable(true)->setComment('备注'));
|
||||
$this->addColumnIfMissing($Table, 'sn_status', Column::tinyInteger('sn_status')->setUnsigned()->setDefault(1)->setComment('状态:1启用,0禁用'));
|
||||
$this->addColumnIfMissing($Table, 'created_at', Column::timestamp('created_at')->setDefault('CURRENT_TIMESTAMP'));
|
||||
$this->addColumnIfMissing($Table, 'updated_at', Column::timestamp('updated_at')->setDefault('CURRENT_TIMESTAMP')->setUpdate('CURRENT_TIMESTAMP'));
|
||||
|
||||
$this->addIndexIfMissing($Table, ['sn_code'], ['unique' => true]);
|
||||
$this->addIndexIfMissing($Table, ['sn_status']);
|
||||
$this->addIndexIfMissing($Table, ['sn_host']);
|
||||
|
||||
$Table->update();
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if ($this->hasTable('server_node')) {
|
||||
$this->table('server_node')->drop()->save();
|
||||
}
|
||||
}
|
||||
|
||||
protected function addColumnIfMissing($table, string $columnName, Column $column): void
|
||||
{
|
||||
if (!$table->hasColumn($columnName)) {
|
||||
$table->addColumn($column);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addIndexIfMissing($table, array $columns, array $options = []): void
|
||||
{
|
||||
if (!$table->hasIndex($columns)) {
|
||||
$table->addIndex($columns, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,40 @@ CREATE TABLE `plan_task` (
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `server_node`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `server_node`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `server_node` (
|
||||
`sn_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`sn_name` varchar(255) NOT NULL DEFAULT '' COMMENT '节点名称',
|
||||
`sn_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
|
||||
`sn_host` varchar(255) NOT NULL DEFAULT '' COMMENT 'SSH 主机/IP',
|
||||
`sn_port` smallint unsigned NOT NULL DEFAULT '22' COMMENT 'SSH 端口',
|
||||
`sn_username` varchar(100) NOT NULL DEFAULT '' COMMENT 'SSH 用户名',
|
||||
`sn_auth_type` varchar(20) NOT NULL DEFAULT 'password' COMMENT '认证方式: password/private_key',
|
||||
`sn_password` text COMMENT 'SSH 密码',
|
||||
`sn_private_key` mediumtext COMMENT 'SSH 私钥',
|
||||
`sn_passphrase` text COMMENT '私钥口令',
|
||||
`sn_region` varchar(100) DEFAULT NULL COMMENT '区域/环境',
|
||||
`sn_purpose` varchar(255) DEFAULT NULL COMMENT '机器用途',
|
||||
`sn_project_dir` varchar(500) DEFAULT NULL COMMENT '项目目录',
|
||||
`sn_log_dir` varchar(500) DEFAULT NULL COMMENT '日志目录',
|
||||
`sn_nginx_dir` varchar(500) DEFAULT NULL COMMENT 'Nginx 配置目录',
|
||||
`sn_note` text COMMENT '备注',
|
||||
`sn_status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态:1启用,0禁用',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`sn_id`) USING BTREE,
|
||||
UNIQUE KEY `sn_code` (`sn_code`) USING BTREE,
|
||||
KEY `sn_status` (`sn_status`) USING BTREE,
|
||||
KEY `sn_host` (`sn_host`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='前端服务器节点';
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `subject_fomart`
|
||||
--
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
* -----------------------------------------------------
|
||||
* - 不使用全局变量
|
||||
* - 支持一个页面多个播放器
|
||||
* - 只初始化 data-engine="dplayer" 的模块
|
||||
* - 优先初始化 data-engine="dplayer" 的模块,兼容旧模板里的 #dplayer
|
||||
* =====================================================
|
||||
*/
|
||||
|
||||
@@ -22,8 +22,16 @@
|
||||
}
|
||||
|
||||
function resolveContainer(root) {
|
||||
if (root && root.querySelector) {
|
||||
return root.querySelector('.dplayer, [id="dplayer"]');
|
||||
if (root) {
|
||||
if (root.matches && (root.matches('.dplayer') || root.id === 'dplayer')) {
|
||||
return root;
|
||||
}
|
||||
if (root.querySelector) {
|
||||
var scoped = root.querySelector('.dplayer, [id="dplayer"]');
|
||||
if (scoped) {
|
||||
return scoped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return document.getElementById('dplayer') || document.querySelector('.dplayer');
|
||||
@@ -35,6 +43,18 @@
|
||||
showError(root, '播放器容器不存在');
|
||||
return null;
|
||||
}
|
||||
if (!strPlayUrl) {
|
||||
showError(root, '播放地址不存在');
|
||||
return null;
|
||||
}
|
||||
if (typeof DPlayer === "undefined") {
|
||||
showError(root, '播放器脚本加载失败');
|
||||
return null;
|
||||
}
|
||||
if (DomDPlayer.dataset.dplayerReady === '1') {
|
||||
return null;
|
||||
}
|
||||
DomDPlayer.dataset.dplayerReady = '1';
|
||||
|
||||
// ---- 创建 DPlayer 实例 ----
|
||||
try {
|
||||
@@ -54,7 +74,11 @@
|
||||
},
|
||||
});
|
||||
|
||||
videoPlayer.play()
|
||||
window.videoPlayer = videoPlayer;
|
||||
var playResult = videoPlayer.play()
|
||||
if (playResult && playResult.catch) {
|
||||
playResult.catch(function () {});
|
||||
}
|
||||
|
||||
// ---- 错误处理(兜底)----
|
||||
videoPlayer.on('error', function () {
|
||||
@@ -64,7 +88,8 @@
|
||||
return videoPlayer;
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.warn(e);
|
||||
DomDPlayer.dataset.dplayerReady = '0';
|
||||
showError(root, '播放器初始化失败');
|
||||
return null;
|
||||
}
|
||||
@@ -77,10 +102,11 @@
|
||||
}
|
||||
|
||||
ensurePrefix(root);
|
||||
var err = root.querySelector('.' + root.dataset.prefix + '-player-status');
|
||||
var errClass = root.dataset.prefix ? root.dataset.prefix + '-player-status' : 'player-status';
|
||||
var err = root.querySelector('.' + errClass);
|
||||
if (!err) {
|
||||
err = document.createElement('div');
|
||||
err.className = root.dataset.prefix + '-player-status is-error';
|
||||
err.className = errClass + ' is-error';
|
||||
root.appendChild(err);
|
||||
}
|
||||
err.innerText = msg;
|
||||
@@ -95,6 +121,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
function resolveRoot() {
|
||||
var root = document.querySelector('[data-engine="dplayer"]');
|
||||
if (root) {
|
||||
return root;
|
||||
}
|
||||
|
||||
var container = document.getElementById('dplayer') || document.querySelector('.dplayer');
|
||||
if (!container) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return container.closest ? (container.closest('[data-engine="dplayer"]') || container) : container;
|
||||
}
|
||||
|
||||
// 获取第一个线路 key(带容错)
|
||||
function getDefaultLine() {
|
||||
if (typeof arrPlayUrl === "undefined" || !arrPlayUrl || typeof arrPlayUrl !== "object") {
|
||||
@@ -120,23 +160,36 @@
|
||||
return arrPlayUrl[playLine][0].url || null;
|
||||
}
|
||||
|
||||
function getIndexedPlayUrl(playLine, playIndex) {
|
||||
if (!arrPlayUrl || !arrPlayUrl[playLine]) {
|
||||
console.warn("播放线路不存在:", playLine, arrPlayUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
var intIndex = Math.max(1, parseInt(playIndex, 10) || 1);
|
||||
var activeUrl = arrPlayUrl[playLine][intIndex - 1] || arrPlayUrl[playLine][0];
|
||||
return activeUrl ? (activeUrl.url || null) : null;
|
||||
}
|
||||
|
||||
// DOM Ready
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
try {
|
||||
if (typeof strVideoId == "undefined") return;
|
||||
var root = document.querySelector('[data-engine="dplayer"]');
|
||||
var root = resolveRoot();
|
||||
if (!root) return;
|
||||
|
||||
boot();
|
||||
ensurePrefix(root);
|
||||
|
||||
console.log(strPlayType)
|
||||
console.log(boolIsPlayPage)
|
||||
if (typeof strPlayType !== "undefined" && boolIsPlayPage) {
|
||||
|
||||
// 用户指定线路
|
||||
if (strPlayType !== "default") {
|
||||
const activeUrl = arrPlayUrl[strPlayType][intPlayUrlIndex - 1];
|
||||
strPlayUrl = activeUrl.url;
|
||||
strPlayUrl = getIndexedPlayUrl(strPlayType, intPlayUrlIndex);
|
||||
if (!strPlayUrl) {
|
||||
const defaultLine = getDefaultLine();
|
||||
strPlayUrl = getFirstPlayUrl(defaultLine);
|
||||
}
|
||||
// checkLine(strPlayType);
|
||||
|
||||
// 默认播放第一个线路的第一个 URL
|
||||
@@ -145,7 +198,6 @@
|
||||
strPlayUrl = getFirstPlayUrl(defaultLine);
|
||||
// checkLine(defaultLine);
|
||||
}
|
||||
console.log('strPlayUrlstrPlayUrl')
|
||||
initDPlayer(root, strPlayUrl);
|
||||
|
||||
} else if (strPlayType == "default" && !boolIsPlayPage) {
|
||||
@@ -157,9 +209,7 @@
|
||||
initDPlayer(root, strPlayUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.warn(error);
|
||||
}
|
||||
})
|
||||
})();
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ yum install ImageMagick
|
||||
- 3. 关闭服务 `php think task stop`
|
||||
- 4. 启动服务 `php think task start -d`
|
||||
|
||||
|
||||
php think migrate:run
|
||||
php think db:manage seed
|
||||
php think task restart
|
||||
|
||||
|
||||
|
||||
|
||||
166
docs/gpt-template-seo/23-GPT模板5月12蜘蛛日志与7日后续进度-2026-05-12.md
Normal file
166
docs/gpt-template-seo/23-GPT模板5月12蜘蛛日志与7日后续进度-2026-05-12.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# GPT 模板 5 月 12 日蜘蛛日志与 7 日后续进度
|
||||
|
||||
记录时间:2026-05-12 00:03 CST
|
||||
|
||||
本次只分析当前 GPT 主测试源相关数据。5 月 12 日目前只有 1 批早盘日志,样本太小,不能当作全天效果判断;今天的主结论以 2026-05-11 完整日为准,5 月 12 日只作为零点观察。
|
||||
|
||||
## 一、今天总判断
|
||||
|
||||
7 日“10 个关键词上首页”目标仍未达成。5 月 9 日完成 sitemap、百度推送、txt/json 产物的非 www 规范化后,301 浪费确实下降了,但蜘蛛量、百度收录状态和爱站出词还没有形成正反馈。
|
||||
|
||||
目前阶段不能继续盲目扩词,策略要从“冲 10 词”切到“保历史词 + 把已收录候选转成出词 + 降异常浪费”。
|
||||
|
||||
关键结论:
|
||||
|
||||
- 5 月 11 日百度 indexed_like 站点数为 5,爱站 PC 词为 0。
|
||||
- 历史反馈池里还有 7 个爱站关键词记录,但这些是 30 日历史记录,不能当作今天实际排名。
|
||||
- 5 月 9 日修正后,301 从 198 降到 146,再降到 112,说明 URL 规范化有效。
|
||||
- 同期百度蜘蛛从 432 降到 292,再降到 221,说明只是减少了浪费,还没有把抓取量重新拉起来。
|
||||
- 5 月 11 日深页抓取比 5 月 10 日恢复了一部分,重点看 `codohealth.com`、`jingxifa.com`、`lgyz.net`。
|
||||
|
||||
## 二、蜘蛛趋势
|
||||
|
||||
| 日期 | 统计批次 | 总蜘蛛 | 百度 | 字节 | 搜狗 | 首页 | 栏目 | 详情 | 播放 | robots | 200 | 301 | 403 | 444 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 2026-05-09 | 143 | 578 | 432 | 75 | 71 | 316 | 32 | 57 | 39 | 85 | 276 | 198 | 71 | 32 |
|
||||
| 2026-05-10 | 143 | 471 | 292 | 97 | 82 | 287 | 54 | 12 | 16 | 80 | 203 | 146 | 90 | 31 |
|
||||
| 2026-05-11 | 143 | 355 | 221 | 75 | 59 | 132 | 36 | 28 | 36 | 70 | 145 | 112 | 63 | 30 |
|
||||
| 2026-05-12 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 |
|
||||
|
||||
判读:
|
||||
|
||||
- URL 规范化修正有效,301 连续下降,这是正向信号。
|
||||
- 200 也从 276 降到 145,说明蜘蛛总量没有跟着恢复,当前不是“推更多页面”能解决的阶段。
|
||||
- 详情页 + 播放页从 5 月 10 日的 28 回升到 5 月 11 日的 64,深页有恢复,但还集中在少数站。
|
||||
- 5 月 12 日零点数据只有 1 条,且无百度蜘蛛,上午 9 点后需要继续观察是否恢复正常批量采集。
|
||||
|
||||
## 三、百度收录与爱站信息
|
||||
|
||||
| 日期 | 百度 indexed_like | 百度 failed | 百度 unknown | 爱站有词域名数 | 爱站 PC 词 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| 2026-05-08 | 6 | 7 | 19 | 0 | 0 |
|
||||
| 2026-05-09 | 5 | 4 | 23 | 0 | 0 |
|
||||
| 2026-05-10 | 4 | 7 | 21 | 0 | 0 |
|
||||
| 2026-05-11 | 5 | 9 | 18 | 0 | 0 |
|
||||
|
||||
5 月 11 日 indexed_like 站点:
|
||||
|
||||
- `codohealth.com`
|
||||
- `hbczccq.com`
|
||||
- `hyjssb.com`
|
||||
- `lsrxs.com`
|
||||
- `nblssy.com`
|
||||
|
||||
重点判断:
|
||||
|
||||
- `codohealth.com` 同时具备 indexed_like、百度蜘蛛 20、详情页抓取 4,是今天最值得转词的候选。
|
||||
- `glae.cc` 5 月 11 日转为 unknown,且百度蜘蛛只有 1,不能再作为短期主冲站。
|
||||
- `jxxgygy.com` 5 月 10 日 indexed_like,5 月 11 日 unknown,且蜘蛛量只有 4,需要低成本观察。
|
||||
- `zbsv3.com`、`sdxhtgcl.com`、`sdxtwnc.com` 有历史词基础,但 5 月 11 日外部快照没有形成爱站词回归。
|
||||
|
||||
## 四、域名分层
|
||||
|
||||
### P0:历史词恢复,不再盲目扩词
|
||||
|
||||
`sdxtwnc.com`
|
||||
|
||||
- 5 月 11 日百度蜘蛛 55,全部集中首页,200 为 46,301 为 9。
|
||||
- 蜘蛛非常强,但外部快照显示百度 failed,爱站 0。
|
||||
- 今天动作:保留标题和主关键词稳定性,检查首页内容、canonical、sitemap、推送 URL 是否完全一致;暂不扩词。
|
||||
|
||||
`zbsv3.com`
|
||||
|
||||
- 历史反馈池有 3 个词,是之前最有价值的老目标。
|
||||
- 5 月 11 日外部快照为 failed,且蜘蛛热度明显下降。
|
||||
- 今天动作:从主冲降为保词复核,只做 URL/收录/推送复查,不继续加新词。
|
||||
|
||||
`sdxhtgcl.com`
|
||||
|
||||
- 历史反馈池有 1 个词。
|
||||
- 5 月 11 日外部快照为 failed。
|
||||
- 今天动作:保历史词,先恢复可抓取和可收录状态。
|
||||
|
||||
### P1:收录转词候选
|
||||
|
||||
`codohealth.com`
|
||||
|
||||
- 5 月 11 日 indexed_like。
|
||||
- 百度蜘蛛 20,详情页抓取 4,200 为 10,301 为 10,403 为 4。
|
||||
- 今天动作:作为第一转词候选,只锁 1 个主词,优先降低 301/403,不扩大页面池。
|
||||
|
||||
`jingxifa.com`
|
||||
|
||||
- 5 月 11 日百度蜘蛛 24,详情页 4,播放页 20。
|
||||
- 当前外部快照仍是 unknown。
|
||||
- 今天动作:保深页抓取,重点降 301;如果 5 月 12 日转 indexed_like,再进入转词候选。
|
||||
|
||||
`lgyz.net`
|
||||
|
||||
- 5 月 11 日百度蜘蛛 18,详情页 8,播放页 8。
|
||||
- 403 为 7,浪费偏高。
|
||||
- 今天动作:观察为第二深页候选,先降 403,再考虑推词。
|
||||
|
||||
`hyjssb.com`、`nblssy.com`、`lsrxs.com`
|
||||
|
||||
- 5 月 11 日 indexed_like。
|
||||
- 先作为收录观察站,不直接占用 GPT 主冲资源,除非确认模板、内容和蜘蛛路径都适合。
|
||||
|
||||
### P2:异常修复或低成本观察
|
||||
|
||||
`glae.cc`
|
||||
|
||||
- 历史反馈池仍有 2 个词。
|
||||
- 5 月 11 日百度蜘蛛只有 1,爱站 0。
|
||||
- 今天动作:保历史词,不再作为 48 小时内的主冲站。
|
||||
|
||||
`jxxgygy.com`
|
||||
|
||||
- 5 月 10 日 indexed_like,5 月 11 日 unknown。
|
||||
- 5 月 11 日百度蜘蛛 4,全部首页 200。
|
||||
- 今天动作:低成本观察,等蜘蛛量回升后再判断。
|
||||
|
||||
`cnzhenbang.com`
|
||||
|
||||
- 5 月 11 日栏目抓取 36,但百度蜘蛛只有 2,301 为 18,403 为 20。
|
||||
- 今天动作:异常修复站,不做关键词推进。
|
||||
|
||||
`vikau.com`
|
||||
|
||||
- 5 月 11 日 robots 和 403/444 仍偏高。
|
||||
- 今天动作:异常修复站,不做关键词推进。
|
||||
|
||||
## 五、5 月 9 日代码修正验收
|
||||
|
||||
已生效的正向部分:
|
||||
|
||||
- sitemap、百度推送、txt/json 产物统一到非 www 后,301 明显下降。
|
||||
- 运行产物已按关键域名刷新,推送口径不再继续制造 `https://www.domain` 与 `https://domain` 混用。
|
||||
|
||||
仍未解决的问题:
|
||||
|
||||
- 百度蜘蛛总量没有恢复,5 月 11 日只有 221。
|
||||
- 爱站 PC 词仍为 0。
|
||||
- 部分域名仍有较高 403/444,说明抓取链路和防护/路径状态还在浪费蜘蛛。
|
||||
|
||||
结论:5 月 9 日修正是必要底座修复,但它只能减少浪费,不能单独创造排名。接下来要靠“收录候选转词”和“历史词恢复”两条线推进。
|
||||
|
||||
## 六、今天执行动作
|
||||
|
||||
1. 上午 9 点后继续看 5 月 12 日蜘蛛批次。如果百度仍然接近 0,先查采集/日志链路,再判断是否真无蜘蛛。
|
||||
2. `codohealth.com` 作为今天第一转词候选,保 1 个主词,先降详情页 301/403。
|
||||
3. `sdxtwnc.com` 作为历史词恢复候选,重点诊断“百度蜘蛛强但外部快照 failed”的原因,暂不改标题、不扩词。
|
||||
4. `jingxifa.com` 和 `lgyz.net` 保持深页抓取,优先降 301/403。
|
||||
5. `glae.cc`、`zbsv3.com`、`sdxhtgcl.com` 只做历史词恢复复核,不继续加页面池。
|
||||
6. `cnzhenbang.com`、`vikau.com` 只修异常,不计入今天关键词冲刺资源。
|
||||
|
||||
## 七、明天验收口径
|
||||
|
||||
2026-05-13 复盘时看这几个硬指标:
|
||||
|
||||
- 百度蜘蛛总量是否回到 250 以上。
|
||||
- 301 是否继续压到 100 以下。
|
||||
- 百度 indexed_like 是否稳定不低于 5。
|
||||
- 爱站 PC 词是否至少恢复到 1。
|
||||
- `codohealth.com` 或 `sdxtwnc.com` 是否出现更明确的正向信号。
|
||||
|
||||
如果明天仍然是 indexed_like 有少量、爱站 0、百度蜘蛛继续下降,就要继续缩小主冲范围,只保 2 到 3 个站,把其它站全部转为观察或异常修复。
|
||||
117
docs/gpt-template-seo/24-GPT模板5月12午盘蜘蛛日志与7日进度续报-2026-05-12.md
Normal file
117
docs/gpt-template-seo/24-GPT模板5月12午盘蜘蛛日志与7日进度续报-2026-05-12.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# GPT 模板 5 月 12 日午盘蜘蛛日志与 7 日进度续报
|
||||
|
||||
记录时间:2026-05-12 13:30 CST
|
||||
窗口范围:2026-05-06 至 2026-05-12,5 月 12 日为午盘非完整日。
|
||||
数据源:`code/storage/domain-spider-crawl/runs/YYYYMMDD/*/crawl-logs.summary.json`、`seo_external_snapshot`、`keyword_feedback` 最新反馈文件。
|
||||
口径说明:蜘蛛主表只统计 `baiduspider`、`bytespider`、`sogou`,排除 Google/Bing 的 robots 444 干扰。Baidu `site:` 线上直连请求会触发安全验证,本次以外部快照表为准。
|
||||
|
||||
## 一句话结论
|
||||
|
||||
7 日 10 词上首页目标仍未达成。5 月 12 日午盘核心蜘蛛只有 160 次,其中百度 74 次,低于 5 月 8 日至 5 月 9 日峰值;百度 `site:` 快照由 5 月 11 日的 5 个 `indexed_like` 降到 5 月 12 日的 3 个;爱站当前日 PC 词仍为 0。
|
||||
|
||||
`leici1940.com` 不是当前可放大的主线:5 月 9 日曾出现 `indexed_like`,但 5 月 10 日至 5 月 12 日回到 `unknown`;5 月 12 日午盘无百度蜘蛛命中,且 7 日内没有 detail/play 蜘蛛深抓。播放器 JS 报错已定位到前端容器空指针与前端缓存问题,仍需要清前置缓存,但 SEO 主因更偏向抓取深度和 canonical/301 消耗。
|
||||
|
||||
## 7 日核心蜘蛛进度
|
||||
|
||||
| 日期 | 批次 | 核心蜘蛛 | 百度 | 头条 | 搜狗 | 首页 | 详情+播放 | robots | 200 | 301 | 403 | 444 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 2026-05-06 | 141 | 263 | 147 | 79 | 37 | 84 | 67 | 64 | 102 | 77 | 57 | 27 |
|
||||
| 2026-05-07 | 144 | 285 | 180 | 62 | 43 | 118 | 60 | 58 | 135 | 70 | 49 | 30 |
|
||||
| 2026-05-08 | 144 | 626 | 470 | 80 | 76 | 406 | 62 | 85 | 393 | 128 | 78 | 27 |
|
||||
| 2026-05-09 | 143 | 578 | 432 | 75 | 71 | 316 | 96 | 85 | 276 | 198 | 71 | 32 |
|
||||
| 2026-05-10 | 143 | 471 | 292 | 97 | 82 | 287 | 28 | 80 | 203 | 146 | 90 | 31 |
|
||||
| 2026-05-11 | 143 | 355 | 221 | 75 | 59 | 132 | 64 | 70 | 145 | 112 | 63 | 30 |
|
||||
| 2026-05-12 | 80 | 160 | 74 | 48 | 38 | 63 | 24 | 44 | 49 | 51 | 40 | 20 |
|
||||
|
||||
判断:
|
||||
|
||||
1. 百度蜘蛛峰值出现在 5 月 8 日和 5 月 9 日,之后连续回落:470 -> 432 -> 292 -> 221 -> 74 午盘。
|
||||
2. URL 规范化后 301 在 5 月 9 日至 5 月 11 日从 198 降到 112,但 5 月 12 日午盘已到 51,按全日投影仍可能接近 90 至 100,说明 www/non-www 和历史入口还在消耗蜘蛛。
|
||||
3. 详情+播放页 5 月 11 日为 64,5 月 12 日午盘只有 24,深抓没有形成稳定放大。
|
||||
|
||||
## Baidu Site 与爱站进度
|
||||
|
||||
| 日期 | Baidu indexed_like | Baidu failed | Baidu unknown | 爱站快照站点 | 爱站 PC 词 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| 2026-05-06 | 1 | 28 | 3 | 32 | 2 |
|
||||
| 2026-05-07 | 3 | 14 | 15 | 16 | 2 |
|
||||
| 2026-05-08 | 6 | 7 | 19 | 16 | 0 |
|
||||
| 2026-05-09 | 5 | 4 | 23 | 15 | 0 |
|
||||
| 2026-05-10 | 4 | 7 | 21 | 17 | 0 |
|
||||
| 2026-05-11 | 5 | 9 | 18 | 17 | 0 |
|
||||
| 2026-05-12 | 3 | 18 | 11 | 13 | 0 |
|
||||
|
||||
5 月 12 日 `indexed_like` 域名:`cnzhenbang.com`、`gulenyuzlercocukevi.com`、`gz-yxsw.com`。
|
||||
5 月 6 日至 5 月 7 日爱站 PC 词只出现在 `glae.cc`,各 2 个;5 月 8 日以后当前日爱站 PC 词为 0。30 日反馈里仍有历史词,但不能按当前出词计算。
|
||||
|
||||
## 5 月 12 日午盘域名层表现
|
||||
|
||||
按去掉 `www.` 后的主域聚合,取核心蜘蛛前 15:
|
||||
|
||||
| 域名 | 核心蜘蛛 | 百度 | 头条 | 搜狗 | 首页 | 详情+播放 | 200 | 301 | 403 | 444 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| cnzhenbang.com | 24 | 0 | 2 | 22 | 2 | 0 | 0 | 11 | 13 | 0 |
|
||||
| lcdchq.com | 23 | 17 | 6 | 0 | 17 | 0 | 13 | 7 | 3 | 0 |
|
||||
| jingxifa.com | 14 | 12 | 0 | 2 | 2 | 12 | 6 | 7 | 1 | 0 |
|
||||
| sjzyunyang.com | 14 | 12 | 0 | 2 | 2 | 12 | 6 | 7 | 1 | 0 |
|
||||
| vikau.com | 11 | 2 | 7 | 2 | 3 | 0 | 0 | 1 | 4 | 6 |
|
||||
| hyjssb.com | 10 | 5 | 5 | 0 | 5 | 0 | 5 | 1 | 1 | 3 |
|
||||
| sdxtwnc.com | 10 | 10 | 0 | 0 | 10 | 0 | 6 | 4 | 0 | 0 |
|
||||
| nblssy.com | 7 | 1 | 6 | 0 | 4 | 0 | 1 | 3 | 3 | 0 |
|
||||
| jxxgygy.com | 6 | 0 | 0 | 6 | 4 | 0 | 0 | 3 | 3 | 0 |
|
||||
| lmjcg.com | 6 | 0 | 2 | 4 | 2 | 0 | 0 | 2 | 4 | 0 |
|
||||
| stsgf.com | 6 | 3 | 3 | 0 | 3 | 0 | 3 | 0 | 0 | 3 |
|
||||
| codohealth.com | 5 | 4 | 1 | 0 | 0 | 0 | 2 | 2 | 1 | 0 |
|
||||
| lgyz.net | 5 | 1 | 4 | 0 | 1 | 0 | 1 | 0 | 4 | 0 |
|
||||
| visitsumenep.com | 4 | 0 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 4 |
|
||||
| caosheninan.com | 3 | 3 | 0 | 0 | 3 | 0 | 2 | 1 | 0 | 0 |
|
||||
|
||||
域名判断:
|
||||
|
||||
1. `jingxifa.com` 和 `sjzyunyang.com` 是今天少数有详情+播放深抓的域名,各 12 次,优先保留在收录转词观察层。
|
||||
2. `sdxtwnc.com` 仍有百度首页抓取,但 `site:` 快照 5 月 12 日为 `failed`,需要先查为什么“有抓取但快照不稳”。
|
||||
3. `cnzhenbang.com` 虽然 5 月 12 日为 `indexed_like`,但午盘核心蜘蛛里没有百度,且 301/403 占满,不适合直接当转词主力。
|
||||
4. `vikau.com` 403/444 仍偏高,只做异常修复,不进入放大。
|
||||
5. `codohealth.com` 5 月 11 日有 `indexed_like`,但 5 月 12 日午盘深抓为 0,先观察,不加资源。
|
||||
|
||||
## leici1940.com 单站复查
|
||||
|
||||
| 日期 | 核心蜘蛛 | 百度 | 头条 | 搜狗 | 详情+播放 | 200 | 301 | 403 | 444 | Baidu site |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
||||
| 2026-05-06 | 11 | 7 | 4 | 0 | 0 | 3 | 6 | 2 | 0 | failed |
|
||||
| 2026-05-07 | 5 | 3 | 2 | 0 | 0 | 2 | 2 | 1 | 0 | failed |
|
||||
| 2026-05-08 | 23 | 17 | 6 | 0 | 0 | 11 | 9 | 3 | 0 | unknown |
|
||||
| 2026-05-09 | 8 | 4 | 4 | 0 | 0 | 2 | 4 | 2 | 0 | indexed_like |
|
||||
| 2026-05-10 | 23 | 12 | 11 | 0 | 0 | 6 | 10 | 6 | 1 | unknown |
|
||||
| 2026-05-11 | 14 | 12 | 2 | 0 | 0 | 6 | 7 | 1 | 0 | unknown |
|
||||
| 2026-05-12 | 3 | 0 | 3 | 0 | 0 | 0 | 2 | 1 | 0 | unknown |
|
||||
|
||||
结论:
|
||||
|
||||
1. `leici1940.com` 的 watch 页面播放器报错会影响用户侧体验和页面质量信号,但蜘蛛层面更大的问题是:7 日没有形成详情/播放深抓,5 月 12 日午盘也没有百度蜘蛛。
|
||||
2. 该站 301 占比偏高,且 5 月 12 日 3 次核心蜘蛛中 2 次 301、1 次 403,没有有效 200 落点。
|
||||
3. 前端 JS 文件已经采用带签名的新文件名绕开路径缓存,但线上普通入口仍可能命中前置缓存旧 HTML。需要清前置缓存后,再用同一 watch URL 复查 console 是否还报 `classList` 空指针。
|
||||
|
||||
## 下一步执行口径
|
||||
|
||||
P0:保历史词,不扩词。
|
||||
保留 `glae.cc`、`zbsv3.com`、`sdxhtgcl.com`、`sdxtwnc.com` 的历史词反馈链路,但按当前日爱站 0 词处理,不做新增关键词铺量。
|
||||
|
||||
P1:收录转词只看有深抓的域名。
|
||||
`jingxifa.com`、`sjzyunyang.com` 今天有详情+播放抓取,优先检查详情页和播放页 200 落点、标题差异、canonical、sitemap 是否一致。`codohealth.com`、`hyjssb.com`、`nblssy.com` 等先等 5 月 13 日回访确认。
|
||||
|
||||
P2:异常域名先降 301/403/444。
|
||||
`cnzhenbang.com`、`vikau.com`、`jxxgygy.com`、`lmjcg.com` 目前异常状态占比高,不作为首页词冲刺对象。先处理 www/non-www、robots、WAF/封禁和历史入口 301。
|
||||
|
||||
`leici1940.com` 专项:
|
||||
先清前置缓存并复查播放器;然后观察 5 月 13 日是否恢复百度蜘蛛、详情/播放深抓和 `site:` 稳定状态。未恢复前,不把它列入关键词放大主线。
|
||||
|
||||
## 5 月 13 日验收线
|
||||
|
||||
1. 核心蜘蛛全日大于等于 350,百度大于等于 180。
|
||||
2. 详情+播放页全日大于等于 60,且至少 2 个候选域名有稳定深抓。
|
||||
3. 301 全日控制在 100 以下,403/444 不继续扩大。
|
||||
4. Baidu `indexed_like` 回到 5 个以上。
|
||||
5. 爱站当前日 PC 词出现非 0,或者历史词域名重新被 Baidu `site:` 捕获。
|
||||
|
||||
如果以上 5 条达不到,不进入新一轮关键词扩张,只继续做收录稳定、深抓和异常状态修复。
|
||||
@@ -60,6 +60,10 @@
|
||||
作用:把 7 日验收后的下一步拆成保历史词、收录转词、降异常三条执行线,并明确 2026-05-10 验收口径。
|
||||
18. [22-GPT模板Sitemap与百度推送URL规范化修正-2026-05-09.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/22-GPT模板Sitemap与百度推送URL规范化修正-2026-05-09.md)
|
||||
作用:记录当前 GPT 主测试源 sitemap、运行生成逻辑、百度推送 URL 从 `https://www.domain` 统一到 `https://domain` 的代码修正、产物刷新和次日验收口径。
|
||||
19. [23-GPT模板5月12蜘蛛日志与7日后续进度-2026-05-12.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/23-GPT模板5月12蜘蛛日志与7日后续进度-2026-05-12.md)
|
||||
作用:复盘 2026-05-11 完整日与 5 月 12 日零点蜘蛛、百度收录、爱站信息,验收 sitemap/推送 URL 规范化后的实际效果,并重排历史词恢复、收录转词和异常修复优先级。
|
||||
20. [24-GPT模板5月12午盘蜘蛛日志与7日进度续报-2026-05-12.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/24-GPT模板5月12午盘蜘蛛日志与7日进度续报-2026-05-12.md)
|
||||
作用:按 2026-05-06 至 2026-05-12 午盘窗口复盘核心蜘蛛、Baidu site、爱站和 leici1940.com 单站状态,给出 5 月 13 日继续验收的域名分层与阈值。
|
||||
|
||||
首批 4 站的执行材料也全部放在本目录:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user