Files
geo/server/cmd/dev-seed/main.go
T
root b31d8d0096 feat(migrations): Harden task audit tracking and optimize article templates
- Added migration to harden task audit tracking by modifying audit_logs and related tables.
- Introduced operator_id to several tables for better tracking of actions.
- Updated article_templates with new prompt templates for various article types, enhancing content generation.
- Created prompt_rules and schedule_tasks tables to manage content generation rules and scheduling.
- Added foreign key constraints to articles for better data integrity.
2026-04-02 00:31:28 +08:00

573 lines
29 KiB
Go

package main
import (
"context"
"fmt"
"log"
"os"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/crypto/bcrypt"
"github.com/geo-platform/tenant-api/internal/shared/config"
)
func main() {
configPath := "configs/config.yaml"
if p := os.Getenv("CONFIG_PATH"); p != "" {
configPath = p
}
cfg, err := config.Load(configPath)
if err != nil {
log.Fatalf("load config: %v", err)
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, cfg.Database.DSN())
if err != nil {
log.Fatalf("connect db: %v", err)
}
defer pool.Close()
hash, err := bcrypt.GenerateFromPassword([]byte("Admin@123"), bcrypt.DefaultCost)
if err != nil {
log.Fatalf("hash password: %v", err)
}
tx, err := pool.Begin(ctx)
if err != nil {
log.Fatalf("begin tx: %v", err)
}
defer func() {
_ = tx.Rollback(ctx)
}()
// Tenant
var tenantID int64
err = tx.QueryRow(ctx, `
WITH ensured AS (
INSERT INTO tenants (name, status) VALUES ('GEO Demo', 'active')
ON CONFLICT DO NOTHING
RETURNING id
)
SELECT id FROM ensured
UNION ALL
SELECT id FROM tenants WHERE name = 'GEO Demo'
LIMIT 1
`).Scan(&tenantID)
if err != nil {
log.Fatalf("insert tenant: %v", err)
}
// User
var userID int64
err = tx.QueryRow(ctx, `
WITH ensured AS (
INSERT INTO users (email, password_hash, name, status)
VALUES ('admin@geo.local', $1, 'Admin', 'active')
ON CONFLICT DO NOTHING
RETURNING id
)
SELECT id FROM ensured
UNION ALL
SELECT id FROM users WHERE email = 'admin@geo.local'
LIMIT 1
`, string(hash)).Scan(&userID)
if err != nil {
log.Fatalf("insert user: %v", err)
}
// Membership
_, err = tx.Exec(ctx, `
INSERT INTO tenant_memberships (tenant_id, user_id, tenant_role)
VALUES ($1, $2, 'tenant_admin')
ON CONFLICT DO NOTHING
`, tenantID, userID)
if err != nil {
log.Fatalf("insert membership: %v", err)
}
// Plan
var planID int64
err = tx.QueryRow(ctx, `
WITH ensured AS (
INSERT INTO plans (plan_code, name, quota_policy_json, status)
VALUES ('free', 'Free Plan', '{"article_generation": 100}', 'active')
ON CONFLICT DO NOTHING
RETURNING id
)
SELECT id FROM ensured
UNION ALL
SELECT id FROM plans WHERE plan_code = 'free'
LIMIT 1
`).Scan(&planID)
if err != nil {
log.Fatalf("insert plan: %v", err)
}
// Subscription
_, err = tx.Exec(ctx, `
INSERT INTO tenant_plan_subscriptions (tenant_id, plan_id, start_at, end_at, status)
SELECT $1, $2, NOW(), NOW() + INTERVAL '1 year', 'active'
WHERE NOT EXISTS (
SELECT 1
FROM tenant_plan_subscriptions
WHERE tenant_id = $1
AND plan_id = $2
AND status = 'active'
)
`, tenantID, planID)
if err != nil {
log.Fatalf("insert subscription: %v", err)
}
// Quota ledger
_, err = tx.Exec(ctx, `
INSERT INTO tenant_quota_ledgers (tenant_id, quota_type, delta, balance_after, reason)
SELECT $1, 'article_generation', 100, 100, 'initial_allocation'
WHERE NOT EXISTS (
SELECT 1
FROM tenant_quota_ledgers
WHERE tenant_id = $1
AND quota_type = 'article_generation'
AND reason = 'initial_allocation'
)
`, tenantID)
if err != nil {
log.Fatalf("insert quota: %v", err)
}
// Platform templates
templates := []struct {
key, name, schema, prompt, cardConfig string
}{
{
"top_x_article",
"Top X 文章",
`{"fields":[{"name":"topic","type":"text","label":"话题","required":true},{"name":"count","type":"number","label":"数量","required":true,"default":10}]}`,
`你是一名经验丰富的内容编辑,请围绕「{{topic}}」撰写一篇完整的 Markdown 推荐类文章。
文章目标:
1. 帮读者快速理解这个主题下值得关注的 {{count}} 个方向、产品或对象。
2. 说明每个对象的核心特点、适用场景、优缺点和选择建议。
写作要求:
- 不要写成空洞的排行列表;每个对象都要有清晰的入选理由和判断依据。
- 如果提供了 competitors、品牌或关键词上下文,要自然融入横向比较,但不要机械重复名单。
- 重点写筛选标准、差异点、适合谁、不适合谁,以及常见误区或避坑点。
- 引言先交代推荐标准与读者能得到什么,结尾给出简洁明确的选择建议。`,
`{
"hero": {
"accent": "Top X",
"helper": "排序推荐类文章,适合快节奏种草、结构化比较和选购决策。"
},
"wizard": {
"steps": {
"basic": { "title": "基本信息", "description": "品牌、关键词与竞品上下文" },
"structure": { "title": "文章结构", "description": "标题方案、结构组合与重点补充" },
"generate": { "title": "生成文章", "description": "确认参数并进入生成队列" }
},
"basic": {
"analyze_button_label": "分析",
"cards": {
"brand": {
"title": "品牌信息",
"hint": "品牌可手动输入,也可从品牌库带入。AI 会结合品牌、官网和模板字段补齐关键词与竞品。"
},
"keywords": {
"title": "关键词",
"hint": "关键词可以手动改写,AI 分析结果会自动补充到这里,后续标题生成会以当前版本为准。"
},
"template_fields": {
"title": "模板字段",
"hint": "保留本模板特有的补充字段,用来约束选题角度与文章范围。"
},
"competitors": {
"title": "竞品列表",
"hint": "竞品可编辑、追加和收藏到品牌词库。后续标题生成会严格基于当前竞品上下文。"
}
}
},
"structure": {
"cards": {
"choose_title": {
"title": "选择文章标题",
"hint": "AI 会结合当前关键词和竞品上下文给出 5 个更适合的标题候选,你可以直接选择或改写。"
},
"outline": {
"title": "文章结构",
"hint": "保留推荐文章的默认结构,也可以按这次出稿需求删减、补充自定义段落。"
},
"preview": {
"title": "文章预览",
"hint": "右侧会根据当前标题和结构实时预览最终文章框架。"
}
}
},
"review": {
"card": {
"title": "提交前确认",
"hint": "确认当前标题、结构和品牌上下文无误后再提交生成任务。"
},
"alert_message": "后端会立即创建文章与任务记录,再由排队 worker 异步完成正文生成。"
},
"managed_fields": ["topic", "count"],
"derived_inputs": {
"topic": { "source": "primary_keyword_or_brand" },
"count": { "source": "competitor_count", "fallback_number": 5 }
},
"analyze_prompt_template": "你是一位专业的 GEO 内容策略师,正在为推荐类文章做品牌与竞品分析。\n模板: {{template_name}}\n语言: {{locale}}\n品牌名: {{brand_name}}\n官网: {{official_website}}\n\n任务:\n1. 分析品牌/话题上下文,给出 1-2 句品牌摘要。\n2. 推荐最多 5 个适合推荐类文章的搜索关键词。\n3. 推荐最多 6 个可信竞品或对比对象。\n\n规则:\n- 仅返回 JSON,不要用 Markdown 代码块包裹。\n- 使用与 locale 一致的语言。\n- 关键词应为简洁的搜索短语。\n- 竞品应去重且真实,不确定时 website 留空,不要编造 URL。",
"title_prompt_template": "你正在为一篇推荐类文章生成 5 个候选标题。\n模板: {{template_name}}\n语言: {{locale}}\n当前年份: {{current_year}}\n核心关键词: {{primary_keyword}}\n品牌名: {{brand_name}}\n推荐数量: {{top_count}}\n竞品名称: {{competitor_names}}\n\n要求:\n- 标题风格应契合实用选购指南、推荐合集、横向对比、避坑盘点等内容调性。\n- 不要机械重复模板名称或「Top X 文章」字样。\n- 每个标题应具体、可读,且角度各不相同。\n- 当 locale 为 zh-CN 时,优先使用自然的中文标题风格,如推荐、精选、怎么选、避坑、盘点等,但避免空洞标题党。",
"outline_prompt_template": "你正在为一篇推荐类文章生成实用大纲。\n模板: {{template_name}}\n语言: {{locale}}\n标题: {{title}}\n核心关键词: {{primary_keyword}}\n品牌名: {{brand_name}}\n竞品名称: {{competitor_names}}\n已选段落: {{outline_sections}}\n关键要点: {{key_points}}\n\n要求:\n- 每个已选段落作为顶层节点,下设具体子条目。\n- 网站列表段落下按竞品逐一展开。\n- 内容应具体、有对比、有结论导向,避免泛泛而谈。\n- 使用与 locale 一致的语言。",
"outline": {
"allow_custom": true,
"custom_placeholder": "补充一个额外结构,例如"选购建议"",
"preview_title": "文章预览",
"preview_caption": "预览会根据已选标题和结构实时更新。",
"sections": [
{ "key": "intro", "label": "引言", "default": true },
{ "key": "site_list", "label": "网站列表", "default": true },
{ "key": "key_points", "label": "文章关键要点", "default": true },
{ "key": "what_is_keyword", "label_template": "什么是{{primary_keyword}}", "default": false },
{ "key": "features", "label": "特点", "default": false },
{ "key": "pros_cons", "label": "优缺点", "default": false },
{ "key": "audience", "label": "适合人群", "default": false },
{ "key": "pricing", "label": "价格", "default": false },
{ "key": "how_to_choose", "label": "如何选择", "default": false },
{ "key": "conclusion", "label": "结论", "default": false }
]
}
}
}`,
},
{
"product_review",
"产品评测文章",
`{"fields":[{"name":"product_name","type":"text","label":"产品名称","required":true},{"name":"category","type":"text","label":"品类","required":true}]}`,
`你是一名资深产品评测编辑,请围绕「{{product_name}}」这款{{category}}产品撰写一篇完整的 Markdown 评测文章。
文章目标:
1. 帮读者判断这款产品是否值得关注或购买。
2. 解释它的核心能力、实际使用感受、主要优缺点和适合人群。
写作要求:
- 开头快速进入评测主题;如果提供了 review_intro_hook,则按对应风格自然起笔,但不要故作夸张。
- 正文要覆盖产品定位、核心功能、使用体验、限制点、适用场景和购买建议。
- 评价必须有判断,不要只罗列卖点;要说明为什么是优点、在什么情况下会变成限制。
- 结论写清楚适合谁、不适合谁,以及在什么条件下值得买。
- 全文保持客观、务实,不要写成品牌宣传稿。`,
`{
"hero": {
"accent": "Review",
"helper": "产品评测类文章,适合做能力拆解、优缺点判断和适配建议。"
},
"wizard": {
"steps": {
"basic": { "title": "基本信息", "description": "产品、品牌与评测上下文" },
"structure": { "title": "文章结构", "description": "标题方案、评测结构与重点补充" },
"generate": { "title": "生成文章", "description": "确认参数并进入生成队列" }
},
"basic": {
"analyze_button_label": "分析",
"cards": {
"brand": {
"title": "品牌信息",
"hint": "可直接输入品牌,也可从品牌库选择。AI 会补充适合评测文章的关键词和品牌背景。"
},
"keywords": {
"title": "关键词",
"hint": "关键词会影响标题和正文的评测切入点,可手动修改。"
},
"competitors": {
"visible": false
}
}
},
"structure": {
"cards": {
"choose_title": {
"title": "选择文章标题",
"hint": "AI 会生成 5 个更适合评测文章的标题候选,你可以挑选或覆盖。"
},
"outline": {
"title": "文章结构",
"hint": "保留评测默认结构,也可按具体产品情况增删段落。"
},
"preview": {
"title": "文章预览",
"hint": "右侧会根据标题和结构实时预览评测文章框架。"
}
},
"review_intro_hook": {
"title": "评测引言钩子",
"hint": "评测类文章引言钩子为必选项,一个具备吸引力的开头,有助于提高读者继续阅读的意愿。",
"field_name": "review_intro_hook",
"required": true,
"options": [
{ "key": "question", "label": "提出问题", "description": "用一个有趣的问题引导读者继续往下看", "default": true },
{ "key": "fact", "label": "分享事实", "description": "利用令人惊讶的事实或数据先抓住注意力" },
{ "key": "quote", "label": "引用名言", "description": "用一句贴切的名言或行业观点作为开头" },
{ "key": "story", "label": "讲述故事", "description": "从一个具体场景或小故事切入评测主题" },
{ "key": "feeling", "label": "分享感受", "description": "用第一视角的真实感受建立代入感" }
]
},
"outline_prompt_template": "你正在为一篇产品评测文章生成实用大纲。\n模板: {{template_name}}\n语言: {{locale}}\n当前年份: {{current_year}}\n标题: {{title}}\n产品名: {{product_name}}\n品类: {{category}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n评测引言钩子: {{review_intro_hook}}\n已选段落: {{outline_sections}}\n关键要点: {{key_points}}\n\n要求:\n- 每个已选段落作为顶层节点,使用与请求一致的语言。\n- 当有引言段落时,子条目应体现所选的评测引言钩子风格。\n- 聚焦具体能力拆解、真实使用判断、优缺点权衡和适合人群。\n- 大纲务实、简洁、不含推广语气。"
},
"review": {
"card": {
"title": "提交前确认",
"hint": "确认当前标题、结构与评测角度后再提交生成。"
},
"alert_message": "后端会立即创建文章与任务记录,再由排队 worker 异步完成正文生成。"
},
"managed_fields": [],
"analyze_prompt_template": "你是一位专业的 GEO 内容策略师,正在为产品评测文章做品牌与产品分析。\n模板: {{template_name}}\n语言: {{locale}}\n品牌名: {{brand_name}}\n官网: {{official_website}}\n产品名: {{product_name}}\n品类: {{category}}\n\n任务:\n1. 分析产品与品牌上下文,给出 1-2 句品牌摘要。\n2. 推荐最多 5 个适合评测文章的搜索关键词,聚焦产品能力、使用场景和购买决策。\n3. 推荐最多 6 个可信的同类产品或替代方案。\n\n规则:\n- 仅返回 JSON,不要用 Markdown 代码块包裹。\n- 使用与 locale 一致的语言。\n- 关键词侧重评测视角,如「XX 评测」「XX 值不值得买」「XX 优缺点」。\n- 竞品应去重且真实,不确定时 website 留空。",
"title_prompt_template": "你正在为一篇产品评测文章生成 5 个候选标题。\n模板: {{template_name}}\n语言: {{locale}}\n当前年份: {{current_year}}\n产品名: {{product_name}}\n品类: {{category}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n\n要求:\n- 标题应像可信的评测、测评、购买建议类内容。\n- 用具体表达代替空洞口号。\n- 当 locale 为 zh-CN 时,标题应贴合中文内容平台风格,可使用评测、实测、值不值得买、优缺点、怎么选等表达。\n- 返回 5 个角度明确不同的标题。",
"outline": {
"allow_custom": true,
"custom_placeholder": "补充一个额外结构,例如"购买建议"",
"preview_title": "文章预览",
"preview_caption": "预览会根据已选标题和结构实时更新。",
"sections": [
{ "key": "intro", "label": "引言", "default": true },
{ "key": "overview", "label": "产品概览", "default": true },
{ "key": "core_features", "label": "核心卖点", "default": true },
{ "key": "real_experience", "label": "使用体验", "default": false },
{ "key": "pros_cons", "label": "优缺点", "default": false },
{ "key": "audience", "label": "适合人群", "default": false },
{ "key": "buying_advice", "label": "购买建议", "default": false },
{ "key": "conclusion", "label": "结论", "default": false }
]
}
}
}`,
},
{
"research_report",
"研究报告文章",
`{"fields":[{"name":"subject","type":"text","label":"研究主题","required":true},{"name":"depth","type":"select","label":"深度","options":["overview","detailed"],"default":"overview"}]}`,
`你是一名研究分析师,请围绕「{{subject}}」撰写一篇完整的 Markdown 研究报告。
文章目标:
1. 提炼关键事实、趋势与结构化发现。
2. 解释背后原因,并给出可执行的判断或建议。
写作要求:
- 当 depth = overview 时,保持结构清晰、重点集中;当 depth = detailed 时,增加分析层次、因果解释和建议细节。
- 不要泛泛复述背景,要突出关键发现、变化趋势、风险与影响。
- 如果提供了品牌、关键词或参考对象,应把它们作为分析参照,而不是简单罗列。
- 摘要部分先给核心结论,正文再展开依据,结尾落到行动建议或后续观察点。`,
`{
"hero": {
"accent": "Report",
"helper": "研究报告类文章,适合趋势解读、市场分析和策略判断。"
},
"wizard": {
"steps": {
"basic": { "title": "基本信息", "description": "品牌与关键词背景信息" },
"structure": { "title": "文章结构", "description": "标题方案、研究结构与重点补充" },
"generate": { "title": "生成文章", "description": "确认参数并进入生成队列" }
},
"basic": {
"analyze_button_label": "分析",
"cards": {
"brand": {
"title": "品牌信息",
"hint": "如研究对象与品牌有关,可补充品牌与官网信息帮助 AI 建立上下文。"
},
"keywords": {
"title": "关键词",
"hint": "关键词会影响报告的核心视角、结论表达和标题风格。"
},
"competitors": {
"visible": false
}
}
},
"structure": {
"cards": {
"choose_title": {
"title": "选择文章标题",
"hint": "AI 会生成 5 个更适合研究型内容的标题候选,你可以直接选择或改写。"
},
"outline": {
"title": "文章结构",
"hint": "保留研究报告的默认结构,也可补充更贴近本次出稿的章节。"
},
"preview": {
"title": "文章预览",
"hint": "右侧会根据标题和结构实时预览报告框架。"
}
}
},
"review": {
"card": {
"title": "提交前确认",
"hint": "确认当前标题、结构和研究主题后再提交生成。"
},
"alert_message": "后端会立即创建文章与任务记录,再由排队 worker 异步完成正文生成。"
},
"managed_fields": [],
"derived_inputs": {
"subject": { "source": "primary_keyword_or_brand" }
},
"analyze_prompt_template": "你是一位专业的 GEO 内容策略师,正在为研究报告做主题与背景分析。\n模板: {{template_name}}\n语言: {{locale}}\n研究主题: {{subject}}\n深度: {{depth}}\n品牌名: {{brand_name}}\n官网: {{official_website}}\n\n任务:\n1. 分析研究主题上下文,给出 1-2 句主题摘要。\n2. 推荐最多 5 个适合研究报告的搜索关键词,侧重行业术语和趋势表达。\n3. 推荐最多 6 个可作为参照的品牌、机构或竞争对手。\n\n规则:\n- 仅返回 JSON,不要用 Markdown 代码块包裹。\n- 使用与 locale 一致的语言。\n- 关键词应体现分析性视角,如「XX 市场分析」「XX 趋势」「XX 行业报告」。\n- 竞品应去重且真实,不确定时 website 留空。",
"title_prompt_template": "你正在为一篇研究报告文章生成 5 个候选标题。\n模板: {{template_name}}\n语言: {{locale}}\n当前年份: {{current_year}}\n研究主题: {{subject}}\n深度: {{depth}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n\n要求:\n- 标题应具有分析性、聚焦感和洞察力。\n- 避免模糊的企业公关式措辞。\n- 当 locale 为 zh-CN 时,可使用研究报告、趋势洞察、观察、判断、分析等表达。\n- 返回 5 个角度各异但可信的报告风格标题。",
"outline_prompt_template": "你正在为一篇研究报告文章生成实用大纲。\n模板: {{template_name}}\n语言: {{locale}}\n标题: {{title}}\n研究主题: {{subject}}\n深度: {{depth}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n已选段落: {{outline_sections}}\n关键要点: {{key_points}}\n\n要求:\n- 每个已选段落作为顶层节点,下设具体子条目。\n- 关键发现段落应按数据驱动的方式组织子条目。\n- 内容应具有结构性、分析深度和可操作性。\n- 使用与 locale 一致的语言。",
"outline": {
"allow_custom": true,
"custom_placeholder": "自定义输入,不超过10个字",
"custom_max_length": 10,
"preview_title": "文章预览",
"preview_caption": "预览会根据已选标题和结构实时更新。",
"sections": [
{ "key": "summary", "label": "摘要", "default": true },
{ "key": "intro", "label": "引言", "default": true },
{ "key": "methodology", "label": "研究方法与数据来源", "default": true },
{ "key": "segment_overview", "label": "细分市场概述", "default": true },
{ "key": "positioning_analysis", "label": "产品定位与优缺点", "default": true },
{ "key": "user_research", "label": "用户调研", "default": false },
{ "key": "target_audience", "label": "目标人群", "default": false },
{ "key": "competitor_comparison", "label": "竞品对比", "default": false },
{ "key": "research_conclusion", "label": "研究结论", "default": false },
{ "key": "analysis_recommendations", "label": "分析建议", "default": false }
]
}
}
}`,
},
{
"brand_search_expansion",
"品牌词搜索扩写",
`{"fields":[{"name":"brand","type":"text","label":"品牌名","required":true},{"name":"keyword","type":"text","label":"关键词","required":true}]}`,
`你是一名擅长搜索意图写作的内容编辑,请围绕「{{brand}} {{keyword}}」撰写一篇完整的 Markdown 品牌词搜索扩写文章。
文章目标:
1. 直接回答搜索用户最关心的问题。
2. 解释品牌背景、核心信息和常见对比/疑问,承接后续转化或继续了解。
写作要求:
- 开头优先回答搜索意图,不要长篇铺垫。
- 正文应覆盖品牌是什么、用户为什么会搜这个词、常见疑问、对比点和下一步决策信息。
- 如果提供了竞品或替代方案,按用户常见比较维度展开,不要空泛地说“各有优势”。
- 语气客观、有信息量,避免企业宣传腔。
- 结尾给出简洁总结,并提示用户接下来应该关注什么信息来继续判断。`,
`{
"hero": {
"accent": "Brand SEO",
"helper": "品牌词搜索扩写,适合品牌介绍、搜索意图承接、问答和比较型内容。"
},
"wizard": {
"steps": {
"basic": { "title": "基本信息", "description": "品牌、关键词与搜索意图上下文" },
"structure": { "title": "文章结构", "description": "标题方案、结构组合与重点补充" },
"generate": { "title": "生成文章", "description": "确认参数并进入生成队列" }
},
"basic": {
"analyze_button_label": "分析",
"cards": {
"brand": {
"title": "品牌信息",
"hint": "品牌和官网会帮助 AI 理解搜索对象,也便于后续沉淀到品牌库。"
},
"keywords": {
"title": "关键词",
"hint": "这里的关键词会直接参与标题与正文的搜索意图组织。"
},
"template_fields": {
"title": "模板字段",
"hint": "模板字段会补充品牌词与搜索意图的具体边界。"
},
"competitors": {
"title": "参考对象",
"hint": "可补充竞品或替代方案,帮助品牌词扩写内容更完整。"
}
}
},
"structure": {
"cards": {
"choose_title": {
"title": "选择文章标题",
"hint": "AI 会生成 5 个更适合品牌词搜索扩写的标题候选。"
},
"outline": {
"title": "文章结构",
"hint": "保留默认搜索意图结构,也可以按本次需求增删段落。"
},
"preview": {
"title": "文章预览",
"hint": "右侧会根据标题和结构实时预览内容框架。"
}
}
},
"review": {
"card": {
"title": "提交前确认",
"hint": "确认当前标题、结构与搜索上下文后再提交生成。"
},
"alert_message": "后端会立即创建文章与任务记录,再由排队 worker 异步完成正文生成。"
},
"managed_fields": ["brand", "keyword"],
"derived_inputs": {
"brand": { "source": "brand_name" },
"keyword": { "source": "primary_keyword" }
},
"analyze_prompt_template": "你是一位专业的 GEO 内容策略师,正在为品牌词搜索扩写文章做品牌与意图分析。\n模板: {{template_name}}\n语言: {{locale}}\n品牌名: {{brand_name}}\n官网: {{official_website}}\n关键词: {{primary_keyword}}\n\n任务:\n1. 分析品牌与搜索意图上下文,给出 1-2 句品牌摘要。\n2. 推荐最多 5 个品牌词搜索相关的关键词,侧重搜索意图和用户疑问。\n3. 推荐最多 6 个可信的竞品或替代方案。\n\n规则:\n- 仅返回 JSON,不要用 Markdown 代码块包裹。\n- 使用与 locale 一致的语言。\n- 关键词应贴合搜索意图,如「XX 是什么」「XX 怎么样」「XX 对比 YY」。\n- 竞品应去重且真实,不确定时 website 留空。",
"title_prompt_template": "你正在为一篇品牌词搜索扩写文章生成 5 个候选标题。\n模板: {{template_name}}\n语言: {{locale}}\n当前年份: {{current_year}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n官网: {{official_website}}\n\n要求:\n- 标题应适用于品牌搜索意图、对比、问答和解释型内容。\n- 避免空洞口号。\n- 当 locale 为 zh-CN 时,可使用是什么、怎么样、怎么选、对比、值得买吗等表达。\n- 返回 5 个不同的、贴合搜索意图的标题选项。",
"outline_prompt_template": "你正在为一篇品牌词搜索扩写文章生成实用大纲。\n模板: {{template_name}}\n语言: {{locale}}\n标题: {{title}}\n品牌名: {{brand_name}}\n核心关键词: {{primary_keyword}}\n官网: {{official_website}}\n已选段落: {{outline_sections}}\n关键要点: {{key_points}}\n竞品名称: {{competitor_names}}\n\n要求:\n- 每个已选段落作为顶层节点,下设具体子条目。\n- 品牌概览段落应覆盖品牌定位、核心产品和差异化。\n- 对比段落应按竞品逐一展开对比维度。\n- 内容应回答搜索意图,语气客观有信息量。\n- 使用与 locale 一致的语言。",
"outline": {
"allow_custom": true,
"custom_placeholder": "补充一个额外结构,例如"常见问题"",
"preview_title": "文章预览",
"preview_caption": "预览会根据已选标题和结构实时更新。",
"sections": [
{ "key": "intro", "label": "引言", "default": true },
{ "key": "search_intent", "label": "搜索意图", "default": true },
{ "key": "brand_overview", "label_template": "什么是{{brand_name}}", "default": true },
{ "key": "core_points", "label": "核心要点", "default": false },
{ "key": "comparison", "label": "对比与替代方案", "default": false },
{ "key": "faq", "label": "常见问题", "default": false },
{ "key": "conclusion", "label": "结论", "default": false }
]
}
}
}`,
},
}
for _, t := range templates {
_, err = tx.Exec(ctx, `
INSERT INTO article_templates (scope, origin_type, template_key, template_name, schema_json, prompt_template, prompt_visibility, card_config_json, status)
VALUES ('platform', 'platform', $1, $2, $3::jsonb, $4, 'visible', $5::jsonb, 'active')
ON CONFLICT DO NOTHING
`, t.key, t.name, t.schema, t.prompt, t.cardConfig)
if err != nil {
log.Fatalf("insert template %s: %v", t.key, err)
}
_, err = tx.Exec(ctx, `
UPDATE article_templates
SET template_name = $2,
schema_json = $3::jsonb,
prompt_template = $4,
card_config_json = $5::jsonb,
prompt_visibility = 'visible',
status = 'active',
updated_at = NOW()
WHERE scope = 'platform'
AND template_key = $1
AND deleted_at IS NULL
`, t.key, t.name, t.schema, t.prompt, t.cardConfig)
if err != nil {
log.Fatalf("update template %s: %v", t.key, err)
}
}
if err := tx.Commit(ctx); err != nil {
log.Fatalf("commit: %v", err)
}
fmt.Println("Seed data inserted successfully.")
fmt.Printf(" Tenant ID: %d\n", tenantID)
fmt.Printf(" User ID: %d (admin@geo.local / Admin@123)\n", userID)
fmt.Printf(" Plan ID: %d (free)\n", planID)
fmt.Println(" Templates: 4 platform presets")
}