348 lines
12 KiB
Markdown
348 lines
12 KiB
Markdown
# 展馆接口数据库字段说明
|
||
|
||
## 接口说明
|
||
|
||
### 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` 有记录)
|