# 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. 获取用户点赞作品列表(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 耗时 20ms,DB 连接池 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 | 前端页面 |