feat: 支持新的关卡数据结构
This commit is contained in:
291
docs/api-changelog-v1.1.0.md
Normal file
291
docs/api-changelog-v1.1.0.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# MemeMind API 变更文档 — 双图关卡 & 体力上限调整
|
||||
|
||||
> **版本**:v1.1.0
|
||||
> **日期**:2026-04-19
|
||||
> **影响范围**:关卡列表、进入关卡、分享挑战、体力系统
|
||||
> **兼容性**:⚠️ Breaking Change — 客户端必须适配后方可上线
|
||||
|
||||
---
|
||||
|
||||
## 一、变更概览
|
||||
|
||||
| 变更项 | 旧值 | 新值 |
|
||||
|--------|------|------|
|
||||
| 关卡图片数量 | 1 张(`imageUrl`) | 2 张(`image1Url` + `image2Url`) |
|
||||
| 图片文本说明 | 无 | 每张图片各有一个 `description` 字段 |
|
||||
| 谐音梗说明 | 无 | 新增 `punchline` 字段 |
|
||||
| 体力上限 | 5 | **50** |
|
||||
| 新用户默认体力 | 5 | **50** |
|
||||
| 体力恢复速率 | 每 10 分钟 1 点 | **不变** |
|
||||
|
||||
---
|
||||
|
||||
## 二、体力系统变更
|
||||
|
||||
### StaminaInfo 结构(不变,数值范围扩大)
|
||||
|
||||
```typescript
|
||||
interface StaminaInfo {
|
||||
current: number; // 0 ~ 50(原 0 ~ 5)
|
||||
max: number; // 固定 50(原 5)
|
||||
nextRecoverAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**客户端注意**:
|
||||
- 体力 UI 需要适配 0–50 的显示范围
|
||||
- 恢复速率不变,满体力恢复时间从 50 分钟变为 500 分钟(约 8.3 小时)
|
||||
- 进入关卡仍消耗 1 点体力,已通关关卡仍免费
|
||||
|
||||
---
|
||||
|
||||
## 三、接口字段变更
|
||||
|
||||
### 3.1 `GET /api/v1/levels` — 获取关卡列表
|
||||
|
||||
**删除字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ~~`imageUrl`~~ | ~~string~~ | 已删除,替换为下方双图字段 |
|
||||
|
||||
**新增字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `image1Url` | string | 图片1 URL |
|
||||
| `image1Description` | string \| null | 图片1 文本说明 |
|
||||
| `image2Url` | string | 图片2 URL |
|
||||
| `image2Description` | string \| null | 图片2 文本说明 |
|
||||
| `punchline` | string \| null | 谐音梗说明(**仅通关后返回**,未通关为 null) |
|
||||
|
||||
**完整响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"levels": [
|
||||
{
|
||||
"id": "level_001",
|
||||
"level": 1,
|
||||
"image1Url": "https://cdn.example.com/levels/001_1.png",
|
||||
"image1Description": "一只猫在看鱼",
|
||||
"image2Url": "https://cdn.example.com/levels/001_2.png",
|
||||
"image2Description": "一条鱼在飞",
|
||||
"answer": "猫和鱼",
|
||||
"punchline": "谐音梗:鱼跃龙门 → 鱼越猫门",
|
||||
"hint1": "这是一个经典的...",
|
||||
"hint2": "和某个动物有关",
|
||||
"hint3": null,
|
||||
"completed": true,
|
||||
"timeSpent": 45
|
||||
},
|
||||
{
|
||||
"id": "level_002",
|
||||
"level": 2,
|
||||
"image1Url": "https://cdn.example.com/levels/002_1.png",
|
||||
"image1Description": "一个人在走路",
|
||||
"image2Url": "https://cdn.example.com/levels/002_2.png",
|
||||
"image2Description": "一辆车在跑",
|
||||
"answer": null,
|
||||
"punchline": null,
|
||||
"hint1": null,
|
||||
"hint2": null,
|
||||
"hint3": null,
|
||||
"completed": false,
|
||||
"timeSpent": null
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
},
|
||||
"message": null,
|
||||
"timestamp": "2026-04-19T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
> **可见性规则**:`answer`、`punchline`、`hint1`、`hint2`、`hint3` 仅在 `completed: true` 时返回,未通关均为 `null`。`image1Url`、`image1Description`、`image2Url`、`image2Description` 始终返回。
|
||||
|
||||
---
|
||||
|
||||
### 3.2 `POST /api/v1/levels/{id}/enter` — 进入关卡
|
||||
|
||||
**删除字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ~~`imageUrl`~~ | ~~string~~ | 已删除 |
|
||||
|
||||
**新增字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `image1Url` | string | 图片1 URL |
|
||||
| `image1Description` | string \| null | 图片1 文本说明 |
|
||||
| `image2Url` | string | 图片2 URL |
|
||||
| `image2Description` | string \| null | 图片2 文本说明 |
|
||||
| `punchline` | string \| null | 谐音梗说明 |
|
||||
|
||||
**完整响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "level_002",
|
||||
"level": 2,
|
||||
"image1Url": "https://cdn.example.com/levels/002_1.png",
|
||||
"image1Description": "一个人在走路",
|
||||
"image2Url": "https://cdn.example.com/levels/002_2.png",
|
||||
"image2Description": "一辆车在跑",
|
||||
"answer": "人车赛跑",
|
||||
"punchline": "谐音梗:车水马龙 → 车水人龙",
|
||||
"hint1": "第一个线索",
|
||||
"hint2": "第二个线索",
|
||||
"hint3": null,
|
||||
"stamina": {
|
||||
"current": 47,
|
||||
"max": 50,
|
||||
"nextRecoverAt": "2026-04-19T12:10:00.000Z"
|
||||
}
|
||||
},
|
||||
"message": null,
|
||||
"timestamp": "2026-04-19T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:进入关卡时 `answer` 和 `punchline` 始终返回(无论是否通关),因为用户已消耗体力进入。
|
||||
|
||||
---
|
||||
|
||||
### 3.3 `POST /api/v1/share/{shareCode}/join` — 加入分享挑战
|
||||
|
||||
分享挑战中的关卡数据同步变更。
|
||||
|
||||
**删除字段**(`levels[]` 中每个关卡):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ~~`imageUrl`~~ | ~~string~~ | 已删除 |
|
||||
|
||||
**新增字段**(`levels[]` 中每个关卡):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `image1Url` | string | 图片1 URL |
|
||||
| `image1Description` | string \| null | 图片1 文本说明 |
|
||||
| `image2Url` | string | 图片2 URL |
|
||||
| `image2Description` | string \| null | 图片2 文本说明 |
|
||||
| `punchline` | string \| null | 谐音梗说明 |
|
||||
|
||||
---
|
||||
|
||||
### 3.4 未变更的接口
|
||||
|
||||
以下接口**无任何变更**,客户端无需修改:
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `POST /api/v1/auth/wx-login` | 登录(新用户 stamina 初始为 50,但 login 返回结构不变) |
|
||||
| `GET /api/v1/user/profile` | 用户资料(stamina.max 变为 50,结构不变) |
|
||||
| `GET /api/v1/user/game-data` | 游戏数据(stamina.max 变为 50,结构不变) |
|
||||
| `POST /api/v1/levels/{id}/complete` | 通关上报(结构完全不变) |
|
||||
| `GET /api/v1/game-configs` | 游戏配置(不变) |
|
||||
| `GET /api/v1/game-configs/{key}` | 单个配置(不变) |
|
||||
|
||||
---
|
||||
|
||||
## 四、客户端适配清单
|
||||
|
||||
### 必须修改
|
||||
|
||||
- [ ] 所有使用 `imageUrl` 的地方改为 `image1Url` + `image2Url`
|
||||
- [ ] 关卡详情页展示两张图片,每张图片下方展示 `image1Description` / `image2Description`
|
||||
- [ ] 通关后展示 `punchline`(谐音梗说明)
|
||||
- [ ] 体力 UI 适配 0–50 范围(进度条、数字显示等)
|
||||
- [ ] 更新 TypeScript 接口定义(见下方)
|
||||
|
||||
### 建议修改
|
||||
|
||||
- [ ] 体力恢复倒计时逻辑无需修改(恢复速率不变)
|
||||
- [ ] `punchline` 为 `null` 时不展示(未配置谐音梗的关卡)
|
||||
- [ ] `image2Url` 为空字符串时做兜底处理(历史关卡可能尚未配置第二张图)
|
||||
|
||||
---
|
||||
|
||||
## 五、客户端 TypeScript 接口定义
|
||||
|
||||
直接复制替换旧接口:
|
||||
|
||||
```typescript
|
||||
/** 关卡列表项 */
|
||||
interface LevelListItem {
|
||||
id: string;
|
||||
level: number;
|
||||
image1Url: string;
|
||||
image1Description: string | null;
|
||||
image2Url: string;
|
||||
image2Description: string | null;
|
||||
answer: string | null;
|
||||
punchline: string | null;
|
||||
hint1: string | null;
|
||||
hint2: string | null;
|
||||
hint3: string | null;
|
||||
completed: boolean;
|
||||
timeSpent: number | null;
|
||||
}
|
||||
|
||||
/** 进入关卡响应 */
|
||||
interface EnterLevelResponse {
|
||||
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;
|
||||
stamina: StaminaInfo;
|
||||
}
|
||||
|
||||
/** 体力信息(结构不变,数值范围 0-50) */
|
||||
interface StaminaInfo {
|
||||
current: number;
|
||||
max: number; // 50
|
||||
nextRecoverAt: string | null;
|
||||
}
|
||||
|
||||
/** 分享关卡 */
|
||||
interface ShareLevel {
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、字段映射速查表
|
||||
|
||||
方便全局搜索替换:
|
||||
|
||||
| 旧字段 | 新字段 | 备注 |
|
||||
|--------|--------|------|
|
||||
| `imageUrl` | `image1Url` | 原图片字段,直接重命名 |
|
||||
| — | `image1Description` | 新增,图片1 说明文字 |
|
||||
| — | `image2Url` | 新增,第二张图片 |
|
||||
| — | `image2Description` | 新增,图片2 说明文字 |
|
||||
| — | `punchline` | 新增,谐音梗说明 |
|
||||
| `stamina.max = 5` | `stamina.max = 50` | 数值变更 |
|
||||
@@ -91,8 +91,8 @@ Authorization: Bearer <token>
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 默认体力 | 5(新用户注册时) |
|
||||
| 上限 | 5 |
|
||||
| 默认体力 | 50(新用户注册时) |
|
||||
| 上限 | 50 |
|
||||
| 恢复速度 | 每 **10 分钟** 恢复 1 点 |
|
||||
| 消耗 | 进入**未通关**关卡时消耗 1 点 |
|
||||
| 已通关关卡 | 再次进入不消耗体力 |
|
||||
@@ -104,7 +104,7 @@ Authorization: Bearer <token>
|
||||
```typescript
|
||||
interface StaminaInfo {
|
||||
current: number; // 当前体力值(已计算恢复)
|
||||
max: number; // 体力上限,固定为 5
|
||||
max: number; // 体力上限,固定为 50
|
||||
nextRecoverAt: string | null; // 下一点体力恢复的时间(ISO 8601),满体力时为 null
|
||||
}
|
||||
```
|
||||
@@ -113,8 +113,8 @@ interface StaminaInfo {
|
||||
|
||||
```json
|
||||
{
|
||||
"current": 3,
|
||||
"max": 5,
|
||||
"current": 45,
|
||||
"max": 50,
|
||||
"nextRecoverAt": "2026-04-10T12:10:00.000Z"
|
||||
}
|
||||
```
|
||||
@@ -168,7 +168,7 @@ interface StaminaInfo {
|
||||
"user": {
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"nickname": null,
|
||||
"stamina": 5
|
||||
"stamina": 50
|
||||
}
|
||||
},
|
||||
"message": null,
|
||||
@@ -211,8 +211,8 @@ interface StaminaInfo {
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"nickname": null,
|
||||
"stamina": {
|
||||
"current": 3,
|
||||
"max": 5,
|
||||
"current": 45,
|
||||
"max": 50,
|
||||
"nextRecoverAt": "2026-04-10T12:10:00.000Z"
|
||||
}
|
||||
},
|
||||
@@ -258,8 +258,8 @@ interface StaminaInfo {
|
||||
"user": {
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"stamina": {
|
||||
"current": 5,
|
||||
"max": 5,
|
||||
"current": 50,
|
||||
"max": 50,
|
||||
"nextRecoverAt": null
|
||||
}
|
||||
},
|
||||
@@ -278,7 +278,7 @@ interface StaminaInfo {
|
||||
|
||||
### 4. 获取关卡列表
|
||||
|
||||
获取所有关卡列表。**已通关的关卡**返回答案和线索,**未通关的关卡**不返回敏感数据。
|
||||
获取所有关卡列表。**已通关的关卡**返回答案、谐音梗说明和线索,**未通关的关卡**不返回敏感数据。
|
||||
|
||||
**接口地址**:`GET /api/v1/levels`
|
||||
|
||||
@@ -297,8 +297,12 @@ interface StaminaInfo {
|
||||
interface LevelListItem {
|
||||
id: string; // 关卡 ID
|
||||
level: number; // 关卡编号(从 1 开始)
|
||||
imageUrl: string; // 关卡图片 URL
|
||||
image1Url: string; // 图片1 URL
|
||||
image1Description: string | null; // 图片1 文本说明
|
||||
image2Url: string; // 图片2 URL
|
||||
image2Description: string | null; // 图片2 文本说明
|
||||
answer: string | null; // 答案(仅已通关时返回,否则 null)
|
||||
punchline: string | null; // 谐音梗说明(仅已通关时返回,否则 null)
|
||||
hint1: string | null; // 线索1(仅已通关时返回,否则 null)
|
||||
hint2: string | null; // 线索2(仅已通关时返回,否则 null)
|
||||
hint3: string | null; // 线索3(仅已通关时返回,否则 null)
|
||||
@@ -317,8 +321,12 @@ interface LevelListItem {
|
||||
{
|
||||
"id": "level_001",
|
||||
"level": 1,
|
||||
"imageUrl": "https://cdn.example.com/levels/001.png",
|
||||
"image1Url": "https://cdn.example.com/levels/001_1.png",
|
||||
"image1Description": "一只猫在看鱼",
|
||||
"image2Url": "https://cdn.example.com/levels/001_2.png",
|
||||
"image2Description": "一条鱼在飞",
|
||||
"answer": "梗答案",
|
||||
"punchline": "谐音梗:鱼和猫的故事",
|
||||
"hint1": "这是一个经典的...",
|
||||
"hint2": "和某个明星有关",
|
||||
"hint3": null,
|
||||
@@ -328,8 +336,12 @@ interface LevelListItem {
|
||||
{
|
||||
"id": "level_002",
|
||||
"level": 2,
|
||||
"imageUrl": "https://cdn.example.com/levels/002.png",
|
||||
"image1Url": "https://cdn.example.com/levels/002_1.png",
|
||||
"image1Description": "一个人在走路",
|
||||
"image2Url": "https://cdn.example.com/levels/002_2.png",
|
||||
"image2Description": "一辆车在跑",
|
||||
"answer": null,
|
||||
"punchline": null,
|
||||
"hint1": null,
|
||||
"hint2": null,
|
||||
"hint3": null,
|
||||
@@ -346,8 +358,9 @@ interface LevelListItem {
|
||||
|
||||
**客户端使用说明**:
|
||||
- 关卡选择页面使用此接口获取关卡列表
|
||||
- 每个关卡有两张图片(`image1Url`、`image2Url`)和对应的文本说明
|
||||
- 根据 `completed` 字段展示不同的 UI 状态(已通关/未通关)
|
||||
- 未通关关卡的 `answer`、`hint1`、`hint2`、`hint3` 均为 `null`,**客户端不应缓存这些字段**
|
||||
- 未通关关卡的 `answer`、`punchline`、`hint1`、`hint2`、`hint3` 均为 `null`,**客户端不应缓存这些字段**
|
||||
|
||||
---
|
||||
|
||||
@@ -373,8 +386,12 @@ interface LevelListItem {
|
||||
{
|
||||
id: string;
|
||||
level: number;
|
||||
imageUrl: string;
|
||||
image1Url: string;
|
||||
image1Description: string | null;
|
||||
image2Url: string;
|
||||
image2Description: string | null;
|
||||
answer: string;
|
||||
punchline: string | null;
|
||||
hint1: string | null;
|
||||
hint2: string | null;
|
||||
hint3: string | null;
|
||||
@@ -390,14 +407,18 @@ interface LevelListItem {
|
||||
"data": {
|
||||
"id": "level_002",
|
||||
"level": 2,
|
||||
"imageUrl": "https://cdn.example.com/levels/002.png",
|
||||
"image1Url": "https://cdn.example.com/levels/002_1.png",
|
||||
"image1Description": "一个人在走路",
|
||||
"image2Url": "https://cdn.example.com/levels/002_2.png",
|
||||
"image2Description": "一辆车在跑",
|
||||
"answer": "这是答案",
|
||||
"punchline": "谐音梗:走和跑的故事",
|
||||
"hint1": "第一个线索",
|
||||
"hint2": "第二个线索",
|
||||
"hint3": null,
|
||||
"stamina": {
|
||||
"current": 2,
|
||||
"max": 5,
|
||||
"current": 47,
|
||||
"max": 50,
|
||||
"nextRecoverAt": "2026-04-10T12:10:00.000Z"
|
||||
}
|
||||
},
|
||||
@@ -658,7 +679,9 @@ interface GameState {
|
||||
currentLevel: { // 当前正在玩的关卡
|
||||
id: string;
|
||||
answer: string;
|
||||
punchline: string | null;
|
||||
hints: (string | null)[];
|
||||
images: { url: string; description: string | null }[];
|
||||
startTime: number; // 开始时间戳,用于计算 timeSpent
|
||||
} | null;
|
||||
}
|
||||
@@ -823,8 +846,12 @@ async function loadGameData(): Promise<GameData> {
|
||||
interface LevelListItem {
|
||||
id: string;
|
||||
level: number;
|
||||
imageUrl: string;
|
||||
image1Url: string;
|
||||
image1Description: string | null;
|
||||
image2Url: string;
|
||||
image2Description: string | null;
|
||||
answer: string | null;
|
||||
punchline: string | null;
|
||||
hint1: string | null;
|
||||
hint2: string | null;
|
||||
hint3: string | null;
|
||||
@@ -847,8 +874,12 @@ async function getLevels(): Promise<LevelListItem[]> {
|
||||
interface EnterLevelResponse {
|
||||
id: string;
|
||||
level: number;
|
||||
imageUrl: string;
|
||||
image1Url: string;
|
||||
image1Description: string | null;
|
||||
image2Url: string;
|
||||
image2Description: string | null;
|
||||
answer: string;
|
||||
punchline: string | null;
|
||||
hint1: string | null;
|
||||
hint2: string | null;
|
||||
hint3: string | null;
|
||||
@@ -969,4 +1000,6 @@ export class GameEntry extends Component {
|
||||
4. **已通关关卡免费进入**:已通关关卡再次进入不消耗体力
|
||||
5. **通关上报仅限成功**:只在用户答对后调用 `complete` 接口,答错不需要上报
|
||||
6. **hint 字段**:`hint1/hint2/hint3` 可能为 `null`,表示该线索未配置
|
||||
7. **网络异常处理**:建议所有接口调用加 loading 状态,并处理 401(重新登录)和网络错误
|
||||
7. **punchline 字段**:谐音梗说明,仅已通关时返回,未通关时为 `null`
|
||||
8. **双图结构**:每个关卡有两张图片(`image1Url`、`image2Url`),分别有对应的文本说明
|
||||
9. **网络异常处理**:建议所有接口调用加 loading 状态,并处理 401(重新登录)和网络错误
|
||||
|
||||
Reference in New Issue
Block a user