docs: 修改文档
This commit is contained in:
parent
6b26ef26db
commit
ad773ffc27
@ -1,6 +1,6 @@
|
|||||||
# 灵感瀑布(横向瀑布流藏品展示)设计文档
|
# 灵感瀑布流(Inspiration Flow)设计文档
|
||||||
|
|
||||||
> **创建日期:** 2026-04-27
|
> **创建日期:** 2026-04-28
|
||||||
> **项目:** TopFans 横向瀑布流藏品展示
|
> **项目:** TopFans 横向瀑布流藏品展示
|
||||||
> **服务:** galleryService (Go Dubbo-go)
|
> **服务:** galleryService (Go Dubbo-go)
|
||||||
> **状态:** 设计中
|
> **状态:** 设计中
|
||||||
@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
## 一、设计目标
|
## 一、设计目标
|
||||||
|
|
||||||
横向瀑布流展示该 star_id 下所有用户展出的藏品,支持随机展示、分页、按类型过滤。
|
横向瀑布流展示该 star_id 下所有用户展出的藏品,支持随机展示、无限滚动加载、按类型过滤。
|
||||||
|
|
||||||
|
**无限滚动实现方式:** 使用游标分页(Cursor-based Pagination),前端滚动到底部时携带 `cursor` 参数加载下一批数据。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -17,20 +19,20 @@
|
|||||||
|
|
||||||
**主表:** Exhibition(展品展示表)
|
**主表:** Exhibition(展品展示表)
|
||||||
|
|
||||||
**关联表:** Asset(资产表)- 用于按 material_type 过滤
|
**关联表:**
|
||||||
|
- Asset(资产表)- 用于获取藏品名称、封面、点赞数
|
||||||
|
- FanProfile(粉丝档案表)- 用于获取展出者昵称
|
||||||
|
|
||||||
**筛选条件:**
|
**筛选条件:**
|
||||||
- `occupier_star_id = ?` (当前用户 star_id)
|
- `occupier_star_id = ?` (当前用户 star_id)
|
||||||
- `expire_at > now` (未过期)
|
- `expire_at > now` (未过期)
|
||||||
- `deleted_at IS NULL` (未删除)
|
- `deleted_at IS NULL` (未删除)
|
||||||
|
|
||||||
**按 type 过滤:** JOIN Asset 表,WHERE assets.material_type = ?
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、API 设计
|
## 三、API 设计
|
||||||
|
|
||||||
### 3.1 获取灵感瀑布(横向瀑布流)藏品列表
|
### 3.1 获取灵感瀑布藏品列表
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/v1/inspiration-flow
|
GET /api/v1/inspiration-flow
|
||||||
@ -40,8 +42,8 @@ GET /api/v1/inspiration-flow
|
|||||||
|
|
||||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|------|------|------|--------|------|
|
|------|------|------|--------|------|
|
||||||
| page | int | 否 | 1 | 页码 |
|
| cursor | string | 否 | 空 | 游标(首次请求为空,加载更多时传上次返回的 cursor) |
|
||||||
| page_size | int | 否 | 20 | 每页数量(最大100) |
|
| limit | int | 否 | 20 | 每页数量(最大 50) |
|
||||||
| type | string | 否 | all | 过滤类型:badge/poster/original/all |
|
| type | string | 否 | all | 过滤类型:badge/poster/original/all |
|
||||||
|
|
||||||
**HTTP 响应:**
|
**HTTP 响应:**
|
||||||
@ -60,14 +62,20 @@ GET /api/v1/inspiration-flow
|
|||||||
"owner_nickname": "粉丝昵称"
|
"owner_nickname": "粉丝昵称"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"page": 1,
|
"cursor": "eyJsaW1pdCI6MjAsIm9mZnNldCI6MjB9",
|
||||||
"page_size": 20,
|
|
||||||
"total": 100,
|
|
||||||
"has_more": true
|
"has_more": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**响应字段说明:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| items | array | 藏品列表 |
|
||||||
|
| cursor | string | 下次请求的游标,base64 编码的 JSON |
|
||||||
|
| has_more | bool | 是否还有更多数据 |
|
||||||
|
|
||||||
**错误码:**
|
**错误码:**
|
||||||
|
|
||||||
| code | 说明 |
|
| code | 说明 |
|
||||||
@ -85,9 +93,9 @@ GET /api/v1/inspiration-flow
|
|||||||
```protobuf
|
```protobuf
|
||||||
// 获取灵感瀑布藏品列表请求
|
// 获取灵感瀑布藏品列表请求
|
||||||
message GetInspirationFlowRequest {
|
message GetInspirationFlowRequest {
|
||||||
int32 page = 1; // 页码(默认1)
|
string cursor = 1; // 游标(首次请求为空)
|
||||||
int32 page_size = 2; // 每页数量(默认20,最大100)
|
int32 limit = 2; // 每页数量(默认20,最大50)
|
||||||
string type = 3; // 过滤类型:badge/poster/original/all(默认all)
|
string type = 3; // 过滤类型:badge/poster/original/all(默认all)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取灵感瀑布藏品列表响应
|
// 获取灵感瀑布藏品列表响应
|
||||||
@ -99,16 +107,14 @@ message GetInspirationFlowResponse {
|
|||||||
// 灵感瀑布数据
|
// 灵感瀑布数据
|
||||||
message InspirationFlowData {
|
message InspirationFlowData {
|
||||||
repeated InspirationFlowItem items = 1; // 藏品列表
|
repeated InspirationFlowItem items = 1; // 藏品列表
|
||||||
int32 page = 2; // 当前页码
|
string cursor = 2; // 下次请求的游标
|
||||||
int32 page_size = 3; // 每页数量
|
bool has_more = 3; // 是否有更多
|
||||||
int64 total = 4; // 总数量
|
|
||||||
bool has_more = 5; // 是否有更多
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 灵感瀑布藏品项
|
// 灵感瀑布藏品项
|
||||||
message InspirationFlowItem {
|
message InspirationFlowItem {
|
||||||
int64 asset_id = 1; // 资产ID
|
int64 asset_id = 1; // 资产ID
|
||||||
string name = 2; // 藏品名称
|
string name = 2; // 藏品名称
|
||||||
string cover_url = 3; // 封面图URL
|
string cover_url = 3; // 封面图URL
|
||||||
int32 like_count = 4; // 点赞数
|
int32 like_count = 4; // 点赞数
|
||||||
string owner_nickname = 5; // 展出者昵称
|
string owner_nickname = 5; // 展出者昵称
|
||||||
@ -137,7 +143,22 @@ service GalleryService {
|
|||||||
|
|
||||||
## 五、核心逻辑
|
## 五、核心逻辑
|
||||||
|
|
||||||
### 5.1 查询逻辑
|
### 5.1 游标分页设计
|
||||||
|
|
||||||
|
**游标结构(JSON,base64 编码):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"offset": 20,
|
||||||
|
"limit": 20
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**为什么用游标分页而非 offset 分页:**
|
||||||
|
1. **性能稳定**:offset 翻页越深性能越差,游标分页性能恒定
|
||||||
|
2. **无限滚动友好**:用户滚动过程中数据可能变化,游标避免重复/遗漏
|
||||||
|
3. **适合随机排序**:配合 RANDOM() 避免翻页时数据错位
|
||||||
|
|
||||||
|
### 5.2 查询逻辑
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
@ -156,35 +177,30 @@ WHERE e.occupier_star_id = ?
|
|||||||
AND a.is_active = true
|
AND a.is_active = true
|
||||||
AND (? = 'all' OR a.material_type = ?)
|
AND (? = 'all' OR a.material_type = ?)
|
||||||
ORDER BY RANDOM()
|
ORDER BY RANDOM()
|
||||||
LIMIT ? OFFSET ?;
|
LIMIT ?;
|
||||||
```
|
```
|
||||||
|
|
||||||
**参数说明:**
|
**参数说明:**
|
||||||
- `? = star_id` (当前用户 star_id)
|
- `? = star_id` (当前用户 star_id)
|
||||||
- `? = now` (当前时间戳)
|
- `? = now` (当前时间戳)
|
||||||
- `? = type` (过滤类型)
|
- `? = type` (过滤类型)
|
||||||
- `? = page_size`
|
- `? = limit` (每页数量)
|
||||||
- `? = (page - 1) * page_size`
|
|
||||||
|
|
||||||
### 5.2 6小时自动下架
|
### 5.3 游标编解码
|
||||||
|
|
||||||
已实现的 `GetExpiredExhibitions` 方法查询已过期的展览记录,调度器定期清理。
|
**编码(服务端):**
|
||||||
|
```go
|
||||||
|
cursor := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"offset":%d,"limit":%d}`, offset, limit)))
|
||||||
|
```
|
||||||
|
|
||||||
**下架时执行:**
|
**解码(服务端):**
|
||||||
1. 设置 `deleted_at = now`
|
```go
|
||||||
2. 调用 `ClearAssetLikeRecords` RPC 重置点赞数为 0
|
decoded, _ := base64.StdEncoding.DecodeString(cursor)
|
||||||
3. 更新 `asset_registry.display_status = 0`
|
var cursorData map[string]int
|
||||||
|
json.Unmarshal(decoded, &cursorData)
|
||||||
### 5.3 重新上架逻辑
|
offset := cursorData["offset"]
|
||||||
|
limit := cursorData["limit"]
|
||||||
当藏品重新展示时:
|
```
|
||||||
1. 创建新的 Exhibition 记录
|
|
||||||
2. 更新 `asset_registry.display_status = 1`
|
|
||||||
3. 点赞数从 0 开始(因为之前下架时已清除点赞记录)
|
|
||||||
|
|
||||||
### 5.4 随机展示
|
|
||||||
|
|
||||||
使用 `ORDER BY RANDOM()` 实现随机排序。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -219,94 +235,103 @@ type Asset struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**新增字段(需数据库变更):**
|
|
||||||
```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 | 展览时长(秒) | 14400(4小时,可配置) |
|
| inspiration_flow_limit | 默认每页数量 | 20 |
|
||||||
| inspiration_flow_page_size | 默认每页数量 | 20 |
|
| inspiration_flow_max_limit | 最大每页数量 | 50 |
|
||||||
| inspiration_flow_max_page_size | 最大每页数量 | 100 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 九、项目文件结构
|
## 八、项目文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
backend/
|
backend/
|
||||||
├── proto/
|
├── proto/
|
||||||
│ └── gallery.proto # 修改:新增 GetInspirationFlow 方法
|
│ └── gallery.proto # 修改:新增 GetInspirationFlow 方法
|
||||||
│
|
|
||||||
├── pkg/proto/
|
├── pkg/proto/
|
||||||
│ ├── gallery/
|
│ ├── gallery/
|
||||||
│ │ ├── gallery.pb.go # 重新生成
|
│ │ ├── gallery.pb.go # 重新生成
|
||||||
│ │ └── gallery.triple.go # 重新生成
|
│ │ └── gallery.triple.go # 重新生成
|
||||||
│
|
|
||||||
├── services/galleryService/
|
├── services/galleryService/
|
||||||
│ ├── repository/
|
│ ├── repository/
|
||||||
│ │ └── gallery_repository.go # 修改:新增 GetActiveExhibitions 方法
|
│ │ └── gallery_repository.go # 修改:新增 GetInspirationFlow 方法
|
||||||
│ │
|
│ │
|
||||||
│ ├── service/
|
│ ├── service/
|
||||||
│ │ └── gallery_service.go # 修改:新增 GetInspirationFlow 方法
|
│ │ └── gallery_service.go # 修改:新增 GetInspirationFlow 方法
|
||||||
│ │
|
│ │
|
||||||
│ ├── provider/
|
│ ├── provider/
|
||||||
│ │ └── gallery_provider.go # 修改:新增 GetInspirationFlow Handler
|
│ │ └── gallery_provider.go # 修改:新增 GetInspirationFlow Handler
|
||||||
│ │
|
│ │
|
||||||
│ └── config/
|
│ └── config/
|
||||||
│ └── gallery_config.go # 修改:新增灵感瀑布配置项
|
│ └── gallery_config.go # 修改:新增灵感瀑布配置项
|
||||||
│
|
|
||||||
├── scripts/
|
|
||||||
│ └── migrate_add_material_type.sql # 新增:material_type 字段迁移脚本
|
|
||||||
│
|
|
||||||
└── gateway/
|
└── gateway/
|
||||||
├── controller/
|
├── controller/
|
||||||
│ └── gallery_controller.go # 修改:新增 GetInspirationFlow 路由处理
|
│ └── gallery_controller.go # 修改:新增 GetInspirationFlow 路由处理
|
||||||
|
│
|
||||||
|
├── dto/
|
||||||
|
│ ├── gallery_dto.go # 修改:新增 InspirationFlow DTO
|
||||||
|
│ └── gallery_converter.go # 修改:新增转换函数
|
||||||
│
|
│
|
||||||
└── router/
|
└── router/
|
||||||
└── router.go # 修改:新增 /api/v1/inspiration-flow 路由
|
└── router.go # 修改:新增 /api/v1/inspiration-flow 路由
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 十、待确认事项
|
## 九、数据库变更(必须执行)
|
||||||
|
|
||||||
1. **material_type 枚举值**:badge/poster/original,后续按需扩展
|
**注意:** 当前 `assets` 表**没有** `material_type` 字段,此变更**必须执行**后才能支持按类型过滤。
|
||||||
2. **随机排序策略**:ORDER BY RANDOM() 在数据量大时可能有性能问题,后续可考虑按 like_count 随机采样
|
|
||||||
3. **展览时长**:默认 6 小时(21600 秒),可通过配置修改
|
|
||||||
|
|
||||||
## 十一、数据库变更
|
### 9.1 DDL
|
||||||
|
|
||||||
### 11.1 新增字段
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- assets 表新增 material_type 字段
|
-- assets 表新增 material_type 字段
|
||||||
ALTER TABLE assets ADD COLUMN material_type VARCHAR(50) DEFAULT 'original';
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS material_type VARCHAR(50) DEFAULT 'original';
|
||||||
|
|
||||||
-- 创建索引优化查询
|
-- 创建索引优化查询
|
||||||
CREATE INDEX IF NOT EXISTS idx_assets_material_type ON assets(material_type);
|
CREATE INDEX IF NOT EXISTS idx_assets_material_type ON assets(material_type);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 11.2 迁移脚本
|
### 9.2 迁移脚本
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 迁移脚本:backend/scripts/migrate_add_material_type.sql
|
-- backend/scripts/migrate_add_material_type.sql
|
||||||
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS material_type VARCHAR(50) DEFAULT 'original';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_assets_material_type ON assets(material_type);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、无限滚动前端对接说明
|
||||||
|
|
||||||
|
### 10.1 首次请求
|
||||||
|
```
|
||||||
|
GET /api/v1/inspiration-flow?limit=20&type=all
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 加载更多
|
||||||
|
```
|
||||||
|
GET /api/v1/inspiration-flow?cursor=eyJsaW1pdCI6MjAsIm9mZnNldCI6MjB9&limit=20&type=all
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 前端逻辑
|
||||||
|
1. 首次请求 cursor 为空
|
||||||
|
2. 解析响应中的 cursor 和 has_more
|
||||||
|
3. 滚动到底部时,若 has_more=true,携带 cursor 发起下一页请求
|
||||||
|
4. 数据变化时重置 cursor 重新加载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、待确认事项
|
||||||
|
|
||||||
|
1. **material_type 枚举值**:badge/poster/original,后续按需扩展
|
||||||
|
2. **随机排序策略**:ORDER BY RANDOM() 在数据量大时可能有性能问题,后续可考虑按 like_count 随机采样
|
||||||
|
3. **每页数量上限**:当前设为 50,是否合适?
|
||||||
|
4. **是否需要缓存**:热门 star_id 的数据可以考虑 Redis 缓存
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user