- 完成 T-001A: 前端项目初始化 (Next.js 14 + TypeScript + Tailwind CSS) - 完成 T-001B: 后端项目初始化 (FastAPI + SQLAlchemy + asyncpg) - 完成 T-002: 数据库配置 (KolVideo 模型 + 索引 + 测试) - 完成 T-003: 基础 UI 框架 (Header/Footer 组件 + 品牌色系) - 完成 T-004: 环境变量配置 (前后端环境变量) Co-Authored-By: Claude <noreply@anthropic.com>
53 KiB
53 KiB
KOL Insight - 开发计划
文档信息
| 项目 | 内容 |
|---|---|
| 版本 | v1.0 |
| 创建日期 | 2025-01-28 |
| 来源文档 | FeatureSummary.md |
1. 项目概述
1.1 项目目标
| 目标 | 指标 | 衡量方式 |
|---|---|---|
| 提升查询效率 | 单次可批量查询多个 KOL | 对比手动查询耗时 |
| 降低计算错误 | 自动计算预估指标准确率 100% | 人工抽检验证 |
| 提高数据可用性 | 支持数据导出 | 导出功能完整性 |
1.2 技术栈
| 层级 | 技术选型 | 版本 | 说明 |
|---|---|---|---|
| 前端框架 | Next.js (App Router) | 14.x | React 框架,纯前端渲染 |
| UI 框架 | React + Tailwind CSS | - | 组件化开发,响应式设计 |
| 后端框架 | Python FastAPI | 0.104+ | 高性能异步 Web 框架 |
| 数据库 | PostgreSQL | 14.x+ | 关系型数据库,存储 KOL 视频数据 |
| Python ORM | SQLAlchemy + asyncpg | 2.0+ | 异步 ORM,类型安全的数据库访问 |
| API 文档 | FastAPI 自动生成 | - | Swagger UI + ReDoc |
| 前端部署 | Docker / Vercel | - | 容器化或 Serverless |
| 后端部署 | Docker + Uvicorn | - | ASGI 服务器 |
| 包管理(前端) | pnpm | 8.x | 高效的包管理器 |
| 包管理(后端) | Poetry / pip | - | Python 依赖管理 |
1.3 开发原则
- 简单优先: 优先选择简单直接的实现方案
- 类型安全: 前端使用 TypeScript,后端使用 Pydantic 类型验证
- 组件化: UI 组件化开发,便于复用和维护
- API 设计: RESTful 风格,清晰的接口契约
- 错误处理: 完善的错误处理和用户提示
- 安全防护: SQL 注入防护,CORS 配置,环境变量管理敏感配置
- 前后端分离: 前端专注 UI 展示,后端专注业务逻辑和数据处理
- 异步优先: 后端使用异步编程,提升并发性能
2. 技术架构
2.1 系统架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Web Browser │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 查询页面 │ │ 结果展示 │ │ 导出操作 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
│ HTTP/HTTPS (API 调用)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Next.js 前端应用层 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ App Router (纯前端) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ React 组件 │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ QueryForm │ │ ResultTable │ │ ExportButton │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
│ HTTP API 调用 (跨域)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ FastAPI 后端应用层 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ API Router │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ RESTful API │ │ │
│ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │
│ │ │ │ POST /api/v1/ │ │ GET /api/v1/ │ │ │ │
│ │ │ │ query │ │ export │ │ │ │
│ │ │ └─────────────────┘ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ 业务逻辑层 (Python) │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ 查询服务 │ │ 计算服务 │ │ 导出服务 │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ 品牌API │ │ 数据库层 │ │ │ │
│ │ │ │ 集成服务 │ │ (SQLAlchemy)│ │ │ │
│ │ │ └───────────┘ └───────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ │
│ SQL (asyncpg) │ HTTP
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ 数据层 │ │ 外部服务 │
│ ┌───────────────────────┐ │ │ ┌───────────────────────┐ │
│ │ PostgreSQL │ │ │ │ 品牌 API │ │
│ │ ┌─────────────────┐ │ │ │ │ /v1/yuntu/brands/ │ │
│ │ │ kol_videos │ │ │ │ └───────────────────────┘ │
│ │ │ (视频数据表) │ │ │ └─────────────────────────────┘
│ │ └─────────────────┘ │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
2.2 模块依赖图
┌────────────────────────────────────────────────────────────────┐
│ 前端模块 (Next.js) │
│ ┌──────────────┐ │
│ │ 页面组件 │ │
│ │ (Page) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 查询表单 │ ──▶ │ 结果表格 │ ──▶ │ 导出按钮 │ │
│ │ 组件 │ │ 组件 │ │ 组件 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────┘
│ │ │
│ HTTP API │ 数据展示 │ HTTP API
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────┐
│ 后端模块 (FastAPI) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 查询 API │ ──▶ │ 计算服务 │ │ 导出 API │ │
│ │ POST /api/v1/│ │ (Python) │ │ GET /api/v1/ │ │
│ │ query │ │ │ │ export │ │
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
│ │ ▲ │ │
│ ▼ │ │ │
│ ┌──────────────┐ │ │ │
│ │ 品牌API服务 │────────────┘ │ │
│ │ (F-010) │ │ │
│ │ (httpx异步) │ │ │
│ └──────┬───────┘ │ │
│ │ │ │
│ └─────────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 数据库服务 │ │
│ │ (SQLAlchemy) │ │
│ └──────┬───────┘ │
└────────────────────────────┼───────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ PostgreSQL │ │ 品牌 API │
│ │ │ (外部服务) │
└──────────────┘ └──────────────┘
2.3 数据流图
用户操作 前端 后端 数据库/外部API
│ │ │ │
│ 1. 输入查询条件 │ │ │
│ ─────────────────────▶ │ │ │
│ │ │ │
│ │ 2. POST /api/query │ │
│ │ ─────────────────────▶ │ │
│ │ │ │
│ │ │ 3. SELECT FROM kol_videos
│ │ │ ─────────────────────────▶│
│ │ │ │
│ │ │ 4. 返回视频数据 │
│ │ │ ◀─────────────────────────│
│ │ │ │
│ │ │ 5. 提取唯一 brand_id │
│ │ │ (去重处理) │
│ │ │ │
│ │ │ 6. 批量 GET brands │
│ │ │ (并发10,超时3s) │
│ │ │ ─────────────────────────▶│
│ │ │ │
│ │ │ 7. 返回品牌名称映射 │
│ │ │ ◀─────────────────────────│
│ │ │ │
│ │ │ 8. 填充品牌名称 │
│ │ │ (失败则降级显示ID) │
│ │ │ │
│ │ │ 9. 计算预估指标 │
│ │ │ (CPM/看后搜人数/成本) │
│ │ │ │
│ │ 10. 返回完整数据 │ │
│ │ ◀───────────────────── │ │
│ │ │ │
│ 11. 展示结果列表 │ │ │
│ ◀───────────────────── │ │ │
│ │ │ │
│ 12. 点击导出 │ │ │
│ ─────────────────────▶ │ │ │
│ │ 13. GET /api/export │ │
│ │ ─────────────────────▶ │ │
│ │ │ │
│ │ 14. 返回 Excel/CSV │ │
│ │ ◀───────────────────── │ │
│ │ │ │
│ 15. 下载文件 │ │ │
│ ◀───────────────────── │ │ │
3. 开发阶段
3.1 阶段时间线
Phase 1 Phase 2 Phase 3
│ │ │
基础架构搭建 核心功能开发 优化与测试
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 项目初始 │ ──────▶ │ 功能实现 │ ──────▶ │ 优化部署 │
│ 化配置 │ │ 与集成 │ │ 与测试 │
└─────────┘ └─────────┘ └─────────┘
交付物: 交付物: 交付物:
• 项目骨架 • 查询功能 • 性能优化
• 数据库连接 • 计算逻辑 • 错误处理
• 基础 UI • 导出功能 • 部署配置
3.2 Phase 1: 基础架构搭建
目标: 完成项目初始化和基础设施配置
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|---|---|---|---|---|---|
| T-001 | 前端项目初始化 | 创建 Next.js 项目,配置 TypeScript、ESLint、Prettier | - | P0 | - |
| T-002 | 后端项目初始化 | 创建 FastAPI 项目,配置 Poetry/pip、项目结构 | - | P0 | - |
| T-003 | 数据库配置 | 配置 SQLAlchemy + asyncpg,定义数据模型,连接 PostgreSQL | T-002 | P0 | F-001~003 |
| T-004 | 基础 UI 框架 | 安装 Tailwind CSS,创建基础布局组件 | T-001 | P0 | F-007 |
| T-005 | 环境变量配置 | 配置前后端环境变量,数据库连接字符串,CORS 配置 | T-001, T-002 | P0 | - |
阶段依赖图:
T-001 (前端初始化) T-002 (后端初始化)
│ │
▼ ▼
T-004 (UI框架) T-003 (数据库)
│ │
└──────────┬───────────┘
▼
T-005 (环境变量)
3.3 Phase 2: 核心功能开发
目标: 实现所有核心功能(查询、计算、展示、导出)
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|---|---|---|---|---|---|
| T-006 | 查询 API 开发 (后端) | FastAPI 实现 POST /api/v1/query 接口,支持三种查询方式 | T-003 | P0 | F-001, F-002, F-003 |
| T-007 | 计算逻辑实现 (后端) | Python 实现 CPM、看后搜人数、成本计算 | T-006 | P0 | F-004, F-005, F-006 |
| T-008 | 品牌 API 批量集成 (后端) | 后端使用 httpx 批量异步调用品牌API,支持并发控制和降级 | T-006 | P0 | F-010 |
| T-009 | 导出 API 开发 (后端) | FastAPI 实现 GET /api/v1/export 接口,生成 Excel/CSV | T-007, T-008 | P1 | F-009 |
| T-010 | 查询表单组件 (前端) | React 组件开发,调用后端查询API | T-004 | P0 | F-001, F-002, F-003 |
| T-011 | 结果表格组件 (前端) | React 组件开发,显示26个字段,数据来自后端API | T-004, T-007, T-008 | P1 | F-007 |
| T-012 | 导出按钮组件 (前端) | React 组件开发,调用后端导出API触发下载 | T-011, T-009 | P1 | F-009 |
阶段依赖图:
后端任务:
T-006 (查询API) ──────▶ T-007 (计算逻辑) ──────▶ T-009 (导出API)
│ │ │
└──▶ T-008 (品牌API) │ │
│ │
前端任务: │ │
T-010 (查询表单) ─────────────┼───────────────────────┤
▼ ▼
T-011 (结果表格) ──────▶ T-012 (导出按钮)
3.4 Phase 3: 优化与测试
目标: 性能优化、错误处理、部署配置
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|---|---|---|---|---|---|
| T-013 | 错误处理 (前后端) | 完善前后端错误处理,添加用户友好提示 | T-012 | P1 | 全部 |
| T-014 | 性能优化 (后端) | 数据库索引优化,异步查询性能调优 | T-012 | P1 | F-001~003 |
| T-015 | 视频链接跳转 (前端) | 实现视频链接点击跳转功能 | T-011 | P2 | F-008 |
| T-016 | 部署配置 (前后端) | Docker 配置,前后端分离部署,CORS 配置 | T-013 | P1 | - |
| T-017 | 集成测试 | 端到端功能测试,前后端联调 | T-013 | P1 | 全部 |
阶段依赖图:
T-012 (导出按钮)
│
├──────────┬──────────┐
▼ ▼ ▼
T-013 T-014 T-015
(错误处理) (性能优化) (链接跳转)
│
├──────────┐
▼ ▼
T-016 T-017
(部署) (测试)
4. 技术方案
4.1 数据查询模块
功能: 支持三种查询方式(星图ID/达人ID/昵称)批量查询 KOL 视频数据
技术选型:
| 组件 | 技术 | 选型理由 |
|---|---|---|
| ORM | SQLAlchemy 2.0+ | 异步 ORM,类型安全,成熟稳定 |
| 异步驱动 | asyncpg | PostgreSQL 异步驱动,高性能 |
| 查询优化 | 数据库索引 | 在 star_id, star_unique_id, star_nickname 字段建立索引 |
| 输入验证 | Pydantic | FastAPI 内置,类型安全的请求参数验证 |
架构设计:
┌─────────────────────────────────────────────────────────────┐
│ 查询模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 请求解析 │ ──▶ │ 参数验证 │ ──▶ │ 查询构建 │ │
│ │ (type/values)│ │ (Zod) │ │ (Prisma) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 数据库查询 │ │
│ │ PostgreSQL │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
接口设计:
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 批量查询 | POST | /api/v1/query | 支持 star_id/unique_id/nickname 三种类型 |
请求/响应格式:
# 请求模型
from pydantic import BaseModel
from typing import List, Literal
class QueryRequest(BaseModel):
type: Literal['star_id', 'unique_id', 'nickname']
values: List[str] # 批量ID 或单个昵称
# 响应模型
class QueryResponse(BaseModel):
success: bool
data: List[VideoData]
total: int
error: str | None = None
实现要点:
- 使用 SQLAlchemy 的
select()配合where()条件 - 星图ID/达人ID 使用
in_()查询批量匹配 - 昵称使用
like()进行模糊匹配(使用%{value}%) - 限制单次查询最大返回 1000 条
- SQL 注入防护由 SQLAlchemy 和 Pydantic 自动处理
- 使用异步查询
session.execute(stmt)提升性能
4.2 数据计算模块
功能: 计算预估自然 CPM、看后搜人数、看后搜成本
技术选型:
| 组件 | 技术 | 选型理由 |
|---|---|---|
| 计算逻辑 | Python | 类型安全(Type Hints),易于维护 |
| 数值处理 | Python 原生 | 简单计算,无需额外库,性能优异 |
架构设计:
┌─────────────────────────────────────────────────────────────┐
│ 计算模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ │
│ │ 查询结果 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ CPM 计算 │ │看后搜人数 │ │ 成本计算 │ │
│ │ F-004 │ │ F-005 │ │ F-006 │ │
│ └───────────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │
│ │ 依赖 │ │
│ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 除零检查 │ │
│ │ natural_play_cnt = 0 → null │ │
│ │ total_play_cnt = 0 → null │ │
│ │ 看后搜人数 = 0 → null │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
计算公式:
# 预估自然CPM (F-004)
def calculate_natural_cpm(estimated_video_cost: float, natural_play_cnt: int) -> float | None:
if natural_play_cnt > 0:
return round((estimated_video_cost / natural_play_cnt) * 1000, 2)
return None
# 预估自然看后搜人数 (F-005)
def calculate_natural_search_uv(
natural_play_cnt: int,
total_play_cnt: int,
after_view_search_uv: int
) -> float | None:
if total_play_cnt > 0:
return round((natural_play_cnt / total_play_cnt) * after_view_search_uv, 2)
return None
# 预估自然看后搜人数成本 (F-006)
def calculate_natural_search_cost(
estimated_video_cost: float,
estimated_natural_search_uv: float | None
) -> float | None:
if estimated_natural_search_uv and estimated_natural_search_uv > 0:
return round(estimated_video_cost / estimated_natural_search_uv, 2)
return None
实现要点:
- 结果保留2位小数:
round(value, 2) - 除零检查:分母为0时返回 None
- None 值在前端显示为 "-"
- 批量计算时使用列表推导式或 map 函数
- 使用 Python 3.10+ 的
float | None类型注解
4.3 数据展示模块
功能: 以表格形式展示查询结果,支持视频链接跳转
技术选型:
| 组件 | 技术 | 选型理由 |
|---|---|---|
| 表格组件 | HTML Table + Tailwind | 简单直接,样式灵活 |
| 数据格式化 | Intl API | 数字格式化、日期格式化 |
架构设计:
┌─────────────────────────────────────────────────────────────┐
│ 展示模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 结果表格组件 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 表头 │ │ 数据行 │ │ 链接列 │ │ 分页 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 空状态组件 │ │
│ │ "未找到匹配数据" │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
实现要点:
- 26个字段使用中文列名
- 视频链接使用
<a target="_blank" rel="noopener noreferrer"> - 数字字段格式化:千分位分隔
- 空值显示为 "-"
- 支持横向滚动(表格宽度超出时)
4.4 数据导出模块
功能: 将查询结果导出为 Excel 或 CSV 文件
技术选型:
| 组件 | 技术 | 选型理由 |
|---|---|---|
| Excel 生成 | xlsx (SheetJS) | 成熟稳定,支持 .xlsx 格式 |
| CSV 生成 | 原生实现 | 简单格式,无需额外库 |
接口设计:
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 数据导出 | GET | /api/export | 参数:format=xlsx/csv,data=查询条件 |
实现要点:
- 使用中文列名作为表头
- Excel 格式使用 xlsx 库生成
- CSV 格式需处理逗号转义
- 文件名格式:
kol_data_${timestamp}.xlsx - 响应头设置
Content-Disposition: attachment - 限制单次导出最大 1000 条
4.5 品牌 API 批量集成
功能: 后端批量调用品牌API获取品牌名称(关联 F-010)
接口详情:
| 项目 | 内容 |
|---|---|
| 地址 | https://api.internal.intelligrow.cn/v1/yuntu/brands/{brand_id} |
| 方法 | GET |
| 响应 | 品牌名称 |
批量调用策略:
查询结果 (N条数据)
│
▼
┌─────────────────────────────────────┐
│ 1. 提取唯一 brand_id │
│ - 过滤空值 │
│ - 去重处理 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. 批量并发请求 │
│ - 并发限制: 10 个请求 │
│ - 单请求超时: 3 秒 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. 构建映射表 │
│ Map<brand_id, brand_name> │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 4. 填充查询结果 │
│ - 成功: 显示品牌名称 │
│ - 失败: 降级显示 brand_id │
└─────────────────────────────────────┘
实现要点:
- 在后端调用: 查询API获取数据库结果后,立即调用品牌API
- 去重处理: 提取查询结果中所有唯一的 brand_id,避免重复请求
- 并发控制: 使用 asyncio.gather 或 asyncio.Semaphore 限制最大 10 个并发请求
- 超时设置: 单个请求超时 3 秒,避免阻塞整体响应
- 降级策略: API 调用失败时,显示原始 brand_id
- 结果合并: 将品牌名称填充到查询结果后返回前端
import asyncio
import httpx
from typing import Dict, List
# 品牌名称批量获取
async def get_brand_names(brand_ids: List[str]) -> Dict[str, str]:
unique_ids = list(set(filter(None, brand_ids)))
brand_map: Dict[str, str] = {}
# 并发控制:限制 10 个并发
CONCURRENCY_LIMIT = 10
TIMEOUT_SECONDS = 3.0
async with httpx.AsyncClient(timeout=TIMEOUT_SECONDS) as client:
# 使用信号量控制并发数
semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)
async def fetch_brand(brand_id: str) -> tuple[str, str]:
async with semaphore:
try:
response = await client.get(
f"https://api.internal.intelligrow.cn/v1/yuntu/brands/{brand_id}"
)
if response.status_code == 200:
data = response.json()
return brand_id, data.get("name", brand_id)
except Exception:
pass # 降级处理
return brand_id, brand_id # 失败时返回原ID
# 批量并发请求
results = await asyncio.gather(
*[fetch_brand(brand_id) for brand_id in unique_ids],
return_exceptions=True
)
# 构建映射表
for result in results:
if isinstance(result, tuple):
brand_id, brand_name = result
brand_map[brand_id] = brand_name
return brand_map
# 在查询 API 中使用
async def handle_query(request: QueryRequest) -> QueryResponse:
# 1. 查询数据库
videos = await query_videos(request)
# 2. 提取品牌ID并批量获取品牌名称
brand_ids = [v.brand_id for v in videos if v.brand_id]
brand_map = await get_brand_names(brand_ids)
# 3. 填充品牌名称
for video in videos:
if video.brand_id:
video.brand_name = brand_map.get(video.brand_id, video.brand_id)
else:
video.brand_name = None
# 4. 计算预估指标并返回
return calculate_and_format(videos)
5. 风险管理
| 风险 | 可能性 | 影响 | 应对措施 | 负责人 |
|---|---|---|---|---|
| 数据库连接不稳定 | 低 | 高 | 使用 Prisma 连接池,实现重试机制 | 后端开发 |
| 大批量查询性能问题 | 中 | 中 | 限制单次查询上限,优化数据库索引 | 后端开发 |
| 品牌 API 不可用/超时 | 中 | 中 | 并发限制(10)、单请求超时(3s)、降级显示 brand_id | 后端开发 | | 导出数据量过大 | 中 | 中 | 限制单次导出 1000 条,分批导出提示 | 后端开发 | | 数据同步延迟 | 中 | 中 | 显示数据更新时间,建立同步监控 | 运维 |
6. 里程碑
M1 M2 M3 M4
│ │ │ │
▼ ▼ ▼ ▼
◆───────────────◆───────────────◆───────────────◆
│ │ │ │
基础架构 核心功能 功能完善 正式上线
搭建完成 开发完成 测试完成 部署完成
| 里程碑 | 目标 | 交付物 | 验收标准 |
|---|---|---|---|
| M1 | 基础架构搭建完成 | 项目骨架、数据库连接、基础UI | 项目可运行,数据库可连接 |
| M2 | 核心功能开发完成 | 查询、计算、展示、导出功能 | 所有 P0/P1 功能可用 |
| M3 | 功能完善测试完成 | 错误处理、性能优化、链接跳转 | 测试通过,性能达标 |
| M4 | 正式上线部署完成 | Docker/PM2 部署配置 | 生产环境可访问 |
7. 资源需求
| 角色 | 人数 | 职责 | 参与阶段 |
|---|---|---|---|
| 前端开发 | 1 | Next.js 开发、UI 组件、API 调用 | Phase 1-3 |
| 后端开发 | 1 | FastAPI 开发、API 设计、数据库操作 | Phase 1-3 |
| 运维/DevOps | 0.5 | 前后端分离部署、CORS 配置、监控告警 | Phase 3 |
注: 也可由全栈开发承担前后端工作
8. 目录结构
kol-insight/
├── frontend/ # 前端项目(Next.js)
│ ├── src/
│ │ ├── app/
│ │ │ ├── page.tsx # 首页(查询页面)
│ │ │ ├── layout.tsx # 根布局
│ │ │ └── globals.css # 全局样式
│ │ ├── components/
│ │ │ ├── QueryForm.tsx # 查询表单组件
│ │ │ ├── ResultTable.tsx # 结果表格组件
│ │ │ └── ExportButton.tsx # 导出按钮组件
│ │ ├── lib/
│ │ │ ├── api.ts # API 调用封装
│ │ │ └── utils.ts # 工具函数
│ │ └── types/
│ │ └── index.ts # 类型定义
│ ├── .env.local # 环境变量(后端API地址)
│ ├── .env.example # 环境变量示例
│ ├── package.json
│ ├── tsconfig.json
│ ├── tailwind.config.ts
│ └── next.config.js
│
├── backend/ # 后端项目(FastAPI)
│ ├── app/
│ │ ├── main.py # FastAPI 应用入口
│ │ ├── config.py # 配置管理
│ │ ├── database.py # 数据库连接
│ │ ├── models/
│ │ │ └── kol_video.py # SQLAlchemy 模型
│ │ ├── schemas/
│ │ │ ├── query.py # Pydantic 请求/响应模型
│ │ │ └── video.py # 视频数据模型
│ │ ├── api/
│ │ │ ├── v1/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── query.py # 查询接口
│ │ │ │ └── export.py # 导出接口
│ │ │ └── deps.py # 依赖注入
│ │ ├── services/
│ │ │ ├── query_service.py # 查询业务逻辑
│ │ │ ├── calculator.py # 计算逻辑
│ │ │ ├── brand_api.py # 品牌 API 集成
│ │ │ └── export_service.py # 导出服务
│ │ └── core/
│ │ ├── security.py # 安全相关
│ │ └── logger.py # 日志配置
│ ├── alembic/ # 数据库迁移
│ │ └── versions/
│ ├── tests/ # 测试
│ ├── .env # 环境变量
│ ├── .env.example # 环境变量示例
│ ├── requirements.txt # 依赖列表(或 pyproject.toml)
│ └── alembic.ini # Alembic 配置
│
├── docker-compose.yml # Docker 编排(可选)
└── README.md # 项目文档
9. 数据库 Schema
# backend/app/models/kol_video.py
from sqlalchemy import Column, String, Integer, Float, DateTime, Index
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class KolVideo(Base):
__tablename__ = "kol_videos"
# 主键
item_id = Column(String, primary_key=True)
# 基础信息
title = Column(String, nullable=True)
viral_type = Column(String, nullable=True)
video_url = Column(String, nullable=True)
star_id = Column(String, nullable=False, index=True)
star_unique_id = Column(String, nullable=False, index=True)
star_nickname = Column(String, nullable=False, index=True)
publish_time = Column(DateTime, nullable=True)
# 曝光指标
natural_play_cnt = Column(Integer, default=0)
heated_play_cnt = Column(Integer, default=0)
total_play_cnt = Column(Integer, default=0)
# 互动指标
total_interact = Column(Integer, default=0)
like_cnt = Column(Integer, default=0)
share_cnt = Column(Integer, default=0)
comment_cnt = Column(Integer, default=0)
# 效果指标
new_a3_rate = Column(Float, nullable=True)
after_view_search_uv = Column(Integer, default=0)
return_search_cnt = Column(Integer, default=0)
# 商业信息
industry_id = Column(String, nullable=True)
industry_name = Column(String, nullable=True)
brand_id = Column(String, nullable=True)
estimated_video_cost = Column(Float, default=0)
# 索引定义(补充)
__table_args__ = (
Index('idx_star_id', 'star_id'),
Index('idx_star_unique_id', 'star_unique_id'),
Index('idx_star_nickname', 'star_nickname'),
)
对应的 Alembic 迁移 SQL:
-- 创建表和索引
CREATE TABLE kol_videos (
item_id VARCHAR PRIMARY KEY,
title VARCHAR,
viral_type VARCHAR,
video_url VARCHAR,
star_id VARCHAR NOT NULL,
star_unique_id VARCHAR NOT NULL,
star_nickname VARCHAR NOT NULL,
publish_time TIMESTAMP,
natural_play_cnt INTEGER DEFAULT 0,
heated_play_cnt INTEGER DEFAULT 0,
total_play_cnt INTEGER DEFAULT 0,
total_interact INTEGER DEFAULT 0,
like_cnt INTEGER DEFAULT 0,
share_cnt INTEGER DEFAULT 0,
comment_cnt INTEGER DEFAULT 0,
new_a3_rate FLOAT,
after_view_search_uv INTEGER DEFAULT 0,
return_search_cnt INTEGER DEFAULT 0,
industry_id VARCHAR,
industry_name VARCHAR,
brand_id VARCHAR,
estimated_video_cost FLOAT DEFAULT 0
);
CREATE INDEX idx_star_id ON kol_videos(star_id);
CREATE INDEX idx_star_unique_id ON kol_videos(star_unique_id);
CREATE INDEX idx_star_nickname ON kol_videos(star_nickname);
10. 前后端分离部署
10.1 部署架构
┌─────────────────────────────────────────────────┐
│ 前端部署 (Next.js) │
│ - Vercel / Docker + Nginx │
│ - 端口: 3000 │
│ - 环境变量: NEXT_PUBLIC_API_URL │
└─────────────────────────────────────────────────┘
│
│ HTTP API 调用
▼
┌─────────────────────────────────────────────────┐
│ 后端部署 (FastAPI) │
│ - Docker + Uvicorn │
│ - 端口: 8000 │
│ - 环境变量: DATABASE_URL, CORS_ORIGINS │
└─────────────────────────────────────────────────┘
│
│ PostgreSQL
▼
┌─────────────────────────────────────────────────┐
│ 数据库 (PostgreSQL) │
│ - 端口: 5432 │
└─────────────────────────────────────────────────┘
10.2 Docker Compose 配置示例
version: '3.8'
services:
# 后端服务
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/kol_insight
- CORS_ORIGINS=http://localhost:3000,https://your-frontend-domain.com
depends_on:
- db
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
# 前端服务
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1
depends_on:
- backend
# 数据库服务
db:
image: postgres:14
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=kol_insight
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
10.3 CORS 配置(后端)
# backend/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
app = FastAPI(title="KOL Insight API", version="1.0.0")
# CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS, # ["http://localhost:3000"]
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
10.4 API 调用封装(前端)
// frontend/src/lib/api.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
export async function queryVideos(request: QueryRequest): Promise<QueryResponse> {
const response = await fetch(`${API_BASE_URL}/query`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error('Query failed');
}
return response.json();
}
export async function exportData(format: 'xlsx' | 'csv'): Promise<Blob> {
const response = await fetch(`${API_BASE_URL}/export?format=${format}`);
if (!response.ok) {
throw new Error('Export failed');
}
return response.blob();
}
10.5 FastAPI 自动生成 API 文档
FastAPI 自动生成交互式 API 文档,无需额外配置:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
前端开发人员可直接通过这些文档了解 API 接口定义和测试 API。