topfans/docs/superpowers/specs/2026-04-27-my-assets-design.md
2026-04-27 20:57:03 +08:00

440 lines
10 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.

# 我的作品统计(点赞/展出)设计文档
> **创建日期:** 2026-04-27
> **项目:** TopFans 我的作品统计
> **服务:** socialService / galleryService
> **状态:** 设计中
---
## 一、设计目标
提供用户查看自己点赞过的作品和展出过的作品的统计接口,返回实时点赞数。
---
## 二、数据来源
### 2.1 我点赞的作品
**主表:** asset_likes点赞记录表
**关联表:** assets资产表- 用于获取藏品信息
**筛选条件:**
- `user_id = ?` (当前用户)
- `star_id = ?` (当前 star_id)
- `assets.deleted_at IS NULL` (藏品未删除)
- `assets.is_active = true` (藏品已激活)
### 2.2 我展出的作品
**主表:** exhibitions展品展示表
**关联表:** assets资产表- 用于获取藏品信息
**筛选条件:**
- `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 响应:**
```json
{
"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.2 获取我展出的作品列表
```
GET /api/v1/me/exhibited-assets
```
**Query 参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量最大100 |
**HTTP 响应:**
```json
{
"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.3 错误码
| code | 说明 |
|------|------|
| 200 | 成功 |
| 401 | 用户认证失败 |
| 500 | 服务器内部错误 |
---
## 四、Proto 定义
### 4.1 我点赞的作品
```protobuf
// 获取我点赞的作品列表请求
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; // 点赞时间(毫秒时间戳)
}
```
---
### 4.2 我展出的作品
```protobuf
// 获取我展出的作品列表请求
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; // 展出过期时间(毫秒时间戳)
}
```
---
### 4.3 Service 方法
在 SocialService 中新增方法:
```protobuf
// 社交服务
service SocialService {
// ... 现有方法 ...
// 获取我点赞的作品列表
rpc GetMyLikedAssets(GetMyLikedAssetsRequest) returns (GetMyLikedAssetsResponse) {
option (google.api.http) = {
get: "/api/v1/me/liked-assets"
};
}
}
```
在 GalleryService 中新增方法:
```protobuf
// 展馆服务
service GalleryService {
// ... 现有方法 ...
// 获取我展出的作品列表
rpc GetMyExhibitedAssets(GetMyExhibitedAssetsRequest) returns (GetMyExhibitedAssetsResponse) {
option (google.api.http) = {
get: "/api/v1/me/exhibited-assets"
};
}
}
```
---
## 五、核心逻辑
### 5.1 查询我点赞的作品
```sql
SELECT
al.asset_id,
a.name,
a.cover_url,
a.like_count,
al.created_at as liked_at
FROM asset_likes al
JOIN assets a ON al.asset_id = a.id
WHERE al.user_id = ?
AND al.star_id = ?
AND a.deleted_at IS NULL
AND a.is_active = true
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?;
-- 计数
SELECT COUNT(*)
FROM asset_likes al
JOIN assets a ON al.asset_id = a.id
WHERE al.user_id = ?
AND al.star_id = ?
AND a.deleted_at IS NULL
AND a.is_active = true;
```
**参数说明:**
- `? = user_id` (当前用户)
- `? = star_id` (当前 star_id)
- `? = page_size`
- `? = (page - 1) * page_size`
---
### 5.2 查询我展出的作品(只返回展出中的)
```sql
SELECT
e.asset_id,
a.name,
a.cover_url,
a.like_count,
e.start_time as exhibited_at,
e.expire_at
FROM exhibitions e
JOIN assets a ON e.asset_id = a.id
WHERE e.occupier_uid = ?
AND e.occupier_star_id = ?
AND e.deleted_at IS NULL
AND 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 表(已有字段)
```go
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 表(已有字段)
```go
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 表(已有字段)
```go
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