# 展馆服务 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 的ID(2) - ✅ 可以成功放置在他人的展馆(同一明星) - ✅ 展位被占用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,没有 name、cover_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