Files
geo/docs/ai-brand-monitoring-tech-design-v2.md
T

1349 lines
53 KiB
Markdown
Raw Normal View History

# 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 QPS90% 命中) | 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**
- 已在现有 planPhase 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<MonitoringOverview>('/monitoring/dashboard/overview', { params: { brand_id: brandId, days } }),
exposureTrend: (brandId: number, days = 7) =>
http.get<TrendPoint[]>('/monitoring/dashboard/exposure-trend', { params: { brand_id: brandId, days } }),
topCompetitors: (brandId: number, days = 7) =>
http.get<CompetitorRankItem[]>('/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<MonitoringQuestionItem[]>('/monitoring/dashboard/recent-questions', { params: { brand_id: brandId } }),
brandImpression: (brandId: number, days = 7) =>
http.get<ImpressionWord[]>('/monitoring/dashboard/brand-impression', { params: { brand_id: brandId, days } }),
// 平台占比
platformStats: (brandId: number, days = 7) =>
http.get<PlatformStatItem[]>('/monitoring/platforms/stats', { params: { brand_id: brandId, days } }),
platformQuestions: (brandId: number, platformId: number, days = 7, page = 1) =>
http.get<Paged<MonitoringQuestionItem>>(`/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<TopicItem[]>('/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<Paged<MonitoringQuestionItem>>('/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<Paged<CitationPageItem>>('/monitoring/citations/pages', { params: { brand_id: brandId, days, page, page_size: pageSize } }),
// 通用
aiPlatforms: () => http.get<AIPlatform[]>('/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) | 该来源被引用的问题数 / 总问题数 | 引用来源的覆盖广度 |