feat: improve gpt import templates and seo docs
This commit is contained in:
@@ -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 [
|
||||
|
||||
@@ -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'] ?? [])),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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 [
|
||||
|
||||
Reference in New Issue
Block a user