# 灵感瀑布(横向瀑布流藏品展示)设计文档 > **创建日期:** 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 | 展览时长(秒) | 14400(4小时,可配置) | | 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 ```