feat: improve gpt import templates and seo docs

This commit is contained in:
Your Name
2026-04-18 14:11:21 +08:00
parent 5cb11b4476
commit a87a4a41cc
13 changed files with 1397 additions and 145 deletions

View File

@@ -1230,9 +1230,14 @@ class Site extends BaseController
$Spreadsheet = IOFactory::load($FilePath);
$Sheet = $Spreadsheet->getActiveSheet();
$arrData = $Sheet->toArray();
$arrData = $Sheet->toArray(null, true, true, false);
array_shift($arrData);
$arrHeaders = array_map(
static function ($strHeader): string {
return DomainBatchImportTemplateHelper::normalizeHeader(trim((string)$strHeader));
},
(array)array_shift($arrData)
);
$strRunRoot = DomainImportProbeRunHelper::createRunRoot($this->domainImportRunRoot(), 'excel_import');
$strUploadedExcelPath = $strRunRoot . '/uploaded.' . $strExt;
@@ -1252,34 +1257,46 @@ class Site extends BaseController
foreach ($arrData as $arrRows) {
$intRowsTotal++;
$strStrategyProfile = $this->normalizeStrategyProfile($arrRows[19] ?? DomainModel::SEO_STRATEGY_PROFILE_STANDARD);
$strTkdMode = DomainModel::normalizeTkdMode((string)($arrRows[20] ?? ''));
$strTkdProvider = DomainModel::normalizeTkdProvider((string)($arrRows[21] ?? DomainModel::TKD_PROVIDER_LOCAL));
$arrRowAssoc = [];
foreach ($arrHeaders as $index => $strHeader) {
if ($strHeader === '') {
continue;
}
$arrRowAssoc[$strHeader] = trim((string)($arrRows[$index] ?? ''));
}
if (empty(array_filter($arrRowAssoc, static fn($value): bool => trim((string)$value) !== ''))) {
continue;
}
$strStrategyProfile = $this->normalizeStrategyProfile($arrRowAssoc['strategy_profile'] ?? DomainModel::SEO_STRATEGY_PROFILE_STANDARD);
$strTkdMode = DomainModel::normalizeTkdMode((string)($arrRowAssoc['tkd_mode'] ?? ''));
$strTkdProvider = DomainModel::normalizeTkdProvider((string)($arrRowAssoc['tkd_provider'] ?? DomainModel::TKD_PROVIDER_LOCAL));
$arrDomain = [
'd_domain' => $this->normalizeStoredDomain($arrRows[0] ?? '', $arrRows[14] ?? DomainModel::MATCH_TYPE_EXACT),
't_id' => $arrRows[1],
'd_name' => $arrRows[2],
'd_keywords' => $arrRows[3],
'd_index_title' => $arrRows[4],
'd_index_keywords' => $arrRows[5],
'd_index_description' => $arrRows[6],
'd_description' => $arrRows[7],
'd_statis' => $arrRows[8],
'd_logo_type' => (int)$arrRows[9],
'd_text_logo' => $arrRows[10],
'd_img_logo' => $arrRows[11],
'd_content_encode' => $arrRows[12],
'info_id' => $arrRows[13],
'd_match_type' => $this->normalizeMatchType($arrRows[14] ?? DomainModel::MATCH_TYPE_EXACT),
'd_parent_domain' => $this->normalizeParentDomain($arrRows[15] ?? ''),
'd_seed_scope' => $this->normalizeSeedScope($arrRows[16] ?? DomainModel::SEED_SCOPE_DOMAIN),
'd_baidu_token' => $arrRows[17] ?? null,
'd_domain' => $this->normalizeStoredDomain($arrRowAssoc['d_domain'] ?? '', $arrRowAssoc['d_match_type'] ?? DomainModel::MATCH_TYPE_EXACT),
't_id' => $arrRowAssoc['t_id'] ?? 1,
'd_name' => $arrRowAssoc['d_name'] ?? '',
'd_keywords' => $arrRowAssoc['d_keywords'] ?? '',
'd_index_title' => $arrRowAssoc['d_index_title'] ?? '',
'd_index_keywords' => $arrRowAssoc['d_index_keywords'] ?? '',
'd_index_description' => $arrRowAssoc['d_index_description'] ?? '',
'd_description' => $arrRowAssoc['d_description'] ?? '',
'd_statis' => $arrRowAssoc['d_statis'] ?? '',
'd_logo_type' => (int)($arrRowAssoc['d_logo_type'] ?? 0),
'd_text_logo' => $arrRowAssoc['d_text_logo'] ?? '',
'd_img_logo' => $arrRowAssoc['d_img_logo'] ?? '',
'd_content_encode' => $arrRowAssoc['d_content_encode'] ?? 0,
'info_id' => $arrRowAssoc['info_id'] ?? 0,
'd_match_type' => $this->normalizeMatchType($arrRowAssoc['d_match_type'] ?? DomainModel::MATCH_TYPE_EXACT),
'd_parent_domain' => $this->normalizeParentDomain($arrRowAssoc['d_parent_domain'] ?? ''),
'd_seed_scope' => $this->normalizeSeedScope($arrRowAssoc['d_seed_scope'] ?? DomainModel::SEED_SCOPE_DOMAIN),
'd_baidu_token' => $arrRowAssoc['d_baidu_token'] ?? null,
];
$arrDomain['d_seo_cfg'] = $this->applySeoStrategyProfileForDomainData($arrRows[18] ?? null, $arrDomain, $strStrategyProfile);
$arrDomain['d_seo_cfg'] = $this->applySeoStrategyProfileForDomainData($arrRowAssoc['d_seo_cfg'] ?? null, $arrDomain, $strStrategyProfile);
$arrDomain['d_seo_cfg']['tkd'] = array_merge(
(array)($arrDomain['d_seo_cfg']['tkd'] ?? []),
[
'mode' => $strTkdMode !== '' ? $strTkdMode : ((trim((string)($arrRows[4] ?? '')) !== '' || trim((string)($arrRows[5] ?? '')) !== '' || trim((string)($arrRows[6] ?? '')) !== '') ? DomainModel::TKD_MODE_FORCE_IMPORT : DomainModel::TKD_MODE_AUTO_GENERATE),
'mode' => $strTkdMode !== '' ? $strTkdMode : ((trim((string)($arrRowAssoc['d_index_title'] ?? '')) !== '' || trim((string)($arrRowAssoc['d_index_keywords'] ?? '')) !== '' || trim((string)($arrRowAssoc['d_index_description'] ?? '')) !== '') ? DomainModel::TKD_MODE_FORCE_IMPORT : DomainModel::TKD_MODE_AUTO_GENERATE),
'provider' => $strTkdProvider,
]
);
@@ -1385,11 +1402,15 @@ class Site extends BaseController
try {
$arrTemplate = DomainBatchImportTemplateHelper::writeTemplate($this->domainImportTemplateRoot());
$strRelativePath = str_replace($this->bootstrapCodeRoot() . '/public/', '', $arrTemplate['file_path']);
$arrSummary = (array)($arrTemplate['summary'] ?? []);
return $this->success([
'excel_template_path' => $strRelativePath,
'template_file_name' => $arrTemplate['file_name'],
'template_summary' => $arrTemplate['summary'],
'template_summary' => $arrSummary,
'template_display_headers' => array_values((array)($arrSummary['display_headers'] ?? [])),
'template_operator_notice' => array_values((array)($arrSummary['operator_notice'] ?? [])),
'template_filling_guidance' => (array)($arrSummary['filling_guidance'] ?? []),
]);
} catch (\Throwable $Throwable) {
return $this->error('9999', $Throwable->getMessage());
@@ -1401,11 +1422,15 @@ class Site extends BaseController
try {
$arrTemplate = DomainSupplyBatchTemplateHelper::writeTemplate($this->domainSupplyTemplateRoot());
$strRelativePath = $this->publicRelativePath((string)$arrTemplate['file_path']);
$arrSummary = (array)($arrTemplate['summary'] ?? []);
return $this->success([
'excel_template_path' => $strRelativePath,
'template_file_name' => $arrTemplate['file_name'],
'template_summary' => $arrTemplate['summary'],
'template_summary' => $arrSummary,
'template_display_headers' => array_values((array)($arrSummary['display_headers'] ?? [])),
'template_operator_notice' => array_values((array)($arrSummary['operator_notice'] ?? [])),
'template_filling_guidance' => (array)($arrSummary['filling_guidance'] ?? []),
]);
} catch (\Throwable $Throwable) {
return $this->error('9999', '生成一体化供给模板失败:' . $Throwable->getMessage());

View File

@@ -10,6 +10,84 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class DomainBatchImportTemplateHelper
{
public static function getHeaderLabels(): array
{
return [
'd_domain' => '域名',
'd_name' => '站点名称',
't_id' => '模板ID',
'd_index_title' => '首页标题',
'd_index_keywords' => '首页关键词',
'd_index_description' => '首页描述',
'strategy_profile' => 'SEO策略',
'd_keywords' => '站点关键词',
'd_description' => '站点描述',
'd_baidu_token' => '百度推送Token',
'd_match_type' => '匹配类型',
'd_parent_domain' => '父域名',
'd_seed_scope' => '种子范围',
'd_logo_type' => 'LOGO类型',
'd_text_logo' => '文字LOGO',
'd_img_logo' => '图片LOGO',
'd_statis' => '统计代码',
'd_content_encode' => '内容编码',
'info_id' => '信息ID',
'd_seo_cfg' => '高级SEO配置JSON',
'tkd_mode' => 'TKD模式',
'tkd_provider' => 'TKD来源',
];
}
public static function getHeaderAliases(): array
{
$labels = self::getHeaderLabels();
$aliases = [];
foreach ($labels as $field => $label) {
$aliases[$field] = $field;
$aliases[mb_strtolower($field)] = $field;
$aliases[$label] = $field;
$aliases[mb_strtolower($label)] = $field;
}
foreach ([
'站点域名' => 'd_domain',
'域名host' => 'd_domain',
'首页SEO标题' => 'd_index_title',
'首页SEO关键词' => 'd_index_keywords',
'首页SEO描述' => 'd_index_description',
'策略' => 'strategy_profile',
'高级配置JSON' => 'd_seo_cfg',
'内容编码类型' => 'd_content_encode',
] as $alias => $field) {
$aliases[$alias] = $field;
$aliases[mb_strtolower($alias)] = $field;
}
return $aliases;
}
public static function normalizeHeader(string $header): string
{
$normalized = trim($header);
if ($normalized === '') {
return '';
}
$aliases = self::getHeaderAliases();
return $aliases[$normalized] ?? $aliases[mb_strtolower($normalized)] ?? '';
}
public static function getDisplayHeaders(): array
{
$labels = self::getHeaderLabels();
return array_map(
static fn(string $field): string => $labels[$field] ?? $field,
self::getHeaders()
);
}
public static function writeWorkbook(
string $filePath,
string $sheetTitle,
@@ -65,25 +143,25 @@ class DomainBatchImportTemplateHelper
{
return [
'd_domain',
't_id',
'd_name',
'd_keywords',
't_id',
'd_index_title',
'd_index_keywords',
'd_index_description',
'strategy_profile',
'd_keywords',
'd_description',
'd_baidu_token',
'd_match_type',
'd_parent_domain',
'd_seed_scope',
'd_statis',
'd_logo_type',
'd_text_logo',
'd_img_logo',
'd_content_encode',
'info_id',
'd_match_type',
'd_parent_domain',
'd_seed_scope',
'd_baidu_token',
'd_seo_cfg',
'strategy_profile',
'tkd_mode',
'tkd_provider',
];
@@ -92,28 +170,28 @@ class DomainBatchImportTemplateHelper
public static function getColumnNotes(): array
{
return [
'd_domain' => '必填站点域名wildcard 模式可填 *.example.com 或 example.com,由后端归一化。',
't_id' => '必填,模板 ID。',
'd_name' => '必填,站点名称。',
'd_keywords' => '可选,站点关键词。',
'd_index_title' => '可选,首页标题。',
'd_index_keywords' => '可选,首页关键词。',
'd_index_description' => '可选,首页描述。',
'd_description' => '可选,站点描述。',
'd_statis' => '可选,统计代码。',
'd_logo_type' => '必填0 或 1。',
'd_text_logo' => '可选,文字 LOGO。',
'd_img_logo' => '可选,图片 LOGO。',
'd_content_encode' => '必填,通常填 0。',
'info_id' => '可选,通常填 0。',
'd_match_type' => '可选exact 或 wildcard。',
'd_parent_domain' => 'wildcard 模式建议填写根域名。',
'd_seed_scope' => '可选domain 或 host。',
'd_baidu_token' => '可选,百度推送 token。',
'd_seo_cfg' => '可选JSON会在 strategy_profile 默认值基础上再做覆盖。',
'strategy_profile' => '可选standard / traffic / expand / closed。',
'tkd_mode' => '可选1/auto_generate=全自动生成2/ai_optimize=导入 TKD 后本地/AI 优化3/force_import=强制使用导入值。',
'tkd_provider' => '可选local 或 openai当前默认 local本地优化已生效后续可接 OpenAI API。',
'd_domain' => '必填。填写站点正式域名,不要带 https:// 。示例jingxifa.com。只有做泛域名时才填 *.example.com。',
'd_name' => '必填。填写站点前台展示名称,会用于页头、页脚和基础 SEO 文案。示例:鲸溪发影视。',
't_id' => '通常直接填 1。只有明确知道要换模板时才改不确定就不要改。',
'd_index_title' => '建议填写。首页给搜索引擎看的标题,尽量自然,不要堆词。建议 28 到 40 字。',
'd_index_keywords' => '建议填写。首页核心关键词,多个词用逗号分隔。示例:短剧推荐,热播短剧,高清短剧。',
'd_index_description' => '建议填写。首页一句话简介,建议 40 到 80 字,写自然一点。',
'strategy_profile' => '通常不用改,默认 standard。只有明确知道要做流量型、扩展型站点时才改成 traffic 或 expand。',
'd_keywords' => '可选。不填也能跑;如要补充站点级关键词,可填在这里。',
'd_description' => '可选。不填时系统会按站点名称自动兜底一版简介。',
'd_baidu_token' => '可选。只有需要百度主动推送时才填写。',
'd_match_type' => '可选。默认 exact。普通单域名站保持默认即可只有泛域名场景才需要改。',
'd_parent_domain' => '只有 wildcard 泛域名场景才需要填根域名。普通单域名站留空即可。',
'd_seed_scope' => '可选。默认 domain。普通单域名站不用改只有特殊 host 级种子策略才改。',
'd_statis' => '可选。统计代码区,运营通常不用填。',
'd_logo_type' => '通常填 0。普通站点不用改如果是纯图片 LOGO 再按技术要求调整。',
'd_text_logo' => '可选。填写前台显示的文字 LOGO不填时可按站点名称兜底。',
'd_img_logo' => '可选。只有要使用图片 LOGO 时才填写。',
'd_content_encode' => '通常填 0。普通运营不用改。',
'info_id' => '通常填 0。普通运营不用改。',
'd_seo_cfg' => '可选。高级配置区,通常给技术同学使用。运营不需要填写;只有明确要覆盖默认 SEO 策略时再填 JSON。',
'tkd_mode' => '可选。一般不用填。留空时系统会根据首页三要素是否导入自动选择模式。',
'tkd_provider' => '可选。一般保持 local不需要运营修改。',
];
}
@@ -121,52 +199,52 @@ class DomainBatchImportTemplateHelper
{
return [
[
'demo-example-com',
1,
'Demo Example',
'Demo Example,影视推荐',
'Demo Example 影视内容推荐',
'Demo Example,影视推荐',
'Demo Example 内容整理与推荐。',
'Demo Example 内容整理与推荐',
'',
0,
'Demo Example',
'',
0,
0,
'exact',
'',
'domain',
'',
'',
'standard',
'1',
'local',
'd_domain' => 'demo-example-com',
'd_name' => 'Demo Example',
't_id' => 1,
'd_index_title' => 'Demo Example 影视内容推荐',
'd_index_keywords' => 'Demo Example,影视推荐',
'd_index_description' => 'Demo Example 内容整理与推荐',
'strategy_profile' => 'standard',
'd_keywords' => 'Demo Example,影视推荐',
'd_description' => 'Demo Example 内容整理与推荐。',
'd_baidu_token' => '',
'd_match_type' => 'exact',
'd_parent_domain' => '',
'd_seed_scope' => 'domain',
'd_statis' => '',
'd_logo_type' => 0,
'd_text_logo' => 'Demo Example',
'd_img_logo' => '',
'd_content_encode' => 0,
'info_id' => 0,
'd_seo_cfg' => '',
'tkd_mode' => '1',
'tkd_provider' => 'local',
],
[
'*.traffic-example.com',
1,
'Traffic Example',
'Traffic Example,热门推荐',
'Traffic Example 热门内容推荐',
'Traffic Example,热门推荐',
'Traffic Example 热门内容整理与推荐。',
'Traffic Example 热门内容整理与推荐',
'',
0,
'Traffic Example',
'',
0,
0,
'wildcard',
'traffic-example.com',
'host',
'',
'{"forge":{"route_group":"film"},"structured_data":{"organization_name":"Traffic Example Studio"}}',
'traffic',
'2',
'local',
'd_domain' => '*.traffic-example.com',
'd_name' => 'Traffic Example',
't_id' => 1,
'd_index_title' => 'Traffic Example 热门内容推荐',
'd_index_keywords' => 'Traffic Example,热门推荐',
'd_index_description' => 'Traffic Example 热门内容整理与推荐',
'strategy_profile' => 'traffic',
'd_keywords' => 'Traffic Example,热门推荐',
'd_description' => 'Traffic Example 热门内容整理与推荐。',
'd_baidu_token' => '',
'd_match_type' => 'wildcard',
'd_parent_domain' => 'traffic-example.com',
'd_seed_scope' => 'host',
'd_statis' => '',
'd_logo_type' => 0,
'd_text_logo' => 'Traffic Example',
'd_img_logo' => '',
'd_content_encode' => 0,
'info_id' => 0,
'd_seo_cfg' => '{"forge":{"route_group":"film"},"structured_data":{"organization_name":"Traffic Example Studio"}}',
'tkd_mode' => '2',
'tkd_provider' => 'local',
],
];
}
@@ -175,8 +253,45 @@ class DomainBatchImportTemplateHelper
{
return [
'headers' => self::getHeaders(),
'display_headers' => self::getDisplayHeaders(),
'header_labels' => self::getHeaderLabels(),
'column_notes' => self::getColumnNotes(),
'example_rows' => self::getExampleRows(),
'template_type' => 'site_domain_import',
'filling_guidance' => [
'main_fields' => [
'd_domain',
'd_name',
't_id',
'd_index_title',
'd_index_keywords',
'd_index_description',
'strategy_profile',
],
'optional_fields' => [
'd_keywords',
'd_description',
'd_baidu_token',
'd_match_type',
'd_parent_domain',
'd_seed_scope',
'd_statis',
'd_logo_type',
'd_text_logo',
'd_img_logo',
'd_content_encode',
'info_id',
'd_seo_cfg',
'tkd_mode',
'tkd_provider',
],
],
'operator_notice' => [
'基础导入模板只负责站点基础信息,不负责 GPT 启动样本字段。',
'运营通常只需要先填写域名、站点名称、模板ID、首页标题、首页关键词、首页描述、SEO策略。',
'其余字段大多数都有默认值或属于技术配置,可留空。',
'如果要做 GPT 供料与启动包,请改用 domain-supply-batch-template.xlsx。',
],
'supported_strategy_profiles' => ['standard', 'traffic', 'expand', 'closed'],
'supported_match_types' => ['exact', 'wildcard'],
'supported_seed_scopes' => ['domain', 'host'],
@@ -189,19 +304,28 @@ class DomainBatchImportTemplateHelper
$fileName = 'site-domain-import-template.xlsx';
$filePath = $outputRoot . '/' . $fileName;
$summary = self::buildSummary();
$headerLabels = self::getHeaderLabels();
$rows = array_map(
static function (array $row): array {
return array_combine(self::getHeaders(), $row) ?: [];
static function (array $row) use ($headerLabels): array {
$displayRow = [];
foreach (self::getHeaders() as $field) {
$displayRow[$headerLabels[$field] ?? $field] = (string)($row[$field] ?? '');
}
return $displayRow;
},
self::getExampleRows()
);
$displayNotes = [];
foreach (self::getColumnNotes() as $field => $note) {
$displayNotes[$headerLabels[$field] ?? $field] = $note;
}
self::writeWorkbook(
$filePath,
'site-domain-import',
self::getHeaders(),
self::getDisplayHeaders(),
$rows,
self::getColumnNotes()
$displayNotes
);
return [

View File

@@ -8,6 +8,92 @@ use PhpOffice\PhpSpreadsheet\IOFactory;
class DomainSupplyBatchHelper
{
protected static function isSampleField(string $field): bool
{
return in_array($field, [
'detail_id',
'detail_slug',
'search_keyword',
'category_parent',
'category_child',
], true);
}
protected static function fillAutoDiscoveredSample(array $row): array
{
$host = self::hostLabelFromRow($row);
if ($host === '') {
return [
'row' => $row,
'status' => 'skipped',
'message' => 'host_empty',
'filled_fields' => [],
];
}
$needsSample = false;
foreach ([
'detail_id',
'detail_slug',
'search_keyword',
'category_parent',
'category_child',
] as $field) {
if (trim((string)($row[$field] ?? '')) === '') {
$needsSample = true;
break;
}
}
if (!$needsSample) {
return [
'row' => $row,
'status' => 'not_needed',
'message' => 'sample_fields_already_present',
'filled_fields' => [],
];
}
$sampleResult = DomainAutoSampleHelper::discover($host, ['limit' => 80]);
if (($sampleResult['status'] ?? '') !== 'passed' || empty($sampleResult['sample']) || !is_array($sampleResult['sample'])) {
return [
'row' => $row,
'status' => 'failed',
'message' => (string)($sampleResult['message'] ?? 'auto_sample_discovery_failed'),
'filled_fields' => [],
];
}
$sample = (array)$sampleResult['sample'];
$filledFields = [];
$mapping = [
'detail_id' => (string)($sample['detail']['detail_id'] ?? ''),
'detail_slug' => (string)($sample['detail']['detail_slug'] ?? ''),
'search_keyword' => (string)($sample['search']['search_keyword'] ?? ''),
'category_parent' => (string)($sample['category']['category_parent'] ?? ''),
'category_child' => (string)($sample['category']['category_child'] ?? ''),
'play_type' => (string)($sample['play']['play_type'] ?? ''),
'play_index' => (string)($sample['play']['play_index'] ?? ''),
];
foreach ($mapping as $field => $value) {
if ($value === '') {
continue;
}
if (trim((string)($row[$field] ?? '')) !== '') {
continue;
}
$row[$field] = $value;
$filledFields[] = $field;
}
return [
'row' => $row,
'status' => empty($filledFields) ? 'failed' : 'filled',
'message' => empty($filledFields) ? 'auto_sample_discovery_returned_empty_fields' : 'auto_sample_discovered',
'filled_fields' => $filledFields,
];
}
protected static function publicRelativeToAbsolutePath(string $path): string
{
$path = str_replace('\\', '/', trim($path));
@@ -122,12 +208,28 @@ class DomainSupplyBatchHelper
throw new \RuntimeException('Replay template rows are empty: ' . $summaryPath);
}
$headerLabels = DomainSupplyBatchTemplateHelper::getHeaderLabels();
$displayRows = array_map(
static function (array $row) use ($headerLabels): array {
$displayRow = [];
foreach (DomainSupplyBatchTemplateHelper::getHeaders() as $field) {
$displayRow[$headerLabels[$field] ?? $field] = (string)($row[$field] ?? '');
}
return $displayRow;
},
$rows
);
$displayNotes = [];
foreach (DomainSupplyBatchTemplateHelper::getColumnNotes() as $field => $note) {
$displayNotes[$headerLabels[$field] ?? $field] = $note;
}
DomainBatchImportTemplateHelper::writeWorkbook(
$outputPath,
'domain-supply-batch',
DomainSupplyBatchTemplateHelper::getHeaders(),
$rows,
DomainSupplyBatchTemplateHelper::getColumnNotes()
DomainSupplyBatchTemplateHelper::getDisplayHeaders(),
$displayRows,
$displayNotes
);
return $outputPath;
@@ -164,7 +266,9 @@ class DomainSupplyBatchHelper
return [];
}
$headers = array_map(static fn ($value): string => trim((string)$value), (array)array_shift($rows));
$headers = array_map(static function ($value): string {
return DomainSupplyBatchTemplateHelper::normalizeHeader(trim((string)$value));
}, (array)array_shift($rows));
$normalized = [];
foreach ($rows as $row) {
$assoc = [];
@@ -362,9 +466,19 @@ class DomainSupplyBatchHelper
continue;
}
$autoSample = self::fillAutoDiscoveredSample($row);
$row = (array)($autoSample['row'] ?? $row);
$missing = self::validateRow($row);
$status = empty($missing) ? 'ready' : 'failed';
$message = empty($missing) ? 'prepared' : ('missing:' . implode(',', $missing));
if (($autoSample['status'] ?? '') === 'filled') {
$message = 'auto_sample_filled:' . implode(',', (array)($autoSample['filled_fields'] ?? []));
} elseif (($autoSample['status'] ?? '') === 'failed') {
$sampleMissing = array_values(array_filter($missing, static fn(string $field): bool => self::isSampleField($field)));
if (!empty($sampleMissing)) {
$message .= ';auto_sample_failed:' . (string)($autoSample['message'] ?? '');
}
}
$siteRow = self::buildSiteImportRow($row);
$bootstrapRow = self::buildBootstrapBatchRow($row);
@@ -385,6 +499,9 @@ class DomainSupplyBatchHelper
'site_domain' => (string)($siteRow['d_domain'] ?? ''),
'bundle_host' => (string)($bootstrapRow['host'] ?? ''),
'missing_fields' => $missing,
'auto_sample_status' => (string)($autoSample['status'] ?? ''),
'auto_sample_message' => (string)($autoSample['message'] ?? ''),
'auto_sample_filled_fields' => array_values((array)($autoSample['filled_fields'] ?? [])),
];
}

View File

@@ -12,61 +12,145 @@ class DomainSupplyBatchTemplateHelper
{
return [
'host',
'template_id',
'strategy_profile',
'match_type',
'parent_domain',
'seed_scope',
'site_name',
'site_keywords',
'site_description',
'template_id',
'site_index_title',
'site_index_keywords',
'site_index_description',
'baidu_token',
'seo_cfg_json',
'strategy_profile',
'detail_id',
'detail_slug',
'search_keyword',
'category_parent',
'category_child',
'site_keywords',
'site_description',
'baidu_token',
'match_type',
'parent_domain',
'seed_scope',
'play_type',
'play_index',
'forge_index',
'rank_slug',
'base_url',
'bundle_name',
'seo_cfg_json',
];
}
public static function getHeaderLabels(): array
{
return [
'host' => '域名',
'site_name' => '站点名称',
'template_id' => '模板ID',
'site_index_title' => '首页标题',
'site_index_keywords' => '首页关键词',
'site_index_description' => '首页描述',
'strategy_profile' => 'SEO策略',
'detail_id' => '详情样本ID',
'detail_slug' => '详情样本Slug',
'search_keyword' => '搜索样本关键词',
'category_parent' => '父分类Slug',
'category_child' => '子分类Slug',
'site_keywords' => '站点关键词',
'site_description' => '站点描述',
'baidu_token' => '百度推送Token',
'match_type' => '匹配类型',
'parent_domain' => '父域名',
'seed_scope' => '种子范围',
'play_type' => '播放类型',
'play_index' => '播放序号',
'forge_index' => '锻造序号',
'rank_slug' => '榜单Slug',
'base_url' => '站点基础地址',
'bundle_name' => '自定义Bundle名称',
'seo_cfg_json' => '高级SEO配置JSON',
];
}
public static function getHeaderAliases(): array
{
$labels = self::getHeaderLabels();
$aliases = [];
foreach ($labels as $field => $label) {
$aliases[$field] = $field;
$aliases[mb_strtolower($field)] = $field;
$aliases[$label] = $field;
$aliases[mb_strtolower($label)] = $field;
}
foreach ([
'站点域名' => 'host',
'域名host' => 'host',
'策略' => 'strategy_profile',
'首页SEO标题' => 'site_index_title',
'首页SEO关键词' => 'site_index_keywords',
'首页SEO描述' => 'site_index_description',
'搜索关键词' => 'search_keyword',
'父分类' => 'category_parent',
'子分类' => 'category_child',
'基础地址' => 'base_url',
'高级配置JSON' => 'seo_cfg_json',
] as $alias => $field) {
$aliases[$alias] = $field;
$aliases[mb_strtolower($alias)] = $field;
}
return $aliases;
}
public static function normalizeHeader(string $header): string
{
$normalized = trim($header);
if ($normalized === '') {
return '';
}
$aliases = self::getHeaderAliases();
return $aliases[$normalized] ?? $aliases[mb_strtolower($normalized)] ?? '';
}
public static function getDisplayHeaders(): array
{
$labels = self::getHeaderLabels();
return array_map(
static fn(string $field): string => $labels[$field] ?? $field,
self::getHeaders()
);
}
public static function getColumnNotes(): array
{
return [
'host' => '必填,域名或 wildcard 域名;会同时用于站点导入和 bundle 生成。',
'template_id' => '必填,站点模板 ID默认 1。',
'strategy_profile' => '可选standard / traffic / expand / closed默认 standard。',
'match_type' => '可选exact 或 wildcard默认 exact。',
'parent_domain' => 'wildcard 模式建议填写根域名。',
'seed_scope' => '可选domain 或 host默认 domain。',
'site_name' => '可选,站点名称。',
'site_keywords' => '可选,站点关键词。',
'site_description' => '可选,站点描述。',
'site_index_title' => '可选,首页标题;不填时导入链按站点名称推导。',
'site_index_keywords' => '可选,首页关键词;不填时导入链按站点关键词推导。',
'site_index_description' => '可选,首页描述;不填时导入链按站点描述推导。',
'baidu_token' => '可选,百度推送 token。',
'seo_cfg_json' => '可选JSON会在 strategy_profile 默认值基础上继续覆盖。',
'detail_id' => '必填,启动包详情样本视频 ID。',
'detail_slug' => '必填,启动包详情样本 slug。',
'search_keyword' => '必填,启动包搜索页样本关键词。',
'category_parent' => '必填,父分类 slug。',
'category_child' => '必填,子分类 slug。',
'play_type' => '可选默认 douban。',
'play_index' => '可选默认 1。',
'forge_index' => '可选默认 1。',
'rank_slug' => '可选默认 daily。',
'base_url' => '可选,默认 https://<host>。',
'bundle_name' => '可选,自定义 bundle 目录名。',
'host' => '必填。填写站点正式域名,不要带 https:// 。示例jingxifa.com。只有做泛域名时才填 *.example.com。',
'site_name' => '必填。填写站点前台展示名称,会用于页头、页脚和基础 SEO 文案。示例:鲸溪发影视。',
'template_id' => '通常直接填 1。只有确认要换模板时才改不确定就不要改。',
'site_index_title' => '建议填写。首页给搜索引擎看的标题,尽量自然,不要堆词。建议 28 到 40 字。',
'site_index_keywords' => '建议填写。首页核心关键词,多个词用逗号分隔。示例:短剧推荐,热播短剧,高清短剧。',
'site_index_description' => '建议填写。首页一句话简介,建议 40 到 80 字,写自然一点。',
'strategy_profile' => '通常不用改,默认 standard。只有明确知道要做流量型、扩展型站点时才改成 traffic 或 expand。',
'detail_id' => '通常不用手填。站点部署成功后,系统会优先通过自动样本发现,从当前站点可用内容里自动提取。只有域名还没部署成功、当前只是试跑库、或自动发现失败时,才人工补填真实详情视频 ID。',
'detail_slug' => '通常不用手填。会和详情样本 ID 一起自动获取。若人工填写,必须和详情样本 ID 对应,不能乱配。',
'search_keyword' => '通常不用手填。系统会优先从站点名称、视频 SEO 词、视频标题里自动挑一个可搜索关键词。只有自动发现失败时,再人工填写一个能搜出结果的词。',
'category_parent' => '通常不用手填。系统会从自动样本视频所属分类里提取一级分类 slug。只有自动发现失败时再人工补填。示例dian-ying。',
'category_child' => '通常不用手填。系统会从自动样本视频所属分类里提取二级分类 slug。只有自动发现失败时再人工补填。示例ju-qing-pian。',
'site_keywords' => '可选。不填也能跑;如要补充站点级关键词,可填在这里。',
'site_description' => '可选。不填时系统会按站点名称自动兜底一版简介。',
'baidu_token' => '可选。只有需要百度主动推送时才填写。',
'match_type' => '可选。默认 exact。普通单域名站保持默认即可只有泛域名场景才需要改。',
'parent_domain' => '只有 wildcard 泛域名场景才需要填根域名。普通单域名站留空即可。',
'seed_scope' => '可选。默认 domain。普通单域名站不用改只有特殊 host 级种子策略才改。',
'play_type' => '可选。默认 douban。自动样本发现成功时通常也能自动带出不需要运营关心。',
'play_index' => '可选默认 1。通常不用改。',
'forge_index' => '可选默认 1。通常不用改。',
'rank_slug' => '可选默认 daily。通常不用改。',
'base_url' => '可选默认自动按 https://域名 生成。只有特殊环境才手填。',
'bundle_name' => '可选。一般留空,系统会按域名自动生成 bundle 目录名。',
'seo_cfg_json' => '可选。高级配置区,通常给技术同学使用。运营不需要填写;只有明确要覆盖默认 SEO 策略时再填 JSON。',
];
}
@@ -134,8 +218,50 @@ class DomainSupplyBatchTemplateHelper
{
return [
'headers' => self::getHeaders(),
'display_headers' => self::getDisplayHeaders(),
'header_labels' => self::getHeaderLabels(),
'column_notes' => self::getColumnNotes(),
'example_rows' => self::getExampleRows(),
'template_type' => 'gpt_supply_batch',
'filling_guidance' => [
'main_fields' => [
'host',
'site_name',
'template_id',
'site_index_title',
'site_index_keywords',
'site_index_description',
'strategy_profile',
],
'auto_discovery_fields' => [
'detail_id',
'detail_slug',
'search_keyword',
'category_parent',
'category_child',
],
'optional_fields' => [
'site_keywords',
'site_description',
'baidu_token',
'match_type',
'parent_domain',
'seed_scope',
'play_type',
'play_index',
'forge_index',
'rank_slug',
'base_url',
'bundle_name',
'seo_cfg_json',
],
],
'operator_notice' => [
'运营通常只需要先填写主填写区。',
'详情样本ID、详情样本Slug、搜索样本关键词、父分类Slug、子分类Slug 通常可以留空。',
'这些样本字段会在域名部署成功或试跑库有可用内容时,优先自动发现并补全。',
'只有自动发现失败时,才需要人工补填样本字段。',
],
'supported_strategy_profiles' => [
DomainModel::SEO_STRATEGY_PROFILE_STANDARD,
DomainModel::SEO_STRATEGY_PROFILE_TRAFFIC,
@@ -165,12 +291,29 @@ class DomainSupplyBatchTemplateHelper
$fileName = 'domain-supply-batch-template.xlsx';
$filePath = $outputRoot . '/' . $fileName;
$headerLabels = self::getHeaderLabels();
$displayHeaders = self::getDisplayHeaders();
$displayRows = array_map(
static function (array $row) use ($headerLabels): array {
$displayRow = [];
foreach (self::getHeaders() as $field) {
$displayRow[$headerLabels[$field] ?? $field] = (string)($row[$field] ?? '');
}
return $displayRow;
},
self::getExampleRows()
);
$displayNotes = [];
foreach (self::getColumnNotes() as $field => $note) {
$displayNotes[$headerLabels[$field] ?? $field] = $note;
}
DomainBatchImportTemplateHelper::writeWorkbook(
$filePath,
'domain-supply-batch',
self::getHeaders(),
self::getExampleRows(),
self::getColumnNotes()
$displayHeaders,
$displayRows,
$displayNotes
);
return [

View File

@@ -299,3 +299,73 @@
2. 再补 B 组 9 站
3. 同时单独盯缓存滞后 4 站
4. 一旦出现首批已抓 / 已收录样本,立即回写主线文档作为样板
## 11. 2026-04-18 当日进度判断
### 今日总判断
今天这轮蜘蛛日志,整体符合“第一阶段起量期预期”,但还没有达到理想推进速度。
更准确地说:
1. 不是偏离预期
2. 不是已经明显爆发
3. 而是“方向正确、已有 deep page 正向信号,但 sitemap 与 search 链路还没真正跑起来”
### 今日符合预期的部分
1. 多个站已经出现真实 `detail / play` 命中
- `jingxifa.com`
- `codohealth.com`
- `lgyz.net`
- `jpjdxs.com`
- `gxhongzhuang.com`
- `leici1940.com`
- `oronorent.com`
2. 当前主问题不再是“大面积基础链路失效”
3. 旧硬 `404` 没有成为这轮主旋律
4. 说明这批 GPT 模板修复已经开始被蜘蛛侧真实吃到
### 今日未达理想预期的部分
1. `sitemap` 命中仍偏弱
2. 搜索页真实回访仍不够,很多还是测试探针
3. 仍有一批站停在首页层 / robots 层
4. 缓存滞后站还没有完全收口
### 当前阶段结论
如果按 7 天主线节奏看:
1. Day 1 到 Day 2 的目标,当前基本达标
2. Day 3 到 Day 4 想看到的 `sitemap -> detail``search -> detail`,现在只完成了一半
因此,今天的正式结论定为:
> SEO 进度总体符合“修复后开始进入正向回访期”的预期但还不符合“sitemap 与搜索链路快速放量”的理想预期。
### 今日最值得继续追的站
1. `jingxifa.com`
2. `codohealth.com`
3. `lgyz.net`
4. `jpjdxs.com`
### 今日最需要警惕的站
1. `cnzhenbang.com`
2. `lcdchq.com`
3. `oronorent.com`
4. `jxxgygy.com`
5. `liangzuan.net`
### 明日优先动作
1. 继续盯缓存滞后 4 站:
- `jingxifa.com`
- `lcdchq.com`
- `cnzhenbang.com`
- `oronorent.com`
2. 单独复核 `jxxgygy.com``/sitemap.xml => 444`
3.`cnzhenbang.com` 是否开始进入 `detail`
4.`lgyz.net``jpjdxs.com` 是否继续扩大 `detail -> play`

View File

@@ -147,3 +147,62 @@
- GPT 模板线今天没有新增结构性故障,重点仍在缓存收口和深页回访扩张。
```
## 2026-04-18 正式复盘
- 日期2026-04-18
- 复盘人Codex / 团队
- 今日处理域名A组6站 + B组9站
- 今日查看日志时间窗口2026-04-17 09:10:03 ~ 2026-04-17 22:20:02
- 今日是否执行清缓存:部分已列优先执行单,尚未全部收口
- 今日是否执行 `curl` 对照:已对部分缓存滞后站做过对照
## 今日新增日志结论
1. GPT 模板线已经出现多站 `detail / play` 正向回访,说明修复方向正确。
2. `jingxifa.com``codohealth.com``lgyz.net``jpjdxs.com` 是当前最明显的正向样本。
3. `sitemap` 命中整体仍偏弱,搜索页真实回访不足,链路还没有完全跑通。
4. `cnzhenbang.com``lcdchq.com``oronorent.com` 仍受缓存或浅层抓取影响。
5. `jxxgygy.com` 出现 `/sitemap.xml => 444`,属于需要单独复核的风险项。
## 今日缓存复查结论
### 已恢复一致
1. 暂未把任何站正式降级为“完全恢复一致”
### 仍需继续跟缓存
1. `jingxifa.com`
2. `lcdchq.com`
3. `cnzhenbang.com`
4. `oronorent.com`
### 疑似不是缓存问题
1. `jxxgygy.com` 的 sitemap 异常更像链路或规则问题
2. `liangzuan.net` 当前窗口无明显蜘蛛信号,不属于缓存结论
## 今日新增正向信号
1. `jingxifa.com` 已有稳定 `detail + play`
2. `codohealth.com` 已有 `detail + play-like`
3. `lgyz.net` 已有 `detail + play`
## 今日风险项
1. `sitemap` 整体抓取信号仍弱
2. 搜索页真实回访不足,测试探针仍在干扰判断
3. 缓存滞后 4 站尚未完成收口
## 明日优先处理
1. 继续盯 `jingxifa.com`
2. 继续盯 `lcdchq.com`
3. 继续盯 `cnzhenbang.com`
4. 继续盯 `oronorent.com`
5. 复核 `jxxgygy.com``/sitemap.xml => 444`
## 一句话复盘
- 今天的 SEO 进度总体符合“修复后进入正向回访期”的预期,但还未达到 sitemap 和搜索链路快速放量的理想状态。

View File

@@ -84,6 +84,37 @@
2. GPT 专属目录负责做节奏管理、跟踪板和阶段汇总
3. 每日复盘优先写到 [03-GPT模板每日复盘模板-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/03-GPT模板每日复盘模板-2026-04-18.md)
## 2026-04-18 当日结论
### 总判断
今天这轮 GPT 模板蜘蛛进度属于:
1. 总体正向
2. 符合第一阶段预期
3. 但尚未达到理想放量速度
### 已确认的正向样本
1. `jingxifa.com`
2. `codohealth.com`
3. `lgyz.net`
4. `jpjdxs.com`
### 当前主要卡点
1. `sitemap` 命中偏弱
2. 搜索页真实回访偏少
3. 缓存滞后站尚未完全收口
4. 部分弱站仍停在首页层 / robots 层
### 明日执行重点
1. P0 继续盯缓存滞后 4 站
2. P1 继续追 `codohealth.com``lgyz.net``jpjdxs.com`
3. 单独复核 `jxxgygy.com` 的 sitemap 风险
4. 观察 `cnzhenbang.com` 是否开始进入 detail 层
## 当前全局优先级
### P0

View File

@@ -0,0 +1,219 @@
# 09 GPT模板次日蜘蛛日志判读模板 2026-04-19
这份文档是给明天继续看 GPT 模板蜘蛛日志时直接套用的。
目标很简单:
1. 不重新发明判读口径
2. 先看最关键的站,再看其余样本站
3. 把“是否符合预期”快速落成统一结论
建议和下面几份文档一起开着看:
- [01-GPT模板7天SEO执行主线-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/01-GPT模板7天SEO执行主线-2026-04-18.md)
- [03-GPT模板每日复盘模板-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/03-GPT模板每日复盘模板-2026-04-18.md)
- [04-GPT模板A-B组日志跟踪板-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/04-GPT模板A-B组日志跟踪板-2026-04-18.md)
- [2026-04-18-7天推收录执行主线.md](/www/wwwroot/diff-maccms/SEONexus/docs/2026-04-18-7天推收录执行主线.md)
## 明日先看什么
先看 P0再看 P1最后补 P2。
### P0 缓存与深页突破优先站
1. `jingxifa.com`
2. `lcdchq.com`
3. `cnzhenbang.com`
4. `oronorent.com`
### P1 已出现正向信号、需要确认是否扩大
1. `codohealth.com`
2. `lgyz.net`
3. `jpjdxs.com`
### P2 风险或弱信号观察站
1. `liangzuan.net`
2. `jxxgygy.com`
3. `gxhongzhuang.com`
4. `leici1940.com`
5. `pcslcl.com`
6. `sdxtwnc.com`
7. `gz-yxsw.com`
8. `glae.cc`
## 明日判读顺序
### 第 1 步:先判断整体方向有没有继续向前
先回答这 3 个问题:
1. 今天有没有新增 `detail / play` 命中站点
2. 昨天已有正向信号的站,今天是否继续重复回访
3. `sitemap / search` 是否比昨天更强
如果这 3 个问题里有 2 个以上是正向,就可以先判为:
- 总体继续向前
如果只有第 1 条或第 2 条成立,但 `sitemap / search` 仍弱,就判为:
- 仍符合预期,但推进速度一般
如果 3 条都没有改善,就判为:
- 进度偏慢,需要怀疑缓存、入口链路或站点质量问题
### 第 2 步:逐站看是不是“浅层”还是“深层”
优先按这个层级判断:
1. `robots.txt` / `/`
2. `category`
3. `detail`
4. `play`
5. `search`
6. `sitemap`
原则:
1. 只停留在 `robots` / 首页,不算明显进展
2. 进入 `detail`,才算真正开始吃内容页
3. 出现 `play`,说明深层链路更进一步
4. `search``sitemap` 增强,才代表放量基础变好
### 第 3 步:逐站判断是不是缓存问题
对 4 个 P0 站,统一问这 3 个问题:
1. 普通访问是否仍落后于 `?__nocache=1`
2. 普通访问 `title / h1 / canonical` 是否已经追平
3. 蜘蛛日志里的深页访问,是否和前台当前页输出相互印证
如果第 1 条仍成立,就继续归类为:
- 缓存滞后未收口
如果第 1 条不成立,但蜘蛛仍只打浅层,就归类为:
- 已不主要是缓存问题,转为抓取层问题
## 明日快速填写模板
- 复盘日期2026-04-19
- 日志时间窗口:
- 复盘人:
- 今日先看组别A组 / B组 / 全量
## 一、整体判断
- 今日总判断:
- 是否符合预期:
- 是否比 2026-04-18 更进一步:
可直接三选一:
1. 明显变好
2. 基本符合预期,但速度一般
3. 明显低于预期
## 二、P0 四站逐站判读
### 1. `jingxifa.com`
- 今日新增信号:
- 当前层级:
- 是否仍有缓存滞后:
- 明日动作:
### 2. `lcdchq.com`
- 今日新增信号:
- 当前层级:
- 是否仍有缓存滞后:
- 明日动作:
### 3. `cnzhenbang.com`
- 今日新增信号:
- 当前层级:
- 是否仍停留在浅层:
- 明日动作:
### 4. `oronorent.com`
- 今日新增信号:
- 当前层级:
- 是否仍有缓存滞后:
- 明日动作:
## 三、P1 正向样本站判读
### `codohealth.com`
- 是否继续有 `detail / play-like`
- 是否扩大:
- 判断:
### `lgyz.net`
- 是否继续有 `detail / play`
- 是否扩大:
- 判断:
### `jpjdxs.com`
- 是否继续有 `detail / play`
- 是否出现 `search`
- 判断:
## 四、P2 风险与弱站观察
### `liangzuan.net`
- 是否出现新信号:
- 是否有异常页面:
- 判断:
### `jxxgygy.com`
- `/sitemap.xml` 是否恢复:
- 是否仍有 `444`
- 判断:
### 其余弱站
只写有变化的:
1.
2.
3.
## 五、明日最关键的 5 个判断句
直接写结论,不写长文。
1.
2.
3.
4.
5.
## 六、一句话总复盘
-
## 建议用词
如果明天情况继续温和改善,可以直接用:
> GPT 模板线今天继续保持正向回访,整体仍符合修复后的收录推进预期,但 `sitemap` 与搜索链路仍未进入理想放量阶段。
如果明天出现明显推进,可以直接用:
> GPT 模板线今天较昨天更进一步,已有更多站点从浅层访问进入 `detail / play`,开始接近第一阶段目标。
如果明天表现偏弱,可以直接用:
> GPT 模板线今天新增有效蜘蛛信号有限,整体低于理想节奏,下一步要优先排除缓存未收口和入口链路偏弱的问题。

View File

@@ -0,0 +1,136 @@
# 10 GPT模板运营导入模板使用说明 2026-04-18
这份文档只解决一个问题:
运营下载模板后,到底该用哪一份,哪些字段必须填,哪些字段通常可以留空。
## 先记结论
当前有两类模板:
1. 基础站点导入模板
2. GPT 供料模板
不要混着用。
## 一、基础站点导入模板是干什么的
适用场景:
1. 先把站点基础信息导入系统
2. 先建站、先落基础 SEO 信息
3. 还没开始做 GPT 启动包和供料
这份模板主要管:
1. 域名
2. 站点名称
3. 模板ID
4. 首页标题
5. 首页关键词
6. 首页描述
7. SEO策略
下载接口对应:
- `getDomainImportTemplate`
## 二、GPT 供料模板是干什么的
适用场景:
1. 已经要做 GPT 模板启动包
2. 要生成 bundle
3. 要继续跑 bootstrap / probe / 供料链
这份模板除了基础信息,还会带:
1. 详情样本ID
2. 详情样本Slug
3. 搜索样本关键词
4. 父分类Slug
5. 子分类Slug
下载接口对应:
- `getDomainSupplyTemplate`
## 三、运营通常该怎么填
### 基础站点导入模板
运营通常先填这 7 项:
1. 域名
2. 站点名称
3. 模板ID
4. 首页标题
5. 首页关键词
6. 首页描述
7. SEO策略
其余字段:
1. 大多数有默认值
2. 大多数属于技术配置
3. 不确定就先留空
### GPT 供料模板
运营通常先填这 7 项:
1. 域名
2. 站点名称
3. 模板ID
4. 首页标题
5. 首页关键词
6. 首页描述
7. SEO策略
下面这 5 项通常可以先留空:
1. 详情样本ID
2. 详情样本Slug
3. 搜索样本关键词
4. 父分类Slug
5. 子分类Slug
原因:
1. 系统会优先尝试自动样本发现
2. 域名部署成功后,可从当前站点真实内容自动提取
3. 如果当前只是试跑库,只要库里有可用视频内容,也会优先自动补全
4. 只有自动发现失败时,才需要人工补填
## 四、什么时候样本字段不能完全指望自动化
下面这几种情况,要提前有心理预期:
1. 域名还没部署成功
2. 当前站点还没有可访问的首页 / 搜索 / 详情链路
3. 试跑库里没有合适的视频样本
4. 分类 slug 数据本身不完整
这时系统可能自动补不全,才需要人工补:
1. 详情样本ID
2. 详情样本Slug
3. 搜索样本关键词
4. 父分类Slug
5. 子分类Slug
## 五、给运营的一句话规则
可以直接这么记:
1. 只建站,用基础站点导入模板
2. 要跑 GPT 启动包和供料,用 GPT 供料模板
3. 两个模板里,前面主填写区优先填
4. GPT 供料模板里的样本字段,通常先留空,系统自动补
5. 自动补失败,再找技术补样本字段
## 六、给接手 Codex 的一句话规则
1. 不要再要求运营手工理解 `detail_id / detail_slug / search_keyword / category_parent / category_child`
2. 优先让自动样本发现补全
3. 只有自动发现失败,才让人工介入

View File

@@ -0,0 +1,224 @@
# 11 GPT模板导入模板中文化与自动样本补全交接 2026-04-18
这份文档用于交接本轮“模板中文化 + 上传兼容 + 自动样本补全”改动。
目标不是讲所有历史,而是让接手同学快速知道:
1. 这轮改了什么
2. 为什么要改
3. 现在运营该怎么用
4. 后续如果继续改,应该从哪里接
## 一、本轮改动目标
本轮主要解决两个问题:
1. 运营下载的 Excel 模板字段太技术化,英文字段不易理解
2. GPT 供料模板中的样本字段原本看起来像“必须人工填写”,但系统其实已经具备自动发现能力
因此这轮统一做了三件事:
1. 下载模板中文化
2. 上传解析兼容中文表头
3. GPT 供料模板支持样本字段留空后自动补全
## 二、已经完成的改动
### 1. 基础站点导入模板中文化
文件:
- [DomainBatchImportTemplateHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainBatchImportTemplateHelper.php)
已完成:
1. 模板表头改为中文
2. 字段顺序重排为“主填写区在前”
3. `notes` 改为运营可理解的人话说明
4. `template_summary` 补充:
- `template_type`
- `filling_guidance`
- `operator_notice`
### 2. 基础站点导入上传兼容中文表头
文件:
- [Site.php](/www/wwwroot/diff-maccms/SEONexus/code/app/admin/controller/Site.php)
已完成:
1. `uploadDomain` 不再死绑固定列号
2. 现在会先读取首行表头
3. 再通过 `normalizeHeader(...)` 映射回内部字段
4. 因此中文表头和旧英文字段都能兼容
### 3. GPT 供料模板中文化
文件:
- [DomainSupplyBatchTemplateHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainSupplyBatchTemplateHelper.php)
已完成:
1. 模板表头改为中文
2. 字段顺序重排为:
- 运营主填写区
- 自动样本区
- 技术参数区
3. `notes` 明确说明:
- 哪些字段通常先填
- 哪些字段通常可以留空
- 哪些字段只有技术同学需要关心
### 4. GPT 供料模板支持自动样本补全
文件:
- [DomainSupplyBatchHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainSupplyBatchHelper.php)
已完成:
1. 供料模板上传后,如果以下字段为空:
- `detail_id`
- `detail_slug`
- `search_keyword`
- `category_parent`
- `category_child`
2. 系统会优先调用自动样本发现
3. 若自动发现成功,则回填并继续处理
4. 若自动发现失败,才把这些字段继续记为缺失
## 三、自动样本补全依赖什么
自动样本补全不是凭空猜,它依赖现有自动化能力:
### 自动样本发现
文件:
- [DomainAutoSampleHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainAutoSampleHelper.php)
会自动产出:
1. `detail_id`
2. `detail_slug`
3. `search_keyword`
4. `category_parent`
5. `category_child`
6. `play_type`
7. `play_index`
### 轨迹探针
文件:
- [DomainTrajectoryProbeHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainTrajectoryProbeHelper.php)
会用自动样本继续验证:
1. 首页
2. 搜索
3. 详情
4. 播放
## 四、现在运营应该怎么用
### 1. 只做基础建站
使用:
- 基础站点导入模板
优先填写:
1. 域名
2. 站点名称
3. 模板ID
4. 首页标题
5. 首页关键词
6. 首页描述
7. SEO策略
### 2. 要做 GPT 启动包和供料
使用:
- GPT 供料模板
优先填写:
1. 域名
2. 站点名称
3. 模板ID
4. 首页标题
5. 首页关键词
6. 首页描述
7. SEO策略
下面 5 个字段通常先留空:
1. 详情样本ID
2. 详情样本Slug
3. 搜索样本关键词
4. 父分类Slug
5. 子分类Slug
原因:
1. 域名部署成功后可从真实站点链路自动发现
2. 若当前只是试跑库,只要库里有可用视频,也会优先自动提取
3. 只有自动发现失败时才人工补
## 五、当前边界
这轮虽然已经大幅改善体验,但还要明确边界:
1. 如果域名未部署成功,自动样本不一定能从前台链路补全
2. 如果试跑库本身缺少有效视频样本,也可能自动失败
3. 如果分类 slug 数据本身缺失,`category_parent / category_child` 可能补不全
所以正确口径不是“永远不用填”,而是:
1. 通常先留空
2. 系统优先自动补
3. 自动失败再人工补
## 六、当前推荐口径
以后对运营和接手同学统一这么说:
1. 基础站点导入模板:先做站点基础信息
2. GPT 供料模板:做 GPT 启动包与供料
3. GPT 供料模板里的样本字段通常先留空
4. 系统会优先自动样本发现
5. 只有自动发现失败时,才需要人工补样本字段
## 七、后续如果继续改,优先从哪里接
### 优先级 P1
1. 在后台下载模板按钮旁增加一句提示文案
2. 在上传结果页把 `operator_notice` 更明显展示出来
### 优先级 P2
1. 让基础导入成功后,直接给出“下一步请使用 GPT 供料模板”的入口
2. 在供料模板执行结果页,把“自动样本已补全哪些字段”展示得更直观
### 优先级 P3
1. 如果后续还有别的模板线,也统一改成中文表头 + 人话 notes
2. 把所有模板下载入口的摘要口径统一化
## 八、关联文档
建议接手时一起看:
1. [README.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/README.md)
2. [10-GPT模板运营导入模板使用说明-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/10-GPT模板运营导入模板使用说明-2026-04-18.md)
3. [08-GPT模板其它服务器更新交接-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/08-GPT模板其它服务器更新交接-2026-04-18.md)
## 一句话交接
> 本轮已经把 GPT 模板线的导入模板从“技术字段驱动”改成“运营可填写驱动”,并把样本字段从“看起来必须手填”收口成“系统优先自动补全、失败才人工补填”的新口径。

View File

@@ -0,0 +1,96 @@
# 12 GPT模板导入模板改造交付清单 2026-04-18
这份文档只做一件事:
把本轮“导入模板中文化 + 自动样本补全”改造的交付边界一次性钉住。
## 一、本轮已交付
### 代码
1. [DomainBatchImportTemplateHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainBatchImportTemplateHelper.php)
2. [DomainSupplyBatchTemplateHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainSupplyBatchTemplateHelper.php)
3. [DomainSupplyBatchHelper.php](/www/wwwroot/diff-maccms/SEONexus/code/app/common/helper/DomainSupplyBatchHelper.php)
4. [Site.php](/www/wwwroot/diff-maccms/SEONexus/code/app/admin/controller/Site.php)
### 文档
1. [10-GPT模板运营导入模板使用说明-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/10-GPT模板运营导入模板使用说明-2026-04-18.md)
2. [11-GPT模板导入模板中文化与自动样本补全交接-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/11-GPT模板导入模板中文化与自动样本补全交接-2026-04-18.md)
3. [README.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/README.md)
## 二、本轮实现结果
### 基础站点导入模板
已实现:
1. 中文表头
2. 中文 `notes`
3. 主填写区前置
4. 上传兼容中文表头
5. 上传兼容旧英文字段表头
### GPT 供料模板
已实现:
1. 中文表头
2. 中文 `notes`
3. 主填写区前置
4. 样本字段单独收口
5. 样本字段留空时优先自动补全
### 后台接口返回
已实现:
1. `template_summary`
2. `template_display_headers`
3. `template_operator_notice`
4. `template_filling_guidance`
这意味着后端已经把“模板怎么展示、怎么提示运营”所需的数据准备好了。
## 三、当前统一口径
以后统一按这句话对外:
> 基础站点导入模板先做建站基础信息GPT 供料模板再做启动包和供料GPT 供料模板中的样本字段通常可留空,系统会优先自动补全,只有自动发现失败时才人工补填。
## 四、本轮不再继续做的内容
本轮先不继续做这些:
1. 不继续改 GPT 前台模板
2. 不继续扩写更多老模板导入逻辑
3. 不继续做前端页面 UI 大改
4. 不继续把所有后台页面提示都改成统一样式
原因:
1. 当前最大问题已经不是 GPT 前台模板结构
2. 本轮目标是把导入模板和执行入口收口
3. 继续扩大会把当前改动边界搞散
## 五、如果下一个人继续接
优先顺序建议:
1. 先确认下载接口返回的 `template_operator_notice``template_filling_guidance` 是否已在前端页面展示
2. 若页面还没展示,再补页面提示文案
3. 若运营仍觉得模板复杂,再继续缩减主填写区,但不要破坏自动样本补全逻辑
## 六、验收标准
本轮到此为止,满足以下标准即可视为收口完成:
1. 运营下载模板看到的是中文表头
2. 运营上传中文表头模板不会报错
3. GPT 供料模板样本字段留空时能自动补全则自动补全
4. 自动补全失败时才提示缺字段
5. 后续接手人能通过文档快速知道怎么继续
## 一句话交付结论
> 这轮改造已经把“模板难懂、字段太技术化、样本看起来必须手填”的问题,收口成了“中文模板 + 中文说明 + 中文上传兼容 + 样本自动补全优先”的可交付状态。

View File

@@ -32,6 +32,14 @@
3. [07-GPT模板交接说明-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/07-GPT模板交接说明-2026-04-18.md)
4. [08-GPT模板其它服务器更新交接-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/08-GPT模板其它服务器更新交接-2026-04-18.md)
作用:给“原本也是 GPT 模板、现在要对齐到最新版”的其它服务器接手使用。
5. [09-GPT模板次日蜘蛛日志判读模板-2026-04-19.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/09-GPT模板次日蜘蛛日志判读模板-2026-04-19.md)
作用:给第二天继续看蜘蛛日志时直接套用,快速判断进度是否符合预期。
6. [10-GPT模板运营导入模板使用说明-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/10-GPT模板运营导入模板使用说明-2026-04-18.md)
作用告诉运营和接手同学“基础导入模板”和“GPT 供料模板”分别怎么用、哪些字段通常可留空。
7. [11-GPT模板导入模板中文化与自动样本补全交接-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/11-GPT模板导入模板中文化与自动样本补全交接-2026-04-18.md)
作用:给后续 Codex 或技术接手同学,快速说明这轮模板中文化与自动补全到底改了什么。
8. [12-GPT模板导入模板改造交付清单-2026-04-18.md](/www/wwwroot/diff-maccms/SEONexus/docs/gpt-template-seo/12-GPT模板导入模板改造交付清单-2026-04-18.md)
作用:把本轮模板改造的交付边界、统一口径和验收标准一次性钉住。
首批 4 站的执行材料也全部放在本目录: