- 完成 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>
1015 lines
53 KiB
Markdown
1015 lines
53 KiB
Markdown
# KOL Insight - 开发计划
|
||
|
||
## 文档信息
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 版本 | v1.0 |
|
||
| 创建日期 | 2025-01-28 |
|
||
| 来源文档 | FeatureSummary.md |
|
||
|
||
## 1. 项目概述
|
||
|
||
### 1.1 项目目标
|
||
|
||
| 目标 | 指标 | 衡量方式 |
|
||
|------|------|----------|
|
||
| 提升查询效率 | 单次可批量查询多个 KOL | 对比手动查询耗时 |
|
||
| 降低计算错误 | 自动计算预估指标准确率 100% | 人工抽检验证 |
|
||
| 提高数据可用性 | 支持数据导出 | 导出功能完整性 |
|
||
|
||
### 1.2 技术栈
|
||
|
||
<!-- MODIFIED: 改用前后端分离架构,后端使用 Python FastAPI -->
|
||
| 层级 | 技术选型 | 版本 | 说明 |
|
||
|------|----------|------|------|
|
||
| 前端框架 | 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 配置,环境变量管理敏感配置
|
||
<!-- NEW START -->
|
||
- **前后端分离**: 前端专注 UI 展示,后端专注业务逻辑和数据处理
|
||
- **异步优先**: 后端使用异步编程,提升并发性能
|
||
<!-- NEW END -->
|
||
|
||
## 2. 技术架构
|
||
|
||
### 2.1 系统架构图
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构,后端使用 FastAPI -->
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ 客户端层 │
|
||
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 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 模块依赖图
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构,后端使用 FastAPI -->
|
||
```
|
||
┌────────────────────────────────────────────────────────────────┐
|
||
│ 前端模块 (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 数据流图
|
||
|
||
<!-- MODIFIED: 更新数据流图,体现批量调用品牌API的流程 (F-010) -->
|
||
```
|
||
用户操作 前端 后端 数据库/外部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: 基础架构搭建
|
||
|
||
**目标**: 完成项目初始化和基础设施配置
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的任务 -->
|
||
| 任务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 | - |
|
||
|
||
**阶段依赖图:**
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的依赖关系 -->
|
||
```
|
||
T-001 (前端初始化) T-002 (后端初始化)
|
||
│ │
|
||
▼ ▼
|
||
T-004 (UI框架) T-003 (数据库)
|
||
│ │
|
||
└──────────┬───────────┘
|
||
▼
|
||
T-005 (环境变量)
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 Phase 2: 核心功能开发
|
||
|
||
**目标**: 实现所有核心功能(查询、计算、展示、导出)
|
||
|
||
<!-- MODIFIED: 更新任务ID编号,明确前后端分离的任务分配 -->
|
||
| 任务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 |
|
||
|
||
**阶段依赖图:**
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的依赖关系 -->
|
||
```
|
||
后端任务:
|
||
T-006 (查询API) ──────▶ T-007 (计算逻辑) ──────▶ T-009 (导出API)
|
||
│ │ │
|
||
└──▶ T-008 (品牌API) │ │
|
||
│ │
|
||
前端任务: │ │
|
||
T-010 (查询表单) ─────────────┼───────────────────────┤
|
||
▼ ▼
|
||
T-011 (结果表格) ──────▶ T-012 (导出按钮)
|
||
```
|
||
|
||
---
|
||
|
||
### 3.4 Phase 3: 优化与测试
|
||
|
||
**目标**: 性能优化、错误处理、部署配置
|
||
|
||
<!-- MODIFIED: 更新任务ID编号,明确前后端分离的部署 -->
|
||
| 任务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 | 全部 |
|
||
|
||
**阶段依赖图:**
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的依赖关系 -->
|
||
```
|
||
T-012 (导出按钮)
|
||
│
|
||
├──────────┬──────────┐
|
||
▼ ▼ ▼
|
||
T-013 T-014 T-015
|
||
(错误处理) (性能优化) (链接跳转)
|
||
│
|
||
├──────────┐
|
||
▼ ▼
|
||
T-016 T-017
|
||
(部署) (测试)
|
||
```
|
||
|
||
## 4. 技术方案
|
||
|
||
### 4.1 数据查询模块
|
||
|
||
**功能**: 支持三种查询方式(星图ID/达人ID/昵称)批量查询 KOL 视频数据
|
||
|
||
**技术选型**:
|
||
|
||
<!-- MODIFIED: 更新为 FastAPI + SQLAlchemy 技术栈 -->
|
||
| 组件 | 技术 | 选型理由 |
|
||
|------|------|----------|
|
||
| ORM | SQLAlchemy 2.0+ | 异步 ORM,类型安全,成熟稳定 |
|
||
| 异步驱动 | asyncpg | PostgreSQL 异步驱动,高性能 |
|
||
| 查询优化 | 数据库索引 | 在 star_id, star_unique_id, star_nickname 字段建立索引 |
|
||
| 输入验证 | Pydantic | FastAPI 内置,类型安全的请求参数验证 |
|
||
|
||
**架构设计**:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 查询模块 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ 请求解析 │ ──▶ │ 参数验证 │ ──▶ │ 查询构建 │ │
|
||
│ │ (type/values)│ │ (Zod) │ │ (Prisma) │ │
|
||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────┐ │
|
||
│ │ 数据库查询 │ │
|
||
│ │ PostgreSQL │ │
|
||
│ └─────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**接口设计**:
|
||
|
||
<!-- MODIFIED: 更新为 FastAPI RESTful API 接口 -->
|
||
| 接口 | 方法 | 路径 | 说明 |
|
||
|------|------|------|------|
|
||
| 批量查询 | POST | /api/v1/query | 支持 star_id/unique_id/nickname 三种类型 |
|
||
|
||
**请求/响应格式**:
|
||
|
||
<!-- MODIFIED: 更新为 Python Pydantic 模型定义 -->
|
||
```python
|
||
# 请求模型
|
||
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
|
||
```
|
||
|
||
**实现要点**:
|
||
|
||
<!-- MODIFIED: 更新为 FastAPI + SQLAlchemy 实现方式 -->
|
||
- 使用 SQLAlchemy 的 `select()` 配合 `where()` 条件
|
||
- 星图ID/达人ID 使用 `in_()` 查询批量匹配
|
||
- 昵称使用 `like()` 进行模糊匹配(使用 `%{value}%`)
|
||
- 限制单次查询最大返回 1000 条
|
||
- SQL 注入防护由 SQLAlchemy 和 Pydantic 自动处理
|
||
- 使用异步查询 `session.execute(stmt)` 提升性能
|
||
|
||
---
|
||
|
||
### 4.2 数据计算模块
|
||
|
||
**功能**: 计算预估自然 CPM、看后搜人数、看后搜成本
|
||
|
||
**技术选型**:
|
||
|
||
<!-- MODIFIED: 更新为 Python 后端实现 -->
|
||
| 组件 | 技术 | 选型理由 |
|
||
|------|------|----------|
|
||
| 计算逻辑 | Python | 类型安全(Type Hints),易于维护 |
|
||
| 数值处理 | Python 原生 | 简单计算,无需额外库,性能优异 |
|
||
|
||
**架构设计**:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 计算模块 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ ┌─────────────┐ │
|
||
│ │ 查询结果 │ │
|
||
│ └──────┬──────┘ │
|
||
│ │ │
|
||
│ ┌────────────┼────────────┐ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||
│ │ CPM 计算 │ │看后搜人数 │ │ 成本计算 │ │
|
||
│ │ F-004 │ │ F-005 │ │ F-006 │ │
|
||
│ └───────────┘ └─────┬─────┘ └─────┬─────┘ │
|
||
│ │ │ │
|
||
│ │ 依赖 │ │
|
||
│ └─────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────┐ │
|
||
│ │ 除零检查 │ │
|
||
│ │ natural_play_cnt = 0 → null │ │
|
||
│ │ total_play_cnt = 0 → null │ │
|
||
│ │ 看后搜人数 = 0 → null │ │
|
||
│ └─────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**计算公式**:
|
||
|
||
<!-- MODIFIED: 更新为 Python 实现 -->
|
||
```python
|
||
# 预估自然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
|
||
```
|
||
|
||
**实现要点**:
|
||
|
||
<!-- MODIFIED: 更新为 Python 实现方式 -->
|
||
- 结果保留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 条
|
||
|
||
---
|
||
|
||
<!-- MODIFIED: 更新为批量调用策略,关联 F-010 -->
|
||
### 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 │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
**实现要点**:
|
||
|
||
<!-- MODIFIED: 更新为 Python FastAPI + httpx 异步实现 -->
|
||
- **在后端调用**: 查询API获取数据库结果后,立即调用品牌API
|
||
- **去重处理**: 提取查询结果中所有唯一的 brand_id,避免重复请求
|
||
- **并发控制**: 使用 asyncio.gather 或 asyncio.Semaphore 限制最大 10 个并发请求
|
||
- **超时设置**: 单个请求超时 3 秒,避免阻塞整体响应
|
||
- **降级策略**: API 调用失败时,显示原始 brand_id
|
||
- **结果合并**: 将品牌名称填充到查询结果后返回前端
|
||
|
||
<!-- MODIFIED: 更新为 Python + httpx 异步实现 -->
|
||
```python
|
||
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 连接池,实现重试机制 | 后端开发 |
|
||
| 大批量查询性能问题 | 中 | 中 | 限制单次查询上限,优化数据库索引 | 后端开发 |
|
||
<!-- MODIFIED: 更新品牌API风险说明,增加批量调用考虑 -->
|
||
| 品牌 API 不可用/超时 | 中 | 中 | 并发限制(10)、单请求超时(3s)、降级显示 brand_id | 后端开发 |
|
||
| 导出数据量过大 | 中 | 中 | 限制单次导出 1000 条,分批导出提示 | 后端开发 |
|
||
| 数据同步延迟 | 中 | 中 | 显示数据更新时间,建立同步监控 | 运维 |
|
||
|
||
## 6. 里程碑
|
||
|
||
```
|
||
M1 M2 M3 M4
|
||
│ │ │ │
|
||
▼ ▼ ▼ ▼
|
||
◆───────────────◆───────────────◆───────────────◆
|
||
│ │ │ │
|
||
基础架构 核心功能 功能完善 正式上线
|
||
搭建完成 开发完成 测试完成 部署完成
|
||
```
|
||
|
||
| 里程碑 | 目标 | 交付物 | 验收标准 |
|
||
|--------|------|--------|----------|
|
||
| M1 | 基础架构搭建完成 | 项目骨架、数据库连接、基础UI | 项目可运行,数据库可连接 |
|
||
| M2 | 核心功能开发完成 | 查询、计算、展示、导出功能 | 所有 P0/P1 功能可用 |
|
||
| M3 | 功能完善测试完成 | 错误处理、性能优化、链接跳转 | 测试通过,性能达标 |
|
||
| M4 | 正式上线部署完成 | Docker/PM2 部署配置 | 生产环境可访问 |
|
||
|
||
## 7. 资源需求
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的人员配置 -->
|
||
| 角色 | 人数 | 职责 | 参与阶段 |
|
||
|------|------|------|----------|
|
||
| 前端开发 | 1 | Next.js 开发、UI 组件、API 调用 | Phase 1-3 |
|
||
| 后端开发 | 1 | FastAPI 开发、API 设计、数据库操作 | Phase 1-3 |
|
||
| 运维/DevOps | 0.5 | 前后端分离部署、CORS 配置、监控告警 | Phase 3 |
|
||
|
||
**注**: 也可由全栈开发承担前后端工作
|
||
|
||
## 8. 目录结构
|
||
|
||
<!-- MODIFIED: 更新为前后端分离架构的目录结构 -->
|
||
```
|
||
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
|
||
|
||
<!-- MODIFIED: 更新为 SQLAlchemy 模型定义 -->
|
||
```python
|
||
# 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**:
|
||
|
||
```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);
|
||
```
|
||
|
||
<!-- NEW START -->
|
||
## 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 配置示例
|
||
|
||
```yaml
|
||
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 配置(后端)
|
||
|
||
```python
|
||
# 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 调用封装(前端)
|
||
|
||
```typescript
|
||
// 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。
|
||
<!-- NEW END -->
|