32 KiB
展馆服务 HTTP 完整测试流程
目录
环境准备
1. 数据库准备
确保 PostgreSQL 数据库已启动并创建了 top-fans 数据库:
CREATE DATABASE "top-fans" OWNER haihuizhu ENCODING 'UTF8';
2. 数据库表检查
确保以下表已创建:
-- 检查展位表
SELECT COUNT(*) FROM booth_slots;
-- 检查展品展示表
SELECT COUNT(*) FROM exhibitions;
-- 检查资产表
SELECT COUNT(*) FROM assets;
-- 检查粉丝档案表
SELECT COUNT(*) FROM fan_profiles;
3. 环境变量配置
# 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. 测试数据准备
确保数据库中有明星数据:
-- 肖战(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
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.
检查端口:
lsof -i :20000
2. 启动 SocialService
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.
检查端口:
lsof -i :20001
3. 启动 AssetService
cd /Users/haihuizhu/infinite_matrix/TopFans/backend/services/assetService
go run main.go
预期输出:
Starting Asset Service...
Asset Service started successfully on port 20003
检查端口:
lsof -i :20003
4. 启动 GalleryService
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
检查端口:
lsof -i :20004
5. 启动 Gateway
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
检查端口:
lsof -i :8080
6. 健康检查
curl http://localhost:8080/health
预期响应:
{
"status": "ok",
"service": "top-fans-gateway"
}
用户和资产准备
步骤 1: 注册用户 A(张三)
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000001",
"password": "password123",
"star_id": 87,
"nickname": "张三"
}'
预期响应:
{
"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
}
}
}
保存变量:
export USER_A_ID=1
export USER_A_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_A_STAR_ID=87
步骤 2: 注册用户 B(李四)
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000002",
"password": "password123",
"star_id": 87,
"nickname": "李四"
}'
保存变量:
export USER_B_ID=2
export USER_B_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_B_STAR_ID=87
步骤 3: 注册用户 C(王五)- 不同明星
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"mobile": "13800000003",
"password": "password123",
"star_id": 88,
"nickname": "王五"
}'
保存变量:
export USER_C_ID=3
export USER_C_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export USER_C_STAR_ID=88
步骤 4: 为用户 A 创建资产
4.1 创建资产 1
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:
export ASSET_A1=1
4.2 创建资产 2
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:
export ASSET_A2=2
步骤 5: 为用户 B 创建资产
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:
export ASSET_B1=3
展馆功能测试
场景 1: 获取我的展馆(首次访问 - 懒加载)
测试目标
验证首次访问展馆时,系统自动创建3个初始展位。
请求示例
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_A_TOKEN"
或者使用别名路由:
curl -X GET http://localhost:8080/api/v1/galleries/me \
-H "Authorization: Bearer $USER_A_TOKEN"
预期响应
{
"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(因为已解锁)
数据库验证
-- 检查是否创建了展位
SELECT * FROM booth_slots WHERE user_id = 1 AND star_id = 87;
-- 预期结果:3条记录
保存展位ID:
export SLOT_A1=1
export SLOT_A2=2
export SLOT_A3=3
场景 2: 在自己的展位放置资产
测试目标
验证用户可以在自己的展位上放置自己的资产。
请求示例
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'
}'
预期响应
{
"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
数据库验证
-- 检查展品展示记录
SELECT * FROM exhibitions WHERE asset_id = 1;
-- 预期结果:1条记录,包含 asset_id, slot_id, occupier_uid, expire_at 等信息
场景 3: 再次获取我的展馆(查看放置结果)
请求示例
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_A_TOKEN"
预期响应
{
"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 的展馆
curl -X GET http://localhost:8080/api/v1/galleries/$USER_A_ID \
-H "Authorization: Bearer $USER_B_TOKEN"
预期响应:
{
"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放置资产
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'
}'
预期响应
{
"code": 200,
"message": "ok",
"data": {
"status": "OCCUPIED",
"occupied_until": "2026-01-14T15:00:00Z",
"occupier_uid": 2
}
}
验证要点
- ✅
occupier_uid为用户 B 的ID(2) - ✅ 可以成功放置在他人的展馆(同一明星)
- ✅ 展位被占用4小时
场景 5: 从展位移除资产(统一接口)
测试目标
验证用户可以从展位移除资产。该接口支持两种场景:
- 占位者下架:资产放置者可以主动下架自己放置的资产
- 所有者踢走:展位所有者可以踢走占据自己展位的其他用户的资产
5.1 占位者主动下架自己的资产
请求示例:
# 用户 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"
预期响应:
{
"code": 200,
"message": "ok",
"data": {
"message": "移除成功"
}
}
验证要点:
- ✅ 响应成功(code = 200)
- ✅ 返回移除成功消息
数据库验证:
-- 检查展品展示记录是否被删除
SELECT * FROM exhibitions WHERE slot_id = $SLOT_A1;
-- 预期结果:0条记录(已删除)
5.2 展位所有者踢走占位者
前置步骤:用户 B 在用户 A 的展位放置资产
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'
}'
请求示例:
# 用户 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"
预期响应:
{
"code": 200,
"message": "ok",
"data": {
"message": "移除成功"
}
}
验证要点:
- ✅ 响应成功(code = 200)
- ✅ 展位所有者可以踢走任何占位者
- ✅ 占位者可以下架自己的资产
数据库验证:
-- 检查展品展示记录是否被删除
SELECT * FROM exhibitions WHERE slot_id = $SLOT_A2;
-- 预期结果:0条记录(已删除)
场景 6: 解锁新展位(等级不够,使用水晶购买)
测试目标
验证用户可以使用水晶购买新的展位。
7.1 先给用户 A 增加水晶
-- 直接在数据库中给用户增加水晶
UPDATE fan_profiles
SET crystal_balance = 500
WHERE user_id = 1 AND star_id = 87;
7.2 解锁第4个展位
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
预期响应
{
"code": 200,
"message": "ok",
"data": {
"slot_total": 4,
"crystal_balance": 400
}
}
验证要点
- ✅
slot_total增加到 4 - ✅
crystal_balance减少100(第4个展位需要100水晶) - ✅ 如果等级达到要求(level >= 5),则不消耗水晶
数据库验证
-- 检查展位数量
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 提升等级
-- 直接在数据库中提升用户等级
UPDATE fan_profiles
SET level = 6
WHERE user_id = 1 AND star_id = 87;
8.2 解锁第5个展位
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
预期响应
{
"code": 200,
"message": "ok",
"data": {
"slot_total": 5,
"crystal_balance": 400
}
}
验证要点
- ✅
slot_total增加到 5 - ✅
crystal_balance保持不变(400)- 等级解锁不消耗水晶 - ✅ 第5个展位需要等级6或水晶200
场景 9: 查看未解锁展位的解锁条件
请求示例
curl -X GET http://localhost:8080/api/v1/mygalleries \
-H "Authorization: Bearer $USER_B_TOKEN"
预期响应
{
"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个展位(未解锁)的数据:
{
"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错误。
请求示例
curl -X GET http://localhost:8080/api/v1/mygalleries
预期响应
{
"code": 401,
"message": "用户未认证"
}
异常场景 2: 放置不存在的资产
测试目标
验证放置不存在的资产时返回错误。
请求示例
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'
}'
预期响应
{
"code": 400,
"message": "资产不存在或不属于当前用户"
}
异常场景 3: 放置他人的资产
测试目标
验证不能放置他人的资产。
请求示例
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'
}'
预期响应
{
"code": 400,
"message": "资产不存在或不属于当前用户"
}
异常场景 4: 在已占用的展位放置资产
测试目标
验证不能在已占用的展位上放置资产。
4.1 先放置一个资产
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 再次在同一展位放置资产
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'
}'
预期响应
{
"code": 400,
"message": "展位已被占用"
}
异常场景 5: 同一资产放置到多个展位
测试目标
验证同一资产不能同时在多个展位展示。
5.1 先在展位1放置资产
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放置同一资产
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'
}'
预期响应
{
"code": 400,
"message": "资产已在其他展位展示中"
}
异常场景 6: 在未解锁的展位放置资产
测试目标
验证不能在未解锁的展位上放置资产。
请求示例
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
}'
预期响应
{
"code": 400,
"message": "展位未解锁或不存在"
}
异常场景 7: 无权限用户尝试移除资产
测试目标
验证只有展位所有者或资产放置者可以移除资产。
7.1 用户 B 在用户 A 的展位放置资产
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 的资产(应该失败)
curl -X DELETE http://localhost:8080/api/v1/galleries/slots/$SLOT_A1/asset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_C_TOKEN"
预期响应
{
"code": 500,
"message": "移除资产失败,无权移除资产,只有展位所有者或占位者可以操作"
}
说明:
- ✅ 展位所有者(用户 A)可以移除任何人的资产
- ✅ 资产放置者(用户 B)可以移除自己的资产
- ❌ 第三方用户(用户 C)无法移除他人的资产
异常场景 8: 跨明星放置资产
测试目标
验证不能在不同明星的展馆放置资产。
请求示例
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
}'
预期响应
{
"code": 400,
"message": "只能在同一明星的展馆中放置展品"
}
异常场景 10: 水晶和等级都不足时解锁展位
测试目标
验证水晶和等级都不足时,无法解锁展位。
10.1 清空用户水晶
UPDATE fan_profiles
SET crystal_balance = 0, level = 1
WHERE user_id = 2 AND star_id = 87;
10.2 尝试解锁展位
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_B_TOKEN"
预期响应
{
"code": 400,
"message": "等级和水晶余额都不足"
}
异常场景 11: 超过最大展位数
测试目标
验证不能解锁超过最大展位数(10个)。
11.1 给用户 A 解锁到最大展位数
-- 直接在数据库中创建展位(测试用)
-- 第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个展位
curl -X POST http://localhost:8080/api/v1/galleries/slots_unlock \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_A_TOKEN"
预期响应
{
"code": 400,
"message": "已达到最大展位数"
}
性能和边界测试
性能测试 1: 并发查询展馆
测试目标
验证系统可以处理并发查询请求。
测试工具
使用 ab (Apache Bench) 或 wrk 进行压力测试。
测试命令
# 使用 ab 进行测试(100个并发,1000个请求)
ab -n 1000 -c 100 -H "Authorization: Bearer $USER_A_TOKEN" \
http://localhost:8080/api/v1/mygalleries
预期结果
- ✅ 成功率 > 99%
- ✅ 平均响应时间 < 100ms
- ✅ 无数据库死锁
- ✅ 无数据不一致
性能测试 2: 并发放置资产
测试目标
验证系统可以处理并发放置资产请求(测试唯一索引)。
测试场景
多个用户同时尝试在同一展位放置资产,只有一个应该成功。
测试脚本
#!/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: 懒加载并发
测试目标
验证多个并发请求首次访问展馆时,不会创建重复的初始展位。
测试脚本
#!/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
数据库验证
-- 检查是否只创建了3个展位(不应该有重复)
SELECT COUNT(*) FROM booth_slots WHERE user_id = (SELECT user_id FROM users WHERE mobile = '13800000099') AND star_id = 87;
-- 预期结果:3条记录(不是30条)
测试数据清理
清理展品展示记录
-- 清理所有展品展示记录
DELETE FROM exhibitions WHERE occupier_uid IN (1, 2, 3);
清理展位记录
-- 清理所有展位记录
DELETE FROM booth_slots WHERE user_id IN (1, 2, 3);
清理资产记录
-- 清理所有资产记录
DELETE FROM assets WHERE owner_uid IN (1, 2, 3);
DELETE FROM mint_orders WHERE user_id IN (1, 2, 3);
清理用户记录
-- 清理用户和粉丝档案
DELETE FROM fan_profiles WHERE user_id IN (1, 2, 3);
DELETE FROM users WHERE id IN (1, 2, 3);
重置序列(可选)
-- 重置自增序列
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 没有自动清理过期展品
症状: 展品过期后仍然显示在展馆中。
排查步骤:
- 检查 GalleryService 日志,确认清理 Worker 已启动
- 检查展品的
expire_at时间戳是否正确 - 手动触发清理(等待1分钟,Worker 会自动运行)
手动清理命令:
-- 查看过期的展品
SELECT * FROM exhibitions WHERE expire_at <= EXTRACT(EPOCH FROM NOW()) * 1000;
-- 手动删除过期展品
DELETE FROM exhibitions WHERE expire_at <= EXTRACT(EPOCH FROM NOW()) * 1000;
问题 2: 展位状态不一致
症状: 数据库中有展示记录,但API返回展位为空。
排查步骤:
- 检查
exhibitions表的记录 - 检查
booth_slots表的记录 - 检查外键关联是否正确
修复命令:
-- 检查孤立的展示记录(没有对应的展位)
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: 无法解锁展位
症状: 调用解锁接口时返回错误。
可能原因:
- 等级不足且水晶不足
- 已达到最大展位数(10个)
- 配置文件中没有该展位的解锁规则
排查步骤:
- 检查用户的等级和水晶余额
- 检查当前展位数量
- 检查
config/gallery_config.go中的解锁规则配置
查询命令:
-- 查询用户的等级和水晶
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"。
可能原因:
- GalleryService 未启动
- 端口被占用
- Dubbo 配置错误
排查步骤:
- 检查 GalleryService 是否正常启动
- 检查端口 20004 是否被占用
- 检查环境变量
DUBBO_GALLERY_SERVICE_URL
检查命令:
# 检查端口
lsof -i :20004
# 检查环境变量
echo $DUBBO_GALLERY_SERVICE_URL
# 检查进程
ps aux | grep galleryService
问题 5: 资产信息显示不完整
症状: 展馆中的资产信息只有 asset_id,没有 name、cover_url 等。
可能原因:
- Asset Service 未启动
- RPC 调用失败
- 资产不存在
排查步骤:
- 检查 Asset Service 是否正常启动
- 检查 GalleryService 日志中的 RPC 调用错误
- 检查资产是否存在于数据库中
查询命令:
-- 检查资产是否存在
SELECT * FROM assets WHERE asset_id = 1;
-- 检查资产的 status
SELECT asset_id, name, status FROM assets WHERE owner_uid = 1;
问题 6: 无法在他人展馆放置资产
症状: 调用 PlaceAsset 接口时返回"只能在同一明星的展馆中放置展品"。
可能原因:
- 两个用户属于不同的明星
- 展位的
star_id与当前用户的star_id不匹配
排查步骤:
- 检查两个用户的
star_id - 检查展位的
star_id
查询命令:
-- 检查用户的明星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