topfans/docs/PRD-Rankings.md
2026-04-07 22:28:50 +08:00

439 lines
10 KiB
Markdown
Raw Permalink 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.

# TopFans 排行榜功能需求文档
## 1. 功能概述
### 1.1 产品背景
排行榜功能旨在提升用户活跃度和参与度,通过展示热门藏品和自制佳作榜单,激励用户创作和互动。
### 1.2 排行榜类型
| 排行类型 | 统计维度 | 数据来源 |
|----------|----------|----------|
| 热度排行 | 展示中 / 本月 / 全部 | 藏品点赞数 |
| 自制排行 | 展示中 / 本月 / 全部 | 自制藏品点赞数 |
> **注意**: 活动排行榜功能待后续单独设计
---
## 2. 数据模型设计
### 2.1 现有数据分析
**Asset (藏品表)**
- `LikeCount` - 点赞数(累计)
- `OwnerUID` - 拥有者ID
- `StarID` - 粉丝身份ID
- `CreatedAt` - 创建时间
**AssetLike (点赞记录表)**
- `AssetID` - 藏品ID
- `UserID` - 点赞用户ID
- `CreatedAt` - 点赞时间(用于本月统计)
**Exhibition (展品展示表)**
- `AssetID` - 展品ID
- `StartTime` - 开始展示时间
- `ExpireAt` - 过期时间(用于"展示中"判定)
### 2.2 新增数据模型
#### 2.2.1 藏品表扩展
在 Asset 表中增加 `IsOriginal` 字段true=自制藏品):
```go
type Asset struct {
// ... 现有字段
IsOriginal bool `gorm:"default:false;column:is_original"` // 是否自制藏品
}
```
---
## 3. API 接口设计
### 3.1 通用响应格式
```json
{
"code": 200,
"message": "ok",
"data": {
"items": [],
"page": 1,
"page_size": 10,
"total": 100
}
}
```
### 3.2 热度排行榜
#### 3.2.1 获取热度排行榜
**接口**: GET /api/v1/rankings/hot
**认证**: 需认证 (JWT Token)
**Query 参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| dimension | string | 是 | 统计维度: `displaying`(展示中) / `month`(本月) / `total`(全部) |
| star_id | int64 | 否 | 粉丝身份ID筛选不传则返回当前身份数据 |
| page | int | 否 | 页码默认1 |
| page_size | int | 否 | 每页数量默认10 |
**返回示例**:
```json
{
"code": 200,
"message": "ok",
"data": {
"my_ranking": {
"rank": 5,
"asset_id": 1005,
"asset_name": "我的藏品",
"cover_url": "https://...",
"like_count": 88,
"status": "ranked"
},
"items": [
{
"rank": 1,
"asset_id": 1001,
"asset_name": "战战生贺",
"cover_url": "https://...",
"owner_uid": 12345,
"owner_nickname": "爱战战",
"like_count": 999,
"is_original": false
},
{
"rank": 2,
"asset_id": 1002,
"asset_name": "肖战同框",
"cover_url": "https://...",
"owner_uid": 12346,
"owner_nickname": "小飞侠",
"like_count": 888,
"is_original": true
}
],
"page": 1,
"page_size": 10,
"total": 100
}
}
```
**我的排名返回说明**:
| 状态 | rank | 说明 |
|------|------|------|
| 上榜 | 5 | 在 Top N 内,显示具体排名 |
| 未上榜 | null | 不在 Top N 内,显示差距 |
**上榜返回示例 (my_ranking)**:
```json
{
"my_ranking": {
"rank": 5,
"asset_id": 1005,
"asset_name": "我的藏品",
"cover_url": "https://...",
"like_count": 88,
"status": "ranked"
}
}
```
**未上榜返回示例 (my_ranking)**:
```json
{
"my_ranking": {
"rank": null,
"asset_id": 1005,
"asset_name": "我的藏品",
"cover_url": "https://...",
"like_count": 50,
"status": "unranked",
"diff_to_rank": 50
}
}
```
**统计逻辑**:
| 维度 | 说明 |
|------|------|
| `displaying` | 统计当前正在展示的藏品的点赞数Exhibition.ExpireAt > Now() |
| `month` | 统计当前自然月内2026年3月1日-3月31日获得的点赞数 |
| `total` | 统计藏品累计点赞数 |
**多藏品处理**:
- 用户有多个藏品时,返回排行最高的那个藏品排名
**分页规则**:
- 每次返回 10 条page_size 默认 10
- 榜单最多显示前 100 名(可配置)
---
### 3.3 自制排行榜
#### 3.3.1 获取自制排行榜
**接口**: GET /api/v1/rankings/original
**认证**: 需认证 (JWT Token)
**Query 参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| dimension | string | 是 | 统计维度: `displaying`(展示中) / `month`(本月) / `total`(全部) |
| star_id | int64 | 否 | 粉丝身份ID筛选不传则返回当前身份数据 |
| page | int | 否 | 页码默认1 |
| page_size | int | 否 | 每页数量默认10 |
**返回示例**:
```json
{
"code": 200,
"message": "ok",
"data": {
"my_ranking": {
"rank": 3,
"asset_id": 1002,
"asset_name": "自制海报",
"cover_url": "https://...",
"like_count": 666,
"status": "ranked"
},
"items": [
{
"rank": 1,
"asset_id": 1002,
"asset_name": "自制海报",
"cover_url": "https://...",
"owner_uid": 12346,
"owner_nickname": "小飞侠",
"like_count": 888,
"material_url": "https://...",
"is_original": true
}
],
"page": 1,
"page_size": 10,
"total": 50
}
}
```
**统计逻辑**:
- 仅统计 `is_original = true` 的自制藏品
- 其他维度逻辑同热度排行榜
---
## 4. 前端界面设计
### 4.1 排行榜入口
在首页广场添加"排行榜"Tab或在个人中心添加入口。
### 4.2 排行榜页面结构
```
┌─────────────────────────────┐
│ 排行榜 │
├─────────────────────────────┤
│ [热度] [自制] │ ← Tab 切换
├─────────────────────────────┤
│ │
│ 🥇 用户A 999 赞 │
│ [图片] │
│ │
│ 🥈 用户B 888 赞 │
│ [图片] │
│ │
│ 🥉 用户C 777 赞 │
│ [图片] │
│ │
├─────────────────────────────┤
│ [展示中] [本月] [全部] │ ← 维度切换
└─────────────────────────────┘
```
### 4.3 我的排名吸底展示
**上榜状态**:
```
┌─────────────────────────────┐
│ 排行榜 │
│ ... │
│ 8. 用户H 100 赞 │
│ [图片] │
│ 9. 用户I 99 赞 │
│ [图片] │
│ 10. 用户J 98 赞 │
│ [图片] │
├─────────────────────────────┤
│ 🏆 我的排名: 第5名 │
│ ❤️ [图片] 我的藏品 88赞 │
└─────────────────────────────┘
```
**未上榜状态**:
```
┌─────────────────────────────┐
│ 排行榜 │
│ ... │
│ 98. 用户H 60 赞 │
│ [图片] │
│ 99. 用户I 59 赞 │
│ [图片] │
│ 100. 用户J 58 赞 │
│ [图片] │
├─────────────────────────────┤
│ 😢 未上榜 │
│ 距离上榜还差 8 热度 │
│ [图片] 我的藏品 50赞 │
└─────────────────────────────┘
```
**空状态**:
```
┌─────────────────────────────┐
│ 排行榜 │
├─────────────────────────────┤
│ │
│ 暂无数据,快去创作吧 │
│ │
│ [去铸造] │
└─────────────────────────────┘
```
---
## 5. 数据库变更
### 5.1 藏品表扩展
```sql
ALTER TABLE assets ADD COLUMN is_original BOOLEAN DEFAULT FALSE;
```
---
## 6. 性能优化
### 6.1 缓存策略
| 数据 | 缓存策略 | 过期时间 |
|------|----------|----------|
| 热度排行榜 | Redis ZSet | 5分钟 |
| 自制排行榜 | Redis ZSet | 5分钟 |
### 6.2 定时任务
1. **排行榜定时刷新** - 每5分钟更新一次排行榜缓存
2. **排名计算** - 每日凌晨重新计算精确排名
---
## 7. 业务流程
### 7.1 点赞时更新排行榜
```
用户点赞藏品
AssetLike 表新增记录 (记录点赞时间用于本月统计)
Asset.LikeCount +1
更新 Redis 热度/自制排行 ZSet
```
### 7.2 展示中藏品统计
```
定时任务扫描 Exhibition 表
筛选 ExpireAt > Now() 的记录
关联 Asset 表获取点赞数
更新 Redis 展示中排行 ZSet
```
### 7.3 本月统计
```
用户查询本月排行时
计算当月起始时间戳 (2026-03-01 00:00:00)
筛选 AssetLike.CreatedAt >= 月起始时间
按 AssetID 分组统计点赞数
返回排行结果
```
### 7.4 我的排名计算
```
用户请求排行榜
获取用户在该粉丝身份下所有藏品
找出点赞数最高的藏品
查询该藏品在榜单中的排名
如果不在 Top 100计算与第100名差距
返回 my_ranking 信息(含 cover_url
```
---
## 8. 待确认问题
1. **粉丝身份隔离**: 排行榜是否需要按粉丝身份隔离?
- ✅ 已确认:按当前用户的 star_id 过滤
2. **展示中判定**: 展位被占后4小时自动下架如何处理
- ✅ Exhibition.ExpireAt > Now() 即为展示中
3. **本月统计**: 自然月切换时如3月1日是否需要清理缓存
- 待定:建议跨月时刷新缓存
4. **Top N 默认值**: 默认显示前多少名?
- ✅ 已确认:默认 100
---
## 9. 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| V1.0 | 2026-03-11 | 初始版本 |
| V1.1 | 2026-03-12 | 移除活动排行榜,补充我的排名、差距计算、分页规则 |
| V1.2 | 2026-03-12 | my_ranking 补充 cover_url 字段 |