topfans/backend/docs/展馆服务HTTP完整测试流程.md
2026-04-07 22:29:48 +08:00

1625 lines
32 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.

# 展馆服务 HTTP 完整测试流程
## 目录
1. [环境准备](#环境准备)
2. [服务启动](#服务启动)
3. [用户和资产准备](#用户和资产准备)
4. [展馆功能测试](#展馆功能测试)
5. [异常场景测试](#异常场景测试)
6. [性能和边界测试](#性能和边界测试)
7. [测试数据清理](#测试数据清理)
8. [常见问题](#常见问题)
---
## 环境准备
### 1. 数据库准备
确保 PostgreSQL 数据库已启动并创建了 `top-fans` 数据库:
```sql
CREATE DATABASE "top-fans" OWNER haihuizhu ENCODING 'UTF8';
```
### 2. 数据库表检查
确保以下表已创建:
```sql
-- 检查展位表
SELECT COUNT(*) FROM booth_slots;
-- 检查展品展示表
SELECT COUNT(*) FROM exhibitions;
-- 检查资产表
SELECT COUNT(*) FROM assets;
-- 检查粉丝档案表
SELECT COUNT(*) FROM fan_profiles;
```
### 3. 环境变量配置
```bash
# Gateway 配置
export SERVER_PORT=8080
export GIN_MODE=debug
export DUBBO_USER_SERVICE_URL="tri://127.0.0.1:20000"
export DUBBO_SOCIAL_SERVICE_URL="tri://127.0.0.1:20001"
export DUBBO_ASSET_SERVICE_URL="tri://127.0.0.1:20003"
export DUBBO_GALLERY_SERVICE_URL="tri://127.0.0.1:20004"
export JWT_SECRET="topfans-secret-key-please-change-in-production"
```
### 4. 工具准备
推荐使用以下工具之一进行API测试
- **Postman** (推荐)
- **curl** 命令行
- **HTTPie**
- **Insomnia**
### 5. 测试数据准备
确保数据库中有明星数据:
```sql
-- 肖战identity_id: xz
INSERT INTO stars (star_id, name, tag, name_en, pic_url, description, identity_id, is_active, created_at, updated_at)
VALUES (87, '肖战', '小飞侠', 'xiaozhan', '', '', 'xz', true, 1767590443835, 1767590443835)
ON CONFLICT (star_id) DO NOTHING;
-- 王一博identity_id: wyb
INSERT INTO stars (star_id, name, tag, name_en, pic_url, description, identity_id, is_active, created_at, updated_at)
VALUES (88, '王一博', '小摩托', 'wangyibo', '', '', 'wyb', true, 1767590443835, 1767590443835)
ON CONFLICT (star_id) DO NOTHING;
```
---
## 服务启动
### 1. 启动 UserService
```bash
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/services/userService
go run main.go
```
**预期输出:**
```
Starting User Service...
Dubbo-go service started successfully. Press Ctrl+C to exit.
```
**检查端口:**
```bash
lsof -i :20000
```
### 2. 启动 SocialService
```bash
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/services/socialService
go run main.go
```
**预期输出:**
```
Starting Social Service...
Social service started successfully. Press Ctrl+C to exit.
```
**检查端口:**
```bash
lsof -i :20001
```
### 3. 启动 AssetService
```bash
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/services/assetService
go run main.go
```
**预期输出:**
```
Starting Asset Service...
Asset Service started successfully on port 20003
```
**检查端口:**
```bash
lsof -i :20003
```
### 4. 启动 GalleryService
```bash
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/services/galleryService
go run main.go
```
**预期输出:**
```
Starting Gallery Service...
Gallery Service started successfully on port 20004
Cleanup worker started
```
**检查端口:**
```bash
lsof -i :20004
```
### 5. 启动 Gateway
```bash
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/gateway
go run main.go
```
**预期输出:**
```
Starting Top-Fans Gateway...
Gallery Service Dubbo client connected successfully
Gateway server started successfully
```
**检查端口:**
```bash
lsof -i :8080
```
### 6. 健康检查
```bash
curl http://localhost:8080/health
```
**预期响应:**
```json
{
"status": "ok",
"service": "top-fans-gateway"
}
```
---
## 用户和资产准备
### 步骤 1: 注册用户 A张三
```bash
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000001",
"password": "password123",
"star_id": 87,
"nickname": "张三"
}'
```
**预期响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"mobile": "13800000001"
},
"fan_profile": {
"id": 1,
"user_id": 1,
"star_id": 87,
"nickname": "张三",
"level": 1,
"crystal_balance": 0
}
}
}
```
**保存变量:**
```bash
export USER_A_ID=1
export USER_A_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_A_STAR_ID=87
```
### 步骤 2: 注册用户 B李四
```bash
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000002",
"password": "password123",
"star_id": 87,
"nickname": "李四"
}'
```
**保存变量:**
```bash
export USER_B_ID=2
export USER_B_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_B_STAR_ID=87
```
### 步骤 3: 注册用户 C王五- 不同明星
```bash
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000003",
"password": "password123",
"star_id": 88,
"nickname": "王五"
}'
```
**保存变量:**
```bash
export USER_C_ID=3
export USER_C_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_C_STAR_ID=88
```
### 步骤 4: 为用户 A 创建资产
#### 4.1 创建资产 1
```bash
curl -X POST http://localhost:8080/api/v1/assets/mints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"name": "肖战-演唱会门票",
"cover_url": "https://example.com/ticket1.jpg",
"description": "2024年演唱会珍藏门票"
}'
```
**保存资产ID**
```bash
export ASSET_A1=1
```
#### 4.2 创建资产 2
```bash
curl -X POST http://localhost:8080/api/v1/assets/mints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"name": "肖战-签名照",
"cover_url": "https://example.com/photo1.jpg",
"description": "亲笔签名照片"
}'
```
**保存资产ID**
```bash
export ASSET_A2=2
```
### 步骤 5: 为用户 B 创建资产
```bash
curl -X POST http://localhost:8080/api/v1/assets/mints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"name": "肖战-海报",
"cover_url": "https://example.com/poster1.jpg",
"description": "电影海报"
}'
```
**保存资产ID**
```bash
export ASSET_B1=3
```
---
## 展馆功能测试
### 场景 1: 获取我的展馆(首次访问 - 懒加载)
#### 测试目标
验证首次访问展馆时系统自动创建3个初始展位。
#### 请求示例
```bash
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_A_TOKEN"
```
**或者使用别名路由:**
```bash
curl -X GET http://localhost:8080/api/v1/galleries/me \
-H "Authorization: Bearer $USER_A_TOKEN"
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 1000087,
"slot_total": 3,
"slots": [
{
"slot_id": 1,
"slot_index": 1,
"status": "EMPTY",
"is_enabled": true,
"unlock_condition": null
},
{
"slot_id": 2,
"slot_index": 2,
"status": "EMPTY",
"is_enabled": true,
"unlock_condition": null
},
{
"slot_id": 3,
"slot_index": 3,
"status": "EMPTY",
"is_enabled": true,
"unlock_condition": null
}
]
}
}
```
#### 验证要点
-`gallery_owner_id` = user_id * 1000000 + star_id (1 * 1000000 + 87 = 1000087)
-`slot_total` = 3初始展位数
- ✅ 所有展位的 `status` 都是 "EMPTY"
- ✅ 所有展位的 `is_enabled` 都是 true已解锁
-`unlock_condition` 都是 null因为已解锁
#### 数据库验证
```sql
-- 检查是否创建了展位
SELECT * FROM booth_slots WHERE user_id = 1 AND star_id = 87;
-- 预期结果3条记录
```
**保存展位ID**
```bash
export SLOT_A1=1
export SLOT_A2=2
export SLOT_A3=3
```
---
### 场景 2: 在自己的展位放置资产
#### 测试目标
验证用户可以在自己的展位上放置自己的资产。
#### 请求示例
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"status": "OCCUPIED",
"occupied_until": "2026-01-14T14:30:00Z",
"occupier_uid": 1
}
}
```
#### 验证要点
-`status` 为 "OCCUPIED"
-`occupied_until` 为当前时间 + 4小时14400秒
-`occupier_uid` 为当前用户ID
#### 数据库验证
```sql
-- 检查展品展示记录
SELECT * FROM exhibitions WHERE asset_id = 1;
-- 预期结果1条记录包含 asset_id, slot_id, occupier_uid, expire_at 等信息
```
---
### 场景 3: 再次获取我的展馆(查看放置结果)
#### 请求示例
```bash
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_A_TOKEN"
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 1000087,
"slot_total": 3,
"slots": [
{
"slot_id": 1,
"slot_index": 1,
"status": "OCCUPIED",
"is_enabled": true,
"asset": {
"asset_id": 1,
"name": "肖战-演唱会门票",
"cover_url": "https://example.com/ticket1.jpg",
"like_count": 0,
"remain_time": 14398
},
"occupier_uid": 1,
"occupied_at": 1736851200000,
"expire_at": 1736865600000
},
{
"slot_id": 2,
"slot_index": 2,
"status": "EMPTY",
"is_enabled": true
},
{
"slot_id": 3,
"slot_index": 3,
"status": "EMPTY",
"is_enabled": true
}
]
}
}
```
#### 验证要点
- ✅ 第1个展位的 `status` 为 "OCCUPIED"
- ✅ 展示了资产信息name, cover_url, like_count
-`remain_time` 约等于 14400秒4小时
- ✅ 其他展位仍然为 "EMPTY"
---
### 场景 4: 在他人的展位放置资产(抢展位)
#### 测试目标
验证用户 B 可以在用户 A 的公有展位上放置自己的资产。
#### 4.1 用户 B 先获取用户 A 的展馆
```bash
curl -X GET http://localhost:8080/api/v1/galleries/$USER_A_ID \
-H "Authorization: Bearer $USER_B_TOKEN"
```
**预期响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 1000087,
"slot_total": 3,
"slots": [
{
"slot_id": 1,
"slot_index": 1,
"status": "OCCUPIED",
"is_enabled": true,
"asset": {
"asset_id": 1,
"name": "肖战-演唱会门票",
"cover_url": "https://example.com/ticket1.jpg",
"like_count": 0,
"remain_time": 14350
},
"occupier_uid": 1
},
{
"slot_id": 2,
"slot_index": 2,
"status": "EMPTY",
"is_enabled": true
},
{
"slot_id": 3,
"slot_index": 3,
"status": "EMPTY",
"is_enabled": true
}
]
}
}
```
#### 4.2 用户 B 在用户 A 的展位2放置资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_B1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A2'
}'
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"status": "OCCUPIED",
"occupied_until": "2026-01-14T15:00:00Z",
"occupier_uid": 2
}
}
```
#### 验证要点
-`occupier_uid` 为用户 B 的ID2
- ✅ 可以成功放置在他人的展馆(同一明星)
- ✅ 展位被占用4小时
---
### 场景 5: 从展位移除资产(统一接口)
#### 测试目标
验证用户可以从展位移除资产。该接口支持两种场景:
1. **占位者下架**:资产放置者可以主动下架自己放置的资产
2. **所有者踢走**:展位所有者可以踢走占据自己展位的其他用户的资产
#### 5.1 占位者主动下架自己的资产
**请求示例:**
```bash
# 用户 A 下架自己放置在自己展位上的资产
curl -X DELETE http://localhost:8080/api/v1/galleries/slots/$SLOT_A1/asset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
```
**预期响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"message": "移除成功"
}
}
```
**验证要点:**
- ✅ 响应成功code = 200
- ✅ 返回移除成功消息
**数据库验证:**
```sql
-- 检查展品展示记录是否被删除
SELECT * FROM exhibitions WHERE slot_id = $SLOT_A1;
-- 预期结果0条记录已删除
```
#### 5.2 展位所有者踢走占位者
**前置步骤:用户 B 在用户 A 的展位放置资产**
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_B1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A2'
}'
```
**请求示例:**
```bash
# 用户 A 踢走用户 B 放置在自己展位上的资产
curl -X DELETE http://localhost:8080/api/v1/galleries/slots/$SLOT_A2/asset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
```
**预期响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"message": "移除成功"
}
}
```
**验证要点:**
- ✅ 响应成功code = 200
- ✅ 展位所有者可以踢走任何占位者
- ✅ 占位者可以下架自己的资产
**数据库验证:**
```sql
-- 检查展品展示记录是否被删除
SELECT * FROM exhibitions WHERE slot_id = $SLOT_A2;
-- 预期结果0条记录已删除
```
---
### 场景 6: 解锁新展位(等级不够,使用水晶购买)
#### 测试目标
验证用户可以使用水晶购买新的展位。
#### 7.1 先给用户 A 增加水晶
```sql
-- 直接在数据库中给用户增加水晶
UPDATE fan_profiles
SET crystal_balance = 500
WHERE user_id = 1 AND star_id = 87;
```
#### 7.2 解锁第4个展位
```bash
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"slot_total": 4,
"crystal_balance": 400
}
}
```
#### 验证要点
-`slot_total` 增加到 4
-`crystal_balance` 减少100第4个展位需要100水晶
- ✅ 如果等级达到要求level >= 5则不消耗水晶
#### 数据库验证
```sql
-- 检查展位数量
SELECT COUNT(*) FROM booth_slots WHERE user_id = 1 AND star_id = 87;
-- 预期结果4条记录
-- 检查水晶余额
SELECT crystal_balance FROM fan_profiles WHERE user_id = 1 AND star_id = 87;
-- 预期结果400
```
---
### 场景 8: 解锁新展位(等级足够,免费解锁)
#### 测试目标
验证用户等级达到要求时,可以免费解锁展位。
#### 8.1 先给用户 A 提升等级
```sql
-- 直接在数据库中提升用户等级
UPDATE fan_profiles
SET level = 6
WHERE user_id = 1 AND star_id = 87;
```
#### 8.2 解锁第5个展位
```bash
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"slot_total": 5,
"crystal_balance": 400
}
}
```
#### 验证要点
-`slot_total` 增加到 5
-`crystal_balance` 保持不变400- 等级解锁不消耗水晶
- ✅ 第5个展位需要等级6或水晶200
---
### 场景 9: 查看未解锁展位的解锁条件
#### 请求示例
```bash
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_B_TOKEN"
```
#### 预期响应
```json
{
"code": 200,
"message": "ok",
"data": {
"gallery_owner_id": 2000087,
"slot_total": 3,
"slots": [
{
"slot_id": 4,
"slot_index": 1,
"status": "EMPTY",
"is_enabled": true
},
{
"slot_id": 5,
"slot_index": 2,
"status": "EMPTY",
"is_enabled": true
},
{
"slot_id": 6,
"slot_index": 3,
"status": "EMPTY",
"is_enabled": true
}
]
}
}
```
**注意**: 用户 B 还没有第4个展位所以只显示3个展位。
#### 如果查询一个已有4个展位但第5个未解锁的用户
**预期第4个展位未解锁的数据**
```json
{
"slot_id": 7,
"slot_index": 4,
"status": "LOCKED",
"is_enabled": false,
"unlock_condition": {
"type": "level",
"value": 5
}
}
```
#### 验证要点
- ✅ 未解锁展位的 `status` 为 "LOCKED"
-`is_enabled` 为 false
-`unlock_condition` 显示解锁条件(优先显示等级要求)
---
## 异常场景测试
### 异常场景 1: 未认证访问
#### 测试目标
验证未提供 Token 时返回401错误。
#### 请求示例
```bash
curl -X GET http://localhost:8080/api/v1/mygalleries
```
#### 预期响应
```json
{
"code": 401,
"message": "用户未认证"
}
```
---
### 异常场景 2: 放置不存在的资产
#### 测试目标
验证放置不存在的资产时返回错误。
#### 请求示例
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": 99999,
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "资产不存在或不属于当前用户"
}
```
---
### 异常场景 3: 放置他人的资产
#### 测试目标
验证不能放置他人的资产。
#### 请求示例
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_B_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "资产不存在或不属于当前用户"
}
```
---
### 异常场景 4: 在已占用的展位放置资产
#### 测试目标
验证不能在已占用的展位上放置资产。
#### 4.1 先放置一个资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 4.2 再次在同一展位放置资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A2',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "展位已被占用"
}
```
---
### 异常场景 5: 同一资产放置到多个展位
#### 测试目标
验证同一资产不能同时在多个展位展示。
#### 5.1 先在展位1放置资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 5.2 再在展位2放置同一资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A2'
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "资产已在其他展位展示中"
}
```
---
### 异常场景 6: 在未解锁的展位放置资产
#### 测试目标
验证不能在未解锁的展位上放置资产。
#### 请求示例
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_B1',
"gallery_owner_id": '$USER_B_ID',
"slot_id": 9999
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "展位未解锁或不存在"
}
```
---
### 异常场景 7: 无权限用户尝试移除资产
#### 测试目标
验证只有展位所有者或资产放置者可以移除资产。
#### 7.1 用户 B 在用户 A 的展位放置资产
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_B1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}'
```
#### 7.2 用户 C第三方尝试移除用户 B 的资产(应该失败)
```bash
curl -X DELETE http://localhost:8080/api/v1/galleries/slots/$SLOT_A1/asset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_C_TOKEN"
```
#### 预期响应
```json
{
"code": 500,
"message": "移除资产失败,无权移除资产,只有展位所有者或占位者可以操作"
}
```
**说明**
- ✅ 展位所有者(用户 A可以移除任何人的资产
- ✅ 资产放置者(用户 B可以移除自己的资产
- ❌ 第三方用户(用户 C无法移除他人的资产
---
### 异常场景 8: 跨明星放置资产
#### 测试目标
验证不能在不同明星的展馆放置资产。
#### 请求示例
```bash
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN" \
-d '{
"asset_id": '$ASSET_A1',
"gallery_owner_id": '$USER_C_ID',
"slot_id": 10
}'
```
#### 预期响应
```json
{
"code": 400,
"message": "只能在同一明星的展馆中放置展品"
}
```
---
### 异常场景 10: 水晶和等级都不足时解锁展位
#### 测试目标
验证水晶和等级都不足时,无法解锁展位。
#### 10.1 清空用户水晶
```sql
UPDATE fan_profiles
SET crystal_balance = 0, level = 1
WHERE user_id = 2 AND star_id = 87;
```
#### 10.2 尝试解锁展位
```bash
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN"
```
#### 预期响应
```json
{
"code": 400,
"message": "等级和水晶余额都不足"
}
```
---
### 异常场景 11: 超过最大展位数
#### 测试目标
验证不能解锁超过最大展位数10个
#### 11.1 给用户 A 解锁到最大展位数
```sql
-- 直接在数据库中创建展位(测试用)
-- 第4-10个展位
INSERT INTO booth_slots (host_profile_id, user_id, star_id, slot_index, visibility, is_enabled, unlock_type, unlock_value, created_at, updated_at)
SELECT
1000087,
1,
87,
generate_series(4, 10),
'public',
true,
'level',
0,
EXTRACT(EPOCH FROM NOW()) * 1000,
EXTRACT(EPOCH FROM NOW()) * 1000;
```
#### 11.2 尝试解锁第11个展位
```bash
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
```
#### 预期响应
```json
{
"code": 400,
"message": "已达到最大展位数"
}
```
---
## 性能和边界测试
### 性能测试 1: 并发查询展馆
#### 测试目标
验证系统可以处理并发查询请求。
#### 测试工具
使用 `ab` (Apache Bench) 或 `wrk` 进行压力测试。
#### 测试命令
```bash
# 使用 ab 进行测试100个并发1000个请求
ab -n 1000 -c 100 -H "Authorization: Bearer $USER_A_TOKEN" \
http://localhost:8080/api/v1/mygalleries
```
#### 预期结果
- ✅ 成功率 > 99%
- ✅ 平均响应时间 < 100ms
- 无数据库死锁
- 无数据不一致
---
### 性能测试 2: 并发放置资产
#### 测试目标
验证系统可以处理并发放置资产请求测试唯一索引)。
#### 测试场景
多个用户同时尝试在同一展位放置资产只有一个应该成功
#### 测试脚本
```bash
#!/bin/bash
# 创建10个用户并发放置资产到同一展位
for i in {1..10}; do
(
curl -X POST http://localhost:8080/api/v1/galleries/place \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN" \
-d '{
"asset_id": '$ASSET_B1',
"gallery_owner_id": '$USER_A_ID',
"slot_id": '$SLOT_A1'
}' &
)
done
wait
```
#### 预期结果
- 只有1个请求成功200
- 其他9个请求失败400 - 展位已被占用
- 数据库中只有1条展示记录
---
### 边界测试 1: 懒加载并发
#### 测试目标
验证多个并发请求首次访问展馆时不会创建重复的初始展位
#### 测试脚本
```bash
#!/bin/bash
# 创建新用户
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000099",
"password": "password123",
"star_id": 87,
"nickname": "测试用户"
}' > /tmp/new_user.json
NEW_USER_TOKEN=$(cat /tmp/new_user.json | jq -r '.data.access_token')
# 10个并发请求首次访问展馆
for i in {1..10}; do
(
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $NEW_USER_TOKEN" &
)
done
wait
```
#### 数据库验证
```sql
-- 检查是否只创建了3个展位不应该有重复
SELECT COUNT(*) FROM booth_slots WHERE user_id = (SELECT user_id FROM users WHERE mobile = '13800000099') AND star_id = 87;
-- 预期结果3条记录不是30条
```
---
## 测试数据清理
### 清理展品展示记录
```sql
-- 清理所有展品展示记录
DELETE FROM exhibitions WHERE occupier_uid IN (1, 2, 3);
```
### 清理展位记录
```sql
-- 清理所有展位记录
DELETE FROM booth_slots WHERE user_id IN (1, 2, 3);
```
### 清理资产记录
```sql
-- 清理所有资产记录
DELETE FROM assets WHERE owner_uid IN (1, 2, 3);
DELETE FROM mint_orders WHERE user_id IN (1, 2, 3);
```
### 清理用户记录
```sql
-- 清理用户和粉丝档案
DELETE FROM fan_profiles WHERE user_id IN (1, 2, 3);
DELETE FROM users WHERE id IN (1, 2, 3);
```
### 重置序列(可选)
```sql
-- 重置自增序列
ALTER SEQUENCE booth_slots_slot_id_seq RESTART WITH 1;
ALTER SEQUENCE exhibitions_id_seq RESTART WITH 1;
ALTER SEQUENCE assets_asset_id_seq RESTART WITH 1;
ALTER SEQUENCE users_id_seq RESTART WITH 1;
```
---
## 常见问题
### 问题 1: 清理 Worker 没有自动清理过期展品
**症状**: 展品过期后仍然显示在展馆中
**排查步骤**:
1. 检查 GalleryService 日志确认清理 Worker 已启动
2. 检查展品的 `expire_at` 时间戳是否正确
3. 手动触发清理等待1分钟Worker 会自动运行
**手动清理命令**:
```sql
-- 查看过期的展品
SELECT * FROM exhibitions WHERE expire_at <= EXTRACT(EPOCH FROM NOW()) * 1000;
-- 手动删除过期展品
DELETE FROM exhibitions WHERE expire_at <= EXTRACT(EPOCH FROM NOW()) * 1000;
```
---
### 问题 2: 展位状态不一致
**症状**: 数据库中有展示记录但API返回展位为空
**排查步骤**:
1. 检查 `exhibitions` 表的记录
2. 检查 `booth_slots` 表的记录
3. 检查外键关联是否正确
**修复命令**:
```sql
-- 检查孤立的展示记录(没有对应的展位)
SELECT e.*
FROM exhibitions e
LEFT JOIN booth_slots bs ON e.slot_id = bs.slot_id
WHERE bs.slot_id IS NULL;
-- 删除孤立记录
DELETE FROM exhibitions
WHERE slot_id NOT IN (SELECT slot_id FROM booth_slots);
```
---
### 问题 3: 无法解锁展位
**症状**: 调用解锁接口时返回错误
**可能原因**:
1. 等级不足且水晶不足
2. 已达到最大展位数10个
3. 配置文件中没有该展位的解锁规则
**排查步骤**:
1. 检查用户的等级和水晶余额
2. 检查当前展位数量
3. 检查 `config/gallery_config.go` 中的解锁规则配置
**查询命令**:
```sql
-- 查询用户的等级和水晶
SELECT level, crystal_balance
FROM fan_profiles
WHERE user_id = 1 AND star_id = 87;
-- 查询当前展位数
SELECT COUNT(*)
FROM booth_slots
WHERE user_id = 1 AND star_id = 87;
```
---
### 问题 4: Dubbo 连接失败
**症状**: Gateway 启动时报错"Failed to create Gallery Service Dubbo client"。
**可能原因**:
1. GalleryService 未启动
2. 端口被占用
3. Dubbo 配置错误
**排查步骤**:
1. 检查 GalleryService 是否正常启动
2. 检查端口 20004 是否被占用
3. 检查环境变量 `DUBBO_GALLERY_SERVICE_URL`
**检查命令**:
```bash
# 检查端口
lsof -i :20004
# 检查环境变量
echo $DUBBO_GALLERY_SERVICE_URL
# 检查进程
ps aux | grep galleryService
```
---
### 问题 5: 资产信息显示不完整
**症状**: 展馆中的资产信息只有 asset_id没有 namecover_url
**可能原因**:
1. Asset Service 未启动
2. RPC 调用失败
3. 资产不存在
**排查步骤**:
1. 检查 Asset Service 是否正常启动
2. 检查 GalleryService 日志中的 RPC 调用错误
3. 检查资产是否存在于数据库中
**查询命令**:
```sql
-- 检查资产是否存在
SELECT * FROM assets WHERE asset_id = 1;
-- 检查资产的 status
SELECT asset_id, name, status FROM assets WHERE owner_uid = 1;
```
---
### 问题 6: 无法在他人展馆放置资产
**症状**: 调用 PlaceAsset 接口时返回"只能在同一明星的展馆中放置展品"。
**可能原因**:
1. 两个用户属于不同的明星
2. 展位的 `star_id` 与当前用户的 `star_id` 不匹配
**排查步骤**:
1. 检查两个用户的 `star_id`
2. 检查展位的 `star_id`
**查询命令**:
```sql
-- 检查用户的明星ID
SELECT user_id, star_id, nickname
FROM fan_profiles
WHERE user_id IN (1, 2);
-- 检查展位的明星ID
SELECT slot_id, user_id, star_id
FROM booth_slots
WHERE slot_id = 1;
```
---
## 测试检查清单
### 基本功能
- [ ] 首次访问展馆懒加载创建展位
- [ ] 获取我的展馆
- [ ] 获取他人展馆
- [ ] 在自己的展位放置资产
- [ ] 在他人的展位放置资产
- [ ] 占位者主动下架自己的资产
- [ ] 展位所有者踢走占位者
- [ ] 解锁展位等级解锁
- [ ] 解锁展位水晶购买
- [ ] 查看未解锁展位的解锁条件
### 异常场景
- [ ] 未认证访问
- [ ] 放置不存在的资产
- [ ] 放置他人的资产
- [ ] 在已占用的展位放置资产
- [ ] 同一资产放置到多个展位
- [ ] 在未解锁的展位放置资产
- [ ] 无权限用户尝试移除资产
- [ ] 跨明星放置资产
- [ ] 水晶和等级都不足时解锁展位
- [ ] 超过最大展位数
### 性能测试
- [ ] 并发查询展馆
- [ ] 并发放置资产
- [ ] 懒加载并发创建
### 清理功能
- [ ] 过期自动清理等待4小时或手动修改时间戳
---
## 测试报告模板
### 测试环境
- **测试日期**: 2026-01-14
- **测试人员**: [姓名]
- **服务版本**: v1.0.0
- **数据库**: PostgreSQL 14
### 测试结果统计
| 功能模块 | 测试用例数 | 通过数 | 失败数 | 通过率 |
|---------|-----------|--------|--------|--------|
| 展馆查询 | 2 | 2 | 0 | 100% |
| 资产放置 | 3 | 3 | 0 | 100% |
| 资产移除 | 2 | 2 | 0 | 100% |
| 展位解锁 | 2 | 2 | 0 | 100% |
| 异常场景 | 10 | 10 | 0 | 100% |
| 性能测试 | 3 | 3 | 0 | 100% |
| **总计** | **22** | **22** | **0** | **100%** |
### 发现的问题
### 测试结论
**通过** - 所有测试用例均通过展馆服务功能正常
---
## 重要更新说明
### 接口变更2026-01-14
为简化API设计已将以下两个接口合并为一个统一接口
**已废弃的接口:**
- ~~`POST /api/v1/galleries/remove`~~ - 下架资产
- ~~`POST /api/v1/galleries/me/slots/{slot_id}/kick`~~ - 踢走占位
**新的统一接口:**
- `DELETE /api/v1/galleries/slots/{slot_id}/asset` - 从展位移除资产
**功能说明:**
新接口会自动识别操作者身份
- 如果是**展位所有者**可以移除任何人放置的资产踢走功能
- 如果是**资产放置者**可以移除自己放置的资产下架功能
- 其他用户无权移除
---
**文档版本**: v1.1
**最后更新**: 2026-01-14
**维护者**: AI Assistant