# AI 品牌曝光监测系统技术方案 V2 ## 1. 文档信息 | 项目 | 内容 | | --- | --- | | 文档名称 | AI 品牌曝光监测系统技术方案 V2 | | 文档版本 | V2.0 | | 文档状态 | 待评审 | | 创建日期 | 2026-04-05 | | 适用范围 | 数据追踪模块全部页面 | | 关联文档 | `docs/geo-platform-prd-v1.md` (PRD 8.4 数据追踪) | | 关联文档 | `docs/question-driven-monitoring-design-v1.md` (V1 数据模型) | | 容量目标 | 5 万并发在线用户 | ## 2. 与 V1 设计的关系 本方案基于 V1 (`question-driven-monitoring-design-v1.md`) 的核心结论,保留其"问题驱动 + 日级汇总 + 版本绑定"的数据模型哲学,在此基础上扩展以下能力: | V1 已定义 | V2 扩展 | | --- | --- | | 品牌曝光趋势 + 高频问题 + 引用排行 | 新增:主页总览、平台占比分析、竞争对手分析、业务主题分析、AI 对话问题列表、AI 引用来源排名 | | 单维度(品牌+关键词+模型) | 多维度:按 AI 平台、按竞品、按主题、按引用来源交叉分析 | | 汇总表 3 张 | 扩展至 8 张汇总表 + 2 张原始采集表 | | 未涉及性能架构 | 明确 5 万并发的缓存层、连接池、部署拓扑 | | 未涉及前端 | 6 个页面 + ECharts 可视化完整方案 | V1 定义的采集逻辑、版本化策略、去重策略、保留策略 **全部沿用,不再重复**。 ## 3. 目标页面与数据需求 基于产品截图,数据追踪模块共 6 个页面: ### 3.1 主页(Dashboard) | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 曝光趋势图 | 近 7/30 天品牌在所有 AI 平台的综合曝光度日趋势 | `monitoring_brand_daily_overview` | | 前五竞争对手 | 品牌及竞品的情感得分、曝光度排名 | `monitoring_competitor_daily` | | 引用来源类型占比 | 编辑类、企业、UGC、参考资料、机构、其他的占比饼图 | `monitoring_citation_source_daily` | | 前五引用平台 | 引用次数最多的外部平台,含百度权重、引用次数、覆盖度 | `monitoring_citation_source_daily` | | 最新 AI 对话 | 最近被触发的 AI 问答问题、排名、前三品牌 | `monitoring_question_daily` | | AI 品牌印象 | 品牌相关的高频词词云 | `monitoring_brand_impression` | ### 3.2 平台占比分析 | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 提及次数排名 | 各 AI 平台对品牌的提及总次数横向柱状图 | `monitoring_platform_daily` | | 各平台排名表现 | 按平台展开:AI 对话问题、提及次数、平均位置、曝光度 | `monitoring_platform_question_daily` | ### 3.3 竞争对手分析 | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 竞争对手曝光度排名 | 品牌 + 竞品在各 AI 平台上的曝光度分组柱状图 | `monitoring_competitor_daily` | | 竞争对手排名情况 | 按品牌/竞品展开:各平台提及次数、平均位置、曝光度 | `monitoring_competitor_platform_daily` | ### 3.4 业务主题分析 | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 业务主题 | 主题名称、问题数、曝光度、提及次数 | `monitoring_topic_daily` | | 引用最高的前五平台 | 外部引用来源平台排名 | `monitoring_citation_source_daily` | | 引用次数最多的页面 | 具体被引用的外部 URL | `monitoring_citation_page_daily` | ### 3.5 AI 对话问题 | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 问题列表 | AI 对话问题、热度指数、曝光度、平均位置、情感倾向、前三品牌 | `monitoring_question_daily` | | 查看效果 / 查看引用 | 单条问题的详细回答效果和引用来源 | `question_monitor_parse_results` | ### 3.6 AI 引用排名 | 区域 | 数据需求 | 数据来源 | | --- | --- | --- | | 最高引用来源平台趋势 | 前 5 引用来源平台的引用次数日趋势折线图 | `monitoring_citation_source_daily` | | 引用页面详情 | 引用平台、百度权重、引用页面 URL、引用次数、覆盖率 | `monitoring_citation_page_daily` | ## 4. 与现有数据模型的关系 ### 4.1 已有表复用 监测系统直接复用以下现有表,**不新建冗余表**: | 现有表 | 用途 | 关键字段 | | --- | --- | --- | | `brands` | 被监测的品牌主体 | `id, tenant_id, name` | | `brand_keywords` | 监测问题的分类容器 | `id, brand_id, name` | | `brand_questions` | 监测采集的最小单元 | `id, brand_id, keyword_id, current_version_id` | | `brand_question_versions` | 保证历史口径稳定 | `id, question_id, question_text, question_hash, version_no` | | `competitors` | 竞品品牌数据 | `id, brand_id, name, website` | | `articles` | 被引用的系统文章 | `id, tenant_id, source_type` | ### 4.2 数据流向 ``` 现有品牌词库 新建监测模块 ┌──────────────┐ ┌──────────────────┐ │ brands │──────────────────▶│ 采集调度器 │ │ keywords │ 品牌+关键词+问题 │ (按问题×模型调度) │ │ questions │──────────────────▶│ │ │ question_ │ 绑定问题版本 └────────┬─────────┘ │ versions │ │ │ competitors │──── 竞品清单 ──────▶ │ 调用 AI 平台 API └──────────────┘ ▼ ┌──────────────────┐ 现有文章表 │ 原始采集表 │ ┌──────────────┐ │ question_monitor_ │ │ articles │◀── 引用检测 ──────│ runs / results │ │ article_ │ └────────┬─────────┘ │ versions │ │ 日级聚合 Job └──────────────┘ ▼ ┌──────────────────┐ │ 汇总表 × 8 │ │ (monitoring_*) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Redis 缓存层 │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Dashboard API │ │ (6 个页面) │ └──────────────────┘ ``` ## 5. 数据库 Schema ### 5.1 参考表 #### `ai_platforms` — AI 平台注册表 ```sql CREATE TABLE ai_platforms ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), name VARCHAR(50) NOT NULL, -- DeepSeek / 通义千问 / 豆包 / 腾讯元宝 / 文心一言 icon VARCHAR(255), provider_key VARCHAR(50) NOT NULL, -- deepseek / qwen / doubao / hunyuan / ernie api_endpoint TEXT, api_model VARCHAR(100), display_order INT NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX uk_ai_platform_tenant_key ON ai_platforms(tenant_id, provider_key); ``` #### `monitoring_topics` — 业务主题分类 ```sql CREATE TABLE monitoring_topics ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), brand_id BIGINT NOT NULL REFERENCES brands(id), name VARCHAR(200) NOT NULL, -- 瑜伽服饰 / 运动生活方式品牌 / 女士运动内衣 description TEXT, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE UNIQUE INDEX uk_topic_tenant_brand_name ON monitoring_topics(tenant_id, brand_id, name) WHERE deleted_at IS NULL; ``` #### `monitoring_topic_questions` — 主题与问题的多对多关系 ```sql CREATE TABLE monitoring_topic_questions ( id BIGSERIAL PRIMARY KEY, topic_id BIGINT NOT NULL REFERENCES monitoring_topics(id), question_id BIGINT NOT NULL REFERENCES brand_questions(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_topic_question UNIQUE (topic_id, question_id) ); ``` ### 5.2 原始采集表 沿用 V1 设计,增加 `tenant_id` 租户隔离字段。 #### `question_monitor_runs` — 单次采集执行记录 ```sql CREATE TABLE question_monitor_runs ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), brand_id BIGINT NOT NULL REFERENCES brands(id), keyword_id BIGINT NOT NULL REFERENCES brand_keywords(id), question_id BIGINT NOT NULL REFERENCES brand_questions(id), question_version_id BIGINT NOT NULL REFERENCES brand_question_versions(id), ai_platform_id BIGINT NOT NULL REFERENCES ai_platforms(id), run_date DATE NOT NULL, run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), task_batch_id VARCHAR(50), status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/running/completed/failed raw_answer_key TEXT, -- MinIO 对象存储路径 error_message TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) PARTITION BY RANGE (run_date); CREATE INDEX idx_monitor_runs_tenant_date ON question_monitor_runs(tenant_id, run_date DESC); CREATE INDEX idx_monitor_runs_batch ON question_monitor_runs(task_batch_id); ``` > **分区策略**:按 `run_date` 月级 Range 分区,自动创建未来 3 个月分区,到期后可 `DROP PARTITION` 清理。 #### `question_monitor_parse_results` — 单次采集解析结果 ```sql CREATE TABLE question_monitor_parse_results ( id BIGSERIAL PRIMARY KEY, run_id BIGINT NOT NULL, -- 指向 question_monitor_runs.id(分区表不建 FK) tenant_id BIGINT NOT NULL REFERENCES tenants(id), -- 品牌提及指标 brand_mentioned BOOLEAN NOT NULL DEFAULT false, brand_position INT, -- 品牌出现位置(1=首位) top1_mentioned BOOLEAN NOT NULL DEFAULT false, first_recommended BOOLEAN NOT NULL DEFAULT false, positive_sentiment BOOLEAN NOT NULL DEFAULT false, sentiment_label VARCHAR(20), -- excellent/normal/negative -- 竞品提及 competitor_mentions JSONB, -- [{"competitor_id":1,"name":"Nike","position":2,"sentiment":"positive"}] -- 引用检测 citation_urls JSONB, -- [{"url":"https://...","platform":"什么值得买","platform_type":"UGC","baidu_weight":7}] cited_article_ids BIGINT[], -- 系统文章 ID 数组 citation_count INT NOT NULL DEFAULT 0, -- 品牌印象关键词 brand_keywords_extract JSONB, -- [{"word":"性价比高","weight":5},{"word":"设计时尚","weight":3}] parser_version VARCHAR(20) NOT NULL DEFAULT 'v1', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_parse_results_run ON question_monitor_parse_results(run_id); CREATE INDEX idx_parse_results_tenant ON question_monitor_parse_results(tenant_id); ``` ### 5.3 汇总表 所有汇总表均由后台聚合 Job 写入,Dashboard 查询 **只读** 汇总表。 #### 1) `monitoring_brand_daily_overview` — 品牌日级总览(主页指标卡 + 趋势图) ```sql CREATE TABLE monitoring_brand_daily_overview ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, metric_date DATE NOT NULL, -- 核心指标 total_question_count INT NOT NULL DEFAULT 0, -- 采集的问题总数 total_ask_count INT NOT NULL DEFAULT 0, -- 总提问次数(跨平台) mention_count INT NOT NULL DEFAULT 0, top1_mention_count INT NOT NULL DEFAULT 0, first_recommend_count INT NOT NULL DEFAULT 0, positive_mention_count INT NOT NULL DEFAULT 0, citation_count INT NOT NULL DEFAULT 0, -- 比率 mention_rate NUMERIC(5,4) NOT NULL DEFAULT 0, top1_mention_rate NUMERIC(5,4) NOT NULL DEFAULT 0, first_recommend_rate NUMERIC(5,4) NOT NULL DEFAULT 0, positive_mention_rate NUMERIC(5,4) NOT NULL DEFAULT 0, exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, -- 曝光度 -- 情感得分 sentiment_score INT, -- 0~100 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_brand_overview UNIQUE (tenant_id, brand_id, metric_date) ); ``` #### 2) `monitoring_platform_daily` — 按 AI 平台维度的品牌日统计(平台占比分析) ```sql CREATE TABLE monitoring_platform_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, ai_platform_id BIGINT NOT NULL, metric_date DATE NOT NULL, mention_count INT NOT NULL DEFAULT 0, avg_position NUMERIC(5,2), -- 平均排名位置 exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, top1_count INT NOT NULL DEFAULT 0, question_count INT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_platform_daily UNIQUE (tenant_id, brand_id, ai_platform_id, metric_date) ); ``` #### 3) `monitoring_platform_question_daily` — 按平台×问题的明细(平台展开表) ```sql CREATE TABLE monitoring_platform_question_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, ai_platform_id BIGINT NOT NULL, question_id BIGINT NOT NULL, metric_date DATE NOT NULL, question_text TEXT NOT NULL, -- 快照 mention_count INT NOT NULL DEFAULT 0, avg_position NUMERIC(5,2), exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_platform_question_daily UNIQUE (tenant_id, brand_id, ai_platform_id, question_id, metric_date) ); ``` #### 4) `monitoring_competitor_daily` — 竞品对比日统计(竞争对手分析) ```sql CREATE TABLE monitoring_competitor_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, -- 主品牌 compared_brand_name VARCHAR(200) NOT NULL, -- 品牌名或竞品名 is_self BOOLEAN NOT NULL DEFAULT false, competitor_id BIGINT, -- NULL 表示自身品牌 metric_date DATE NOT NULL, mention_count INT NOT NULL DEFAULT 0, avg_position NUMERIC(5,2), exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, sentiment_score INT, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_competitor_daily UNIQUE (tenant_id, brand_id, compared_brand_name, metric_date) ); ``` #### 5) `monitoring_competitor_platform_daily` — 竞品×平台交叉统计 ```sql CREATE TABLE monitoring_competitor_platform_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, compared_brand_name VARCHAR(200) NOT NULL, ai_platform_id BIGINT NOT NULL, metric_date DATE NOT NULL, mention_count INT NOT NULL DEFAULT 0, avg_position NUMERIC(5,2), exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_comp_platform_daily UNIQUE (tenant_id, brand_id, compared_brand_name, ai_platform_id, metric_date) ); ``` #### 6) `monitoring_topic_daily` — 业务主题日统计 ```sql CREATE TABLE monitoring_topic_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, topic_id BIGINT NOT NULL, metric_date DATE NOT NULL, question_count INT NOT NULL DEFAULT 0, exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, mention_count INT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_topic_daily UNIQUE (tenant_id, brand_id, topic_id, metric_date) ); ``` #### 7) `monitoring_question_daily` — 单问题日统计(AI 对话问题页) ```sql CREATE TABLE monitoring_question_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, question_id BIGINT NOT NULL, question_version_id BIGINT NOT NULL, metric_date DATE NOT NULL, question_text TEXT NOT NULL, heat_index INT NOT NULL DEFAULT 0, -- 热度指数 exposure_rate NUMERIC(5,4) NOT NULL DEFAULT 0, avg_position NUMERIC(5,2), sentiment_label VARCHAR(20), -- excellent/normal/negative top3_brands JSONB, -- [{"name":"lululemon","position":1},...] ask_count INT NOT NULL DEFAULT 0, mention_count INT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_question_daily UNIQUE (tenant_id, brand_id, question_id, metric_date) ); ``` #### 8) `monitoring_citation_source_daily` — 引用来源日统计(AI 引用排名) ```sql CREATE TABLE monitoring_citation_source_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, metric_date DATE NOT NULL, source_platform VARCHAR(200) NOT NULL, -- 夸克 / 排行榜123网 / 什么值得买 source_type VARCHAR(50) NOT NULL, -- UGC / 编辑类 / 企业 / 参考资料 / 机构 / 其他 baidu_weight INT, -- 百度权重 0~10 citation_count INT NOT NULL DEFAULT 0, coverage_rate NUMERIC(5,4) NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_citation_source_daily UNIQUE (tenant_id, brand_id, source_platform, metric_date) ); ``` #### 9) `monitoring_citation_page_daily` — 引用页面明细日统计 ```sql CREATE TABLE monitoring_citation_page_daily ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, metric_date DATE NOT NULL, source_platform VARCHAR(200) NOT NULL, baidu_weight INT, page_url TEXT NOT NULL, citation_count INT NOT NULL DEFAULT 0, coverage_rate NUMERIC(5,4) NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_citation_page_daily UNIQUE (tenant_id, brand_id, page_url, metric_date) ); ``` #### 10) `monitoring_brand_impression` — 品牌印象词(词云数据) ```sql CREATE TABLE monitoring_brand_impression ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, brand_id BIGINT NOT NULL, metric_date DATE NOT NULL, word VARCHAR(100) NOT NULL, weight INT NOT NULL DEFAULT 1, -- 词频权重 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_impression_word UNIQUE (tenant_id, brand_id, word, metric_date) ); ``` ### 5.4 分区策略 | 表 | 分区方式 | 保留周期 | | --- | --- | --- | | `question_monitor_runs` | RANGE by `run_date`(月级) | 90 天 | | `question_monitor_parse_results` | 不分区(通过 `run_id` 关联) | 90 天 | | 所有 `monitoring_*` 汇总表 | 不分区(数据量可控) | 180 天+ | ### 5.5 索引策略 所有汇总表的 UNIQUE 约束自动创建复合索引,天然覆盖 Dashboard 的查询路径(`WHERE tenant_id = ? AND brand_id = ? AND metric_date BETWEEN ? AND ?`)。 额外索引: ```sql -- 主页趋势图和指标卡的高频查询 CREATE INDEX idx_brand_overview_range ON monitoring_brand_daily_overview(tenant_id, brand_id, metric_date DESC); -- 竞品分析页的高频查询 CREATE INDEX idx_competitor_range ON monitoring_competitor_daily(tenant_id, brand_id, metric_date DESC); -- 问题页排序 CREATE INDEX idx_question_daily_heat ON monitoring_question_daily(tenant_id, brand_id, metric_date DESC, heat_index DESC); ``` ## 6. 后端 API 设计 ### 6.1 路由规划 所有路由挂载在 `tenant` 认证组下,复用现有 JWT + TenantScope 中间件。 ``` GET /api/tenant/monitoring/dashboard/overview → 主页指标卡 GET /api/tenant/monitoring/dashboard/exposure-trend → 主页曝光趋势图 GET /api/tenant/monitoring/dashboard/competitors → 主页前五竞争对手 GET /api/tenant/monitoring/dashboard/citation-sources → 主页引用来源类型占比 + 前五引用平台 GET /api/tenant/monitoring/dashboard/recent-questions → 主页最新 AI 对话 GET /api/tenant/monitoring/dashboard/brand-impression → 主页品牌印象词云 GET /api/tenant/monitoring/platforms/stats → 平台占比 - 提及次数排名 GET /api/tenant/monitoring/platforms/:id/questions → 平台占比 - 各平台问题明细 GET /api/tenant/monitoring/competitors/exposure → 竞争对手 - 曝光度排名 GET /api/tenant/monitoring/competitors/detail → 竞争对手 - 排名情况明细 GET /api/tenant/monitoring/topics → 业务主题列表 GET /api/tenant/monitoring/topics/:id/citations → 业务主题 - 引用详情 GET /api/tenant/monitoring/questions → AI 对话问题列表(分页) GET /api/tenant/monitoring/questions/:id/effect → 问题效果详情 GET /api/tenant/monitoring/questions/:id/citations → 问题引用详情 GET /api/tenant/monitoring/citations/trend → AI 引用排名 - 来源平台趋势 GET /api/tenant/monitoring/citations/pages → AI 引用排名 - 引用页面列表 GET /api/tenant/monitoring/ai-platforms → AI 平台下拉列表 ``` ### 6.2 通用查询参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | | --- | --- | --- | --- | --- | | `brand_id` | int64 | 是 | - | 品牌 ID | | `ai_platform_id` | int64 | 否 | 0 (全部) | AI 平台筛选 | | `days` | int | 否 | 7 | 时间范围:7 / 30 / 90 | | `page` | int | 否 | 1 | 分页页码 | | `page_size` | int | 否 | 20 | 分页大小,最大 100 | ### 6.3 响应格式 复用现有 `response.Success` / `response.Error` 统一格式: ```json { "code": 0, "data": { ... }, "request_id": "req_abc123" } ``` ### 6.4 关键 API 响应结构 #### Dashboard Overview ```json { "exposure_rate": 0.6333, "mention_rate": 0.7500, "top1_mention_rate": 0.4800, "positive_rate": 0.8200, "sentiment_score": 83, "exposure_rate_change": 0.05, "mention_rate_change": -0.02 } ``` #### Competitors ```json { "items": [ { "rank": 1, "name": "lululemon", "is_self": true, "sentiment_score": 83, "sentiment_label": "excellent", "exposure_rate": 0.64 }, { "rank": 2, "name": "Nike", "is_self": false, "sentiment_score": 69, "sentiment_label": "normal", "exposure_rate": 0.47 } ] } ``` #### Questions (分页) ```json { "items": [ { "question_id": 101, "question_text": "瑜伽垫品牌推荐", "heat_index": 5, "exposure_rate": 1.0, "avg_position": 1, "sentiment_label": "excellent", "top3_brands": ["lululemon", "Alo Yoga", "Nike"] } ], "total": 42, "page": 1, "page_size": 20 } ``` ## 7. 后端架构 ### 7.1 服务层设计 新增文件与现有代码的关系: ``` server/internal/tenant/ ├── app/ │ ├── monitoring_service.go ← 新建:Dashboard 查询服务 │ ├── monitoring_collector.go ← 新建:AI 平台采集服务 │ ├── monitoring_aggregator.go ← 新建:日级聚合 Job │ ├── monitoring_parser.go ← 新建:回答解析器 │ ├── brand_service.go ← 已有:品牌 CRUD │ └── ... ├── transport/ │ ├── monitoring_handler.go ← 新建:监测 API Handler │ ├── router.go ← 修改:注册监测路由 │ └── ... └── repository/ └── queries/ ├── monitoring.sql ← 新建:监测查询 SQL └── ... ``` ### 7.2 MonitoringService 结构 ```go type MonitoringService struct { pool *pgxpool.Pool cache cache.Cache // 复用现有 cache.Cache 接口(Redis) } // Dashboard 查询方法(全部走缓存 → 汇总表路径) func (s *MonitoringService) DashboardOverview(ctx, tenantID, brandID, days) → DashboardOverviewResp func (s *MonitoringService) ExposureTrend(ctx, tenantID, brandID, days) → []TrendPoint func (s *MonitoringService) TopCompetitors(ctx, tenantID, brandID, days, limit) → []CompetitorItem func (s *MonitoringService) CitationSources(ctx, tenantID, brandID, days) → CitationSourceResp func (s *MonitoringService) RecentQuestions(ctx, tenantID, brandID, limit) → []RecentQuestionItem func (s *MonitoringService) BrandImpression(ctx, tenantID, brandID, days) → []ImpressionWord func (s *MonitoringService) PlatformStats(ctx, tenantID, brandID, days) → []PlatformStatItem func (s *MonitoringService) PlatformQuestions(ctx, tenantID, brandID, platformID, days, page) → PagedQuestions func (s *MonitoringService) CompetitorExposure(ctx, tenantID, brandID, days) → CompetitorExposureResp func (s *MonitoringService) CompetitorDetail(ctx, tenantID, brandID, days) → []CompetitorDetailItem func (s *MonitoringService) Topics(ctx, tenantID, brandID, platformID, days) → []TopicItem func (s *MonitoringService) Questions(ctx, tenantID, brandID, platformID, days, page, pageSize) → PagedQuestions func (s *MonitoringService) CitationTrend(ctx, tenantID, brandID, platformID, days) → []CitationTrendPoint func (s *MonitoringService) CitationPages(ctx, tenantID, brandID, days, page, pageSize) → PagedCitationPages func (s *MonitoringService) AIPlatforms(ctx, tenantID) → []AIPlatform ``` ### 7.3 MonitoringCollector 结构 ```go type MonitoringCollector struct { pool *pgxpool.Pool llmClients map[string]llm.Client // 每个 AI 平台一个 LLM Client objectStorage objectstorage.Client // 原始回答存 MinIO logger *zap.Logger } // 采集流程 func (c *MonitoringCollector) RunDailyCollection(ctx, tenantID, brandID) error func (c *MonitoringCollector) CollectSingle(ctx, question, platform) (*MonitorRun, error) func (c *MonitoringCollector) ParseAnswer(ctx, run, rawAnswer) (*ParseResult, error) ``` ### 7.4 MonitoringAggregator 结构 ```go type MonitoringAggregator struct { pool *pgxpool.Pool cache cache.Cache logger *zap.Logger } // 聚合 Job(通常每小时或每天运行一次) func (a *MonitoringAggregator) AggregateDaily(ctx, tenantID, brandID, date) error func (a *MonitoringAggregator) InvalidateCache(ctx, tenantID, brandID) error ``` ### 7.5 Bootstrap 集成 在 `bootstrap.go` 的 `App` 结构中添加: ```go type App struct { // ... 现有字段 ... MonitoringCollector *app.MonitoringCollector MonitoringAggregator *app.MonitoringAggregator } ``` 在 `New()` 中初始化新的 LLM clients(每个 AI 平台一个),注入到 Collector。 ### 7.6 配置扩展 在 `config.yaml` 中新增: ```yaml monitoring: # 采集配置 collection: daily_run_count: 1 # 每题每天默认执行次数 worker_concurrency: 5 # 并发采集 worker 数 question_timeout: 30s # 单次采集超时 batch_size: 50 # 每批提交数量 # 聚合配置 aggregation: schedule: "0 3 * * *" # 每天凌晨 3 点聚合 lookback_days: 1 # 回溯天数 # AI 平台 API platforms: deepseek: base_url: "https://api.deepseek.com/v1" api_key: "" model: "deepseek-chat" qwen: base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1" api_key: "" model: "qwen-max" doubao: base_url: "https://ark.cn-beijing.volces.com/api/v3" api_key: "" model: "doubao-seed-2-0-lite-260215" hunyuan: base_url: "https://hunyuan.tencentcloudapi.com" api_key: "" model: "hunyuan-lite" ernie: base_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1" api_key: "" model: "ernie-4.0-8k" # 缓存配置 cache: overview_ttl: 5m trend_ttl: 15m competitor_ttl: 15m topic_ttl: 30m question_ttl: 10m citation_ttl: 15m ``` ## 8. 缓存策略(5 万并发核心) ### 8.1 缓存架构 ``` Browser → CDN (静态资源) → Nginx → Go API → Redis Cache → PostgreSQL ↑ 90%+ 命中率 直接返回 ``` ### 8.2 缓存 Key 设计 复用 V1 的 Key 格式,扩展到新维度: ``` mon:{tenant}:{brand}:overview:{days} → 指标卡 mon:{tenant}:{brand}:trend:{days} → 趋势图 mon:{tenant}:{brand}:competitors:{days} → 竞品排名 mon:{tenant}:{brand}:citation_sources:{days} → 引用来源 mon:{tenant}:{brand}:recent_questions → 最新对话 mon:{tenant}:{brand}:impression:{days} → 品牌印象 mon:{tenant}:{brand}:platform_stats:{days} → 平台统计 mon:{tenant}:{brand}:platform:{pid}:questions:{days}:{page} → 平台问题 mon:{tenant}:{brand}:comp_exposure:{days} → 竞品曝光 mon:{tenant}:{brand}:topics:{pid}:{days} → 业务主题 mon:{tenant}:{brand}:questions:{pid}:{days}:{page} → 对话问题列表 mon:{tenant}:{brand}:citation_trend:{pid}:{days} → 引用趋势 mon:{tenant}:{brand}:citation_pages:{days}:{page} → 引用页面 ``` ### 8.3 缓存 TTL 矩阵 | 数据类型 | TTL | 原因 | | --- | --- | --- | | Overview 指标卡 | 5 min | 高频访问入口,需要相对实时 | | 趋势图 | 15 min | 日级数据变化慢 | | 竞品排名 | 15 min | 日级数据 | | 引用来源 / 引用页面 | 15 min | 日级聚合 | | 业务主题 | 30 min | 低频变更 | | 问题列表 | 10 min | 带分页,命中粒度细 | | 品牌印象 | 30 min | 日级聚合 | | AI 平台列表 | 1 hour | 几乎不变 | ### 8.4 缓存失效策略 1. **TTL 自动过期**:主要策略,简单可靠。 2. **聚合 Job 主动失效**:`MonitoringAggregator.InvalidateCache()` 在每次聚合完成后删除对应品牌的所有缓存 Key。 3. **不做 Cache-Aside Write-Through**:汇总表是离线 Job 写入的,不是用户请求触发的,因此只需被动读取缓存。 ### 8.5 50000 并发容量分析 | 层 | 单机容量 | 5 万并发时 QPS 估算 | 需要实例数 | | --- | --- | --- | --- | | Go API | 20K+ 并发连接 | ~10K QPS | 3 实例 | | Redis | 100K+ QPS | ~10K QPS(90% 命中) | 1 主实例 | | PostgreSQL | 5K QPS | ~1K QPS(缓存穿透) | 1 主 + 1 只读副本 | > 假设:平均用户每 30 秒发起 1 次 API 请求 → 50000/30 ≈ 1700 QPS;峰值 3x → ~5000 QPS。Redis 缓存承接 90%+ → PG 仅 ~500 QPS。 ## 9. 前端架构 ### 9.1 页面结构 ``` apps/admin-web/src/ ├── views/monitoring/ │ ├── MonitoringDashboardView.vue ← 主页 │ ├── MonitoringPlatformView.vue ← 平台占比分析 │ ├── MonitoringCompetitorView.vue ← 竞争对手分析 │ ├── MonitoringTopicView.vue ← 业务主题分析 │ ├── MonitoringQuestionsView.vue ← AI 对话问题 │ └── MonitoringCitationView.vue ← AI 引用排名 ├── components/monitoring/ │ ├── ExposureTrendChart.vue ← ECharts 折线图 │ ├── CompetitorBarChart.vue ← 分组柱状图 │ ├── PlatformHorizontalBar.vue ← 横向柱状图 │ ├── CitationDonutChart.vue ← 环形图 │ ├── CitationTrendChart.vue ← 引用趋势折线图 │ ├── BrandImpressionCloud.vue ← 词云 │ ├── HeatIndicator.vue ← 热度指示器(IIIII) │ ├── SentimentBadge.vue ← 情感标签(优秀/一般) │ ├── CompetitorRankTable.vue ← 竞品排名表格 │ └── MonitoringFilters.vue ← 通用筛选栏(品牌+平台+时间) └── lib/ └── api.ts ← 修改:新增 monitoringApi ``` ### 9.2 路由配置 在 `router/index.ts` 中新增,替换现有的 `/tracking` FeatureStubView: ```typescript // 数据追踪 - 主路由组 { path: '/monitoring', name: 'monitoring', redirect: '/monitoring/dashboard', children: [ { path: 'dashboard', name: 'monitoring-dashboard', component: MonitoringDashboardView }, { path: 'platforms', name: 'monitoring-platforms', component: MonitoringPlatformView }, { path: 'competitors', name: 'monitoring-competitors', component: MonitoringCompetitorView }, { path: 'topics', name: 'monitoring-topics', component: MonitoringTopicView }, { path: 'questions', name: 'monitoring-questions', component: MonitoringQuestionsView }, { path: 'citations', name: 'monitoring-citations', component: MonitoringCitationView }, ] } ``` ### 9.3 Chart Library **选用 ECharts + vue-echarts**: - 已在现有 plan(Phase 18)中确定 - 原生中文支持,社区生态成熟 - 支持所有所需图表类型:折线图、分组柱状图、横向柱状图、环形图 - 词云使用 `echarts-wordcloud` 扩展 依赖添加: ```json { "echarts": "^5.5", "vue-echarts": "^7.0", "echarts-wordcloud": "^2.1" } ``` ### 9.4 Shared Types 在 `packages/shared-types/src/index.ts` 中新增接口: ```typescript // AI 平台 export interface AIPlatform { id: number name: string icon: string provider_key: string } // 主页 export interface MonitoringOverview { exposure_rate: number mention_rate: number top1_mention_rate: number positive_rate: number sentiment_score: number exposure_rate_change: number mention_rate_change: number } export interface TrendPoint { date: string exposure_rate: number brands: Array<{ name: string; exposure_rate: number }> } export interface CompetitorRankItem { rank: number name: string is_self: boolean icon?: string sentiment_score: number sentiment_label: string exposure_rate: number } export interface CitationSourceItem { source_platform: string source_type: string baidu_weight: number citation_count: number coverage_rate: number } export interface MonitoringQuestionItem { question_id: number question_text: string heat_index: number exposure_rate: number avg_position: number sentiment_label: string top3_brands: string[] } export interface CitationPageItem { source_platform: string baidu_weight: number page_url: string citation_count: number coverage_rate: number } export interface ImpressionWord { word: string weight: number } export interface PlatformStatItem { ai_platform_id: number platform_name: string platform_icon: string mention_count: number avg_position: number exposure_rate: number } export interface TopicItem { topic_id: number topic_name: string question_count: number exposure_rate: number mention_count: number } ``` ### 9.5 API Client ```typescript export const monitoringApi = { // 主页 dashboardOverview: (brandId: number, days = 7) => http.get('/monitoring/dashboard/overview', { params: { brand_id: brandId, days } }), exposureTrend: (brandId: number, days = 7) => http.get('/monitoring/dashboard/exposure-trend', { params: { brand_id: brandId, days } }), topCompetitors: (brandId: number, days = 7) => http.get('/monitoring/dashboard/competitors', { params: { brand_id: brandId, days } }), citationSources: (brandId: number, days = 7) => http.get<{ type_distribution: ..., top_platforms: CitationSourceItem[] }>('/monitoring/dashboard/citation-sources', { params: { brand_id: brandId, days } }), recentQuestions: (brandId: number) => http.get('/monitoring/dashboard/recent-questions', { params: { brand_id: brandId } }), brandImpression: (brandId: number, days = 7) => http.get('/monitoring/dashboard/brand-impression', { params: { brand_id: brandId, days } }), // 平台占比 platformStats: (brandId: number, days = 7) => http.get('/monitoring/platforms/stats', { params: { brand_id: brandId, days } }), platformQuestions: (brandId: number, platformId: number, days = 7, page = 1) => http.get>(`/monitoring/platforms/${platformId}/questions`, { params: { brand_id: brandId, days, page } }), // 竞争对手 competitorExposure: (brandId: number, days = 7) => http.get('/monitoring/competitors/exposure', { params: { brand_id: brandId, days } }), competitorDetail: (brandId: number, days = 7) => http.get('/monitoring/competitors/detail', { params: { brand_id: brandId, days } }), // 业务主题 topics: (brandId: number, platformId = 0, days = 7) => http.get('/monitoring/topics', { params: { brand_id: brandId, ai_platform_id: platformId, days } }), // AI 对话问题 questions: (brandId: number, params: { ai_platform_id?: number; days?: number; page?: number; page_size?: number }) => http.get>('/monitoring/questions', { params: { brand_id: brandId, ...params } }), // AI 引用排名 citationTrend: (brandId: number, platformId = 0, days = 7) => http.get('/monitoring/citations/trend', { params: { brand_id: brandId, ai_platform_id: platformId, days } }), citationPages: (brandId: number, days = 7, page = 1, pageSize = 20) => http.get>('/monitoring/citations/pages', { params: { brand_id: brandId, days, page, page_size: pageSize } }), // 通用 aiPlatforms: () => http.get('/monitoring/ai-platforms'), } ``` ## 10. 采集流水线设计 ### 10.1 采集流程 ``` ┌──────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐ │ 调度器 │ │ Redis Stream │ │ Collector │ │ Parser │ │ (cron) │────▶│ / 内部队列 │────▶│ Worker ×5 │────▶│ (解析回答) │ │ 生成任务 │ │ │ │ 调用 AI API │ │ 提取指标 │ └──────────┘ └───────────────┘ └──────┬───────┘ └──────┬───────┘ │ │ ▼ ▼ ┌─────────────┐ ┌──────────────┐ │ MinIO │ │ PostgreSQL │ │ (原始回答) │ │ (解析结果) │ └─────────────┘ └──────────────┘ ``` ### 10.2 调度策略 ```go // 每天凌晨 1 点执行 func (c *MonitoringCollector) RunDailyCollection(ctx context.Context, tenantID int64) error { // 1. 查询该租户所有 active 品牌 brands := listActiveBrands(ctx, tenantID) for _, brand := range brands { // 2. 查询品牌下所有 active 问题(含 question_hash 去重) questions := listUniqueActiveQuestions(ctx, brand.ID) // 3. 查询所有 active AI 平台 platforms := listActivePlatforms(ctx, tenantID) // 4. 生成采集任务 = questions × platforms for _, q := range questions { for _, p := range platforms { enqueueTask(ctx, brand, q, p) } } } } ``` ### 10.3 回答解析逻辑 ```go func (c *MonitoringCollector) ParseAnswer(ctx context.Context, question, brand string, competitors []string, rawAnswer string) *ParseResult { // 1. 品牌提及检测:在回答中查找品牌名出现位置 // 2. 首位提及检测:品牌是否出现在推荐列表第一位 // 3. 首选推荐检测:品牌是否被明确推荐 // 4. 情感分析:使用 LLM 对品牌相关段落做情感判断 // 5. 竞品提及检测:检测 competitors 列表中的品牌出现情况 // 6. 引用 URL 提取:从回答中提取所有外部链接 // 7. 系统文章引用匹配:与 articles 表做 URL/标题匹配 // 8. 品牌印象词提取:使用 LLM 从回答中提取品牌相关形容词 } ``` ### 10.4 聚合 Job ```go // 每天凌晨 3 点执行(在采集完成后) func (a *MonitoringAggregator) AggregateDaily(ctx context.Context, date time.Time) error { tenants := listAllTenants(ctx) for _, t := range tenants { brands := listActiveBrands(ctx, t.ID) for _, b := range brands { // 1. 从 parse_results 聚合 → monitoring_brand_daily_overview a.aggregateBrandOverview(ctx, t.ID, b.ID, date) // 2. 聚合 → monitoring_platform_daily / monitoring_platform_question_daily a.aggregatePlatformStats(ctx, t.ID, b.ID, date) // 3. 聚合 → monitoring_competitor_daily / monitoring_competitor_platform_daily a.aggregateCompetitorStats(ctx, t.ID, b.ID, date) // 4. 聚合 → monitoring_topic_daily a.aggregateTopicStats(ctx, t.ID, b.ID, date) // 5. 聚合 → monitoring_question_daily a.aggregateQuestionStats(ctx, t.ID, b.ID, date) // 6. 聚合 → monitoring_citation_source_daily / monitoring_citation_page_daily a.aggregateCitationStats(ctx, t.ID, b.ID, date) // 7. 聚合 → monitoring_brand_impression a.aggregateBrandImpression(ctx, t.ID, b.ID, date) // 8. 清除该品牌缓存 a.InvalidateCache(ctx, t.ID, b.ID) } } } ``` ## 11. 部署拓扑(5 万并发) ### 11.1 推荐部署架构 ``` ┌───────────────────────────┐ │ CDN / 云 LB (SLB) │ │ 静态资源 + API 路由 │ └─────────────┬─────────────┘ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ tenant-api│ │ tenant-api│ │ tenant-api│ ×3 (API 服务) │ Pod/容器 │ │ Pod/容器 │ │ Pod/容器 │ └────┬──────┘ └────┬──────┘ └────┬──────┘ │ │ │ ┌────┴─────────────┴─────────────┴────┐ │ Redis 6+ (单主) │ 缓存 + 会话 │ 或 Redis Cluster │ └───────────────────┬──────────────────┘ │ ┌───────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ PG Primary │ │ PG Replica │ │ MinIO │ │ (读写) │────▶│ (只读) │ │ (原始回答) │ └───────────┘ └───────────┘ └───────────┘ ╔════════════════════════════════════════════════════════╗ ║ 独立进程 / CronJob(不接入 LB) ║ ║ ║ ║ ┌─────────────────┐ ┌─────────────────┐ ║ ║ │ Collector Worker │ │ Aggregator Job │ ║ ║ │ (采集 AI 平台) │ │ (日级聚合) │ ║ ║ └─────────────────┘ └─────────────────┘ ║ ╚════════════════════════════════════════════════════════╝ ``` ### 11.2 资源估算 | 组件 | 规格建议 | 说明 | | --- | --- | --- | | API 实例 ×3 | 2C4G | Go 服务轻量,3 实例可处理 ~60K 并发连接 | | Redis | 4G 内存 | 缓存数据量不大,主要是 Key 数量 | | PostgreSQL Primary | 4C16G | 汇总表小,索引少,500 QPS 绰绰有余 | | PostgreSQL Replica ×1 | 4C16G | 可选,用于聚合 Job 的重查询分流 | | MinIO | 50G+ | 原始回答存储,按 90 天保留 | | Collector Worker ×1 | 2C4G | 采集任务非实时,单实例 5 并发即可 | | Aggregator Job ×1 | 2C4G | 凌晨运行,单实例即可 | ### 11.3 数据库连接池配置 ```yaml database: max_open_conns: 50 # 从 25 提升到 50(3 个 API 实例共享) max_idle_conns: 10 ``` 按 3 个 API 实例计算:每实例 ~17 连接上限,足够应对缓存穿透后的查询。 ## 12. 数据量估算 ### 12.1 汇总表大小 假设:100 个品牌,每品牌 5 个竞品,5 个 AI 平台,10 个主题,30 天窗口。 | 汇总表 | 行数/天 | 30 天行数 | 估算大小 | | --- | --- | --- | --- | | `monitoring_brand_daily_overview` | 100 | 3,000 | < 1 MB | | `monitoring_platform_daily` | 500 | 15,000 | < 5 MB | | `monitoring_platform_question_daily` | 50,000 | 1,500,000 | ~200 MB | | `monitoring_competitor_daily` | 600 | 18,000 | < 5 MB | | `monitoring_competitor_platform_daily` | 3,000 | 90,000 | ~15 MB | | `monitoring_topic_daily` | 1,000 | 30,000 | < 5 MB | | `monitoring_question_daily` | 50,000 | 1,500,000 | ~250 MB | | `monitoring_citation_source_daily` | 5,000 | 150,000 | ~25 MB | | `monitoring_citation_page_daily` | 10,000 | 300,000 | ~60 MB | | `monitoring_brand_impression` | 5,000 | 150,000 | ~20 MB | **30 天汇总表总计 < 600 MB**,PostgreSQL 完全可控。 ### 12.2 原始采集表大小 ``` 100 品牌 × 100 问题 × 5 平台 × 1 次/天 = 50,000 条/天 90 天保留 = 4,500,000 条原始记录 每条约 1KB(不含原始回答文本)→ ~4.5 GB 原始回答文本(~5KB/条)→ MinIO 约 22 GB/90 天 ``` ## 13. 实施计划 ### Phase 15: 数据库迁移 + AI 平台注册表 - 创建所有新表的 migration 文件 - 种子数据:5 个默认 AI 平台 - 估时:1 天 ### Phase 16: 后端 - 监测查询服务 + API - 实现 `MonitoringService` 的所有查询方法 - 实现 `monitoring_handler.go` 的所有路由 - 实现 Redis 缓存层 - 注册路由到 `router.go` - 估时:3 天 ### Phase 17: 后端 - 采集流水线 - 实现 `MonitoringCollector` + `MonitoringParser` - 对接 5 个 AI 平台 API(复用现有 `llm.Client` 接口) - 原始回答存储 MinIO - 估时:3 天 ### Phase 18: 后端 - 聚合 Job - 实现 `MonitoringAggregator` 的 8 个聚合函数 - 实现缓存失效逻辑 - 实现定时调度(可先用 in-process cron) - 估时:2 天 ### Phase 19: 种子数据 - 生成 30 天真实结构的 mock 数据,覆盖所有汇总表 - 确保前端开发可立即使用 - 估时:1 天 ### Phase 20: 前端 - 6 页面 + ECharts - 安装 echarts / vue-echarts / echarts-wordcloud - 实现 6 个页面 + 10 个图表组件 - 实现通用筛选栏组件 - i18n 扩展 - 估时:4 天 ### Phase 21: 前端验证 + 集成测试 - `pnpm typecheck:admin && pnpm build:admin` 通过 - `go test ./...` 通过 - 全链路验证:种子数据 → API → 前端图表渲染 - 估时:1 天 **总估时:约 15 个工作日** ## 14. 风险与决策项 ### 14.1 待确认 | 序号 | 决策项 | 影响范围 | 建议 | | --- | --- | --- | --- | | 1 | 各 AI 平台 API Key 如何获取和管理 | 采集流水线 | 建议 config.yaml 配置,生产环境用环境变量覆盖 | | 2 | 业务主题的分类方式(人工还是 LLM 自动归类) | 主题分析页 | V1 建议人工创建主题并关联问题,V2 可自动归类 | | 3 | 百度权重数据来源 | 引用排名页 | 建议接入第三方 SEO API 或定期手动维护 | | 4 | 情感分析的准确度要求 | 竞品分析 + 问题列表 | 建议先用规则匹配,重要场景用 LLM 二次判断 | | 5 | 是否需要支持自定义 AI 平台 | 平台管理 | V1 建议固定 5 个平台,用户不可增删 | | 6 | 部署环境选择(K8s / Docker Compose / 裸机) | 部署架构 | 开发阶段 Docker Compose,生产建议 K8s | ### 14.2 已确认(沿用 V1 决策) | 决策 | 来源 | | --- | --- | | 问题是最小监控采集单元 | V1 核心结论 | | 原始回答存对象存储,不存 PG | V1 Section 8.2 | | 每问题每天默认执行 1 次 | V1 Section 8.3 | | 问题 hash 去重 | V1 Section 8.4 | | 问题版本化保证历史口径 | V1 Section 9 | | 页面只查汇总表 | V1 Section 10 | | 原始记录保留 90 天 | V1 Section 12 | ## 15. 附录 ### 15.1 已有表 DDL 参考 ```sql -- brands(已存在) CREATE TABLE brands ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), name VARCHAR(200) NOT NULL, description TEXT, status VARCHAR(20) NOT NULL DEFAULT 'active', ... ); -- brand_keywords(已存在) CREATE TABLE brand_keywords ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), brand_id BIGINT NOT NULL REFERENCES brands(id), name VARCHAR(200) NOT NULL, ... ); -- brand_questions(已存在) CREATE TABLE brand_questions ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), brand_id BIGINT NOT NULL REFERENCES brands(id), keyword_id BIGINT NOT NULL REFERENCES brand_keywords(id), current_version_id BIGINT, ... ); -- brand_question_versions(已存在) CREATE TABLE brand_question_versions ( id BIGSERIAL PRIMARY KEY, question_id BIGINT NOT NULL REFERENCES brand_questions(id), question_text TEXT NOT NULL, question_hash VARCHAR(64) NOT NULL, version_no INT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, ... ); -- competitors(已存在) CREATE TABLE competitors ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL REFERENCES tenants(id), brand_id BIGINT NOT NULL REFERENCES brands(id), name VARCHAR(200) NOT NULL, website TEXT, description TEXT, product_lines_json JSONB, ... ); ``` ### 15.2 指标口径定义 | 指标 | 计算公式 | 说明 | | --- | --- | --- | | 曝光度 (exposure_rate) | 品牌在 AI 回答中被提及的问题数 / 采集的总问题数 | 反映品牌在 AI 平台的总体可见度 | | 提及率 (mention_rate) | 品牌被提及的回答次数 / 总提问次数 | 跨平台汇总 | | 首位提及率 (top1_mention_rate) | 品牌出现在推荐列表首位的次数 / 总提问次数 | 衡量品牌在 AI 推荐中的优先级 | | 首选推荐率 (first_recommend_rate) | 品牌被明确推荐的次数 / 总提问次数 | 衡量 AI 对品牌的推荐倾向 | | 正面提及率 (positive_mention_rate) | 正面情感的提及次数 / 总提及次数 | 衡量品牌的 AI 口碑 | | 情感得分 (sentiment_score) | 正面提及占比 × 100,修正后 0~100 | 综合情感指标 | | 热度指数 (heat_index) | 近期被触发的 AI 问答频次标准化后 1~5 档 | 问题热度 | | 覆盖率 (coverage_rate) | 该来源被引用的问题数 / 总问题数 | 引用来源的覆盖广度 |