docs: 文档

This commit is contained in:
zheng020 2026-04-27 20:57:03 +08:00
parent 8b7a80f792
commit 6b26ef26db
2 changed files with 752 additions and 0 deletions

View File

@ -0,0 +1,312 @@
# 灵感瀑布(横向瀑布流藏品展示)设计文档
> **创建日期:** 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
```

View File

@ -0,0 +1,440 @@
# 我的作品统计(点赞/展出)设计文档
> **创建日期:** 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