topfans/backend/docs/展馆接口数据库字段说明.md
2026-04-07 22:29:48 +08:00

348 lines
12 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.

# 展馆接口数据库字段说明
## 接口说明
### GET /api/v1/galleries/{target_uid} - 获取他人展馆
**路径参数**
- `target_uid`: 目标用户ID数据库 `users.id`
**功能**:查看指定用户的展馆信息(展位列表、展品展示情况)
---
## 数据库表与字段映射
### 1. 主要数据表
#### `booth_slots` 表(展位表)
| 数据库字段 | Go 模型字段 | 类型 | 说明 |
|-----------|------------|------|------|
| `slot_id` | `SlotID` | `int64` | 展位ID主键自增 |
| `host_profile_id` | `HostProfileID` | `int64` | 展馆所有者ID计算值`user_id * 1000000 + star_id` |
| `user_id` | `UserID` | `int64` | 用户ID外键关联 `users.id` |
| `star_id` | `StarID` | `int64` | 明星ID用于数据隔离 |
| `slot_index` | `SlotIndex` | `int` | 展位序号1, 2, 3...,与 `host_profile_id` 联合唯一) |
| `visibility` | `Visibility` | `string` | 可见性(默认 `'public'` |
| `is_enabled` | `IsEnabled` | `bool` | 是否已解锁(`false`=未解锁/LOCKED`true`=已解锁) |
| `unlock_type` | `UnlockType` | `string` | 解锁类型(`'level'` 或 `'crystal'` |
| `unlock_value` | `UnlockValue` | `int` | 解锁条件值(等级或水晶数量) |
| `created_at` | `CreatedAt` | `int64` | 创建时间(毫秒时间戳) |
| `updated_at` | `UpdatedAt` | `int64` | 更新时间(毫秒时间戳) |
**查询逻辑**
```sql
SELECT * FROM booth_slots
WHERE user_id = ? AND star_id = ?
ORDER BY slot_index ASC
```
#### `exhibitions` 表(展品展示表)
| 数据库字段 | Go 模型字段 | 类型 | 说明 |
|-----------|------------|------|------|
| `id` | `ID` | `int64` | 主键(自增) |
| `asset_id` | `AssetID` | `int64` | 资产ID外键关联 `assets.id`,唯一索引) |
| `slot_id` | `SlotID` | `int64` | 展位ID外键关联 `booth_slots.slot_id` |
| `host_profile_id` | `HostProfileID` | `int64` | 展馆所有者ID`booth_slots.host_profile_id` 一致) |
| `occupier_uid` | `OccupierUID` | `int64` | 占位者用户ID实际放置藏品的用户 |
| `occupier_star_id` | `OccupierStarID` | `int64` | 占位者明星ID |
| `start_time` | `StartTime` | `int64` | 占用开始时间(毫秒时间戳) |
| `expire_at` | `ExpireAt` | `int64` | 占用过期时间(毫秒时间戳,有索引) |
| `created_at` | `CreatedAt` | `int64` | 创建时间 |
| `updated_at` | `UpdatedAt` | `int64` | 更新时间 |
**查询逻辑**(判断展位是否被占用):
```sql
SELECT * FROM exhibitions
WHERE slot_id = ?
LIMIT 1
```
---
## 响应包字段说明
### 响应结构
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 37000088,
"slot_total": 3,
"slots": [...]
}
}
```
### 字段详解
#### `gallery_owner_id`展馆所有者ID
- **计算方式**`user_id * 1000000 + star_id`
- **示例**`37000088` = `37 * 1000000 + 88`
- 表示用户ID=37明星ID=88 的展馆
- **用途**
- 用于标识展馆的唯一所有者(同一用户在不同明星下有不同的展馆)
- 在放置藏品时,需要传入此 ID 作为 `gallery_owner_id` 参数
#### `slot_total`(展位总数)
- **来源**`booth_slots` 表中该用户在该明星下的展位数量
- **说明**:初始展位数量由配置决定(通常为 3 个)
#### `slots[]`(展位列表)
每个展位对象包含:
| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| `slot_id` | `int64` | 展位ID主键 | `booth_slots.slot_id` |
| `slot_index` | `int32` | 展位序号1, 2, 3... | `booth_slots.slot_index` |
| `status` | `string` | 展位状态 | **计算得出**(见下方) |
| `is_enabled` | `bool` | 是否已解锁 | `booth_slots.is_enabled` |
| `asset` | `object\|null` | 展品信息(仅 `status=OCCUPIED` 时存在) | 从 `exhibitions` + `assets` 表关联查询 |
| `occupier_uid` | `int64\|null` | 占位者用户ID`status=OCCUPIED` 时存在) | `exhibitions.occupier_uid` |
| `occupied_at` | `int64\|null` | 占用开始时间(仅 `status=OCCUPIED` 时存在) | `exhibitions.start_time` |
| `expire_at` | `int64\|null` | 占用过期时间(仅 `status=OCCUPIED` 时存在) | `exhibitions.expire_at` |
| `unlock_condition` | `object\|null` | 解锁条件(仅 `status=LOCKED` 时存在) | 从 `booth_slots.unlock_type` + `unlock_value` 计算 |
### `status` 状态计算逻辑
```go
if !slot.IsEnabled {
status = "LOCKED" // 未解锁
} else {
exhibition := GetExhibitionBySlot(slot.SlotID)
if exhibition == nil {
status = "EMPTY" // 已解锁但空展位
} else {
status = "OCCUPIED" // 已解锁且有展品
}
}
```
**状态值说明**
- `LOCKED`:展位未解锁(`is_enabled=false`),需要满足解锁条件才能使用
- `EMPTY`:展位已解锁(`is_enabled=true`),但当前没有展品
- `OCCUPIED`:展位已解锁且有展品展示(`exhibitions` 表中有对应记录)
### `asset` 对象(展品信息)
`status=OCCUPIED` 时,`asset` 字段包含:
| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| `asset_id` | `int64` | 资产ID | `exhibitions.asset_id``assets.id` |
| `name` | `string` | 资产名称 | `assets.name` |
| `cover_url` | `string` | 封面图URL | `assets.cover_url` |
| `like_count` | `int32` | 点赞数 | `assets.like_count` |
| `remain_time` | `int64` | 剩余时间(秒) | `(exhibitions.expire_at - now) / 1000` |
**注意**`asset` 信息通过 RPC 调用 `AssetService.GetAsset` 获取,需要传入 `occupier_uid``occupier_star_id`(因为资产属于占位者,不是展馆所有者)。
### `unlock_condition` 对象(解锁条件)
`status=LOCKED` 时,`unlock_condition` 字段包含:
| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| `type` | `string` | 解锁类型:`"level"` 或 `"crystal"` | `booth_slots.unlock_type` 或配置规则 |
| `value` | `int32` | 解锁条件值(等级或水晶数量) | `booth_slots.unlock_value` 或配置规则 |
---
## 与 `/api/v1/galleries/me` 的响应格式对比
### 响应格式是否一致?
**答案:是的,响应格式完全一致。**
#### 代码证据
1. **DTO 定义**`gateway/dto/gallery_dto.go`
```go
// GetMyGalleryResponseDTO 获取我的展馆响应
type GetMyGalleryResponseDTO struct {
GalleryOwnerID int64 `json:"gallery_owner_id"`
SlotTotal int32 `json:"slot_total"`
Slots []SlotInfoDTO `json:"slots"`
}
// GetUserGalleryResponseDTO 获取他人展馆响应(与我的展馆响应相同)
type GetUserGalleryResponseDTO struct {
GalleryOwnerID int64 `json:"gallery_owner_id"`
SlotTotal int32 `json:"slot_total"`
Slots []SlotInfoDTO `json:"slots"`
}
```
**注释明确说明:`GetUserGalleryResponseDTO` 与 `GetMyGalleryResponseDTO` 相同**
2. **转换函数**`gateway/dto/gallery_converter.go`
```go
// ConvertGalleryData 转换展馆数据(我的展馆)
func ConvertGalleryData(pbData *pbGallery.GalleryData) *GetMyGalleryResponseDTO {
// ... 转换逻辑
}
// ConvertGalleryDataToUser 转换展馆数据(他人展馆)
func ConvertGalleryDataToUser(pbData *pbGallery.GalleryData) *GetUserGalleryResponseDTO {
// ... 转换逻辑(与 ConvertGalleryData 完全相同)
}
```
**两个函数的转换逻辑完全一致**
3. **Service 层返回**`services/galleryService/service/gallery_service.go`
```go
// GetMyGallery 和 GetUserGallery 都返回 *pb.GalleryData
// 结构完全相同,只是查询的目标用户不同
```
#### 实际差异
虽然响应格式相同,但**数据内容**有区别:
| 对比项 | `/api/v1/galleries/me` | `/api/v1/galleries/{target_uid}` |
|--------|------------------------|----------------------------------|
| **查询目标** | 当前登录用户(`user_id` 从 JWT 获取) | 指定用户(`target_uid` 从路径参数获取) |
| **`gallery_owner_id`** | `user_id * 1000000 + star_id`(当前用户) | `target_uid * 1000000 + star_id`(目标用户) |
| **`slots[]` 数据** | 当前用户的展位 | 目标用户的展位 |
| **权限** | 可查看自己的所有展位(包括未解锁的) | 只能查看目标用户已解锁的展位(`is_enabled=true` |
**注意**:代码中 `buildSlotInfos``isOwner` 参数用于区分是否显示解锁条件等细节,但响应结构本身是一致的。
---
## 使用示例
### 1. 查看他人展馆
```bash
# 查看用户ID=37的展馆
curl -X GET "http://localhost:8080/api/v1/galleries/37" \
-H "Authorization: Bearer <JWT_TOKEN>"
```
**响应示例**
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 37000088, // 37 * 1000000 + 88
"slot_total": 3,
"slots": [
{
"slot_id": 46,
"slot_index": 1,
"status": "EMPTY", // 已解锁但空展位
"is_enabled": true,
// asset, occupier_uid 等字段不存在(因为 status=EMPTY
},
{
"slot_id": 47,
"slot_index": 2,
"status": "OCCUPIED", // 有展品
"is_enabled": true,
"asset": {
"asset_id": 123,
"name": "数字艺术品",
"cover_url": "https://...",
"like_count": 10,
"remain_time": 86400
},
"occupier_uid": 20, // 占位者用户ID
"occupied_at": 1705747200000,
"expire_at": 1705833600000
},
{
"slot_id": 48,
"slot_index": 3,
"status": "LOCKED", // 未解锁
"is_enabled": false,
"unlock_condition": {
"type": "crystal",
"value": 100
}
}
]
}
}
```
### 2. 前端使用建议
1. **展示展位列表**
- 遍历 `data.slots[]`,根据 `status` 显示不同状态
- `EMPTY`:显示空展位占位图
- `OCCUPIED`:显示 `asset.cover_url` 和资产信息
- `LOCKED`:显示锁定图标和 `unlock_condition`
2. **放置藏品**
- 需要 `gallery_owner_id`、`slot_id`、`asset_id`
- 调用 `POST /api/v1/galleries/place`
3. **权限判断**
- 只有展馆所有者可以放置/移除展品
- 他人只能查看已解锁的展位(`is_enabled=true`
---
## 数据库查询示例
### 查询展位列表
```sql
-- 查询用户ID=37明星ID=88的所有展位
SELECT
slot_id,
slot_index,
is_enabled,
unlock_type,
unlock_value
FROM booth_slots
WHERE user_id = 37 AND star_id = 88
ORDER BY slot_index ASC;
```
### 查询展品展示情况
```sql
-- 查询展位ID=47的展品信息
SELECT
e.asset_id,
e.occupier_uid,
e.start_time,
e.expire_at,
a.name AS asset_name,
a.cover_url
FROM exhibitions e
LEFT JOIN assets a ON e.asset_id = a.id
WHERE e.slot_id = 47;
```
---
## 总结
1. **数据库表**
- `booth_slots`:展位基础信息
- `exhibitions`:展品展示关系
2. **响应格式**
- `/api/v1/galleries/me``/api/v1/galleries/{target_uid}` **响应格式完全一致**
- 都返回 `{ gallery_owner_id, slot_total, slots[] }`
- 差异仅在于数据内容(查询的目标用户不同)
3. **`gallery_owner_id` 计算**
- 公式:`user_id * 1000000 + star_id`
- 示例用户37、明星88 → `37000088`
4. **`status` 状态**
- `LOCKED`:未解锁(`is_enabled=false`
- `EMPTY`:已解锁但空展位(`is_enabled=true` 且 `exhibitions` 无记录)
- `OCCUPIED`:有展品(`is_enabled=true` 且 `exhibitions` 有记录)