1349 lines
53 KiB
Markdown
1349 lines
53 KiB
Markdown
|
|
# 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<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) | 该来源被引用的问题数 / 总问题数 | 引用来源的覆盖广度 |
|