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

312 lines
7.8 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 横向瀑布流藏品展示
> **服务:** galleryService (Go Dubbo-go)
> **状态:** 设计中
---
## 一、设计目标
横向瀑布流展示该 star_id 下所有用户展出的藏品,支持随机展示、分页、按类型过滤。
---
## 二、数据来源
**主表:** Exhibition展品展示表
**关联表:** Asset资产表- 用于按 material_type 过滤
**筛选条件:**
- `occupier_star_id = ?` (当前用户 star_id)
- `expire_at > now` (未过期)
- `deleted_at IS NULL` (未删除)
**按 type 过滤:** JOIN Asset 表WHERE assets.material_type = ?
---
## 三、API 设计
### 3.1 获取灵感瀑布(横向瀑布流)藏品列表
```
GET /api/v1/inspiration-flow
```
**Query 参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量最大100 |
| type | string | 否 | all | 过滤类型badge/poster/original/all |
**HTTP 响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"items": [
{
"asset_id": 123,
"name": "藏品名称",
"cover_url": "https://xxx.com/cover.png",
"like_count": 100,
"owner_nickname": "粉丝昵称"
}
],
"page": 1,
"page_size": 20,
"total": 100,
"has_more": true
}
}
```
**错误码:**
| code | 说明 |
|------|------|
| 200 | 成功 |
| 401 | 用户认证失败 |
| 500 | 服务器内部错误 |
---
## 四、Proto 定义
### 4.1 Request / Response
```protobuf
// 获取灵感瀑布藏品列表请求
message GetInspirationFlowRequest {
int32 page = 1; // 页码默认1
int32 page_size = 2; // 每页数量默认20最大100
string type = 3; // 过滤类型badge/poster/original/all默认all
}
// 获取灵感瀑布藏品列表响应
message GetInspirationFlowResponse {
topfans.common.BaseResponse base = 1;
InspirationFlowData data = 2;
}
// 灵感瀑布数据
message InspirationFlowData {
repeated InspirationFlowItem items = 1; // 藏品列表
int32 page = 2; // 当前页码
int32 page_size = 3; // 每页数量
int64 total = 4; // 总数量
bool has_more = 5; // 是否有更多
}
// 灵感瀑布藏品项
message InspirationFlowItem {
int64 asset_id = 1; // 资产ID
string name = 2; // 藏品名称
string cover_url = 3; // 封面图URL
int32 like_count = 4; // 点赞数
string owner_nickname = 5; // 展出者昵称
}
```
### 4.2 Service 方法
在 GalleryService 中新增方法:
```protobuf
// 展馆服务
service GalleryService {
// ... 现有方法 ...
// 获取灵感瀑布藏品列表
rpc GetInspirationFlow(GetInspirationFlowRequest) returns (GetInspirationFlowResponse) {
option (google.api.http) = {
get: "/api/v1/inspiration-flow"
};
}
}
```
---
## 五、核心逻辑
### 5.1 查询逻辑
```sql
SELECT
e.asset_id,
a.name,
a.cover_url,
a.like_count,
fp.nickname as owner_nickname
FROM exhibitions e
JOIN assets a ON e.asset_id = a.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 = ?
AND e.expire_at > ?
AND e.deleted_at IS NULL
AND a.status = 1
AND a.is_active = true
AND (? = 'all' OR a.material_type = ?)
ORDER BY RANDOM()
LIMIT ? OFFSET ?;
```
**参数说明:**
- `? = star_id` (当前用户 star_id)
- `? = now` (当前时间戳)
- `? = type` (过滤类型)
- `? = page_size`
- `? = (page - 1) * page_size`
### 5.2 6小时自动下架
已实现的 `GetExpiredExhibitions` 方法查询已过期的展览记录,调度器定期清理。
**下架时执行:**
1. 设置 `deleted_at = now`
2. 调用 `ClearAssetLikeRecords` RPC 重置点赞数为 0
3. 更新 `asset_registry.display_status = 0`
### 5.3 重新上架逻辑
当藏品重新展示时:
1. 创建新的 Exhibition 记录
2. 更新 `asset_registry.display_status = 1`
3. 点赞数从 0 开始(因为之前下架时已清除点赞记录)
### 5.4 随机展示
使用 `ORDER BY RANDOM()` 实现随机排序。
---
## 六、数据模型
### 6.1 Exhibition 表(已有字段)
```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"`
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.2 Asset 表关联字段
```go
type Asset struct {
// ... 现有字段 ...
MaterialType string `gorm:"column:material_type"` // 素材类型badge/poster/original
IsOriginal bool `gorm:"column:is_original"`
LikeCount int32 `gorm:"not null;default:0"`
}
```
**新增字段(需数据库变更):**
```sql
ALTER TABLE assets ADD COLUMN material_type VARCHAR(50) DEFAULT 'original';
```
**material_type 枚举值:**
- `original` — 原创(默认值)
- `badge` — 吧唧
- `poster` — 海报
---
## 七、收益记录
**预留:** 收益记录通过经济系统已有的 `exhibition_revenue` change_type 实现。
后续按需在 Exhibition 创建/删除时写入 `crystal_transaction_records`change_type 为 `exhibition_revenue`
---
## 八、配置项
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| exhibition_duration | 展览时长(秒) | 144004小时可配置 |
| inspiration_flow_page_size | 默认每页数量 | 20 |
| inspiration_flow_max_page_size | 最大每页数量 | 100 |
---
## 九、项目文件结构
```
backend/
├── proto/
│ └── gallery.proto # 修改:新增 GetInspirationFlow 方法
├── pkg/proto/
│ ├── gallery/
│ │ ├── gallery.pb.go # 重新生成
│ │ └── gallery.triple.go # 重新生成
├── services/galleryService/
│ ├── repository/
│ │ └── gallery_repository.go # 修改:新增 GetActiveExhibitions 方法
│ │
│ ├── service/
│ │ └── gallery_service.go # 修改:新增 GetInspirationFlow 方法
│ │
│ ├── provider/
│ │ └── gallery_provider.go # 修改:新增 GetInspirationFlow Handler
│ │
│ └── config/
│ └── gallery_config.go # 修改:新增灵感瀑布配置项
├── scripts/
│ └── migrate_add_material_type.sql # 新增material_type 字段迁移脚本
└── gateway/
├── controller/
│ └── gallery_controller.go # 修改:新增 GetInspirationFlow 路由处理
└── router/
└── router.go # 修改:新增 /api/v1/inspiration-flow 路由
```
---
## 十、待确认事项
1. **material_type 枚举值**badge/poster/original后续按需扩展
2. **随机排序策略**ORDER BY RANDOM() 在数据量大时可能有性能问题,后续可考虑按 like_count 随机采样
3. **展览时长**:默认 6 小时21600 秒),可通过配置修改
## 十一、数据库变更
### 11.1 新增字段
```sql
-- assets 表新增 material_type 字段
ALTER TABLE assets ADD COLUMN material_type VARCHAR(50) DEFAULT 'original';
-- 创建索引优化查询
CREATE INDEX IF NOT EXISTS idx_assets_material_type ON assets(material_type);
```
### 11.2 迁移脚本
```sql
-- 迁移脚本backend/scripts/migrate_add_material_type.sql
```