diff --git a/code/.gitignore b/code/.gitignore index 2d80360..1578b60 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -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/* diff --git a/code/app/admin/controller/Site.php b/code/app/admin/controller/Site.php index 6db3459..58a453e 100644 --- a/code/app/admin/controller/Site.php +++ b/code/app/admin/controller/Site.php @@ -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()); diff --git a/code/app/api/controller/SeoCollector.php b/code/app/api/controller/SeoCollector.php index 7d3c875..741d805 100644 --- a/code/app/api/controller/SeoCollector.php +++ b/code/app/api/controller/SeoCollector.php @@ -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) { diff --git a/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php b/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php index 67ed8c7..c011036 100644 --- a/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php +++ b/code/app/common/helper/DomainExternalSeoSnapshotRunIndexHelper.php @@ -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())), ]; } diff --git a/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php b/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php index 628ff16..f87c629 100644 --- a/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php +++ b/code/app/common/helper/DomainExternalSeoSnapshotValidateRunIndexHelper.php @@ -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())), ]; } diff --git a/code/app/common/helper/SiteStyle.php b/code/app/common/helper/SiteStyle.php index 04df061..35f5348 100644 --- a/code/app/common/helper/SiteStyle.php +++ b/code/app/common/helper/SiteStyle.php @@ -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 diff --git a/code/scripts/old_tmp_domain_daily_probe.php b/code/scripts/old_tmp_domain_daily_probe.php new file mode 100644 index 0000000..f87bb6b --- /dev/null +++ b/code/scripts/old_tmp_domain_daily_probe.php @@ -0,0 +1,210 @@ +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); +} diff --git a/docs/2026-04-19-domain-seo-external-storage收口说明.md b/docs/2026-04-19-domain-seo-external-storage收口说明.md new file mode 100644 index 0000000..e746517 --- /dev/null +++ b/docs/2026-04-19-domain-seo-external-storage收口说明.md @@ -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/...`,后续不应再把这类后台摘要数据当源码提交。 diff --git a/docs/old-tmp-seo/38-老模板SEO交班总览-2026-04-17.md b/docs/old-tmp-seo/38-老模板SEO交班总览-2026-04-17.md index 27b646d..f3a5b02 100644 --- a/docs/old-tmp-seo/38-老模板SEO交班总览-2026-04-17.md +++ b/docs/old-tmp-seo/38-老模板SEO交班总览-2026-04-17.md @@ -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) --- diff --git a/docs/old-tmp-seo/43-老模板每日例行巡检-2026-04-19.md b/docs/old-tmp-seo/43-老模板每日例行巡检-2026-04-19.md new file mode 100644 index 0000000..aaca825 --- /dev/null +++ b/docs/old-tmp-seo/43-老模板每日例行巡检-2026-04-19.md @@ -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` 起,日常例行工作固定为“两张表一起看”:一张是蜘蛛日志,一张是数据库域名全量访问巡检;只看其中一张,判断都会偏。 diff --git a/docs/old-tmp-seo/44-老模板环境核对与后台报错说明-2026-04-19.md b/docs/old-tmp-seo/44-老模板环境核对与后台报错说明-2026-04-19.md new file mode 100644 index 0000000..d4a4941 --- /dev/null +++ b/docs/old-tmp-seo/44-老模板环境核对与后台报错说明-2026-04-19.md @@ -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 执行”的顺序推进。 diff --git a/docs/old-tmp-seo/README.md b/docs/old-tmp-seo/README.md index 66d43cb..4958408 100644 --- a/docs/old-tmp-seo/README.md +++ b/docs/old-tmp-seo/README.md @@ -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) 昨天的复盘在这里: