35 KiB
MemeMind 分享挑战功能 API 文档
本文档面向微信小游戏客户端(Cocos Creator)开发人员
Changelog
- 2026-05-14: 新增
GET /api/v1/share/participated我参与的挑战列表接口,返回挑战名称、已提交参与人数和当前用户名次。 - 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,客户端一次提交整场挑战的每关答案和耗时,服务端校验答案后返回排名、答对题数、参与人数和完整关卡答案,并持久化提交结果。
目录
概述
分享挑战功能允许用户创建包含 6 个关卡的挑战链接,分享给好友。好友通过分享码加入挑战,独立完成关卡,并在挑战结束后一次性提交每一关的答案与耗时。服务端校验答案后,会返回当前用户排名、答对题数、已提交挑战结果的参与人数,以及分享中每一关的完整内容和正确答案。
新增功能点(当前分支)
- 关卡时间限制:
levels表新增time_limit字段,支持关卡通关时间限制 - 整场挑战提交:
POST /api/v1/share/{code}/submit接口,用于一次性提交分享挑战中每一关的答案和耗时 - 挑战结果返回:提交后返回当前用户排名、答对题数、参与人数、总耗时和每关校验结果
- 分享挑战详情:
GET /api/v1/share/{code}接口,用于查询单个挑战的基本信息和完整排行榜 - 我创建的挑战列表:
GET /api/v1/share/created接口,用于查询当前用户创建过的分享挑战、参与人数、本人排名和当前第一名用户信息 - 我参与的挑战列表:
GET /api/v1/share/participated接口,用于查询当前用户参与过的分享挑战、参与人数和本人排名 - 关卡排序:关卡全局顺序按
levels.sort_key的应用层字节序计算,接口中的sortOrder为排序后的 0-based 连续序号
认证方式
除微信登录接口外,所有接口均需通过 JWT Token 进行身份认证。
请求头格式
Authorization: Bearer <token>
token 为微信登录接口返回的 JWT 令牌。
通用响应格式
所有接口均返回以下 JSON 结构:
{
"success": boolean, // 请求是否成功
"data": T | null, // 成功时返回的数据,失败时为 null
"message": string | null,// 错误信息,成功时为 null
"timestamp": string // 服务器响应时间(ISO 8601 格式)
}
成功响应示例
{
"success": true,
"data": {
"shareCode": "abc12345",
"title": "我的挑战",
"levelCount": 6
},
"message": null,
"timestamp": "2026-04-08T12:00:00.000Z"
}
失败响应示例
{
"success": false,
"data": null,
"message": "分享不存在或已过期",
"timestamp": "2026-04-08T12:00:00.000Z"
}
接口列表
1. 微信登录
获取用户身份令牌。
接口地址:POST /api/v1/auth/wx-login
是否需要认证:否
请求体:
{
"code": "微信 wx.login 返回的 code"
}
响应数据:
{
token: string; // JWT 访问令牌,有效期 7 天
user: {
id: string; // 用户 ID
nickname: string | null; // 用户昵称(微信昵称)
stamina: number; // 当前体力值
}
}
成功响应示例:
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_abc123",
"nickname": "游戏玩家",
"stamina": 5
}
},
"message": null,
"timestamp": "2026-04-08T12:00:00.000Z"
}
客户端调用时机:
- 用户首次进入游戏时调用
- 小游戏冷启动时调用(建议缓存 token)
2. 创建分享
创建一个新的分享挑战。
接口地址:POST /api/v1/share
是否需要认证:是(JWT Bearer Token)
请求头:
Authorization: Bearer <token>
Content-Type: application/json
请求体:
{
"title": "我的挑战", // 分享标题,不超过 100 字符
"levelIds": [
// 恰好 6 个关卡 ID
"level_id_1",
"level_id_2",
"level_id_3",
"level_id_4",
"level_id_5",
"level_id_6"
]
}
响应数据:
{
shareCode: string; // 8 位分享码,用于分享和加入
title: string; // 分享标题
levelCount: number; // 关卡数量(固定为 6)
}
成功响应示例:
{
"success": true,
"data": {
"shareCode": "abc12345",
"title": "我的挑战",
"levelCount": 6
},
"message": null,
"timestamp": "2026-04-08T12:00:00.000Z"
}
分享码生成规则:
- 使用 nanoid 生成 8 位字符
- 字符集为 a-z, A-Z, 0-9
- 发生碰撞时最多重试 3 次
客户端调用场景:
- 用户点击「分享挑战」按钮时调用
- 用户选择 6 个关卡后,生成分享码
- 将分享码拼接为分享链接或二维码
3. 加入分享
通过分享码加入一个分享挑战,获取关卡数据。
接口地址:POST /api/v1/share/{code}/join
是否需要认证:是(JWT Bearer Token)
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 分享码(8 位) |
响应数据:
{
shareCode: string; // 分享码
title: string; // 分享标题
levels: [
{
id: string; // 关卡 ID
level: number; // 关卡序号(1-6)
image1Url: string; // 图片 1 URL
image1Description: string | null;
image2Url: string; // 图片 2 URL
image2Description: string | null;
answer: string; // 正确答案
punchline: string | null;
hint1: string | null; // 提示 1
hint2: string | null; // 提示 2
hint3: string | null; // 提示 3
sortOrder: number; // 排序顺序
}
]
}
成功响应示例:
{
"success": true,
"data": {
"shareCode": "abc12345",
"title": "我的挑战",
"levels": [
{
"id": "level_001",
"level": 1,
"image1Url": "https://example.com/levels/1-a.png",
"image1Description": null,
"image2Url": "https://example.com/levels/1-b.png",
"image2Description": null,
"answer": "答案1",
"punchline": null,
"hint1": "提示1",
"hint2": null,
"hint3": null,
"sortOrder": 0
}
]
},
"message": null,
"timestamp": "2026-04-08T12:00:00.000Z"
}
特殊逻辑:
- 如果
userId与分享创建者相同,不会创建ShareParticipant记录 levels数组顺序保持分享创建时传入的levelIds顺序;每个关卡的sortOrder字段为按levels.sort_key全局排序后回填的 0-based 连续序号- 返回的关卡列表按
levelIds创建时的顺序排列
客户端调用场景:
- 用户通过分享码/链接进入游戏时调用
- 解析 URL 参数中的分享码,调用此接口获取关卡数据
4. 获取分享挑战详情
获取单个分享挑战的基本信息,以及该挑战下所有已提交结果用户的排行榜。
接口地址:GET /api/v1/share/{code}
是否需要认证:是(JWT Bearer Token)
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 分享码(8 位) |
响应数据:
{
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;// 总耗时(秒)
}
];
}
成功响应示例:
{
"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"
}
业务逻辑说明:
- 返回挑战基本信息:分享 ID、分享码、标题、关卡数量、已提交人数、当前用户排名和创建时间。
rankings只包含已经调用POST /api/v1/share/{code}/submit提交整场挑战结果的用户。- 排行榜按答对题数降序排列;答对题数相同时,按总耗时升序排列。
- 如果答对题数和总耗时都相同,服务端继续按提交时间升序、
participantId升序做稳定排序。 userRank表示当前登录用户在该挑战中的排名;当前用户尚未提交结果时为null。
客户端调用场景:
- 用户打开单个挑战详情页或结算页时调用。
- 需要展示完整排行榜时调用;列表页使用
GET /api/v1/share/created或GET /api/v1/share/participated。
5. 获取我创建的分享挑战
获取当前登录用户创建过的分享挑战列表。
接口地址:GET /api/v1/share/created
是否需要认证:是(JWT Bearer Token)
请求头:
Authorization: Bearer <token>
响应数据:
{
items: [
{
id: string; // 分享挑战 ID
shareCode: string; // 分享码
title: string; // 分享标题
levelCount: number; // 关卡数量
participantCount: number; // 已提交结果的参与人数
userRank: number | null; // 当前用户在该挑战中的排名;尚未提交结果时为 null
firstPlaceUser: { // 当前第一名用户信息;暂无提交结果时为 null
nickname: string | null; // 第一名用户昵称
avatarUrl: string | null;// 第一名用户头像 URL
} | null;
createdAt: string; // 创建时间,ISO 8601 字符串
}
]
}
成功响应示例:
{
"success": true,
"data": {
"items": [
{
"id": "share_001",
"shareCode": "abc12345",
"title": "我的挑战",
"levelCount": 6,
"participantCount": 8,
"userRank": 2,
"firstPlaceUser": {
"nickname": "第一名玩家",
"avatarUrl": "https://example.com/avatar-first.png"
},
"createdAt": "2026-04-13T10:00:00.000Z"
},
{
"id": "share_002",
"shareCode": "xyz67890",
"title": "速度挑战",
"levelCount": 6,
"participantCount": 1,
"userRank": null,
"firstPlaceUser": {
"nickname": null,
"avatarUrl": null
},
"createdAt": "2026-04-12T09:00:00.000Z"
}
]
},
"message": null,
"timestamp": "2026-04-13T12:00:00.000Z"
}
排名规则:
- 只有已经调用
POST /api/v1/share/{code}/submit提交整场挑战结果的用户才会进入排名。 - 排名按答对题数降序计算,答对越多排名越高。
- 答对题数相同时,按总耗时升序、提交时间升序、
participantId升序做稳定排序。 userRank表示当前登录用户在自己创建的该挑战中的排名。如果自己尚未提交挑战结果,则返回null。firstPlaceUser取当前排名第一的已提交用户资料。如果该挑战还没有任何提交结果,则返回null。
参与人数统计规则:
- 统计该挑战中已提交完整挑战结果的用户数量。
- 创建者本人如果调用提交接口,也会作为参与用户进入统计和排名。
客户端调用场景:
- 用户进入「我发起的挑战」页面时调用。
- 用于展示每个分享挑战的传播效果和本人当前成绩。
6. 获取我参与的分享挑战
获取当前登录用户参与过的分享挑战列表。
接口地址:GET /api/v1/share/participated
是否需要认证:是(JWT Bearer Token)
请求头:
Authorization: Bearer <token>
响应数据:
{
items: [
{
title: string; // 挑战名称
participantCount: number; // 已提交结果的参与人数
userRank: number | null; // 当前用户在该挑战中的排名;尚未提交结果时为 null
}
]
}
成功响应示例:
{
"success": true,
"data": {
"items": [
{
"title": "好友挑战",
"participantCount": 5,
"userRank": 3
},
{
"title": "速度挑战",
"participantCount": 1,
"userRank": null
}
]
},
"message": null,
"timestamp": "2026-05-14T12:00:00.000Z"
}
排名规则:
- 列表只返回当前用户在
share_participants中有参与记录的挑战。 - 只有已经调用
POST /api/v1/share/{code}/submit提交整场挑战结果的用户才会进入排名。 - 排名按答对题数降序计算,答对题数相同时,按总耗时升序、提交时间升序、
participantId升序做稳定排序。 userRank表示当前登录用户在该挑战中的排名。如果当前用户已加入但尚未提交挑战结果,则返回null。
参与人数统计规则:
- 统计该挑战中已提交完整挑战结果的用户数量。
- 创建者本人如果调用提交接口,也会作为参与用户进入统计和排名。
客户端调用场景:
- 用户进入「我参与的挑战」页面时调用。
- 列表只展示挑战名称、参与人数和当前用户名次。
7. 提交分享挑战结果
用户完成分享挑战后,一次性提交分享中每一关的耗时和答案。服务端校验答案、持久化结果,并返回当前用户的排名和完整关卡答案。
接口地址:POST /api/v1/share/{code}/submit
是否需要认证:是(JWT Bearer Token)
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 分享码(8 位) |
请求体:
{
"levels": [
{
"levelId": "level_001",
"answer": "答案1",
"timeSpent": 30
},
{
"levelId": "level_002",
"answer": "答案2",
"timeSpent": 45
}
]
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| levels | array | 是 | 每一关的提交结果,必须覆盖分享挑战中的全部关卡 |
| levels[].levelId | string | 是 | 关卡 ID |
| levels[].answer | string | 是 | 用户提交的答案,空字符串表示未作答 |
| levels[].timeSpent | number | 是 | 本关耗时(秒),最小值为 0 |
响应数据:
{
shareCode: string;
title: string;
rank: number; // 当前用户在该挑战中的排名
correctCount: number; // 当前用户答对题数
levelCount: number; // 挑战关卡总数
participantCount: number; // 已提交结果的参与用户总数
totalTimeSpent: number; // 当前用户本次提交总耗时(秒)
levels: [
{
id: string;
level: number;
image1Url: string;
image1Description: string | null;
image2Url: string;
image2Description: string | null;
answer: string;
punchline: string | null;
hint1: string | null;
hint2: string | null;
hint3: string | null;
sortOrder: number;
submittedAnswer: string;
timeSpent: number;
isCorrect: boolean;
timeLimit: number | null;
withinTimeLimit: boolean;
}
];
}
成功响应示例:
{
"success": true,
"data": {
"shareCode": "abc12345",
"title": "我的挑战",
"rank": 2,
"correctCount": 5,
"levelCount": 6,
"participantCount": 8,
"totalTimeSpent": 180,
"levels": [
{
"id": "level_001",
"level": 1,
"image1Url": "https://example.com/levels/1-a.png",
"image1Description": null,
"image2Url": "https://example.com/levels/1-b.png",
"image2Description": null,
"answer": "答案1",
"punchline": null,
"hint1": "提示1",
"hint2": null,
"hint3": null,
"sortOrder": 0,
"submittedAnswer": "答案1",
"timeSpent": 30,
"isCorrect": true,
"timeLimit": 60,
"withinTimeLimit": true
}
]
},
"message": null,
"timestamp": "2026-04-08T12:00:00.000Z"
}
业务逻辑说明:
- 完整提交:
levels必须覆盖分享中的全部关卡,不能缺少、重复或提交不属于该分享的关卡。 - 答案校验:服务端按去除首尾空白并忽略大小写后的答案进行比对,返回每关
isCorrect。 - 排名规则:按答对题数降序排名;答对题数相同时,按总耗时升序、提交时间升序、
participantId升序稳定排序。 - 参与者登记:提交成功后会写入或更新
share_participants的聚合成绩;创建者本人提交时也会计入参与人数和排名。 - 结果持久化:每关的
submittedAnswer、timeSpent、isCorrect会保存到share_level_progress,整场的correctCount、totalTimeSpent、submittedAt会保存到share_participants。 - 重复提交:同一用户重复提交同一挑战时,服务端会覆盖该用户本挑战的上一份结果,并重新计算排名。
- 时间限制判断:
withinTimeLimit只表示耗时是否不超过关卡timeLimit;答题正确与否由isCorrect表示。 - 跨挑战独立:结果按
(shareConfigId, participantId, levelId)唯一记录,同一用户在不同分享挑战中对同一关卡的提交互不影响。
客户端调用场景:
- 用户完成整场分享挑战后调用
- 结果页需要展示排名、答对题数、完整答案解析时调用
错误码说明
| HTTP Status | message | 说明 |
|---|---|---|
| 400 | 关卡ID不能重复,需要恰好6个不同的关卡 | 创建分享时 levelIds 格式错误 |
| 400 | 以下关卡不存在: xxx | 创建分享时关卡 ID 不存在 |
| 400 | 生成分享码失败,请重试 | 服务器生成分享码失败 |
| 400 | 提交关卡数量必须与分享挑战关卡数量一致 | 提交挑战结果时关卡数量不完整 |
| 400 | 关卡 xxx 重复提交 | 提交挑战结果时同一关卡重复 |
| 400 | 关卡 xxx 不属于此分享挑战 | 提交挑战结果时包含外部关卡 |
| 400 | 缺少关卡提交: xxx | 提交挑战结果时缺少指定关卡 |
| 401 | 未提供访问令牌 | 请求头缺少 Authorization |
| 401 | 访问令牌无效或已过期 | JWT Token 无效或过期 |
| 404 | 分享不存在或已过期 | 分享码不存在或已被删除 |
| 404 | 以下关卡不存在: xxx | 分享配置中的关卡不存在 |
| 500 | Internal server error | 服务器内部错误 |
接入流程
整体流程
┌─────────────────────────────────────────────────────────────────┐
│ 分享发起方 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 用户选择 6 个关卡 │
│ 2. 调用 POST /api/v1/share 创建分享 │
│ 3. 获取 8 位分享码 │
│ 4. 生成分享链接/二维码,分享给好友 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 分享接收方 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 通过分享链接/二维码进入游戏 │
│ 2. 解析分享码 │
│ 3. 调用 POST /api/v1/auth/wx-login 获取/确认身份 │
│ 4. 调用 POST /api/v1/share/{code}/join 加入挑战 │
│ 5. 获取 6 个关卡数据,开始挑战 │
│ 6. 完成整场挑战后,调用 POST /api/v1/share/{code}/submit 提交结果 │
└─────────────────────────────────────────────────────────────────┘
客户端状态管理建议
// 建议在客户端维护以下状态
interface ShareChallengeState {
isInChallenge: boolean; // 是否正在参与分享挑战
shareCode: string | null; // 当前分享码
currentLevelIndex: number; // 当前关卡索引(0-5)
levels: LevelData[]; // 关卡数据
submissions: {
[levelId: string]: {
answer: string;
timeSpent: number;
};
};
result: SubmitChallengeResponse | null;
}
分享链接格式建议
wechatminiprogram://pages/challenge?code=abc12345
或在微信中使用 wx.openUrl 打开 H5 页面,H5 页面再跳转到小程序。
Cocos Creator 调用示例
1. HTTP 请求工具类
// HttpManager.ts
import { Color } from 'cc';
export interface ApiResponse<T> {
success: boolean;
data: T | null;
message: string | null;
timestamp: string;
}
export class HttpManager {
private baseUrl = 'https://your-api-domain.com/api';
private token: string | null = null;
setToken(token: string) {
this.token = token;
}
getToken(): string | null {
return this.token;
}
async request<T>(
method: 'GET' | 'POST',
url: string,
body?: object,
): Promise<ApiResponse<T>> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, this.baseUrl + url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
if (this.token) {
xhr.setRequestHeader('Authorization', `Bearer ${this.token}`);
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
try {
const errorResp = JSON.parse(xhr.responseText);
reject(new Error(errorResp.message || '请求失败'));
} catch {
reject(new Error(`请求失败: ${xhr.status}`));
}
}
};
xhr.onerror = () => {
reject(new Error('网络错误'));
};
if (body) {
xhr.send(JSON.stringify(body));
} else {
xhr.send();
}
});
}
// GET 请求
async get<T>(url: string): Promise<ApiResponse<T>> {
return this.request<T>('GET', url);
}
// POST 请求
async post<T>(url: string, body: object): Promise<ApiResponse<T>> {
return this.request<T>('POST', url, body);
}
}
export const httpManager = new HttpManager();
2. 微信登录
// 调用时机:小游戏启动时,或缓存的 token 过期时
async function wxLogin() {
// 1. 调用微信 wx.login 获取 code
const wxLoginRes = await new Promise<{ code: string }>((resolve, reject) => {
wx.login({
success: (res) => resolve({ code: res.code }),
fail: reject,
});
});
// 2. 将 code 发送到服务器换取 token
try {
const response = await httpManager.post<{
token: string;
user: { id: string; nickname: string | null; stamina: number };
}>('/v1/auth/wx-login', {
code: wxLoginRes.code,
});
if (response.success && response.data) {
// 3. 缓存 token
httpManager.setToken(response.data.token);
wx.setStorageSync('jwt_token', response.data.token);
console.log('登录成功,用户信息:', response.data.user);
return response.data;
} else {
throw new Error(response.message || '登录失败');
}
} catch (error) {
console.error('登录失败:', error);
throw error;
}
}
3. 创建分享
interface CreateShareResponse {
shareCode: string;
title: string;
levelCount: number;
}
async function createShare(
title: string,
levelIds: string[],
): Promise<CreateShareResponse> {
// 确保已登录
if (!httpManager.getToken()) {
await wxLogin();
}
const response = await httpManager.post<CreateShareResponse>('/v1/share', {
title,
levelIds,
});
if (response.success && response.data) {
console.log('分享创建成功:', response.data);
return response.data;
} else {
throw new Error(response.message || '创建分享失败');
}
}
// 使用示例
const levelIds = [
'level_1',
'level_2',
'level_3',
'level_4',
'level_5',
'level_6',
];
const share = await createShare('一起来挑战!', levelIds);
console.log('分享码:', share.shareCode);
// 生成分享链接: `https://your-game.com/invite?code=${share.shareCode}`
4. 加入分享
interface LevelData {
id: string;
level: number;
image1Url: string;
image1Description: string | null;
image2Url: string;
image2Description: string | null;
answer: string;
punchline: string | null;
hint1: string | null;
hint2: string | null;
hint3: string | null;
sortOrder: number;
}
interface JoinShareResponse {
shareCode: string;
title: string;
levels: LevelData[];
}
async function joinShare(shareCode: string): Promise<JoinShareResponse> {
// 确保已登录
if (!httpManager.getToken()) {
await wxLogin();
}
const response = await httpManager.post<JoinShareResponse>(
`/v1/share/${shareCode}/join`,
);
if (response.success && response.data) {
console.log('加入分享成功:', response.data);
return response.data;
} else {
throw new Error(response.message || '加入分享失败');
}
}
// 使用示例
// 从分享链接中获取分享码
const shareCode = 'abc12345'; // 从 URL 参数解析
const shareData = await joinShare(shareCode);
// 保存关卡数据,开始游戏
5. 获取挑战详情
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. 获取我参与的挑战列表
interface ParticipatedShareItem {
title: string;
participantCount: number;
userRank: number | null;
}
interface ParticipatedShareListResponse {
items: ParticipatedShareItem[];
}
async function getParticipatedShares(): Promise<ParticipatedShareListResponse> {
// 确保已登录
if (!httpManager.getToken()) {
await wxLogin();
}
const response = await httpManager.get<ParticipatedShareListResponse>(
'/v1/share/participated',
);
if (response.success && response.data) {
console.log('我参与的挑战:', response.data.items);
return response.data;
} else {
throw new Error(response.message || '获取我参与的挑战失败');
}
}
// 使用示例
const participatedShares = await getParticipatedShares();
participatedShares.items.forEach((item) => {
console.log(
`${item.title}: ${item.participantCount} 人参与,当前排名 ${
item.userRank ?? '暂未上榜'
}`,
);
});
7. 提交挑战结果
interface SubmitChallengeLevel {
levelId: string;
answer: string;
timeSpent: number;
}
interface SubmittedLevelResult extends LevelData {
submittedAnswer: string;
timeSpent: number;
isCorrect: boolean;
timeLimit: number | null;
withinTimeLimit: boolean;
}
interface SubmitChallengeResponse {
shareCode: string;
title: string;
rank: number;
correctCount: number;
levelCount: number;
participantCount: number;
totalTimeSpent: number;
levels: SubmittedLevelResult[];
}
async function submitShareChallenge(
shareCode: string,
levels: SubmitChallengeLevel[],
): Promise<SubmitChallengeResponse> {
// 确保已登录
if (!httpManager.getToken()) {
await wxLogin();
}
const response = await httpManager.post<SubmitChallengeResponse>(
`/v1/share/${shareCode}/submit`,
{ levels },
);
if (response.success && response.data) {
console.log('挑战结果提交成功:', response.data);
return response.data;
} else {
throw new Error(response.message || '挑战结果提交失败');
}
}
// 使用示例
async function onChallengeFinished() {
const result = await submitShareChallenge(this.shareCode, [
{ levelId: 'level_001', answer: '答案1', timeSpent: 30 },
{ levelId: 'level_002', answer: '答案2', timeSpent: 45 },
{ levelId: 'level_003', answer: '', timeSpent: 60 },
{ levelId: 'level_004', answer: '答案4', timeSpent: 20 },
{ levelId: 'level_005', answer: '答案5', timeSpent: 15 },
{ levelId: 'level_006', answer: '答案6', timeSpent: 10 },
]);
console.log(`当前排名: 第 ${result.rank} 名`);
console.log(`答对: ${result.correctCount}/${result.levelCount}`);
console.log(`参与人数: ${result.participantCount}`);
}
8. 启动流程示例
// GameEntry.ts - 游戏入口脚本
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;
@ccclass('GameEntry')
export class GameEntry extends Component {
async start() {
// 1. 检查本地缓存的 token
const cachedToken = wx.getStorageSync('jwt_token');
if (cachedToken) {
httpManager.setToken(cachedToken);
}
// 2. 尝试登录(无论是否有缓存 token)
try {
await wxLogin();
console.log('登录成功');
} catch (error) {
console.error('登录失败:', error);
}
// 3. 检查是否有分享码(从启动参数或 URL 获取)
const launchOptions = wx.getLaunchOptionsSync();
console.log('启动参数:', launchOptions);
// 解析分享码(具体解析方式根据你的分享链接格式)
// if (launchOptions.query && launchOptions.query.code) {
// await joinShare(launchOptions.query.code);
// }
}
}
关卡数据结构
LevelData 完整字段
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 关卡唯一标识 |
| level | number | 关卡序号(1-6) |
| image1Url | string | 图片 1 URL |
| image1Description | string | null | 图片 1 文本说明 |
| image2Url | string | 图片 2 URL |
| image2Description | string | null | 图片 2 文本说明 |
| answer | string | 正确答案 |
| punchline | string | null | 谐音梗说明 |
| hint1 | string | null | 第 1 个提示 |
| hint2 | string | null | 第 2 个提示 |
| hint3 | string | null | 第 3 个提示 |
| sortOrder | number | 全局排序顺序 |
关卡时间限制说明
POST /api/v1/share/{code}/join 返回的关卡数据不包含 timeLimit。挑战结束后调用 POST /api/v1/share/{code}/submit,返回的每关结果会包含:
timeLimit:该关卡时间限制,null表示无限制。withinTimeLimit:用户提交的timeSpent是否不超过timeLimit。isCorrect:用户提交答案是否正确。
注意事项
- Token 有效期:JWT Token 有效期为 7 天,客户端应缓存并在启动时使用
- 重复提交:同一用户重复提交同一分享挑战会覆盖上一份提交结果,并重新计算排名
- 分享码格式:8 位字母数字组合,大小写敏感
- 关卡数量:每次分享挑战固定包含 6 个关卡
- 网络异常:建议在调用接口时显示 loading 状态,并处理网络异常情况
- hint 字段:
hint1/hint2/hint3可能为null,表示该提示未配置 - 时间限制:
timeLimit为null表示该关卡没有时间限制