439 lines
10 KiB
Markdown
439 lines
10 KiB
Markdown
# 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 字段 |
|