This commit is contained in:
Your Name
2026-04-19 20:50:02 +08:00
parent d469c4b93d
commit 53a1d7e4fd
12 changed files with 801 additions and 91 deletions

2
code/.gitignore vendored
View File

@@ -18,5 +18,7 @@ public/static/css/compiled/*
public/static/js/compiled/*
public/static/favicon/generated/*
/public/_seo_copy_release/*
/app/public/_admin_templates/video-metadata-missing-task-pool/*
/app/public/_admin_templates/video-metadata-missing-workbench/*
/data/seo_copy_published/*
/storage/*

View File

@@ -1057,7 +1057,7 @@ class Site extends BaseController
protected function domainExternalSeoSummaryRoot(): string
{
return $this->bootstrapCodeRoot() . '/public/_admin_templates/domain-seo-external/latest';
return $this->bootstrapCodeRoot() . '/storage/external-seo/latest';
}
protected function domainExternalSeoManualImportRoot(): string
@@ -1067,12 +1067,12 @@ class Site extends BaseController
protected function domainExternalSeoSnapshotRunRoot(): string
{
return $this->bootstrapCodeRoot() . '/public/_admin_templates/domain-seo-external/snapshot-runs';
return $this->bootstrapCodeRoot() . '/storage/external-seo/snapshot-runs';
}
protected function domainExternalSeoSnapshotValidateRunRoot(): string
{
return $this->bootstrapCodeRoot() . '/public/_admin_templates/domain-seo-external/snapshot-validate-runs';
return $this->bootstrapCodeRoot() . '/storage/external-seo/snapshot-validate-runs';
}
protected function domainExternalSeoStorageRoot(): string
@@ -1122,6 +1122,18 @@ class Site extends BaseController
return $strPath;
}
protected function storageRelativePath(string $strPath): string
{
$strStorageRoot = rtrim($this->bootstrapCodeRoot() . '/storage/', '/');
$strPath = str_replace('\\', '/', $strPath);
$strStorageRoot = str_replace('\\', '/', $strStorageRoot);
if (str_starts_with($strPath, $strStorageRoot . '/')) {
return substr($strPath, strlen($strStorageRoot . '/'));
}
return $strPath;
}
protected function bootstrapTemplateKey(?string $strTemplate): string
{
$strTemplate = strtolower(trim((string)$strTemplate));
@@ -2180,8 +2192,8 @@ class Site extends BaseController
$intLimit
);
$arrSummary = DomainExternalSeoSummaryHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
@@ -2215,8 +2227,8 @@ class Site extends BaseController
$intLimit
);
$arrSummary = DomainExternalSeoTrendHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
@@ -2334,8 +2346,8 @@ class Site extends BaseController
'mapped_hosts_count' => (int)($arrResult['mapped_hosts_count'] ?? 0),
'provider_summary' => $arrProviderSummary,
'real_summary_status' => (string)($arrRealSummary['status'] ?? ''),
'summary_json_path' => $this->publicRelativePath((string)($arrRealSummary['summary_json_path'] ?? '')),
'summary_html_path' => $this->publicRelativePath((string)($arrRealSummary['summary_html_path'] ?? '')),
'summary_json_path' => $this->storageRelativePath((string)($arrRealSummary['summary_json_path'] ?? '')),
'summary_html_path' => $this->storageRelativePath((string)($arrRealSummary['summary_html_path'] ?? '')),
]);
} catch (\Throwable $Throwable) {
return $this->error('9999', '上传 Search Console property map 失败:' . $Throwable->getMessage());
@@ -2348,8 +2360,8 @@ class Site extends BaseController
$intDays = max(1, min(30, (int)$Request->get('days', 7)));
$arrSummary = DomainExternalSeoSearchConsoleProviderPlanHelper::buildSummary($this->bootstrapCodeRoot(), $intDays);
$arrSummary = DomainExternalSeoSearchConsoleProviderPlanHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取 Search Console provider 计划失败:' . $Throwable->getMessage());
@@ -2362,8 +2374,8 @@ class Site extends BaseController
$intDays = max(1, min(30, (int)$Request->get('days', 7)));
$arrSummary = DomainExternalSeoSearchConsoleFetchHelper::buildSummary($this->bootstrapCodeRoot(), $intDays);
$arrSummary = DomainExternalSeoSearchConsoleFetchHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取 Search Console fetch 摘要失败:' . $Throwable->getMessage());
@@ -2375,8 +2387,8 @@ class Site extends BaseController
try {
$arrSummary = DomainExternalSeoBaiduProviderPlanHelper::buildSummary($this->bootstrapCodeRoot());
$arrSummary = DomainExternalSeoBaiduProviderPlanHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度 provider 计划失败:' . $Throwable->getMessage());
@@ -2389,8 +2401,8 @@ class Site extends BaseController
$intLimit = max(1, min(200, (int)$Request->get('limit', 50)));
$arrSummary = DomainExternalSeoBaiduPushSummaryHelper::buildSummary($this->bootstrapCodeRoot(), $intLimit);
$arrSummary = DomainExternalSeoBaiduPushSummaryHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度推送观察摘要失败:' . $Throwable->getMessage());
@@ -2439,8 +2451,8 @@ class Site extends BaseController
$intLimit = max(1, min(300, (int)$Request->get('limit', 100)));
$arrQueue = DomainExternalSeoBaiduPushAttentionQueueHelper::buildQueue($this->bootstrapCodeRoot(), $intLimit);
$arrQueue = DomainExternalSeoBaiduPushAttentionQueueHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrQueue);
$arrQueue['summary_json_path'] = $this->publicRelativePath((string)($arrQueue['summary_json_path'] ?? ''));
$arrQueue['summary_html_path'] = $this->publicRelativePath((string)($arrQueue['summary_html_path'] ?? ''));
$arrQueue['summary_json_path'] = $this->storageRelativePath((string)($arrQueue['summary_json_path'] ?? ''));
$arrQueue['summary_html_path'] = $this->storageRelativePath((string)($arrQueue['summary_html_path'] ?? ''));
return $this->success($arrQueue);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度推送异常队列失败:' . $Throwable->getMessage());
@@ -2453,8 +2465,8 @@ class Site extends BaseController
$intLimit = max(1, min(100, (int)$Request->get('limit', 20)));
$arrSummary = DomainExternalSeoBaiduPushTrendHelper::buildSummary($this->bootstrapCodeRoot(), $intLimit);
$arrSummary = DomainExternalSeoBaiduPushTrendHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度推送趋势摘要失败:' . $Throwable->getMessage());
@@ -2467,8 +2479,8 @@ class Site extends BaseController
$intLimit = max(1, min(100, (int)$Request->get('limit', 20)));
$arrSummary = DomainExternalSeoBaiduFeedbackSummaryHelper::buildSummary($this->bootstrapCodeRoot(), $intLimit);
$arrSummary = DomainExternalSeoBaiduFeedbackSummaryHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度反馈摘要失败:' . $Throwable->getMessage());
@@ -2481,8 +2493,8 @@ class Site extends BaseController
$intLimit = max(1, min(100, (int)$Request->get('limit', 20)));
$arrSummary = DomainExternalSeoSiteQueryProbeHelper::buildSummary($this->bootstrapCodeRoot(), $intLimit);
$arrSummary = DomainExternalSeoSiteQueryProbeHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取百度 site: 收录探测失败:' . $Throwable->getMessage());
@@ -2509,8 +2521,8 @@ class Site extends BaseController
$intLimit = max(1, min(300, (int)$Request->get('limit', 100)));
$arrQueue = DomainExternalSeoAizhanKeywordAttentionQueueHelper::buildQueue($this->bootstrapCodeRoot(), $intLimit);
$arrQueue = DomainExternalSeoAizhanKeywordAttentionQueueHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrQueue);
$arrQueue['summary_json_path'] = $this->publicRelativePath((string)($arrQueue['summary_json_path'] ?? ''));
$arrQueue['summary_html_path'] = $this->publicRelativePath((string)($arrQueue['summary_html_path'] ?? ''));
$arrQueue['summary_json_path'] = $this->storageRelativePath((string)($arrQueue['summary_json_path'] ?? ''));
$arrQueue['summary_html_path'] = $this->storageRelativePath((string)($arrQueue['summary_html_path'] ?? ''));
return $this->success($arrQueue);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取爱站关键词异常队列失败:' . $Throwable->getMessage());
@@ -2523,8 +2535,8 @@ class Site extends BaseController
$intLimit = max(1, min(100, (int)$Request->get('limit', 20)));
$arrSummary = DomainExternalSeoAizhanKeywordTrendHelper::buildSummary($this->bootstrapCodeRoot(), $intLimit);
$arrSummary = DomainExternalSeoAizhanKeywordTrendHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取爱站关键词趋势摘要失败:' . $Throwable->getMessage());
@@ -2568,40 +2580,40 @@ class Site extends BaseController
$arrResult = DomainExternalSeoSnapshotHelper::ingestSnapshots((array)$arrSnapshots, $strSource);
$strRunRoot = DomainExternalSeoSnapshotRunHelper::createRunRoot($this->domainExternalSeoSnapshotRunRoot(), 'snapshot_push');
$arrResult = DomainExternalSeoSnapshotRunHelper::writeRunArtifacts(
$this->bootstrapCodeRoot() . '/public',
$this->bootstrapCodeRoot() . '/storage',
$strRunRoot,
$arrResult,
(array)$arrSnapshots
);
$arrResult['run_root'] = $this->publicRelativePath($strRunRoot);
$arrResult['summary_json_path'] = $this->publicRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->publicRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->publicRelativePath((string)($arrResult['payload_json_path'] ?? ''));
$arrResult['run_root'] = $this->storageRelativePath($strRunRoot);
$arrResult['summary_json_path'] = $this->storageRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->storageRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->storageRelativePath((string)($arrResult['payload_json_path'] ?? ''));
$arrOverview = DomainExternalSeoCnOverviewHelper::buildOverview(14, 10);
$arrOverview = DomainExternalSeoCnOverviewHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrOverview);
$arrOverview['summary_json_path'] = $this->publicRelativePath((string)($arrOverview['summary_json_path'] ?? ''));
$arrOverview['summary_html_path'] = $this->publicRelativePath((string)($arrOverview['summary_html_path'] ?? ''));
$arrOverview['summary_json_path'] = $this->storageRelativePath((string)($arrOverview['summary_json_path'] ?? ''));
$arrOverview['summary_html_path'] = $this->storageRelativePath((string)($arrOverview['summary_html_path'] ?? ''));
$arrSnapshotTrend = DomainExternalSeoSnapshotTrendHelper::buildSummary($this->domainExternalSeoSnapshotRunRoot(), 10);
$arrSnapshotTrend = DomainExternalSeoSnapshotTrendHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSnapshotTrend);
$arrSnapshotTrend['summary_json_path'] = $this->publicRelativePath((string)($arrSnapshotTrend['summary_json_path'] ?? ''));
$arrSnapshotTrend['summary_html_path'] = $this->publicRelativePath((string)($arrSnapshotTrend['summary_html_path'] ?? ''));
$arrSnapshotTrend['summary_json_path'] = $this->storageRelativePath((string)($arrSnapshotTrend['summary_json_path'] ?? ''));
$arrSnapshotTrend['summary_html_path'] = $this->storageRelativePath((string)($arrSnapshotTrend['summary_html_path'] ?? ''));
$arrResult['post_ingest_overview'] = $arrOverview;
$arrResult['post_ingest_snapshot_trend'] = $arrSnapshotTrend;
$arrResult = DomainExternalSeoSnapshotRunHelper::writeRunArtifacts(
$this->bootstrapCodeRoot() . '/public',
$this->bootstrapCodeRoot() . '/storage',
$strRunRoot,
$arrResult,
(array)$arrSnapshots
);
$arrResult['summary_json_path'] = $this->publicRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->publicRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->publicRelativePath((string)($arrResult['payload_json_path'] ?? ''));
$arrResult['summary_json_path'] = $this->storageRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->storageRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->storageRelativePath((string)($arrResult['payload_json_path'] ?? ''));
$arrSnapshotTrend = DomainExternalSeoSnapshotTrendHelper::buildSummary($this->domainExternalSeoSnapshotRunRoot(), 10);
$arrSnapshotTrend = DomainExternalSeoSnapshotTrendHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSnapshotTrend);
$arrSnapshotTrend['summary_json_path'] = $this->publicRelativePath((string)($arrSnapshotTrend['summary_json_path'] ?? ''));
$arrSnapshotTrend['summary_html_path'] = $this->publicRelativePath((string)($arrSnapshotTrend['summary_html_path'] ?? ''));
$arrSnapshotTrend['summary_json_path'] = $this->storageRelativePath((string)($arrSnapshotTrend['summary_json_path'] ?? ''));
$arrSnapshotTrend['summary_html_path'] = $this->storageRelativePath((string)($arrSnapshotTrend['summary_html_path'] ?? ''));
$arrResult['post_ingest_snapshot_trend'] = $arrSnapshotTrend;
return $this->success($arrResult);
} catch (\Throwable $Throwable) {
@@ -2651,15 +2663,15 @@ class Site extends BaseController
$arrResult = DomainExternalSeoSnapshotSchemaHelper::validateSnapshots((array)$arrSnapshots);
$strRunRoot = DomainExternalSeoSnapshotValidateRunHelper::createRunRoot($this->domainExternalSeoSnapshotValidateRunRoot(), 'snapshot_validate');
$arrResult = DomainExternalSeoSnapshotValidateRunHelper::writeRunArtifacts(
$this->bootstrapCodeRoot() . '/public',
$this->bootstrapCodeRoot() . '/storage',
$strRunRoot,
$arrResult,
(array)$arrSnapshots
);
$arrResult['run_root'] = $this->publicRelativePath($strRunRoot);
$arrResult['summary_json_path'] = $this->publicRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->publicRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->publicRelativePath((string)($arrResult['payload_json_path'] ?? ''));
$arrResult['run_root'] = $this->storageRelativePath($strRunRoot);
$arrResult['summary_json_path'] = $this->storageRelativePath((string)($arrResult['summary_json_path'] ?? ''));
$arrResult['summary_html_path'] = $this->storageRelativePath((string)($arrResult['summary_html_path'] ?? ''));
$arrResult['payload_json_path'] = $this->storageRelativePath((string)($arrResult['payload_json_path'] ?? ''));
return $this->success($arrResult);
} catch (\Throwable $Throwable) {
@@ -2944,8 +2956,8 @@ class Site extends BaseController
$intDays = max(1, min(30, (int)$Request->get('days', 7)));
$arrSummary = DomainExternalSeoRealSummaryHelper::buildSummary($this->bootstrapCodeRoot(), $intDays);
$arrSummary = DomainExternalSeoRealSummaryHelper::writeArtifacts($this->domainExternalSeoSummaryRoot(), $arrSummary);
$arrSummary['summary_json_path'] = $this->publicRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->publicRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
$arrSummary['summary_json_path'] = $this->storageRelativePath((string)($arrSummary['summary_json_path'] ?? ''));
$arrSummary['summary_html_path'] = $this->storageRelativePath((string)($arrSummary['summary_html_path'] ?? ''));
return $this->success($arrSummary);
} catch (\Throwable $Throwable) {
return $this->error('9999', '读取真实站外 summary 骨架失败:' . $Throwable->getMessage());
@@ -3021,8 +3033,8 @@ class Site extends BaseController
'real_metrics_available' => !empty($arrRealSummary['real_metrics_available']),
'real_summary_provider' => (string)($arrRealSummary['provider_key'] ?? ''),
'metrics' => (array)($arrRealSummary['metrics'] ?? []),
'summary_json_path' => $this->publicRelativePath((string)($arrRealSummary['summary_json_path'] ?? '')),
'summary_html_path' => $this->publicRelativePath((string)($arrRealSummary['summary_html_path'] ?? '')),
'summary_json_path' => $this->storageRelativePath((string)($arrRealSummary['summary_json_path'] ?? '')),
'summary_html_path' => $this->storageRelativePath((string)($arrRealSummary['summary_html_path'] ?? '')),
]);
} catch (\Throwable $Throwable) {
return $this->error('9999', '导入站外手工报表失败:' . $Throwable->getMessage());

View File

@@ -35,17 +35,17 @@ class SeoCollector extends BaseController
protected function summaryRoot(): string
{
return $this->publicRoot() . '/_admin_templates/domain-seo-external/latest';
return $this->storageRoot() . '/external-seo/latest';
}
protected function snapshotRunRoot(): string
{
return $this->publicRoot() . '/_admin_templates/domain-seo-external/snapshot-runs';
return $this->storageRoot() . '/external-seo/snapshot-runs';
}
protected function snapshotValidateRunRoot(): string
{
return $this->publicRoot() . '/_admin_templates/domain-seo-external/snapshot-validate-runs';
return $this->storageRoot() . '/external-seo/snapshot-validate-runs';
}
protected function spiderCrawlLatestRoot(): string
@@ -138,15 +138,15 @@ class SeoCollector extends BaseController
$result = DomainExternalSeoSnapshotSchemaHelper::validateSnapshots((array)$snapshots);
$runRoot = DomainExternalSeoSnapshotValidateRunHelper::createRunRoot($this->snapshotValidateRunRoot(), 'snapshot_validate');
$result = DomainExternalSeoSnapshotValidateRunHelper::writeRunArtifacts(
$this->publicRoot(),
$this->storageRoot(),
$runRoot,
$result,
(array)$snapshots
);
$result['run_root'] = $this->publicRelativePath($runRoot);
$result['summary_json_path'] = $this->publicRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->publicRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->publicRelativePath((string)($result['payload_json_path'] ?? ''));
$result['run_root'] = $this->storageRelativePath($runRoot);
$result['summary_json_path'] = $this->storageRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->storageRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->storageRelativePath((string)($result['payload_json_path'] ?? ''));
return $this->success($result);
} catch (\Throwable $throwable) {
@@ -166,37 +166,37 @@ class SeoCollector extends BaseController
$result = DomainExternalSeoSnapshotHelper::ingestSnapshots((array)$snapshots, $source);
$runRoot = DomainExternalSeoSnapshotRunHelper::createRunRoot($this->snapshotRunRoot(), 'snapshot_push');
$result = DomainExternalSeoSnapshotRunHelper::writeRunArtifacts(
$this->publicRoot(),
$this->storageRoot(),
$runRoot,
$result,
(array)$snapshots
);
$result['run_root'] = $this->publicRelativePath($runRoot);
$result['summary_json_path'] = $this->publicRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->publicRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->publicRelativePath((string)($result['payload_json_path'] ?? ''));
$result['run_root'] = $this->storageRelativePath($runRoot);
$result['summary_json_path'] = $this->storageRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->storageRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->storageRelativePath((string)($result['payload_json_path'] ?? ''));
$overview = DomainExternalSeoCnOverviewHelper::buildOverview(14, 10);
$overview = DomainExternalSeoCnOverviewHelper::writeArtifacts($this->summaryRoot(), $overview);
$overview['summary_json_path'] = $this->publicRelativePath((string)($overview['summary_json_path'] ?? ''));
$overview['summary_html_path'] = $this->publicRelativePath((string)($overview['summary_html_path'] ?? ''));
$overview['summary_json_path'] = $this->storageRelativePath((string)($overview['summary_json_path'] ?? ''));
$overview['summary_html_path'] = $this->storageRelativePath((string)($overview['summary_html_path'] ?? ''));
$snapshotTrend = DomainExternalSeoSnapshotTrendHelper::buildSummary($this->snapshotRunRoot(), 10);
$snapshotTrend = DomainExternalSeoSnapshotTrendHelper::writeArtifacts($this->summaryRoot(), $snapshotTrend);
$snapshotTrend['summary_json_path'] = $this->publicRelativePath((string)($snapshotTrend['summary_json_path'] ?? ''));
$snapshotTrend['summary_html_path'] = $this->publicRelativePath((string)($snapshotTrend['summary_html_path'] ?? ''));
$snapshotTrend['summary_json_path'] = $this->storageRelativePath((string)($snapshotTrend['summary_json_path'] ?? ''));
$snapshotTrend['summary_html_path'] = $this->storageRelativePath((string)($snapshotTrend['summary_html_path'] ?? ''));
$result['post_ingest_overview'] = $overview;
$result['post_ingest_snapshot_trend'] = $snapshotTrend;
$result = DomainExternalSeoSnapshotRunHelper::writeRunArtifacts(
$this->publicRoot(),
$this->storageRoot(),
$runRoot,
$result,
(array)$snapshots
);
$result['summary_json_path'] = $this->publicRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->publicRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->publicRelativePath((string)($result['payload_json_path'] ?? ''));
$result['summary_json_path'] = $this->storageRelativePath((string)($result['summary_json_path'] ?? ''));
$result['summary_html_path'] = $this->storageRelativePath((string)($result['summary_html_path'] ?? ''));
$result['payload_json_path'] = $this->storageRelativePath((string)($result['payload_json_path'] ?? ''));
return $this->success($result);
} catch (\Throwable $throwable) {

View File

@@ -6,12 +6,12 @@ namespace app\common\helper;
class DomainExternalSeoSnapshotRunIndexHelper
{
protected static function publicRelativePath(string $strPath): string
protected static function storageRelativePath(string $strPath): string
{
$strPublicRoot = str_replace('\\', '/', rtrim(dirname(__DIR__, 3) . '/public', '/'));
$strStorageRoot = str_replace('\\', '/', rtrim(dirname(__DIR__, 3) . '/storage', '/'));
$strPath = str_replace('\\', '/', $strPath);
if (str_starts_with($strPath, $strPublicRoot . '/')) {
return substr($strPath, strlen($strPublicRoot . '/'));
if (str_starts_with($strPath, $strStorageRoot . '/')) {
return substr($strPath, strlen($strStorageRoot . '/'));
}
return $strPath;
@@ -60,7 +60,7 @@ class DomainExternalSeoSnapshotRunIndexHelper
$arrItems[] = [
'run_id' => basename($strRunRoot),
'run_root_public' => self::publicRelativePath($strRunRoot),
'run_root_public' => self::storageRelativePath($strRunRoot),
'source' => (string)($arrSummary['source'] ?? ''),
'received_count' => $intReceivedCount,
'inserted_count' => (int)($arrSummary['inserted_count'] ?? 0),
@@ -82,9 +82,9 @@ class DomainExternalSeoSnapshotRunIndexHelper
'post_ingest_snapshot_trend' => (array)($arrSummary['post_ingest_snapshot_trend'] ?? []),
'post_ingest_health_label' => (string)(($arrSummary['post_ingest_overview']['health_label'] ?? '')),
'post_ingest_trend_label' => (string)(($arrSummary['post_ingest_snapshot_trend']['success_trend']['label'] ?? '')),
'summary_json_path' => self::publicRelativePath($strSummaryPath),
'summary_html_path' => self::publicRelativePath($strRunRoot . '/snapshot-ingest.summary.html'),
'payload_json_path' => self::publicRelativePath($strRunRoot . '/snapshots.payload.json'),
'summary_json_path' => self::storageRelativePath($strSummaryPath),
'summary_html_path' => self::storageRelativePath($strRunRoot . '/snapshot-ingest.summary.html'),
'payload_json_path' => self::storageRelativePath($strRunRoot . '/snapshots.payload.json'),
'updated_at' => date(DATE_ATOM, (int)(filemtime($strSummaryPath) ?: time())),
];
}

View File

@@ -6,12 +6,12 @@ namespace app\common\helper;
class DomainExternalSeoSnapshotValidateRunIndexHelper
{
protected static function publicRelativePath(string $strPath): string
protected static function storageRelativePath(string $strPath): string
{
$strPublicRoot = str_replace('\\', '/', rtrim(dirname(__DIR__, 3) . '/public', '/'));
$strStorageRoot = str_replace('\\', '/', rtrim(dirname(__DIR__, 3) . '/storage', '/'));
$strPath = str_replace('\\', '/', $strPath);
if (str_starts_with($strPath, $strPublicRoot . '/')) {
return substr($strPath, strlen($strPublicRoot . '/'));
if (str_starts_with($strPath, $strStorageRoot . '/')) {
return substr($strPath, strlen($strStorageRoot . '/'));
}
return $strPath;
@@ -50,7 +50,7 @@ class DomainExternalSeoSnapshotValidateRunIndexHelper
$arrItems[] = [
'run_id' => basename($strRunRoot),
'run_root_public' => self::publicRelativePath($strRunRoot),
'run_root_public' => self::storageRelativePath($strRunRoot),
'received_count' => $intReceivedCount,
'valid_count' => (int)($arrSummary['valid_count'] ?? 0),
'error_count' => $intErrorCount,
@@ -63,9 +63,9 @@ class DomainExternalSeoSnapshotValidateRunIndexHelper
'warnings' => (array)($arrSummary['warnings'] ?? []),
'error_buckets' => (array)($arrSummary['error_buckets'] ?? []),
'warning_buckets' => (array)($arrSummary['warning_buckets'] ?? []),
'summary_json_path' => self::publicRelativePath($strSummaryPath),
'summary_html_path' => self::publicRelativePath($strRunRoot . '/snapshot-validate.summary.html'),
'payload_json_path' => self::publicRelativePath($strRunRoot . '/snapshots.payload.json'),
'summary_json_path' => self::storageRelativePath($strSummaryPath),
'summary_html_path' => self::storageRelativePath($strRunRoot . '/snapshot-validate.summary.html'),
'payload_json_path' => self::storageRelativePath($strRunRoot . '/snapshots.payload.json'),
'updated_at' => date(DATE_ATOM, (int)(filemtime($strSummaryPath) ?: time())),
];
}

View File

@@ -1710,7 +1710,13 @@ class SiteStyle
{
if (!$domainRow) return null;
$raw = is_array($domainRow) ? ($domainRow['t_cfg'] ?? null) : ($domainRow->t_cfg ?? null);
return $raw ? json_decode($raw, true) : null;
if (is_array($raw)) {
return $raw;
}
if (!is_string($raw) || trim($raw) === '') {
return null;
}
return json_decode($raw, true);
}
private static function writeDbConfig($domainRow, array $cfg): void

View File

@@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
use app\common\helper\DomainImportProbeRunHelper;
use app\model\DomainModel;
use think\App;
use think\facade\Db;
function printUsage(): void
{
echo "Usage:\n";
echo " php scripts/old_tmp_domain_daily_probe.php [--templates=1001,1002,1003,1004,1005] [--run-root=/abs/path] [--format=json|text]\n\n";
echo "Example:\n";
echo " php scripts/old_tmp_domain_daily_probe.php --format=text\n";
echo " php scripts/old_tmp_domain_daily_probe.php --templates=1002,1003 --format=json\n";
}
function ensureAppInitialized(): void
{
static $initialized = false;
if ($initialized) {
return;
}
(new App())->initialize();
$initialized = true;
}
function normalizeTemplateIds(string $raw): array
{
$items = array_filter(array_map('trim', explode(',', $raw)), static fn(string $v): bool => $v !== '');
$ids = [];
foreach ($items as $item) {
$ids[] = (int)$item;
}
$ids = array_values(array_unique(array_filter($ids, static fn(int $v): bool => $v > 0)));
sort($ids);
return $ids;
}
function loadHosts(array $templateIds): array
{
ensureAppInitialized();
$rows = Db::name('domain')
->whereIn('t_id', $templateIds)
->field(['d_domain', 't_id'])
->select()
->toArray();
$hosts = [];
$templateBuckets = [];
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$host = DomainModel::normalizeHost((string)($row['d_domain'] ?? ''));
$templateId = (int)($row['t_id'] ?? 0);
if ($host === '') {
continue;
}
$hosts[] = $host;
if (!isset($templateBuckets[$templateId])) {
$templateBuckets[$templateId] = [];
}
$templateBuckets[$templateId][] = $host;
}
$hosts = array_values(array_unique($hosts));
sort($hosts);
ksort($templateBuckets);
foreach ($templateBuckets as &$bucket) {
$bucket = array_values(array_unique($bucket));
sort($bucket);
}
return [
'hosts' => $hosts,
'template_buckets' => $templateBuckets,
];
}
function renderText(array $summary): string
{
$lines = [
'status: ' . (string)($summary['status'] ?? ''),
'processed_count: ' . (int)($summary['processed_count'] ?? 0),
'passed_count: ' . (int)($summary['passed_count'] ?? 0),
'failed_count: ' . (int)($summary['failed_count'] ?? 0),
'run_root: ' . (string)($summary['run_root'] ?? ''),
'summary_json_path: ' . (string)($summary['summary_json_path'] ?? ''),
'summary_html_path: ' . (string)($summary['summary_html_path'] ?? ''),
'template_ids: ' . implode(',', (array)($summary['meta']['template_ids'] ?? [])),
'hosts_count: ' . count((array)($summary['meta']['hosts'] ?? [])),
'failed_hosts:',
];
foreach ((array)($summary['items'] ?? []) as $item) {
if (!empty($item['all_passed'])) {
continue;
}
$lines[] = '- '
. (string)($item['host'] ?? '')
. ' | status=' . (string)($item['status'] ?? '')
. ' | failed_stage=' . (string)($item['failed_stage'] ?? '')
. ' | message=' . (string)($item['message'] ?? '');
}
return implode(PHP_EOL, $lines) . PHP_EOL;
}
$args = $argv;
array_shift($args);
$templateIds = [1001, 1002, 1003, 1004, 1005];
$runRoot = dirname(__DIR__) . '/storage/domain-import-runs';
$format = 'json';
foreach ($args as $arg) {
if (str_starts_with($arg, '--templates=')) {
$parsed = normalizeTemplateIds(substr($arg, strlen('--templates=')));
if (!empty($parsed)) {
$templateIds = $parsed;
}
continue;
}
if (str_starts_with($arg, '--run-root=')) {
$runRoot = rtrim(trim(substr($arg, strlen('--run-root='))), '/');
continue;
}
if (str_starts_with($arg, '--format=')) {
$format = strtolower(trim(substr($arg, strlen('--format='))));
continue;
}
if (in_array($arg, ['-h', '--help'], true)) {
printUsage();
exit(0);
}
}
if (!in_array($format, ['json', 'text'], true)) {
fwrite(STDERR, "Invalid format: {$format}\n");
printUsage();
exit(1);
}
try {
$loaded = loadHosts($templateIds);
$hosts = (array)($loaded['hosts'] ?? []);
$templateBuckets = (array)($loaded['template_buckets'] ?? []);
if (empty($hosts)) {
$empty = [
'status' => 'empty',
'processed_count' => 0,
'passed_count' => 0,
'failed_count' => 0,
'items' => [],
'meta' => [
'template_ids' => $templateIds,
'hosts' => [],
'template_buckets' => $templateBuckets,
],
'generated_at' => date(DATE_ATOM),
];
echo $format === 'text'
? renderText($empty)
: json_encode($empty, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
exit(0);
}
$probeRunRoot = DomainImportProbeRunHelper::createRunRoot($runRoot, 'old_tmp_daily_probe');
$summary = DomainImportProbeRunHelper::run($hosts, $probeRunRoot, [
'template_ids' => $templateIds,
'template_buckets' => $templateBuckets,
'hosts' => $hosts,
'scope' => 'old_tmp_daily_probe',
]);
$summary['run_root'] = $probeRunRoot;
echo $format === 'text'
? renderText($summary)
: json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
} catch (Throwable $throwable) {
$error = [
'status' => 'script_failed',
'error_class' => get_class($throwable),
'message' => $throwable->getMessage(),
'file' => $throwable->getFile(),
'line' => $throwable->getLine(),
'trace' => $throwable->getTraceAsString(),
];
if ($format === 'text') {
echo 'status: script_failed' . PHP_EOL;
echo 'error_class: ' . $error['error_class'] . PHP_EOL;
echo 'message: ' . $error['message'] . PHP_EOL;
echo 'file: ' . $error['file'] . PHP_EOL;
echo 'line: ' . $error['line'] . PHP_EOL;
echo 'trace:' . PHP_EOL . $error['trace'] . PHP_EOL;
} else {
echo json_encode($error, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
}
exit(1);
}

View File

@@ -0,0 +1,152 @@
# 2026-04-19 domain-seo-external storage 收口说明
## 背景
`domain-seo-external` 这套后台摘要原先把最新运行产物持续写到:
- `code/public/_admin_templates/domain-seo-external/latest`
- `code/public/_admin_templates/domain-seo-external/snapshot-runs`
- `code/public/_admin_templates/domain-seo-external/snapshot-validate-runs`
这会带来两个直接问题:
1. 运行数据混进 `public`,容易污染 Git 待提交区
2. `latest/*.summary.json/html` 属于运行产物,不适合作为源码提交
---
## 本次收口目标
`domain-seo-external` 的最新摘要与快照运行产物统一切到:
- `code/storage/external-seo/latest`
- `code/storage/external-seo/snapshot-runs`
- `code/storage/external-seo/snapshot-validate-runs`
并确保管理端 / API 返回路径同步改成 `storage/...` 相对路径。
---
## 本次实际修改
### 1. 管理端 root 改到 storage
文件:
- [Site.php](/www/wwwroot/VideoSource2/code/app/admin/controller/Site.php:1058)
调整:
1. `domainExternalSeoSummaryRoot()` 改到 `storage/external-seo/latest`
2. `domainExternalSeoSnapshotRunRoot()` 改到 `storage/external-seo/snapshot-runs`
3. `domainExternalSeoSnapshotValidateRunRoot()` 改到 `storage/external-seo/snapshot-validate-runs`
4. 新增 `storageRelativePath()`,供站外 SEO 这条链专门返回 `storage` 相对路径
### 2. 管理端站外 SEO 摘要返回值改走 storage 路径
文件:
- [Site.php](/www/wwwroot/VideoSource2/code/app/admin/controller/Site.php:2192)
覆盖范围:
1. `getDomainExternalSeoSummary`
2. `getDomainExternalSeoTrendSummary`
3. `getDomainExternalSeoRealSummary`
4. Search Console / 百度推送 / 爱站关键词 / site: probe 等摘要接口
5. `saveDomainExternalSeoSnapshots`
6. `validateDomainExternalSeoSnapshots`
7. 手工导入后返回的 `real_summary_*` 路径
结果:
这些接口现在返回的是:
- `external-seo/latest/...`
- `external-seo/snapshot-runs/...`
- `external-seo/snapshot-validate-runs/...`
而不是 `public/_admin_templates/...`
### 3. API 推送入口改到 storage
文件:
- [SeoCollector.php](/www/wwwroot/VideoSource2/code/app/api/controller/SeoCollector.php:35)
调整:
1. `summaryRoot()` 改到 `storage/external-seo/latest`
2. `snapshotRunRoot()` 改到 `storage/external-seo/snapshot-runs`
3. `snapshotValidateRunRoot()` 改到 `storage/external-seo/snapshot-validate-runs`
4. `saveSnapshots()` / `validateSnapshots()` 的返回路径统一改走 `storageRelativePath()`
### 4. snapshot run 索引 helper 也改走 storage
文件:
- [DomainExternalSeoSnapshotRunIndexHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php:7)
- [DomainExternalSeoSnapshotValidateRunIndexHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php:7)
调整:
1. 原先 helper 内部只识别 `public`
2. 现在改为识别 `storage`
3. 避免列表接口继续吐绝对路径,或继续带旧 `public` 前缀
---
## 兼容处理
为了避免切换后后台立刻读不到最近一次摘要,这次已把当前:
- `code/public/_admin_templates/domain-seo-external/latest/*`
复制了一份到:
- `code/storage/external-seo/latest/*`
这样下一次后台读 `storage` 时,至少已有一份最近摘要可用。
---
## 已验证
已通过语法检查:
```bash
php -l code/app/admin/controller/Site.php
php -l code/app/api/controller/SeoCollector.php
php -l code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php
php -l code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php
```
---
## 当前仍需主线 Codex 注意
1. `code/public/_admin_templates/domain-seo-external/latest/*` 这批文件在仓库里当前仍是已跟踪文件
2. 本次收口已经阻止后续新运行继续写这条 `public` 路径
3. 但历史上已被 Git 跟踪的 `public` 运行产物,这次没有直接强删或批量移出版本库
4. 主线 Codex 提交前,建议明确把这批运行产物排除在提交之外
---
## 建议提交范围
建议优先给主线 Codex 复核这几份代码:
- [Site.php](/www/wwwroot/VideoSource2/code/app/admin/controller/Site.php:1058)
- [SeoCollector.php](/www/wwwroot/VideoSource2/code/app/api/controller/SeoCollector.php:35)
- [DomainExternalSeoSnapshotRunIndexHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php:7)
- [DomainExternalSeoSnapshotValidateRunIndexHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php:7)
这份说明文档:
- [2026-04-19-domain-seo-external-storage收口说明.md](/www/wwwroot/VideoSource2/docs/2026-04-19-domain-seo-external-storage收口说明.md:1)
---
## 一句话结论
> 本次已经把 `domain-seo-external` 的 latest / snapshot 运行产物输出主链从 `public/_admin_templates/...` 收口到 `storage/external-seo/...`,后续不应再把这类后台摘要数据当源码提交。

View File

@@ -163,6 +163,7 @@
今天最新一轮已落档:
- [40-老模板每日复盘-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/40-老模板每日复盘-2026-04-19.md)
- [44-老模板环境核对与后台报错说明-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/44-老模板环境核对与后台报错说明-2026-04-19.md)
---

View File

@@ -0,0 +1,127 @@
# 43-老模板每日例行巡检 - 2026-04-19
## 目的
从今天开始,老模板 `1001-1005` 这条线每天不是只看蜘蛛日志。
还必须多做一个固定动作:
- 把数据库里的老模板域名全部跑一遍访问巡检
这属于例行工作,不再靠人工临时记忆。
---
## 一、每日固定动作
每天至少做这 2 件事:
1.`24h spider workbench`,分析抓取趋势与 SEO 目标偏差
2. 把数据库里老模板 `1001-1005` 的域名全量跑一次访问巡检
这两件事缺一不可。
原因很简单:
1. 蜘蛛日志只能说明“百度怎么抓”
2. 但它不能替代“这些域名现在能不能正常访问”
3. 如果域名本身访问已经异常,单看 spider workbench 很容易误判
---
## 二、已固化脚本
今天已经新增了一个老模板专用脚本:
- [old_tmp_domain_daily_probe.php](/www/wwwroot/VideoSource2/code/scripts/old_tmp_domain_daily_probe.php:1)
它会做这几件事:
1. 从数据库读取老模板域名
2. 默认范围是:
- `1001`
- `1002`
- `1003`
- `1004`
- `1005`
3. 调用现有的 `DomainImportProbeRunHelper`
4. 对每个 host 做采样和轨迹探测
5. 生成 `json/html` 巡检摘要
---
## 三、默认运行方式
在项目根目录执行:
```bash
cd /www/wwwroot/VideoSource2/code
php scripts/old_tmp_domain_daily_probe.php --format=text
```
如果想看完整 JSON
```bash
cd /www/wwwroot/VideoSource2/code
php scripts/old_tmp_domain_daily_probe.php --format=json
```
如果只想巡检部分模板:
```bash
cd /www/wwwroot/VideoSource2/code
php scripts/old_tmp_domain_daily_probe.php --templates=1002,1003 --format=text
```
---
## 四、输出结果在哪里
默认会落到:
- `code/storage/domain-import-runs/YYYYMMDD/HHMMSS_old_tmp_daily_probe_xxxxxx/`
重点文件有两个:
1. `import-probe.summary.json`
2. `import-probe.summary.html`
文本模式下终端也会直接给出:
1. 总处理数
2. 通过数
3. 失败数
4. 失败 host 列表
5. 对应失败阶段和说明
---
## 五、每天回报最少要写什么
每天给主 Codex 或人工交班时,除了蜘蛛日志结论,还要补这 5 条:
1. 今天数据库老模板域名总巡检数是多少
2. 通过了多少
3. 失败了多少
4. 失败 host 主要卡在哪个阶段
5. 蜘蛛日志异常和访问异常是否落在同一批 host
---
## 六、和蜘蛛日志怎么配合看
最稳的方式是:
1. 先看 `old_tmp_domain_daily_probe.php` 结果
2. 确认是不是有整批域名访问异常
3. 再看 `24h spider workbench`
4. 最后判断:
- 是抓取问题
- 还是访问问题
- 还是两者同时存在
---
## 七、一句话交接
> 老模板这条线从 `2026-04-19` 起,日常例行工作固定为“两张表一起看”:一张是蜘蛛日志,一张是数据库域名全量访问巡检;只看其中一张,判断都会偏。

View File

@@ -0,0 +1,198 @@
# 44-老模板环境核对与后台报错说明 - 2026-04-19
## 目的
这份文档专门记录 `2026-04-19` 这次环境核对结论,解决两个后台报错的判断混乱问题:
1. `读取真实站外 summary 骨架失败`
2. `读取 SEO 结果观察面板失败`
结论先说:
> 当前核心问题不是老模板代码,也不是 `domain` 表本身,而是当前 `video2` 库缺少 `seo_external_snapshot` 表,同时相关 migration 尚未执行。
---
## 一、今天实际核对了什么
按固定顺序核对:
1. `migrate:status`
2. `domain` 表字段状态
3. `seo_external_snapshot` 是否存在
4. 当前库里的 migration 记录表情况
特别约束:
- `migrate` 只能由 `www` 用户执行
今天的 `migrate:status` 也是按这个约束执行的。
---
## 二、`migrate:status` 结果
今天已确认下面 3 条 migration 当前都是 `down`
1. `20260119132713 AddBaiduTokenToVideoTable`
2. `20260410010000 CreateSeoExternalSnapshotTable`
3. `20260410020000 OptimizeSeoExternalSnapshotIndexes`
这说明:
1. `d_baidu_token` 这类字段补充没有执行
2. `seo_external_snapshot` 建表没有执行
3. `seo_external_snapshot` 的索引优化也没有执行
---
## 三、当前库的实际状态
当前 `.env` 已确认连接的是:
- `DB_NAME = video2`
### 1. `domain` 表
今天已确认:
- `domain` 表存在
- 基础字段如:
- `d_domain`
- `t_id`
存在
但今天没有查到:
- `d_baidu_token`
### 2. `seo_external_snapshot` 表
今天已确认:
- `seo_external_snapshot` 表不存在
这和后台报错完全一致。
---
## 四、为什么“真实站外 summary 骨架失败”
触发链路是:
1. 后台接口:
- [Site.php getDomainExternalSeoRealSummary](/www/wwwroot/VideoSource2/code/app/admin/controller/Site.php:2941)
2. 调用:
- [DomainExternalSeoRealSummaryHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoRealSummaryHelper.php:7)
3. 它一上来先走:
- `buildAutoSnapshotSummary($days)`
4. 这一步内部又调用:
- [DomainExternalSeoSnapshotAnalysisHelper.php](/www/wwwroot/VideoSource2/code/app/common/helper/DomainExternalSeoSnapshotAnalysisHelper.php:7)
5. 该 helper 底层模型:
- [SeoExternalSnapshotModel.php](/www/wwwroot/VideoSource2/code/app/model/SeoExternalSnapshotModel.php:9)
6. 绑定表名:
- `seo_external_snapshot`
所以这个报错的根因不是:
- 骨架模板坏了
- provider skeleton 坏了
而是:
- 在进入 skeleton fallback 之前,自动快照分支先查表
- 当前库没有 `seo_external_snapshot`
- 所以直接 SQL 异常
---
## 五、为什么“SEO 结果观察面板失败”
这次也已核对过后台“SEO 结果观察面板”不是单独一块静态 UI而是会去读“站外 SEO 观察数据”。
最像当前入口的是:
- [router.php](/www/wwwroot/VideoSource2/code/app/admin/config/router.php:161)
- `/domain/external/seo/cn/overview`
- [Site.php](/www/wwwroot/VideoSource2/code/app/admin/controller/Site.php:2580)
- `DomainExternalSeoCnOverviewHelper::buildOverview(...)`
而这个 overview 底层直接依赖:
1. `DomainExternalSeoSnapshotAnalysisHelper::buildSummary(...)`
2. `DomainExternalSeoSnapshotAnalysisHelper::buildIndexMovementSummary(...)`
3. `DomainExternalSeoSnapshotAnalysisHelper::buildKeywordMovementSummary(...)`
这些 helper 的底层数据源仍然是:
- `seo_external_snapshot`
所以这条报错本质上和“真实站外 summary 骨架失败”是同一类问题:
- 观察面板不是单独坏了
- 而是它背后的历史快照表没建起来
---
## 六、当前不要误判的点
### 1. 不要把主矛盾误判成 `domain` 表
当前 `domain` 表的 migration 确实没补齐,`d_baidu_token` 也没进库。
但今天这两个后台报错的第一矛盾不是它。
当前更直接的主矛盾是:
- `seo_external_snapshot` 缺表
### 2. 不要把问题误判成老模板 `1001-1005`
这两个报错属于:
- 后台站外 SEO 观察能力
- migration / 表结构层
而不是:
- 老模板前台 SEO 模板渲染失败
### 3. 不要直接在非 `www` 用户下跑 migrate
今天已经再次确认这条规则要固定下来:
- `migrate` 只能由 `www` 用户执行
---
## 七、当前最稳结论
截至 `2026-04-19` 当前环境,最稳结论是:
1. 当前数据库是 `video2`
2. `domain` 表存在,但相关新增字段未补齐
3. `seo_external_snapshot` 表不存在
4. 对应 migration 当前是 `down`
5. 后台“真实站外 summary / SEO 结果观察面板”这两类报错,根因都优先指向:
- `seo_external_snapshot` 缺失
---
## 八、后续最稳动作顺序
后续如果要继续推进,不要跳步,按下面顺序:
1.`migrate:status`
2. 再核对 `domain` 表字段
3. 再核对 `seo_external_snapshot` 是否缺失
4. 最后再决定是否执行 migration
执行约束:
- 只能由 `www` 用户执行
---
## 九、一句话交接
> `2026-04-19` 这次后台报错核对已经确认:当前环境主矛盾不是老模板也不是 `domain` 表本身,而是 `video2` 库缺少 `seo_external_snapshot`,且相关 migration 仍是 `down`;后续如果要处理,必须按“先 status、再核字段和缺表、最后由 www 执行”的顺序推进。

View File

@@ -28,6 +28,8 @@
- [40-老模板每日复盘-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/40-老模板每日复盘-2026-04-19.md)
- [41-老模板SEO代码优化清单-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/41-老模板SEO代码优化清单-2026-04-19.md)
- [42-老模板SEO代码优化实录-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/42-老模板SEO代码优化实录-2026-04-19.md)
- [43-老模板每日例行巡检-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/43-老模板每日例行巡检-2026-04-19.md)
- [44-老模板环境核对与后台报错说明-2026-04-19.md](/www/wwwroot/VideoSource2/docs/old-tmp-seo/44-老模板环境核对与后台报错说明-2026-04-19.md)
昨天的复盘在这里: