topfans/docs/superpowers/specs/2026-05-27-热门推荐模块设计.md

15 KiB
Raw Blame History

热门推荐模块设计方案

一、需求概述

在广场页面顶部新增 4 个热门分类区块,每个分类显示 8 张高点赞作品,支持单个分类刷新和查看更多分页。

页面结构

┌─────────────────────────────────┐
│         BannerCarousel          │
├─────────────────────────────────┤
│  热门推荐 (8张图)                │
│  [刷新]              [查看更多 >]  │
├─────────────────────────────────┤
│  热门星卡 (8张图)                │
│  [刷新]              [查看更多 >]  │
├─────────────────────────────────┤
│  热门吧唧 (8张图)                │
│  [刷新]              [查看更多 >]  │
├─────────────────────────────────┤
│  热门海报 (8张图)                │
│  [刷新]              [查看更多 >]  │
├─────────────────────────────────┤
│  (CreationGrid 组件 - 不变)       │
│  [热门作品] [最新作品] [星卡] ...   │
└─────────────────────────────────┘

分类配置

区块 type值 说明
热门推荐 hot_recommend 混合所有类型,高点赞作品
热门星卡 hot_star_card 只展示星卡
热门吧唧 hot_badge 只展示吧唧
热门海报 hot_poster 只展示海报

二、接口设计

2.1 接口清单

接口 方法 参数 说明
/api/v1/inspiration-flow/hot/batch GET 页面加载批量获取
/api/v1/inspiration-flow/hot GET type 单个分类刷新
/api/v1/inspiration-flow/hot/more GET type, cursor, limit 查看更多分页
/api/v1/inspiration-flow GET type, cursor, limit, direction 灵感瀑布流(逻辑调整)

2.2 调用时序

页面加载 → 批量请求1次
     └── GET /api/v1/inspiration-flow/hot/batch

刷新 → 单个分类刷新(点击哪个刷新请求哪个)
     └── GET /api/v1/inspiration-flow/hot?type=hot_star_card

查看更多 → 新页面分页
     └── GET /api/v1/inspiration-flow/hot/more?type=hot_star_card&cursor=xxx&limit=20

2.3 批量获取热门分类

接口: GET /api/v1/inspiration-flow/hot/batch

请求参数: 无

业务逻辑:

  1. 计算各分类作品的点赞平均值
  2. 筛选点赞数 ≥ 平均值的作品
  3. 基于时间窗口伪随机排序取 8 条
  4. 后端动态返回分类数据,前端根据返回的分类动态渲染
  5. 排序说明:使用随机排序,同一分类每次请求返回的作品可能不同

响应结构:

{
  "code": 200,
  "data": {
    "categories": [
      {
        "type": "hot_recommend",
        "title": "热门推荐",
        "items": [
          {
            "asset_id": "xxx",
            "cover_url": "https://xxx.jpg",
            "owner_nickname": "用户名",
            "owner_avatar": "https://xxx.jpg",
            "likes": 1234
          }
        ]
      },
      {
        "type": "hot_star_card",
        "title": "热门星卡",
        "items": [...]
      },
      {
        "type": "hot_badge",
        "title": "热门吧唧",
        "items": [...]
      },
      {
        "type": "hot_poster",
        "title": "热门海报",
        "items": [...]
      }
    ]
  }
}

空分类处理: 如果某个分类没有作品或查询失败,该分类不返回。

2.4 单个分类刷新

接口: GET /api/v1/inspiration-flow/hot

请求参数:

参数 类型 必填 说明
type string hot_recommend / hot_star_card / hot_badge / hot_poster

业务逻辑:

  1. 计算该分类作品的点赞平均值
  2. 筛选点赞数 ≥ 平均值的作品
  3. 基于时间窗口伪随机排序取 8 条
  4. 排序说明:批量和刷新接口使用随机排序,每次刷新可能看到不同作品

响应结构:

{
  "code": 200,
  "data": {
    "type": "hot_star_card",
    "title": "热门星卡",
    "items": [...]
  }
}

2.5 查看更多分页

接口: GET /api/v1/inspiration-flow/hot/more

请求参数:

参数 类型 必填 说明
type string 分类类型(hot_recommend/hot_star_card/hot_badge/hot_poster
cursor string 翻页游标Base64 编码的 JSON包含 like_countasset_id
limit int 每页数量,默认 20

业务逻辑:

  1. 计算该分类作品点赞平均值
  2. 筛选点赞数 ≥ 平均值的作品
  3. 按点赞数 DESC、asset_id DESC 排序
  4. Cursor 分页返回(游标编码 last_like_count 和 last_asset_id

Cursor 编码格式:

// Base64 编码 {"like_count": 1234, "asset_id": 5678}

响应结构:

{
  "code": 200,
  "data": {
    "items": [
      {
        "asset_id": "xxx",
        "cover_url": "https://xxx.jpg",
        "owner_nickname": "用户名",
        "owner_avatar": "https://xxx.jpg",
        "likes": 1234,
        "material_type": "star_card",
        "sub_type": "raster"
      }
    ],
    "cursor": "xxx",
    "has_more": true
  }
}

注意material_type 表示素材类型(star_card/badge/poster),不是热门分类类型。

2.6 灵感瀑布流(逻辑调整)

接口: GET /api/v1/inspiration-flow

请求参数:

参数 类型 必填 说明
type string 素材类型过滤:badge/poster/original/all,默认 all
cursor string 翻页游标
direction string 滚动方向:right(加载新数据)/ left(加载历史)
limit int 每页数量,默认 20
session_id string 会话 ID用于去重排除已展示的作品

业务逻辑(调整后):

  1. 计算该分类作品的点赞平均值(基于展出中的作品 JOIN assets
  2. 第一段:点赞 > 平均值的作品,基于时间窗口伪随机排列,取前 20 条
  3. 第二段:如果不够 20 条,从点赞 ≥ 平均值的作品中(排除第一段已选的)补充,伪随机排列
  4. 两段组合返回
  5. 通过 session_id 排除已展示的作品(excludeIDs

响应结构:

{
  "code": 200,
  "data": {
    "items": [...],
    "cursor": "xxx",
    "has_more": true
  }
}

注意:灵感瀑布的 type 参数与热门推荐接口的 type 参数含义完全不同:

  • 灵感瀑布:badge/poster/original/all(素材类型过滤)
  • 热门推荐:hot_recommend/hot_star_card/hot_badge/hot_poster(分类类型)

2.7 热门分类 type 与资产类型映射

热门分类 type 对应 assetType 说明
hot_recommend 空字符串 "" 混合所有类型,不过滤
hot_star_card star_card 只展示星卡
hot_badge badge 只展示吧唧
hot_poster poster 只展示海报

三、数据模型

3.1 数据库表结构

参考现有 assets 表,无需新建表。

3.2 伪 SQL以 star_card 为例)

说明:热门作品基于展出中的作品筛选,只有在 exhibitions 表中有有效展出记录且未过期的作品才能参与热门排名。

-- 计算该分类作品的点赞平均值(基于展出中的作品)
AVG_LIKES := SELECT AVG(a.like_count)
FROM exhibitions e
JOIN assets a ON a.id = e.asset_id
WHERE e.occupier_star_id = :star_id
  AND e.expire_at > :now
  AND e.deleted_at IS NULL
  AND a.status = 1 AND a.is_active = true
  AND a.asset_type = 'star_card';

-- 随机取8条基于时间窗口的伪随机避免 ORDER BY RANDOM()
WINDOW_SEED := :now / 30000 % 10;  -- 每30秒变化一次

SELECT e.id as exhibition_id, e.asset_id, a.name, a.cover_url, a.like_count,
       fp.nickname as owner_nickname, fp.avatar_url as owner_avatar
FROM exhibitions e
JOIN assets a ON a.id = e.asset_id
JOIN fan_profiles fp ON e.occupier_uid = fp.user_id AND e.occupier_star_id = fp.star_id
WHERE e.occupier_star_id = :star_id
  AND e.expire_at > :now
  AND e.deleted_at IS NULL
  AND a.status = 1 AND a.is_active = true
  AND a.asset_type = 'star_card'
  AND a.like_count >= :AVG_LIKES
  AND e.id % 10 = :WINDOW_SEED
ORDER BY e.id
LIMIT 8;

3.3 随机算法说明

避免 ORDER BY RANDOM()(大表性能差),改用基于时间窗口的伪随机:

  • WINDOW_SEED = now / 30000 % 10 — 每 30 秒变化一次
  • 通过 exhibition_id % 10 = WINDOW_SEED 筛选作品
  • 同一时间窗口内结果固定,不同时间窗口返回不同作品
  • 如果数量不够,再补充查询(不加窗口过滤)

四、后端实现

4.1 文件改动

文件 改动内容
backend/proto/gallery.proto 新增 Proto 消息定义
backend/pkg/proto/gallery/gallery.pb.go 重新生成 Proto 代码
backend/gateway/controller/gallery_controller.go 新增 GetHotInspirationFlowBatchGetHotInspirationFlowGetHotInspirationFlowMore 方法
backend/gateway/router/router.go 新增 /inspiration-flow/hot/* 路由注册
backend/gateway/dto/gallery_dto.go 新增 DTO 结构体
backend/gateway/dto/gallery_converter.go 新增 DTO 转换函数
backend/services/galleryService/service/gallery_service.go 新增 GetHotInspirationFlowBatchGetHotInspirationFlowGetHotInspirationFlowMore Service 方法
backend/services/galleryService/repository/gallery_repository.go 新增 Repository 方法
backend/pkg/database/redis.go 新增热门推荐缓存辅助函数

4.2 性能优化

  1. 并行查询 — 批量接口用 goroutine 并行查 4 个分类
  2. 缓存平均值 — 每个分类的点赞均值独立缓存TTL: 5 分钟),避免重复计算
  3. 结果缓存 — 热门列表缓存 30-60 秒,降低数据库压力
  4. 随机优化 — 避免 ORDER BY RANDOM(),使用基于时间窗口的伪随机

4.3 缓存策略

缓存项 TTL 说明
分类点赞均值 5 分钟 每个分类单独缓存,减少重复计算平均值
热门列表(批量) 30 秒 /hot/batch 结果缓存,降低数据库压力
热门列表(单个刷新) 不缓存 用户主动刷新应获取新数据,只缓存点赞均值

五、前端实现

5.1 文件结构

frontend/pages/square/
├── square.vue                    # 修改集成4个热门分类区块
├── components/
│   └── HotCategoryBlock.vue      # 新增:单个热门分类区块组件
└── hot-category-more.vue         # 新增:热门分类查看更多页面

5.2 新增文件

文件 说明
pages/square/components/HotCategoryBlock.vue 单个热门分类区块组件
pages/square/hot-category-more.vue 热门分类查看更多页面

5.3 修改文件

文件 改动内容
frontend/pages/square/square.vue 顶部新增 4 个 HotCategoryBlock
frontend/utils/api.js 新增批量获取、单个刷新、查看更多 API

5.4 HotCategoryBlock 组件

HotCategoryBlock.vue
├── props: { categoryType }  // 传入后端返回的 type如 "hot_star_card"
├── 状态: items, loading, refreshing, title
├── 方法: handleRefresh(), handleViewMore()
└── 模板:
    ├── 标题 (title)
    ├── 4x2 网格 (items) + loading 骨架屏
    ├── 刷新按钮 (loading 状态)
    └── 查看更多按钮

说明

  • categoryType 直接使用后端返回的 typehot_star_card
  • 刷新时调用 getHotInspirationFlowApi(categoryType)
  • 查看更多时调用 getHotInspirationFlowMoreApi(categoryType, ...)

刷新交互:

  • 整个区块显示骨架屏 loading
  • 刷新按钮显示 loading 状态
  • 刷新完成后正常显示

5.5 hot-category-more.vue 页面

hot-category-more.vue
├── onLoad: 获取 type 参数
├── 状态: items, cursor, loading, hasMore, title
├── 方法: loadMore(), loadData()
└── 模板:
    ├── 返回按钮 + 标题
    └── 分页网格列表

星卡子标签(仅星卡分类显示):

子标签 value 说明
全部 all 全部星卡
光栅卡 raster 光栅卡类型
镭射卡 holographic 镭射卡类型
撕拉卡 tear_off 撕拉卡类型
拍立得 polaroid 拍立得类型

5.6 API 封装

// 批量获取热门分类(页面加载用)
export function getHotInspirationFlowBatchApi() {
  return request({
    url: '/api/v1/inspiration-flow/hot/batch',
    method: 'GET'
  })
}

// 单个分类刷新
export function getHotInspirationFlowApi(type) {
  return request({
    url: '/api/v1/inspiration-flow/hot',
    method: 'GET',
    data: { type }
  })
}

// 查看更多分页
export function getHotInspirationFlowMoreApi(type, cursor = '', limit = 20) {
  return request({
    url: '/api/v1/inspiration-flow/hot/more',
    method: 'GET',
    data: { type, cursor, limit }
  })
}

调用示例

// 批量加载(页面初始化)
const res = await getHotInspirationFlowBatchApi()
res.data.categories.forEach(cat => {
  // cat.type: "hot_recommend", cat.title: "热门推荐", cat.items: [...]
  console.log(cat.type, cat.items.length)
})

// 单个刷新(点击刷新按钮)
const refreshRes = await getHotInspirationFlowApi('hot_star_card')

// 查看更多(跳转 hot-category-more.vue
uni.navigateTo({
  url: `/pages/square/hot-category-more?type=${type}&title=${encodeURIComponent(title)}`
})

六、实现步骤

后端

  • Phase 1: 新增 /api/v1/inspiration-flow/hot/batch 批量接口(并行查询 + 缓存)
  • Phase 2: 新增 /api/v1/inspiration-flow/hot 单个分类接口
  • Phase 3: 新增 /api/v1/inspiration-flow/hot/more 查看更多接口
  • Phase 4: 修改 /api/v1/inspiration-flow 逻辑(分段填充)

前端

  • Phase 5: 新增 utils/api.js API 方法
  • Phase 6: 新增 HotCategoryBlock.vue 组件
  • Phase 7: 新增 hot-category-more.vue 页面
  • Phase 8: 修改 square.vue 集成 4 个热门分类区块
  • Phase 9: CreationGrid 保持不变

联调

  • Phase 10: 前后端联调测试

七、注意事项

  1. 后端动态返回分类 — 前端无需硬编码分类,根据接口返回的 categories 动态渲染
  2. 空分类不显示 — 如果某个分类没有作品,该分类不返回或前端忽略
  3. 扩展性 — 后续增加新分类(如"热门贴纸"),只需后端调整,前端无需改动
  4. 性能优先 — 后端必须做好并行查询和缓存优化,确保批量接口响应时间 < 200ms