# 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爆款 | 排行榜排名为 Y(10≥Y>5) | 作品进入排行榜前10但未进前5 | | **T1** | 排名破Z | 排行榜排名达到 Z(Z∈{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. 获取用户点赞作品列表 items, total, hasMore := s.getLikedAssetsList(ctx, userID, page, pageSize) // 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 } ``` ### 4.3 status_text 计算函数 ```go func computeStatusText(item *LikedAssetItem) string { // T0: 眼光拉满 - 用户点赞后新增点赞≥50 且仍在展出 if item.UserLikedCountAfter >= 50 && !item.IsExpired { return "眼光拉满" } // T1: 排名型 if item.Rank >= 1 && item.Rank <= 5 { return fmt.Sprintf("屠榜顶流Top%d", item.Rank) } if item.Rank > 5 && item.Rank <= 10 { return fmt.Sprintf("第%d爆款", item.Rank) } if item.Rank == 20 || item.Rank == 50 || item.Rank == 100 || item.Rank == 200 { return fmt.Sprintf("排名破%d", item.Rank) } // T3/T4: 状态型 if item.HourlyNewLikes >= 20 { return "火速破圈" } if item.HourlyNewLikes >= 10 { return "小爆出圈" } if item.HourlyNewLikes >= 5 { return "热度积累" } if item.HourlyNewLikes >= 0 { return "缓慢涨粉" } // 默认 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. 附录 ### 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 | 前端页面 |