muse_creative_hotspots/doc/DevelopmentPlan.md
wxs 6cc703ada2 feat: monorepo 重构 + 新增 5 个平台适配器
项目从单体结构重构为 pnpm monorepo (shared/backend/frontend),
新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台适配器,
包含完整的单元测试和 E2E 测试覆盖。

- 完成 T-031~T-044: 5 个适配器实现、注册、配置和测试
- 重构前后端分离: Hono 后端 + Next.js 前端
- 151 个单元测试 + 21 个 Mock E2E + 25 个真实 E2E
- 适配器基于真实 TikHub API 响应结构实现

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:43:25 +08:00

670 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Muse Creative Hotspots — 开发计划
## 文档信息
| 项目 | 内容 |
|------|------|
| 版本 | v1.0 |
| 创建日期 | 2026-03-02 |
| 来源文档 | FeatureSummary.md, PRD.md |
## 1. 项目概述
### 1.1 项目目标
构建面向个人创意工作者的全平台热点内容聚合浏览器MVP 阶段实现抖音 + TikTok + 小红书三个平台的热点内容聚合浏览,包含卡片信息流、筛选排序、内容详情、收藏系统、数据刷新和设置管理。
### 1.2 技术栈
| 层级 | 技术选型 | 版本 | 说明 |
|------|----------|------|------|
| 框架 | Next.js (App Router) | 14+ | 全栈能力API Routes 做后端代理Vercel 部署 |
| UI 库 | Tailwind CSS + shadcn/ui | Tailwind 3.x | 简约现代风格,组件丰富 |
| 状态管理 | Zustand | 4.x | 轻量状态管理persist 中间件支持持久化 |
| 数据请求 | TanStack Query | 5.x | 缓存、自动刷新、loading/error 状态管理 |
| 本地存储 | localStorage | - | MVP 阶段收藏/设置持久化 |
| 语言 | TypeScript | 5.x | 类型安全 |
| 包管理器 | pnpm | 8+ | 快速、节省磁盘 |
| 部署 | localhost → Vercel | - | 先本地开发,后期线上部署 |
### 1.3 开发原则
- **渐进式开发**:先跑通数据链路,再完善 UI 和交互
- **适配器模式**:平台差异封装在适配器内,新增平台零侵入
- **安全优先**API Key 仅存在于服务端,前端不暴露
- **类型驱动**:先定义 TypeScript 类型,再实现逻辑
- **组件化**UI 组件遵循单一职责,可独立测试
---
## 2. 技术架构
### 2.1 系统架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端(浏览器) │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Next.js App (React) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌───────────┐ │ │
│ │ │ 首页 │ │ 详情页 │ │ 收藏页 │ │ 设置页 │ │ │
<!-- MODIFIED: 原内容为 "[id]/",补充 platform 参数M-001 -->
│ │ │ page.tsx│ │[plt]/[id]│ │favorites│ │ settings │ │ │
│ │ └────┬────┘ └────┬─────┘ └────┬────┘ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────────────▼─────────────▼──────────────▼─────┐ │ │
│ │ │ TanStack Query (缓存 + 自动刷新) │ │ │
│ │ └────────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────▼───────────────────────────┐ │ │
│ │ │ Zustand Stores (settings / favorites) │ │ │
│ │ │ ↕ localStorage │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────┬───────────────────────────────┘ │
└──────────────────────────────┼──────────────────────────────────┘
│ fetch /api/tikhub/[platform]
┌─────────────────────────────────────────────────────────────────┐
│ Next.js API Routes (服务端) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ /api/tikhub/[platform]/route.ts │ │
│ │ ┌──────────┐ ┌──────────────┐ ┌───────────────────┐ │ │
│ │ │ 请求验证 │→│ 频率限制 │→│ 平台适配器分发 │ │ │
│ │ │ API Key │ │ 10 req/s │ │ douyin/tiktok/xhs │ │ │
│ │ └──────────┘ └──────────────┘ └─────────┬─────────┘ │ │
│ └────────────────────────────────────────────┼─────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 平台适配器层 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 抖音 │ │ TikTok │ │ 小红书 │ ... (扩展) │ │
│ │ │ Adapter │ │ Adapter │ │ Adapter │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────┼─────────────┘ │ │
│ │ ▼ │ │
│ │ ContentItem[] 统一数据模型 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────────┘
│ Bearer Token
┌─────────────────────┐
│ TikHub API │
│ api.tikhub.io │
│ 10 req/s 限制 │
│ $0.001/请求 │
└─────────────────────┘
```
### 2.2 模块依赖图
```
┌───────────────────────────────────────────────────────┐
│ 页面层 (Pages) │
│ ┌──────────┐ ┌──────────┐ ┌──────┐ ┌──────────┐ │
│ │ 首页 │ │ 详情页 │ │收藏页│ │ 设置页 │ │
│ └────┬─────┘ └────┬─────┘ └──┬───┘ └────┬─────┘ │
└───────┼─────────────┼───────────┼───────────┼─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ 组件层 (Components) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CardGrid │ │ DetailPnl│ │ Toolbar │ ... │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼──────────────┼──────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ 数据层 (Hooks + Stores) │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ TanStack Query │ │ Zustand Stores │ │
│ │ useContentQuery │ │ useFavoritesStore │ │
│ │ useDetailQuery │ │ useSettingsStore │ │
│ └────────┬─────────┘ └────────────┬─────────────┘ │
└───────────┼─────────────────────────┼─────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌────────────────────────────┐
│ API 代理层 │ │ 本地存储 │
│ /api/tikhub/[plat] │ │ localStorage │
│ │ │ └────────────────────────────┘
│ ▼ │
│ 平台适配器层 │
│ adapters/*.ts │
│ │ │
│ ▼ │
│ ContentItem 类型 │
└───────────────────────┘
```
### 2.3 数据流图
```
用户操作 前端 API Route TikHub
│ │ │ │
│ 1.打开首页/切换Tab │ │ │
├─────────────────────▶│ │ │
│ │ 2.useContentQuery() │ │
│ ├───────────────────────▶│ │
│ │ │ 3.读取 API Key │
│ │ │ 4.选择适配器 │
│ │ ├────────────────────▶│
│ │ │ 5.TikHub原始响应 │
│ │ │◀────────────────────┤
│ │ │ 6.适配器转换 │
│ │ │ → ContentItem[] │
│ │ 7.返回标准化数据 │ │
│ │◀───────────────────────┤ │
│ │ 8.TanStack Query缓存 │ │
│ │ 9.前端排序/筛选 │ │
│ 10.渲染卡片网格 │ │ │
│◀─────────────────────┤ │ │
│ │ │ │
│ 11.点击收藏 │ │ │
├─────────────────────▶│ │ │
│ │ 12.Zustand更新 │ │
│ │ 13.localStorage持久化 │ │
│ 14.收藏状态反馈 │ │ │
│◀─────────────────────┤ │ │
```
---
## 3. 开发阶段
### 3.1 阶段时间线
```
Phase 1 Phase 2 Phase 3
基础架构搭建 核心功能实现 辅助功能 & 联调
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 项目初始化│ │ 内容展示 │ │ 收藏系统 │
│ 类型定义 │ ──────▶ │ 筛选排序 │ ──────▶ │ 设置页面 │
│ API代理层 │ │ 详情页 │ │ 性能优化 │
│ 适配器 │ │ 刷新机制 │ │ 联调验收 │
└──────────┘ └──────────┘ └──────────┘
交付物: 交付物: 交付物:
• 项目骨架 • 首页卡片信息流 • 收藏功能
• 类型系统 • 平台Tab切换 • 收藏夹页面
• 3平台适配器 • 排序功能 • 设置页面
• API代理可用 • 详情页 • 图片懒加载
• 全局布局 • 自动/手动刷新 • 全链路验收
```
### 3.2 Phase 1: 基础架构搭建
**目标**: 搭建项目骨架,打通 API 代理 → 适配器 → 统一数据模型的完整链路,确保能从 TikHub 获取到标准化的 ContentItem 数据。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
<!-- MODIFIED: 补充 next.config.ts 图片域名配置S-002 -->
| T-001 | 项目初始化 | Next.js 14+ App Router + Tailwind + shadcn/ui + pnpm配置 next.config.ts images.remotePatterns各平台图片 CDN 域名白名单) | - | P0 | - |
| T-002 | TypeScript 类型定义 | ContentItem、Platform、PlatformAdapter 接口定义 | T-001 | P0 | F-015 |
| T-003 | API 代理层实现 | `/api/tikhub/[platform]/route.ts`Bearer Token 认证,频率限制 | T-001 | P0 | F-014 |
| T-004 | TikHub API 客户端 | 封装 HTTP 请求错误处理10 req/s 限流 | T-003 | P0 | F-014 |
| T-005 | 平台适配器 — 抖音 | 热搜榜 + 内容详情 API字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-006 | 平台适配器 — TikTok | 趋势内容 + 内容详情 API字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-007 | 平台适配器 — 小红书 | 推荐内容 + 笔记详情 API字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-008 | Zustand Store 基础 | settingsStoreAPI Key、刷新间隔+ favoritesStore 骨架 | T-001 | P0 | F-009, F-010 |
| T-009 | 全局布局组件 | HeaderLogo + 平台 Tab + 设置入口)+ 主内容区域 | T-001 | P0 | - |
**阶段依赖图:**
```
T-001 (项目初始化)
├──▶ T-002 (类型定义) ──┐
├──▶ T-003 (API代理层) │
│ └──▶ T-004 (API客户端) ──┐
├──▶ T-008 (Zustand) │ │
└──▶ T-009 (全局布局) │ │
▼ ▼
T-005 (抖音适配器)
T-006 (TikTok适配器)
T-007 (小红书适配器)
```
**Phase 1 验收**: `GET /api/tikhub/douyin` 返回标准化 ContentItem[] JSON。
---
### 3.3 Phase 2: 核心功能实现
**目标**: 实现首页卡片信息流、平台 Tab 切换、排序、详情页、自动/手动刷新,完成核心浏览体验。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
| T-010 | TanStack Query 集成 | 配置 QueryClient封装 `useContentQuery(platform)` hook | T-005~T-007 | P0 | F-001 |
| T-011 | 内容卡片组件 | ContentCard 组件:封面图、标题、平台图标、数据指标、作者信息 | T-002, T-009 | P0 | F-002 |
| T-012 | 卡片网格布局 | 响应式网格布局CSS Grid支持不同屏幕尺寸 | T-011 | P0 | F-002 |
| T-013 | 平台 Tab 切换 | 顶部 Tab 栏,切换平台触发数据重新获取,支持"全部"聚合视图 | T-010, T-012 | P0 | F-003 |
| T-014 | 排序功能 | 工具栏排序控件,支持 play_count/like_count/comment_count/publish_time + asc/desc | T-010 | P0 | F-003 |
<!-- MODIFIED: 路由改为 /detail/[platform]/[id]语义更清晰M-001 -->
| T-015 | 内容详情页 | `/detail/[platform]/[id]` 页面,完整信息展示 + "查看原文"跳转 + 收藏按钮 | T-010 | P0 | F-004 |
| T-016 | 自动定时刷新 | TanStack Query refetchInterval读取设置中的刷新间隔页面不可见时暂停 | T-010, T-008 | P0 | F-005 |
<!-- MODIFIED: 合并 T-018 到 T-017T-018 粒度过细S-003 -->
| T-017 | 手动刷新 + 刷新时间 | 工具栏刷新按钮invalidateQueriesloading 状态,防抖处理,重置自动刷新计时器;工具栏显示"上次刷新: HH:MM",刷新后更新 | T-010, T-016 | P0 | F-005, F-006 |
**阶段依赖图:**
```
T-010 (TanStack Query) ──┬──▶ T-013 (平台Tab)
├──▶ T-014 (排序)
├──▶ T-015 (详情页)
├──▶ T-016 (自动刷新) ──▶ T-017 (手动刷新+刷新时间)
T-011 (卡片组件) ──▶ T-012 (网格布局) ──▶ T-013
```
**Phase 2 验收**: 首页展示三个平台的热点内容卡片网格,可切换平台/排序,点击进入详情页,自动/手动刷新正常。
---
### 3.4 Phase 3: 辅助功能 & 联调
**目标**: 完成收藏系统、设置页面、错误处理、性能优化,通过全部 MVP 验收标准。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
| T-019 | 收藏功能实现 | favoritesStore 完善addFavorite/removeFavoritepersist 到 localStorage | T-008, T-011 | P0 | F-007, F-009 |
| T-020 | 卡片收藏按钮 | ContentCard + DetailPage 中添加收藏按钮,实心/空心状态切换 | T-019, T-015 | P0 | F-007 |
| T-021 | 收藏夹页面 | `/favorites` 页面,网格展示收藏内容,支持取消收藏和跳转详情 | T-019, T-012 | P0 | F-008 |
| T-022 | 设置页面 | `/settings` 页面API Key 输入框 + 刷新间隔选择 | T-008 | P0 | F-010, F-011 |
| T-023 | 错误处理 & 空状态 | API 错误提示、Key 未配置引导、数据为空提示、封面图加载失败占位 | T-010~T-022 | P0 | - |
| T-024 | 图片懒加载 | Next.js Image 组件 + loading="lazy",首屏性能优化 | T-012 | P0 | - |
| T-025 | 全链路联调 & 验收 | 按 PRD 第8节 MVP 验收标准逐项测试 | T-023, T-024 | P0 | 全部 |
**阶段依赖图:**
```
T-019 (收藏Store) ──┬──▶ T-020 (收藏按钮)
└──▶ T-021 (收藏夹页面)
T-022 (设置页面)
T-023 (错误处理) ◀── T-019~T-022 全部完成
T-024 (图片懒加载)
T-025 (联调验收) ◀── T-023 + T-024
```
**Phase 3 验收**: 通过 PRD 第8节全部 MVP 验收标准。
---
## 4. 技术方案
### 4.1 统一数据模型F-015
**功能**: 定义 ContentItem TypeScript 类型,作为全系统的数据契约。
**类型定义**:
```typescript
// src/types/content.ts
interface ContentItem {
id: string;
title: string;
cover_url?: string;
video_url?: string;
author_name: string;
author_avatar?: string;
play_count?: number;
like_count?: number;
comment_count?: number;
share_count?: number;
publish_time: string;
platform: Platform;
original_url: string;
tags?: string[];
}
type Platform =
| 'douyin' | 'tiktok' | 'xiaohongshu' // MVP
| 'youtube' | 'instagram' | 'twitter' // P1
| 'bilibili' | 'weibo' // P1
| string; // P2 扩展
interface PlatformConfig {
id: Platform;
name: string;
icon: string;
color: string;
enabled: boolean;
endpoints: {
trending: string;
detail: string;
};
}
interface PlatformAdapter {
fetchTrending(count: number): Promise<ContentItem[]>;
fetchDetail(id: string): Promise<ContentItem>;
}
```
### 4.2 API 代理层F-014
**功能**: Next.js API Routes 代理 TikHub 请求,隐藏 API Key。
**接口设计**:
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取热榜 | GET | `/api/tikhub/[platform]?count=20` | 返回 ContentItem[] |
| 获取详情 | GET | `/api/tikhub/[platform]/detail?id=xxx` | 返回 ContentItem |
| 保存设置 | POST | `/api/settings` | 保存 API Key服务端 |
| 调用统计 | GET | `/api/stats` | 返回当日调用次数 |
**架构设计**:
```
┌───────────────────────────────────────────────────┐
│ /api/tikhub/[platform]/route.ts │
├───────────────────────────────────────────────────┤
│ │
│ 1. 解析 platform 参数 │
│ │ │
│ ▼ │
│ 2. 读取 API Key (环境变量 / settings) │
│ │ │
│ ▼ │
│ 3. 频率限制检查 (10 req/s) │
│ │ │
│ ▼ │
│ 4. 选择 PlatformAdapter │
│ ┌─────┼─────┐ │
│ ▼ ▼ ▼ │
│ douyin tiktok xiaohongshu │
│ │ │ │ │
│ └─────┼─────┘ │
│ ▼ │
│ 5. 调用 TikHub API + 转换为 ContentItem[] │
│ │ │
│ ▼ │
│ 6. 返回 JSON Response │
│ │
└───────────────────────────────────────────────────┘
```
**实现要点**:
<!-- MODIFIED: 明确 API Key 运行时存储方案和读取优先级M-002 -->
- API Key 读取优先级:① 运行时内存变量(设置页面覆盖值)→ ② `.env.local``TIKHUB_API_KEY` 环境变量(预配置)
- 设置页面保存 API Key 时,通过 `POST /api/settings` 将 Key 写入服务端内存变量(进程生命周期内有效),不写入 `.env.local`(运行时无法修改);服务重启后回退到 `.env.local` 配置
- MVP 阶段localhost推荐在 `.env.local` 中预配置 Key设置页面仅作为运行时覆盖手段
- 使用简单的内存计数器实现 10 req/s 限流(滑动窗口)
- 错误码映射TikHub 401 → 前端提示配置 Key429 → 提示稍后重试5xx → 通用错误
### 4.3 平台适配器F-016
**功能**: 各平台 API 调用和数据格式转换。
**架构设计**:
```
┌────────────────────────────────────────────────┐
│ PlatformAdapter 接口 │
│ fetchTrending(count) → ContentItem[] │
│ fetchDetail(id) → ContentItem │
└──────────────────┬─────────────────────────────┘
│ implements
┌─────────┼─────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Douyin │ │ TikTok │ │ Xhs │
│ Adapter │ │ Adapter │ │ Adapter │
├──────────┤ ├──────────┤ ├──────────┤
│ 端点: │ │ 端点: │ │ 端点: │
│ fetch_hot │ │ trending │ │ fetch_ │
│ _search_ │ │ _post │ │ feed │
│ result │ │ │ │ │
│ │ │ │ │ │
│ 映射: │ │ 映射: │ │ 映射: │
│ 平台特定 │ │ 平台特定 │ │ 平台特定 │
│ → Content │ │ → Content│ │ → Content│
│ Item │ │ Item │ │ Item │
└──────────┘ └──────────┘ └──────────┘
```
**MVP 平台端点配置**:
| 平台 | 热榜端点 | 详情端点 |
|------|----------|----------|
| 抖音 | `/api/v1/douyin/web/fetch_hot_search_result` | `/api/v1/douyin/web/fetch_one_video` |
| TikTok | `/api/v1/tiktok/web/fetch_trending_post` | `/api/v1/tiktok/web/fetch_post_detail` |
| 小红书 | `/api/v1/xiaohongshu/app/v2/fetch_feed` | `/api/v1/xiaohongshu/app/v2/fetch_note_detail` |
**实现要点**:
- 每个适配器独立文件:`src/lib/adapters/douyin.ts``tiktok.ts``xiaohongshu.ts`
- 适配器注册表:`src/lib/adapters/index.ts` 导出 `getAdapter(platform): PlatformAdapter`
- 字段映射中缺失字段使用合理默认值(如 `author_name: "未知作者"`
### 4.4 内容展示层F-002, F-003
**功能**: 卡片网格布局 + 平台 Tab 切换 + 排序。
**组件结构**:
```
┌────────────────────────────────────────────────┐
│ Header │
│ ┌──────────────────────────────────────────┐ │
│ │ Logo [全部][抖音][TikTok][小红书] ⚙️ │ │
│ └──────────────────────────────────────────┘ │
├────────────────────────────────────────────────┤
│ Toolbar │
│ ┌──────────────────────────────────────────┐ │
│ │ 排序: [▼最热|最新] 🔄刷新 上次:10:30│ │
│ └──────────────────────────────────────────┘ │
├────────────────────────────────────────────────┤
│ ContentGrid │
│ ┌──────────────────────────────────────────┐ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Content │ │Content │ │Content │ ... │ │
│ │ │Card │ │Card │ │Card │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
```
**接口设计**:
| 组件 | Props | 说明 |
|------|-------|------|
| `PlatformTabs` | `platforms`, `active`, `onChange` | 平台切换 Tab 栏 |
| `SortToolbar` | `sortBy`, `sortOrder`, `onSort`, `onRefresh`, `lastRefresh` | 排序 + 刷新工具栏 |
| `ContentGrid` | `items: ContentItem[]`, `loading`, `error` | 卡片网格容器 |
| `ContentCard` | `item: ContentItem`, `onFavorite`, `isFavorited` | 单个内容卡片 |
**实现要点**:
- 网格布局使用 CSS Grid`grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))`
- 排序在前端内存中完成useMemo不重新请求 API
- 平台 Tab 切换触发 TanStack Query 的 queryKey 变更,自动重新获取数据
<!-- MODIFIED: 补充 rate-limiter 保护说明与风险管理一致S-001 -->
- "全部"视图使用 `Promise.all` 并发请求所有已启用平台,通过 rate-limiter 确保不超 10 req/s
### 4.5 收藏系统F-007, F-008, F-009
**功能**: Zustand + persist 实现收藏功能。
**接口设计**:
```typescript
// src/stores/favorites.ts
interface FavoritesStore {
items: ContentItem[];
addFavorite: (item: ContentItem) => void;
removeFavorite: (id: string, platform: Platform) => void;
isFavorited: (id: string, platform: Platform) => boolean;
}
```
**实现要点**:
- 使用 Zustand `persist` 中间件,存储到 localStorage key `muse-favorites`
- 收藏去重:以 `id + platform` 组合作为唯一键
- 收藏夹页面复用 `ContentGrid` + `ContentCard` 组件
### 4.6 设置管理F-010, F-011
**功能**: API Key 配置 + 刷新间隔设置。
**接口设计**:
```typescript
// src/stores/settings.ts
interface SettingsStore {
apiKey: string;
refreshInterval: 5 | 10 | 15 | 30 | 60; // 分钟
enabledPlatforms: Record<Platform, boolean>;
displayCount: number;
setApiKey: (key: string) => void;
setRefreshInterval: (minutes: number) => void;
togglePlatform: (platform: Platform) => void;
setDisplayCount: (count: number) => void;
}
```
**实现要点**:
<!-- MODIFIED: 与 4.2 保持一致,明确 API Key 存储为服务端内存变量M-002 -->
- API Key 通过 `/api/settings` POST 接口保存到服务端内存变量(非 localStorage读取优先级见 4.2 节
- 刷新间隔变更后,立即更新 TanStack Query 的 refetchInterval
- 设置页 UI 使用 shadcn/ui 的 Input、Select、Switch 组件
---
## 5. 项目目录结构
```
src/
├── app/
│ ├── layout.tsx # 全局布局 (Header + Main)
│ ├── page.tsx # 首页 (ContentGrid + Toolbar)
<!-- MODIFIED: 路由补充 platform 参数M-001 -->
│ ├── detail/[platform]/[id]/page.tsx # 详情页
│ ├── favorites/page.tsx # 收藏夹页面
│ ├── settings/page.tsx # 设置页面
│ └── api/
│ ├── tikhub/
│ │ └── [platform]/
│ │ ├── route.ts # 热榜内容代理
│ │ └── detail/route.ts # 内容详情代理
│ ├── settings/route.ts # 设置保存接口
│ └── stats/route.ts # API 调用统计
├── components/
│ ├── layout/
│ │ ├── Header.tsx # 顶部导航
│ │ ├── PlatformTabs.tsx # 平台 Tab 栏
│ │ └── SortToolbar.tsx # 排序 + 刷新工具栏
│ ├── card/
│ │ ├── ContentCard.tsx # 内容卡片
│ │ ├── ContentGrid.tsx # 卡片网格容器
│ │ └── CardSkeleton.tsx # 加载骨架屏
│ ├── detail/
│ │ └── DetailPanel.tsx # 详情信息面板
│ ├── common/
│ │ ├── EmptyState.tsx # 空状态组件
│ │ ├── ErrorState.tsx # 错误状态组件
│ │ └── FavoriteButton.tsx # 收藏按钮
│ └── ui/ # shadcn/ui 组件
├── lib/
│ ├── tikhub.ts # TikHub HTTP 客户端
│ ├── rate-limiter.ts # 请求频率限制
│ ├── adapters/
│ │ ├── index.ts # 适配器注册表
│ │ ├── douyin.ts # 抖音适配器
│ │ ├── tiktok.ts # TikTok 适配器
│ │ └── xiaohongshu.ts # 小红书适配器
│ ├── platforms.ts # 平台配置
│ └── utils.ts # 工具函数
├── hooks/
│ ├── useContentQuery.ts # 内容查询 hook
│ └── useDetailQuery.ts # 详情查询 hook
├── stores/
│ ├── favorites.ts # 收藏 store
│ └── settings.ts # 设置 store
└── types/
└── content.ts # 类型定义
```
---
## 6. 风险管理
| 风险 | 可能性 | 影响 | 应对措施 |
|------|--------|------|----------|
| TikHub API 端点变更 | 中 | 高 | 适配器模式隔离变更,仅需修改对应适配器文件 |
| TikHub API 响应格式变化 | 中 | 高 | 字段映射做容错处理,缺失字段使用默认值 |
<!-- MODIFIED: 统一并发策略描述,移除"串行请求"矛盾说法S-001 -->
| API 频率限制触发 (10 req/s) | 高 | 中 | 实现 rate-limiter 请求排队机制,确保并发请求不超 10 req/sMVP 仅 3 平台,并发风险低 |
| API 成本失控 | 低 | 中 | 默认 30 分钟刷新间隔页面不可见暂停刷新F-017 成本监控 |
| localStorage 容量限制 (5MB) | 低 | 低 | 收藏数据量预估较小(数百条 ContentItem ≈ 几百 KB |
| 跨域图片加载失败 | 高 | 中 | 使用 Next.js Image 组件配置 remotePatterns加载失败显示占位图 |
| P1/P2 平台 API 端点不确定 | 高 | 低 | MVP 不涉及P1 阶段前在 TikHub 控制台确认最新端点 |
---
## 7. 里程碑
```
M1 M2 M3 M4
│ │ │ │
▼ ▼ ▼ ▼
◆─────────────────◆─────────────────◆─────────────────◆
│ │ │ │
Phase 1 完成 Phase 2 完成 Phase 3 完成 MVP 发布
数据链路打通 核心浏览体验 全功能可用 验收通过
```
| 里程碑 | 目标 | 交付物 | 验收标准 |
|--------|------|--------|----------|
| M1 — 数据链路打通 | API 代理层 + 3 平台适配器可用 | T-001 ~ T-009 | `GET /api/tikhub/douyin` 返回标准化 JSON |
<!-- MODIFIED: T-018 合并到 T-017范围调整S-003 -->
| M2 — 核心浏览体验 | 首页可浏览、可切换、可排序 | T-010 ~ T-017 | 首页展示卡片网格Tab 切换 + 排序 + 详情页 + 刷新正常 |
| M3 — 全功能可用 | 收藏 + 设置 + 错误处理 | T-019 ~ T-024 | 收藏功能可用,设置页可配置 Key 和刷新间隔 |
| M4 — MVP 发布 | 全链路验收通过 | T-025 | 通过 PRD 第8节全部 8 条 MVP 验收标准 |
---
## 8. 任务与功能映射
| 功能ID | 功能名 | 实现任务 |
|--------|--------|----------|
| F-001 | 内容获取 | T-010 |
| F-002 | 卡片信息流展示 | T-011, T-012 |
| F-003 | 内容筛选与排序 | T-013, T-014 |
| F-004 | 内容详情页 | T-015 |
<!-- MODIFIED: T-017 合并了 T-018 的刷新时间展示补充映射S-003 -->
| F-005 | 自动定时刷新 | T-016, T-017 |
| F-006 | 手动刷新 | T-017 |
| F-007 | 内容收藏 | T-019, T-020 |
| F-008 | 收藏夹管理 | T-021 |
| F-009 | 收藏数据持久化 | T-019 |
| F-010 | API Key 配置 | T-022 |
| F-011 | 刷新间隔设置 | T-022 |
| F-014 | API 请求代理 | T-003, T-004 |
| F-015 | 统一数据模型 | T-002 |
| F-016 | 平台适配器 | T-005, T-006, T-007 |
> F-012平台管理、F-013展示数量设置、F-017API 调用量统计)为 v1.1/v2.0 功能,不在 MVP 任务中。
---
## 9. 资源需求
| 角色 | 人数 | 职责 | 参与阶段 |
|------|------|------|----------|
| 全栈开发 | 1 | 前后端全部实现 | Phase 1-3 |
> 本项目为个人项目,由单人全栈完成。