topfans/frontend/RANKING_API_SPECIFICATION.md
2026-04-07 23:08:49 +08:00

574 lines
13 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.

# 排行榜系统后端API数据格式规范
## 概述
本文档定义了排行榜系统的后端API接口规范包括请求参数、响应格式和数据模型。
---
## 1. 获取排行榜数据
### 接口信息
- **接口路径**: `/api/ranking/list`
- **请求方法**: `GET`
- **接口描述**: 获取指定类型和时间段的排行榜数据
### 请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| rankingType | string | 是 | 榜单类型 | `popularity` / `custom` / `activity` |
| timePeriod | string | 是 | 时间段 | `online` / `monthly` / `history` |
| page | number | 否 | 页码默认1 | `1` |
| pageSize | number | 否 | 每页数量默认20 | `20` |
### 榜单类型枚举
```json
{
"popularity": "热度榜",
"custom": "自制榜",
"activity": "活动榜"
}
```
### 时间段枚举
```json
{
"online": "在线",
"monthly": "本月",
"history": "历史"
}
```
### 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"rankingType": "popularity",
"timePeriod": "online",
"hasArtwork": true,
"total": 100,
"page": 1,
"pageSize": 20,
"list": [
{
"rank": 1,
"userId": "user001",
"avatar": "https://example.com/avatar/1.jpeg",
"nickname": "星光璀璨",
"popularityScore": 73654,
"artworkImage": "https://example.com/artwork/001.png",
"artworkId": "artwork001"
}
]
}
}
```
---
## 2. 获取当前用户排名信息
### 接口信息
- **接口路径**: `/api/ranking/current-user`
- **请求方法**: `GET`
- **接口描述**: 获取当前登录用户在指定榜单中的排名信息
### 请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| rankingType | string | 是 | 榜单类型 | `popularity` |
| timePeriod | string | 是 | 时间段 | `online` |
### 响应格式
**已上榜状态**:
```json
{
"code": 200,
"message": "success",
"data": {
"userId": "currentUser",
"avatar": "https://example.com/avatar/7.jpeg",
"nickname": "我",
"popularityScore": 45000,
"rank": 5,
"artworkImage": "https://example.com/artwork/current.png",
"artworkId": "currentUserArtwork",
"isRanked": true
}
}
```
**未上榜状态**:
```json
{
"code": 200,
"message": "success",
"data": {
"userId": "currentUser",
"avatar": "https://example.com/avatar/7.jpeg",
"nickname": "我",
"popularityScore": 371,
"rank": null,
"artworkImage": "https://example.com/artwork/current.png",
"artworkId": "currentUserArtwork",
"isRanked": false
}
}
```
---
## 3. 数据模型定义
### RankingUser (排行榜用户对象)
| 字段名 | 类型 | 必填 | 说明 | 备注 |
|--------|------|------|------|------|
| rank | number | 是 | 排名 | 从1开始 |
| userId | string | 是 | 用户ID | 唯一标识 |
| avatar | string | 是 | 头像URL | 完整URL地址 |
| nickname | string | 是 | 用户昵称 | 最大长度20字符 |
| popularityScore | number | 是 | 人气值/积分 | 非负整数 |
| artworkImage | string | 否 | 作品图片URL | 热度榜和自制榜必填 |
| artworkId | string | 否 | 作品ID | 热度榜和自制榜必填 |
**TypeScript 类型定义**:
```typescript
interface RankingUser {
rank: number;
userId: string;
avatar: string;
nickname: string;
popularityScore: number;
artworkImage?: string; // 活动榜不需要
artworkId?: string; // 活动榜不需要
}
```
### CurrentUserRanking (当前用户排名对象)
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| userId | string | 是 | 用户ID |
| avatar | string | 是 | 头像URL |
| nickname | string | 是 | 用户昵称 |
| popularityScore | number | 是 | 人气值/积分 |
| rank | number \| null | 是 | 排名null表示未上榜 |
| artworkImage | string | 否 | 作品图片URL |
| artworkId | string | 否 | 作品ID |
| isRanked | boolean | 是 | 是否已上榜 |
**TypeScript 类型定义**:
```typescript
interface CurrentUserRanking {
userId: string;
avatar: string;
nickname: string;
popularityScore: number;
rank: number | null;
artworkImage?: string;
artworkId?: string;
isRanked: boolean;
}
```
---
## 4. 榜单特性配置
### 榜单类型特性
| 榜单类型 | 类型代码 | 是否包含作品 | 说明 |
|---------|---------|------------|------|
| 热度榜 | popularity | ✅ 是 | 包含 artworkImage 和 artworkId |
| 自制榜 | custom | ✅ 是 | 包含 artworkImage 和 artworkId |
| 活动榜 | activity | ❌ 否 | 不包含作品相关字段 |
### 后端返回字段规则
```javascript
// 热度榜和自制榜
{
"hasArtwork": true,
"list": [
{
"rank": 1,
"userId": "user001",
"avatar": "...",
"nickname": "...",
"popularityScore": 73654,
"artworkImage": "...", // 必须返回
"artworkId": "..." // 必须返回
}
]
}
// 活动榜
{
"hasArtwork": false,
"list": [
{
"rank": 1,
"userId": "user201",
"avatar": "...",
"nickname": "...",
"popularityScore": 58321
// 不包含 artworkImage 和 artworkId
}
]
}
```
---
## 5. 错误响应格式
### 通用错误响应
```json
{
"code": 400,
"message": "参数错误rankingType 必须为 popularity、custom 或 activity",
"data": null
}
```
### 错误码定义
| 错误码 | 说明 | 示例消息 |
|--------|------|---------|
| 400 | 请求参数错误 | "参数错误rankingType 不能为空" |
| 401 | 未登录或token失效 | "请先登录" |
| 404 | 资源不存在 | "榜单数据不存在" |
| 500 | 服务器内部错误 | "服务器错误,请稍后重试" |
---
## 6. 请求示例
### 示例1: 获取热度榜在线排行
**请求**:
```http
GET /api/ranking/list?rankingType=popularity&timePeriod=online&page=1&pageSize=20
Authorization: Bearer {token}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": {
"rankingType": "popularity",
"timePeriod": "online",
"hasArtwork": true,
"total": 14,
"page": 1,
"pageSize": 20,
"list": [
{
"rank": 1,
"userId": "user001",
"avatar": "https://cdn.example.com/avatar/1.jpeg",
"nickname": "星光璀璨",
"popularityScore": 73654,
"artworkImage": "https://cdn.example.com/artwork/001.png",
"artworkId": "artwork001"
},
{
"rank": 2,
"userId": "user002",
"avatar": "https://cdn.example.com/avatar/2.jpeg",
"nickname": "梦想追逐者",
"popularityScore": 68921,
"artworkImage": "https://cdn.example.com/artwork/002.jpeg",
"artworkId": "artwork002"
}
]
}
}
```
### 示例2: 获取活动榜本月排行
**请求**:
```http
GET /api/ranking/list?rankingType=activity&timePeriod=monthly&page=1&pageSize=20
Authorization: Bearer {token}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": {
"rankingType": "activity",
"timePeriod": "monthly",
"hasArtwork": false,
"total": 3,
"page": 1,
"pageSize": 20,
"list": [
{
"rank": 1,
"userId": "user203",
"avatar": "https://cdn.example.com/avatar/5.jpeg",
"nickname": "活跃用户",
"popularityScore": 87654
},
{
"rank": 2,
"userId": "user201",
"avatar": "https://cdn.example.com/avatar/3.jpeg",
"nickname": "活动之星",
"popularityScore": 76543
}
]
}
}
```
### 示例3: 获取当前用户排名
**请求**:
```http
GET /api/ranking/current-user?rankingType=popularity&timePeriod=online
Authorization: Bearer {token}
```
**响应(已上榜)**:
```json
{
"code": 200,
"message": "success",
"data": {
"userId": "currentUser",
"avatar": "https://cdn.example.com/avatar/7.jpeg",
"nickname": "我",
"popularityScore": 45000,
"rank": 5,
"artworkImage": "https://cdn.example.com/artwork/current.png",
"artworkId": "currentUserArtwork",
"isRanked": true
}
}
```
**响应(未上榜)**:
```json
{
"code": 200,
"message": "success",
"data": {
"userId": "currentUser",
"avatar": "https://cdn.example.com/avatar/7.jpeg",
"nickname": "我",
"popularityScore": 371,
"rank": null,
"artworkImage": "https://cdn.example.com/artwork/current.png",
"artworkId": "currentUserArtwork",
"isRanked": false
}
}
```
---
## 7. 数据验证规则
### 后端验证要求
1. **rankingType 验证**
- 必填
- 只能是: `popularity`, `custom`, `activity`
2. **timePeriod 验证**
- 必填
- 只能是: `online`, `monthly`, `history`
3. **page 验证**
- 可选,默认为 1
- 必须是正整数
- 最小值: 1
4. **pageSize 验证**
- 可选,默认为 20
- 必须是正整数
- 范围: 1-100
5. **用户数据验证**
- `userId`: 非空字符串
- `nickname`: 非空字符串最大20字符
- `avatar`: 有效的URL格式
- `popularityScore`: 非负整数
- `rank`: 正整数从1开始
6. **作品数据验证**(仅热度榜和自制榜)
- `artworkImage`: 有效的URL格式
- `artworkId`: 非空字符串
---
## 8. 性能优化建议
### 缓存策略
1. **排行榜数据缓存**
- 缓存时间: 5分钟
- 缓存键: `ranking:{rankingType}:{timePeriod}:{page}`
2. **用户排名缓存**
- 缓存时间: 1分钟
- 缓存键: `user_rank:{userId}:{rankingType}:{timePeriod}`
### 数据库查询优化
1.`popularityScore` 字段建立索引
2.`userId` 字段建立索引
3. 使用 Redis 存储实时排行榜数据Sorted Set
---
## 9. 前后端数据映射
### 前端 Mock 数据 → 后端数据库字段
| 前端字段 | 后端数据库字段 | 类型 | 说明 |
|---------|--------------|------|------|
| rank | - | computed | 根据 popularityScore 计算 |
| userId | user_id | varchar(50) | 用户唯一标识 |
| avatar | avatar_url | varchar(255) | 头像URL |
| nickname | nickname | varchar(50) | 用户昵称 |
| popularityScore | popularity_score | int | 人气值 |
| artworkImage | artwork_image_url | varchar(255) | 作品图片URL |
| artworkId | artwork_id | varchar(50) | 作品ID |
### 数据库表结构建议
```sql
-- 用户排行榜表
CREATE TABLE user_rankings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(50) NOT NULL,
ranking_type ENUM('popularity', 'custom', 'activity') NOT NULL,
time_period ENUM('online', 'monthly', 'history') NOT NULL,
popularity_score INT NOT NULL DEFAULT 0,
artwork_id VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_ranking_score (ranking_type, time_period, popularity_score DESC),
INDEX idx_user_ranking (user_id, ranking_type, time_period)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户基本信息表
CREATE TABLE users (
user_id VARCHAR(50) PRIMARY KEY,
nickname VARCHAR(50) NOT NULL,
avatar_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 作品信息表
CREATE TABLE artworks (
artwork_id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
image_url VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_artwork (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
---
## 10. 版本历史
| 版本 | 日期 | 修改内容 | 修改人 |
|------|------|---------|--------|
| 1.0.0 | 2024-03-11 | 初始版本 | - |
---
## 附录: TypeScript 完整类型定义
```typescript
// 榜单类型枚举
export enum RankingType {
POPULARITY = 'popularity',
CUSTOM = 'custom',
ACTIVITY = 'activity'
}
// 时间段枚举
export enum TimePeriod {
ONLINE = 'online',
MONTHLY = 'monthly',
HISTORY = 'history'
}
// 排行榜用户接口
export interface RankingUser {
rank: number;
userId: string;
avatar: string;
nickname: string;
popularityScore: number;
artworkImage?: string;
artworkId?: string;
}
// 当前用户排名接口
export interface CurrentUserRanking extends RankingUser {
rank: number | null;
isRanked: boolean;
}
// 排行榜列表请求参数
export interface RankingListRequest {
rankingType: RankingType;
timePeriod: TimePeriod;
page?: number;
pageSize?: number;
}
// 排行榜列表响应数据
export interface RankingListResponse {
rankingType: RankingType;
timePeriod: TimePeriod;
hasArtwork: boolean;
total: number;
page: number;
pageSize: number;
list: RankingUser[];
}
// 当前用户排名请求参数
export interface CurrentUserRankingRequest {
rankingType: RankingType;
timePeriod: TimePeriod;
}
// API 响应包装
export interface ApiResponse<T> {
code: number;
message: string;
data: T | null;
}
```