topfans/backend/docs/AI-Chat-Service/status_text标签系统设计方案.md

353 lines
11 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.

# status_text 标签系统设计方案
## 1. 概述
### 1.1 需求背景
用户在我的点赞作品页面需要看到每个作品的动态状态标签(如"屠榜顶流"、"火速破圈"等),用于直观展示作品的热度和表现。
### 1.2 实现方案
采用 **后端计算返回** 方案:由后端计算每个作品的 `status_text` 状态标签,前端直接展示。
### 1.3 优势
- 后端拥有完整的排行榜数据和用户行为数据,可准确计算
- 前端无需关心业务逻辑复杂度,保持轻量
- 状态计算逻辑集中,便于维护和修改
- 减少前后端字段依赖,减少数据冗余
---
## 2. 标签体系定义
### 2.1 标签分类
| 优先级 | 类型 | 说明 |
|--------|------|------|
| T0 | 收益型 | 最高优先级,用户点赞后作品表现极佳 |
| T1 | 排名型 | 排行榜相关,作品在榜上表现优秀 |
| T3 | 状态型 | 涨粉速度,体现作品热度变化 |
| T4 | 状态型 | 涨粉速度,体现作品热度变化 |
### 2.2 标签详情
| 优先级 | 标签名 | 显示条件 | 背后逻辑 |
|--------|--------|----------|----------|
| **T0** | 眼光拉满 | 用户点赞后新增点赞≥50 且作品仍在展出 | 用户点赞后作品持续火热,用户收益已达峰值 |
| **T1** | 屠榜顶流TopX | 排行榜排名为 1、2、3、4、5 | 作品稳居排行榜前五,顶级流量 |
| **T1** | 第Y爆款 | 排行榜排名为 Y10≥Y>5 | 作品进入排行榜前10但未进前5 |
| **T1** | 排名破Z | 排行榜排名达到 ZZ∈{20,50,100,200} | 里程碑式突破,达到特定门槛 |
| **T3** | 火速破圈 | 过去1小时新增点赞≥20 | 作品热度急剧上升中 |
| **T4** | 小爆出圈 | 过去1小时新增点赞20>新增点赞≥10 | 作品热度稳步上升 |
| **T4** | 热度积累 | 过去1小时新增点赞10>新增点赞≥5 | 作品热度温和增长 |
| **T4** | 缓慢涨粉 | 过去1小时新增点赞5>新增点赞≥0 | 作品热度缓慢增长 |
| - | 潜力待挖 | 无任何标签满足 | 默认状态,等待挖掘 |
### 2.3 优先级规则
当多个标签条件同时满足时按优先级取最高级别T0>T1>T3>T4
**计算流程:**
```
1. 检查 T0「眼光拉满」→ 满足则返回
2. 检查 T1 排名型(屠榜顶流/第Y爆款/排名破Z→ 满足则返回
3. 检查 T3/T4 状态型(火速破圈/小爆出圈/热度积累/缓慢涨粉)→ 满足则返回
4. 默认返回「潜力待挖」
```
---
## 3. 后端接口设计
### 3.1 修改接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/v1/me/liked-assets` | GET | 获取我点赞的作品列表 |
### 3.2 响应新增字段
`GetMyLikedAssets` 接口的响应 `items` 数组元素中新增:
```json
{
"asset_id": 12345,
"name": "作品名称",
"cover_url": "https://...",
"like_count": 1000,
"liked_at": "2026-05-26T10:00:00Z",
"earnings": 500.00,
"hourly_earnings": 10.00,
"is_lenticular": false,
"expire_at": "2026-05-27T10:00:00Z",
"status_text": "屠榜顶流Top3"
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| status_text | string | 动态状态标签,默认「潜力待挖」 |
---
## 4. 后端实现设计
### 4.1 数据依赖
| 字段 | 来源 | 说明 |
|------|------|------|
| 用户点赞时间 | liked_assets 表 | 用于计算用户点赞后新增点赞数 |
| 用户点赞后新增点赞数 | likes 表聚合 | 用户点赞时刻起到当前时刻,作品累计新增点赞数 |
| 排行榜排名 | ranking 或 likes 表 | 当前作品排名 |
| 过去1小时新增点赞 | likes 表聚合 | 需要按时间窗口聚合 |
### 4.2 Service 层计算逻辑
```go
// pkg/service/social_service.go
func (s *SocialService) GetMyLikedAssets(ctx context.Context, req *pbSocial.GetMyLikedAssetsRequest) (*pbSocial.GetMyLikedAssetsResponse, error) {
// 1. 获取用户点赞作品列表JOIN exhibition 获取 start_time避免 N+1
items, total, hasMore := s.getLikedAssetsList(ctx, userID, page, pageSize)
assetIDs := extractAssetIDs(items)
// 2. 批量获取用户点赞时间
likedAtMap := s.batchGetUserLikedAtMap(ctx, userID, assetIDs)
// 3. 批量获取作品排名(排名数据变化不频繁,可加缓存)
rankMap := s.batchGetAssetRanks(ctx, assetIDs)
// 4. 批量获取过去1小时新增点赞
hourlyNewLikesMap := s.batchGetHourlyNewLikes(ctx, assetIDs)
// 5. 为每个作品计算 status_text
for _, item := range items {
item.UserLikedAt = likedAtMap[item.AssetId]
item.Rank = rankMap[item.AssetId]
item.HourlyNewLikes = hourlyNewLikesMap[item.AssetId]
item.StatusText = computeStatusText(item)
}
return resp, nil
}
```
**性能优化说明:**
| 优化项 | 优化前 | 优化后 | 收益 |
|--------|--------|--------|------|
| exhibition 查询 | 循环内逐条查询N 次) | JOIN 一次获取 | 30 次查询 → 1 次 |
| 排名查询 | - | 批量 IN 查询 | 1 次 |
| 小时级新增点赞 | - | 批量聚合查询 | 1 次 |
**关键索引建议:**
- `likes.asset_id + likes.created_at`(联合索引,时间窗口聚合)
- `likes.asset_id + likes.created_at + user_id`(用户点赞后新增统计)
- `exhibitions.asset_id + exhibitions.deleted_at`JOIN 优化)
### 4.3 status_text 计算函数
```go
func computeStatusText(item *LikedAssetItem) string {
// T0: 眼光拉满
if item.UserLikedCountAfter >= 50 && !item.IsExpired {
return "眼光拉满"
}
// T1: 排名型
switch {
case item.Rank >= 1 && item.Rank <= 5:
return fmt.Sprintf("屠榜顶流Top%d", item.Rank)
case item.Rank > 5 && item.Rank <= 10:
return fmt.Sprintf("第%d爆款", item.Rank)
case item.Rank == 20 || item.Rank == 50 || item.Rank == 100 || item.Rank == 200:
return fmt.Sprintf("排名破%d", item.Rank)
}
// T3/T4: 状态型
switch {
case item.HourlyNewLikes >= 20:
return "火速破圈"
case item.HourlyNewLikes >= 10:
return "小爆出圈"
case item.HourlyNewLikes >= 5:
return "热度积累"
case item.HourlyNewLikes >= 0:
return "缓慢涨粉"
default:
return "潜力待挖"
}
}
```
### 4.4 新增字段结构
```protobuf
// proto/social.proto
message LikedAssetItem {
int64 asset_id = 1;
string name = 2;
string cover_url = 3;
int32 like_count = 4;
int64 liked_at = 5;
double earnings = 6;
double hourly_earnings = 7;
bool is_lenticular = 8;
int64 expire_at = 9;
// 新增字段
string status_text = 10;
}
```
---
## 5. 前端修改设计
### 5.1 修改文件
`frontend/pages/profile/myWorks.vue`
### 5.2 修改点
**修改前 (line 919):**
```javascript
status_text: index < 3 ? '排名进榜' : '潜力待挖',
```
**修改后:**
```javascript
status_text: item.status_text || '潜力待挖',
```
### 5.3 完整修改的代码块
```javascript
if (res.data && res.data.items) {
likedWorks.value = res.data.items.map((item, index) => ({
id: item.asset_id,
cover_url: item.cover_url,
like_count: item.like_count,
earnings: item.earnings,
liked_at: item.liked_at,
expire_at: item.expire_at,
name: item.name,
is_lenticular: item.is_lenticular ?? false,
status_text: item.status_text || '潜力待挖', // 直接使用后端返回
score: item.like_count,
reward: Math.floor(item.earnings || 0),
}));
// ...
}
```
---
## 6. 测试用例
### 6.1 标签测试用例
| 用例编号 | 前提条件 | 输入 | 预期输出 |
|----------|----------|------|----------|
| TC-01 | 用户点赞后作品新增≥50点赞 | status_text | 眼光拉满 |
| TC-02 | 作品排名第1 | status_text | 屠榜顶流Top1 |
| TC-03 | 作品排名第3 | status_text | 屠榜顶流Top3 |
| TC-04 | 作品排名第7 | status_text | 第7爆款 |
| TC-05 | 作品排名第20 | status_text | 排名破20 |
| TC-06 | 过去1小时新增点赞=25 | status_text | 火速破圈 |
| TC-07 | 过去1小时新增点赞=15 | status_text | 小爆出圈 |
| TC-08 | 过去1小时新增点赞=7 | status_text | 热度积累 |
| TC-09 | 过去1小时新增点赞=2 | status_text | 缓慢涨粉 |
| TC-10 | 无任何标签满足 | status_text | 潜力待挖 |
### 6.2 优先级测试用例
| 用例编号 | 前提条件 | 预期输出 | 说明 |
|----------|----------|----------|------|
| TC-11 | 排名第2 且 过去1小时新增点赞=25 | 眼光拉满 | T0 > T1 |
| TC-12 | 排名第5 且 过去1小时新增点赞=30 | 屠榜顶流Top5 | T1 > T3 |
| TC-13 | 排名第15 且 过去1小时新增点赞=22 | 火速破圈 | 无T0/T1满足 |
---
## 7. 里程碑
| 阶段 | 任务 | 负责人 | 状态 |
|------|------|--------|------|
| 1 | 后端 proto/social.proto 新增 status_text 字段 | 后端 | - |
| 2 | 后端 Service 层实现 computeStatusText 逻辑 | 后端 | - |
| 3 | 后端修改 GetMyLikedAssets 接口返回 status_text | 后端 | - |
| 4 | 前端 myWorks.vue 修改 status_text 取值逻辑 | 前端 | - |
| 5 | 联调测试 + 回归测试 | 前端+后端 | - |
---
## 8. 高并发性能评估
### 7.1 单请求成本
| 查询操作 | 次数 | 说明 |
|----------|------|------|
| 点赞列表 + exhibition JOIN | 1 | 批量获取 30 条 |
| 批量获取排名 | 1 | IN 查询 30 条 |
| 批量获取小时级新增 | 1 | IN + 时间聚合 |
| 批量获取点赞后新增 | 1 | IN + 时间聚合 |
| **总计** | **4 次** | |
`computeStatusText` 计算复杂度为 O(1),耗时 < 1μs可忽略
### 7.2 并发容量估算
假设单请求 DB 耗时 20msDB 连接池 500
```
单个连接处理能力1000ms / 20ms = 50,000 请求/秒/连接
500 连接 × 50 = 25,000 理论上限 QPS
10,000 并发用户 → 约 3,000-5,000 实际 QPS
```
### 7.3 必要配置
| 配置项 | 推荐值 | 说明 |
|--------|--------|------|
| DB 连接池 | 300-500 | 根据 DB 服务器规格调整 |
| 排名缓存 TTL | 60s | 排名数据变化不频繁 |
| 索引 | 必建 | 参见 4.2 节索引建议 |
### 7.4 风险点
| 风险 | 缓解方案 |
|------|----------|
| 点赞时频繁触发查询 | 点赞后可异步计算 status_text不阻塞主流程 |
| DB 连接池耗尽 | 限流 + 连接池监控 |
| 慢查询 | 确保索引生效定期 EXPLAIN 分析 |
---
## 9. 附录
### 8.1 标签文案汇总
| 标签名 | 字数 |
|--------|------|
| 眼光拉满 | 4 |
| 屠榜顶流Top1~5 | 6~7 |
| 第6~10爆款 | 4~5 |
| 排名破20/50/100/200 | 5~6 |
| 火速破圈 | 4 |
| 小爆出圈 | 4 |
| 热度积累 | 4 |
| 缓慢涨粉 | 4 |
| 潜力待挖 | 4 |
### 8.2 相关文件
| 文件路径 | 说明 |
|----------|------|
| backend/proto/social.proto | Protobuf 定义 |
| backend/pkg/service/social_service.go | Service 层实现 |
| backend/gateway/controller/social_controller.go | Controller 返回格式 |
| frontend/pages/profile/myWorks.vue | 前端页面 |