feat: 支持分享详情接口
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2026-05-13: 新增 `GET /api/v1/share/{code}` 分享挑战详情接口,返回挑战基本信息和所有已提交用户的排行榜;排行榜项包含用户头像、昵称、答对题数和总耗时。
|
||||
- 2026-05-12: `GET /api/v1/share/created` 响应新增 `firstPlaceUser`,返回每个挑战当前第一名用户的昵称和头像;暂无提交结果时为 `null`。
|
||||
- 2026-05-10: 移除 `POST /api/v1/share/progress` 单关进度上报接口;新增 `POST /api/v1/share/{code}/submit`,客户端一次提交整场挑战的每关答案和耗时,服务端校验答案后返回排名、答对题数、参与人数和完整关卡答案,并持久化提交结果。
|
||||
|
||||
## 目录
|
||||
@@ -27,8 +29,9 @@
|
||||
1. **关卡时间限制**:`levels` 表新增 `time_limit` 字段,支持关卡通关时间限制
|
||||
2. **整场挑战提交**:`POST /api/v1/share/{code}/submit` 接口,用于一次性提交分享挑战中每一关的答案和耗时
|
||||
3. **挑战结果返回**:提交后返回当前用户排名、答对题数、参与人数、总耗时和每关校验结果
|
||||
4. **我创建的挑战列表**:`GET /api/v1/share/created` 接口,用于查询当前用户创建过的分享挑战、参与人数和本人排名
|
||||
5. **关卡排序**:关卡全局顺序按 `levels.sort_key` 的应用层字节序计算,接口中的 `sortOrder` 为排序后的 0-based 连续序号
|
||||
4. **分享挑战详情**:`GET /api/v1/share/{code}` 接口,用于查询单个挑战的基本信息和完整排行榜
|
||||
5. **我创建的挑战列表**:`GET /api/v1/share/created` 接口,用于查询当前用户创建过的分享挑战、参与人数、本人排名和当前第一名用户信息
|
||||
6. **关卡排序**:关卡全局顺序按 `levels.sort_key` 的应用层字节序计算,接口中的 `sortOrder` 为排序后的 0-based 连续序号
|
||||
|
||||
---
|
||||
|
||||
@@ -296,7 +299,105 @@ Content-Type: application/json
|
||||
|
||||
---
|
||||
|
||||
### 4. 获取我创建的分享挑战
|
||||
### 4. 获取分享挑战详情
|
||||
|
||||
获取单个分享挑战的基本信息,以及该挑战下所有已提交结果用户的排行榜。
|
||||
|
||||
**接口地址**:`GET /api/v1/share/{code}`
|
||||
|
||||
**是否需要认证**:是(JWT Bearer Token)
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---- | ------ | ---- | -------------- |
|
||||
| code | string | 是 | 分享码(8 位) |
|
||||
|
||||
**响应数据**:
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: string; // 分享挑战 ID
|
||||
shareCode: string; // 分享码
|
||||
title: string; // 分享标题
|
||||
levelCount: number; // 挑战关卡总数
|
||||
participantCount: number; // 已提交结果的参与用户总数
|
||||
userRank: number | null; // 当前用户排名;尚未提交结果时为 null
|
||||
createdAt: string; // 创建时间,ISO 8601 字符串
|
||||
rankings: [
|
||||
{
|
||||
rank: number; // 名次,从 1 开始
|
||||
participantId: string; // 参与用户 ID
|
||||
nickname: string | null;
|
||||
avatarUrl: string | null;
|
||||
correctCount: number; // 答对题数
|
||||
totalTimeSpent: number;// 总耗时(秒)
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "share_001",
|
||||
"shareCode": "abc12345",
|
||||
"title": "我的挑战",
|
||||
"levelCount": 6,
|
||||
"participantCount": 3,
|
||||
"userRank": 2,
|
||||
"createdAt": "2026-04-13T10:00:00.000Z",
|
||||
"rankings": [
|
||||
{
|
||||
"rank": 1,
|
||||
"participantId": "user_002",
|
||||
"nickname": "速度玩家",
|
||||
"avatarUrl": "https://example.com/avatar-speed.png",
|
||||
"correctCount": 6,
|
||||
"totalTimeSpent": 90
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"participantId": "user_001",
|
||||
"nickname": "挑战创建者",
|
||||
"avatarUrl": null,
|
||||
"correctCount": 6,
|
||||
"totalTimeSpent": 120
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"participantId": "user_003",
|
||||
"nickname": null,
|
||||
"avatarUrl": null,
|
||||
"correctCount": 5,
|
||||
"totalTimeSpent": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": null,
|
||||
"timestamp": "2026-04-13T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑说明**:
|
||||
|
||||
1. 返回挑战基本信息:分享 ID、分享码、标题、关卡数量、已提交人数、当前用户排名和创建时间。
|
||||
2. `rankings` 只包含已经调用 `POST /api/v1/share/{code}/submit` 提交整场挑战结果的用户。
|
||||
3. 排行榜按答对题数降序排列;答对题数相同时,按总耗时升序排列。
|
||||
4. 如果答对题数和总耗时都相同,服务端继续按提交时间升序、`participantId` 升序做稳定排序。
|
||||
5. `userRank` 表示当前登录用户在该挑战中的排名;当前用户尚未提交结果时为 `null`。
|
||||
|
||||
**客户端调用场景**:
|
||||
|
||||
- 用户打开单个挑战详情页或结算页时调用。
|
||||
- 需要展示完整排行榜时调用;列表页仍使用 `GET /api/v1/share/created`。
|
||||
|
||||
---
|
||||
|
||||
### 5. 获取我创建的分享挑战
|
||||
|
||||
获取当前登录用户创建过的分享挑战列表。
|
||||
|
||||
@@ -322,6 +423,10 @@ Authorization: Bearer <token>
|
||||
levelCount: number; // 关卡数量
|
||||
participantCount: number; // 已提交结果的参与人数
|
||||
userRank: number | null; // 当前用户在该挑战中的排名;尚未提交结果时为 null
|
||||
firstPlaceUser: { // 当前第一名用户信息;暂无提交结果时为 null
|
||||
nickname: string | null; // 第一名用户昵称
|
||||
avatarUrl: string | null;// 第一名用户头像 URL
|
||||
} | null;
|
||||
createdAt: string; // 创建时间,ISO 8601 字符串
|
||||
}
|
||||
]
|
||||
@@ -342,6 +447,10 @@ Authorization: Bearer <token>
|
||||
"levelCount": 6,
|
||||
"participantCount": 8,
|
||||
"userRank": 2,
|
||||
"firstPlaceUser": {
|
||||
"nickname": "第一名玩家",
|
||||
"avatarUrl": "https://example.com/avatar-first.png"
|
||||
},
|
||||
"createdAt": "2026-04-13T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
@@ -351,6 +460,10 @@ Authorization: Bearer <token>
|
||||
"levelCount": 6,
|
||||
"participantCount": 1,
|
||||
"userRank": null,
|
||||
"firstPlaceUser": {
|
||||
"nickname": null,
|
||||
"avatarUrl": null
|
||||
},
|
||||
"createdAt": "2026-04-12T09:00:00.000Z"
|
||||
}
|
||||
]
|
||||
@@ -366,6 +479,7 @@ Authorization: Bearer <token>
|
||||
2. 排名按答对题数降序计算,答对越多排名越高。
|
||||
3. 答对题数相同时,按总耗时升序、提交时间升序、`participantId` 升序做稳定排序。
|
||||
4. `userRank` 表示当前登录用户在自己创建的该挑战中的排名。如果自己尚未提交挑战结果,则返回 `null`。
|
||||
5. `firstPlaceUser` 取当前排名第一的已提交用户资料。如果该挑战还没有任何提交结果,则返回 `null`。
|
||||
|
||||
**参与人数统计规则**:
|
||||
|
||||
@@ -379,7 +493,7 @@ Authorization: Bearer <token>
|
||||
|
||||
---
|
||||
|
||||
### 5. 提交分享挑战结果
|
||||
### 6. 提交分享挑战结果
|
||||
|
||||
用户完成分享挑战后,一次性提交分享中每一关的耗时和答案。服务端校验答案、持久化结果,并返回当前用户的排名和完整关卡答案。
|
||||
|
||||
@@ -803,7 +917,56 @@ const shareData = await joinShare(shareCode);
|
||||
// 保存关卡数据,开始游戏
|
||||
```
|
||||
|
||||
### 5. 提交挑战结果
|
||||
### 5. 获取挑战详情
|
||||
|
||||
```typescript
|
||||
interface ChallengeRankingItem {
|
||||
rank: number;
|
||||
participantId: string;
|
||||
nickname: string | null;
|
||||
avatarUrl: string | null;
|
||||
correctCount: number;
|
||||
totalTimeSpent: number;
|
||||
}
|
||||
|
||||
interface ShareChallengeDetailResponse {
|
||||
id: string;
|
||||
shareCode: string;
|
||||
title: string;
|
||||
levelCount: number;
|
||||
participantCount: number;
|
||||
userRank: number | null;
|
||||
createdAt: string;
|
||||
rankings: ChallengeRankingItem[];
|
||||
}
|
||||
|
||||
async function getShareChallengeDetail(
|
||||
shareCode: string,
|
||||
): Promise<ShareChallengeDetailResponse> {
|
||||
// 确保已登录
|
||||
if (!httpManager.getToken()) {
|
||||
await wxLogin();
|
||||
}
|
||||
|
||||
const response = await httpManager.get<ShareChallengeDetailResponse>(
|
||||
`/v1/share/${shareCode}`,
|
||||
);
|
||||
|
||||
if (response.success && response.data) {
|
||||
console.log('挑战详情:', response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(response.message || '获取挑战详情失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const detail = await getShareChallengeDetail('abc12345');
|
||||
console.log(`当前排名: ${detail.userRank ?? '暂未上榜'}`);
|
||||
console.log(`已提交人数: ${detail.participantCount}`);
|
||||
```
|
||||
|
||||
### 6. 提交挑战结果
|
||||
|
||||
```typescript
|
||||
interface SubmitChallengeLevel {
|
||||
@@ -870,7 +1033,7 @@ async function onChallengeFinished() {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 启动流程示例
|
||||
### 7. 启动流程示例
|
||||
|
||||
```typescript
|
||||
// GameEntry.ts - 游戏入口脚本
|
||||
|
||||
Reference in New Issue
Block a user