topfans/docs/superpowers/specs/2026-04-27-my-assets-design.md
zerosaturation e1a61c4519 feat: 实现我的作品统计接口(点赞/展出)
- 新增 GET /api/v1/me/liked-assets 接口
- 新增 GET /api/v1/me/exhibited-assets 接口
- 新增 GetMyLikedAssets 和 GetMyExhibitedAssets RPC 方法
- 新增 ExhibitedAssetItemDTO 和 GetMyExhibitedAssetsResponseDTO
- 前端新增 getUserLikedAssetsApi 和 getUserExhibitedAssetsApi(暂不实现)
- 更新设计文档,标记他人作品统计接口为暂不实现

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 10:51:07 +08:00

19 KiB
Raw Blame History

我的作品统计(点赞/展出)设计文档

创建日期: 2026-04-27 项目: TopFans 我的作品统计 服务: socialService / galleryService 状态: 设计中


一、设计目标

提供用户查看自己点赞过的作品和展出过的作品的统计接口,返回实时点赞数。


二、数据来源

2.1 我点赞的作品

主表: asset_likes点赞记录表

关联表:

  • assets资产表- 用于获取藏品信息
  • exhibitions展品展示表- 用于过滤展出中且未过期的藏品
  • exhibition_revenue_records收益记录表- 用于获取当前可领取收益

筛选条件:

  • user_id = ? (当前用户)
  • star_id = ? (当前 star_id)
  • assets.deleted_at IS NULL (藏品未删除)
  • assets.is_active = true (藏品已激活)
  • exhibitions.deleted_at IS NULL (展出记录未删除)
  • exhibitions.expire_at > now (展出未过期)

2.2 我展出的作品

主表: exhibitions展品展示表

关联表:

  • assets资产表- 用于获取藏品信息
  • exhibition_revenue_records收益记录表- 用于获取当前可领取收益

筛选条件:

  • occupier_uid = ? (当前用户)
  • occupier_star_id = ? (当前 star_id)
  • deleted_at IS NULL (未删除)

三、API 设计

3.1 获取我点赞的作品列表

GET /api/v1/me/liked-assets

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "liked_at": 1714214400000
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 50,
    "has_more": true
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
liked_at int64 用户点赞该作品的时间(毫秒时间戳)
earnings int64 当前可领取收益status='claimable' 的 crystal_amount 汇总)

3.2 获取我展出的作品列表

GET /api/v1/me/exhibited-assets

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "exhibited_at": 1714214400000,
        "expire_at": 1714278400000,
        "earnings": 500
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 10,
    "has_more": false
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
exhibited_at int64 展出开始时间(毫秒时间戳)
expire_at int64 展出过期时间(毫秒时间戳)
earnings int64 当前可领取收益status='claimable' 的 crystal_amount 汇总)

3.3 获取我今日点赞的作品(暂不实现)

GET /api/v1/me/today-liked-assets

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "liked_at": 1714214400000
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 50,
    "has_more": true
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
liked_at int64 用户点赞该作品的时间(毫秒时间戳)

状态:暂不实现


3.4 获取我本周点赞的作品(暂不实现)

GET /api/v1/me/week-liked-assets

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "liked_at": 1714214400000
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 50,
    "has_more": true
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
liked_at int64 用户点赞该作品的时间(毫秒时间戳)

状态:暂不实现


3.5 获取他人点赞的作品列表(暂不实现)

GET /api/v1/users/{user_id}/liked-assets

Path 参数:

参数 类型 必填 说明
user_id int64 他人用户ID

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "liked_at": 1714214400000
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 50,
    "has_more": true
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
liked_at int64 用户点赞该作品的时间(毫秒时间戳)

状态:暂不实现


3.6 获取他人展出的作品列表(暂不实现)

GET /api/v1/users/{user_id}/exhibited-assets

Path 参数:

参数 类型 必填 说明
user_id int64 他人用户ID

Query 参数:

参数 类型 必填 默认值 说明
page int 1 页码
page_size int 20 每页数量最大100

HTTP 响应:

{
  "code": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "asset_id": 123,
        "name": "藏品名称",
        "cover_url": "https://xxx.com/cover.png",
        "like_count": 100,
        "exhibited_at": 1714214400000,
        "expire_at": 1714278400000
      }
    ],
    "page": 1,
    "page_size": 20,
    "total": 10,
    "has_more": false
  }
}

字段说明:

字段 类型 说明
asset_id int64 资产ID
name string 藏品名称
cover_url string 封面图URL
like_count int32 实时点赞数(来自 assets 表)
exhibited_at int64 展出开始时间(毫秒时间戳)
expire_at int64 展出过期时间(毫秒时间戳)

状态:暂不实现


3.7 错误码

code 说明
200 成功
401 用户认证失败
500 服务器内部错误

四、Proto 定义

4.1 我点赞的作品

// 获取我点赞的作品列表请求
message GetMyLikedAssetsRequest {
  int32 page = 1;        // 页码默认1
  int32 page_size = 2;   // 每页数量默认20最大100
}

// 获取我点赞的作品列表响应
message GetMyLikedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  LikedAssetsData data = 2;
}

// 点赞作品数据
message LikedAssetsData {
  repeated LikedAssetItem items = 1;  // 作品列表
  int32 page = 2;                     // 当前页码
  int32 page_size = 3;                // 每页数量
  int64 total = 4;                     // 总数量
  bool has_more = 5;                   // 是否有更多
}

// 点赞作品项
message LikedAssetItem {
  int64 asset_id = 1;        // 资产ID
  string name = 2;           // 藏品名称
  string cover_url = 3;      // 封面图URL
  int32 like_count = 4;      // 实时点赞数
  int64 liked_at = 5;        // 点赞时间(毫秒时间戳)
  int64 earnings = 6;        // 当前可领取收益
}

4.2 我展出的作品

// 获取我展出的作品列表请求
message GetMyExhibitedAssetsRequest {
  int32 page = 1;        // 页码默认1
  int32 page_size = 2;   // 每页数量默认20最大100
}

// 获取我展出的作品列表响应
message GetMyExhibitedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  ExhibitedAssetsData data = 2;
}

// 展出作品数据
message ExhibitedAssetsData {
  repeated ExhibitedAssetItem items = 1;  // 作品列表
  int32 page = 2;                         // 当前页码
  int32 page_size = 3;                     // 每页数量
  int64 total = 4;                         // 总数量
  bool has_more = 5;                       // 是否有更多
}

// 展出作品项
message ExhibitedAssetItem {
  int64 asset_id = 1;        // 资产ID
  string name = 2;           // 藏品名称
  string cover_url = 3;      // 封面图URL
  int32 like_count = 4;      // 实时点赞数
  int64 exhibited_at = 5;    // 展出开始时间(毫秒时间戳)
  int64 expire_at = 6;        // 展出过期时间(毫秒时间戳)
  int64 earnings = 7;        // 当前可领取收益
}

4.3 我今日/本周点赞的作品(暂不实现)

// 获取我今日点赞的作品列表请求
message GetMyTodayLikedAssetsRequest {
  int32 page = 1;        // 页码默认1
  int32 page_size = 2;   // 每页数量默认20最大100
}

// 获取我今日点赞的作品列表响应
message GetMyTodayLikedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  LikedAssetsData data = 2;
}

// 获取我本周点赞的作品列表请求
message GetMyWeekLikedAssetsRequest {
  int32 page = 1;        // 页码默认1
  int32 page_size = 2;   // 每页数量默认20最大100
}

// 获取我本周点赞的作品列表响应
message GetMyWeekLikedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  LikedAssetsData data = 2;
}

状态:暂不实现


4.4 他人点赞/展出的作品(暂不实现)

// 获取他人点赞的作品列表请求
message GetUserLikedAssetsRequest {
  int64 user_id = 1;     // 他人用户ID
  int32 page = 2;        // 页码默认1
  int32 page_size = 3;   // 每页数量默认20最大100
}

// 获取他人点赞的作品列表响应
message GetUserLikedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  LikedAssetsData data = 2;
}

// 获取他人展出的作品列表请求
message GetUserExhibitedAssetsRequest {
  int64 user_id = 1;     // 他人用户ID
  int32 page = 2;        // 页码默认1
  int32 page_size = 3;   // 每页数量默认20最大100
}

// 获取他人展出的作品列表响应
message GetUserExhibitedAssetsResponse {
  topfans.common.BaseResponse base = 1;
  ExhibitedAssetsData data = 2;
}

状态:暂不实现


4.5 Service 方法

在 SocialService 中新增方法:

// 社交服务
service SocialService {
  // ... 现有方法 ...

  // 获取我点赞的作品列表
  rpc GetMyLikedAssets(GetMyLikedAssetsRequest) returns (GetMyLikedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/me/liked-assets"
    };
  }

  // 获取我今日点赞的作品列表(暂不实现)
  rpc GetMyTodayLikedAssets(GetMyTodayLikedAssetsRequest) returns (GetMyTodayLikedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/me/today-liked-assets"
    };
  }

  // 获取我本周点赞的作品列表(暂不实现)
  rpc GetMyWeekLikedAssets(GetMyWeekLikedAssetsRequest) returns (GetMyWeekLikedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/me/week-liked-assets"
    };
  }

  // 获取他人点赞的作品列表(暂不实现)
  rpc GetUserLikedAssets(GetUserLikedAssetsRequest) returns (GetUserLikedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/users/{user_id}/liked-assets"
    };
  }
}

在 GalleryService 中新增方法:

// 展馆服务
service GalleryService {
  // ... 现有方法 ...

  // 获取我展出的作品列表
  rpc GetMyExhibitedAssets(GetMyExhibitedAssetsRequest) returns (GetMyExhibitedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/me/exhibited-assets"
    };
  }

  // 获取他人展出的作品列表(暂不实现)
  rpc GetUserExhibitedAssets(GetUserExhibitedAssetsRequest) returns (GetUserExhibitedAssetsResponse) {
    option (google.api.http) = {
      get: "/api/v1/users/{user_id}/exhibited-assets"
    };
  }
}

五、核心逻辑

5.1 查询我点赞的作品(只返回展出中且未过期的)

SELECT
    al.asset_id,
    a.name,
    a.cover_url,
    a.like_count,
    al.created_at as liked_at,
    COALESCE(SUM(err.crystal_amount), 0) as earnings
FROM asset_likes al
JOIN assets a ON al.asset_id = a.id
JOIN exhibitions e ON e.asset_id = a.id
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
WHERE al.user_id = ?
  AND al.star_id = ?
  AND a.deleted_at IS NULL
  AND a.is_active = true
  AND e.deleted_at IS NULL
  AND e.expire_at > ?
GROUP BY al.asset_id, a.name, a.cover_url, a.like_count, al.created_at
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?;

-- 计数
SELECT COUNT(DISTINCT al.asset_id)
FROM asset_likes al
JOIN assets a ON al.asset_id = a.id
JOIN exhibitions e ON e.asset_id = a.id
WHERE al.user_id = ?
  AND al.star_id = ?
  AND a.deleted_at IS NULL
  AND a.is_active = true
  AND e.deleted_at IS NULL
  AND e.expire_at > ?;

参数说明:

  • ? = user_id (当前用户)
  • ? = star_id (当前 star_id)
  • ? = now (当前时间戳,只显示展出中且未过期的)
  • ? = page_size
  • ? = (page - 1) * page_size

5.2 查询我展出的作品(只返回展出中的)

SELECT
    e.asset_id,
    a.name,
    a.cover_url,
    a.like_count,
    e.start_time as exhibited_at,
    e.expire_at,
    COALESCE(SUM(err.crystal_amount), 0) as earnings
FROM exhibitions e
JOIN assets a ON e.asset_id = a.id
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
WHERE e.occupier_uid = ?
  AND e.occupier_star_id = ?
  AND e.deleted_at IS NULL
  AND e.expire_at > ?  -- 只返回未过期的
GROUP BY e.asset_id, a.name, a.cover_url, a.like_count, e.start_time, e.expire_at
ORDER BY e.start_time DESC
LIMIT ? OFFSET ?;

-- 计数
SELECT COUNT(*)
FROM exhibitions e
WHERE e.occupier_uid = ?
  AND e.occupier_star_id = ?
  AND e.deleted_at IS NULL
  AND e.expire_at > ?;  -- 只返回未过期的

参数说明:

  • ? = user_id (当前用户)
  • ? = star_id (当前 star_id)
  • ? = now (当前时间戳,只显示未过期的)
  • ? = page_size
  • ? = (page - 1) * page_size

六、数据模型

6.1 asset_likes 表(已有字段)

type AssetLike struct {
    ID        int64 `gorm:"primaryKey"`
    AssetID   int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_asset"`
    UserID    int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_asset"`
    StarID    int64 `gorm:"not null;index"`
    CreatedAt int64 `gorm:"not null;index"`
}

6.2 exhibitions 表(已有字段)

type Exhibition struct {
    ID             int64  `gorm:"primaryKey"`
    AssetID        int64  `gorm:"not null"`
    SlotID         int64  `gorm:"not null"`
    HostProfileID  int64  `gorm:"not null"`
    OccupierUID    int64  `gorm:"not null;index"`
    OccupierStarID int64  `gorm:"not null;index"`
    StartTime      int64  `gorm:"not null"`
    ExpireAt       int64  `gorm:"not null;index"`
    CreatedAt      int64  `gorm:"not null"`
    UpdatedAt      int64  `gorm:"not null"`
    DeletedAt      *int64 `gorm:"index"`
}

6.3 assets 表(已有字段)

type Asset struct {
    ID        int64  `gorm:"primaryKey"`
    Name      string `gorm:"type:varchar(100);not null"`
    CoverURL  string `gorm:"type:varchar(500);not null"`
    LikeCount int32  `gorm:"not null;default:0"`
    Status    int32  `gorm:"not null;default:0"`
    DeletedAt *int64 `gorm:"index"`
    IsActive  bool   `gorm:"default:true;not null"`
}

七、项目文件结构

backend/
├── proto/
│   ├── social.proto      # 修改:新增 GetMyLikedAssets 方法
│   └── gallery.proto     # 修改:新增 GetMyExhibitedAssets 方法
│
├── pkg/proto/
│   ├── social/
│   │   ├── social.pb.go         # 重新生成
│   │   └── social.triple.go      # 重新生成
│   └── gallery/
│       ├── gallery.pb.go        # 重新生成
│       └── gallery.triple.go    # 重新生成
│
├── services/socialService/
│   ├── repository/
│   │   └── asset_like_repository.go  # 修改:新增查询方法
│   │
│   ├── service/
│   │   └── asset_like_service.go     # 修改:新增 GetMyLikedAssets 方法
│   │
│   └── provider/
│       └── social_provider.go        # 修改:新增 GetMyLikedAssets Handler
│
├── services/galleryService/
│   ├── repository/
│   │   └── gallery_repository.go     # 修改:新增 GetExhibitionsByOccupier 方法
│   │
│   ├── service/
│   │   └── exhibition_service.go     # 修改:新增 GetMyExhibitedAssets 方法
│   │
│   └── provider/
│       └── gallery_provider.go       # 修改:新增 GetMyExhibitedAssets Handler
│
└── gateway/
    ├── controller/
    │   ├── social_controller.go       # 修改:新增 /api/v1/me/liked-assets 路由
    │   └── gallery_controller.go      # 修改:新增 /api/v1/me/exhibited-assets 路由
    │
    └── router/
        └── router.go                  # 修改:新增路由配置

八、已确认事项

  1. 只显示展出中的作品 — 通过 expire_at > now 过滤
  2. 排序方式 — 按展出时间倒序start_time DESC
  3. 分页大小 — 默认 20最大 100
  4. 点赞作品也只显示展出中且未过期的 — 通过 JOIN exhibitions 并过滤 expire_at > now
  5. 今日/本周点赞暂不实现 — API 和 Proto 已定义,但代码实现待后续
  6. 每个藏品返回当前可领取收益 — 关联 exhibition_revenue_records 表,汇总 status='claimable'crystal_amount
  7. 他人点赞/展出的作品列表暂不实现 — API 和 Proto 已定义,但代码实现待后续