feat(challenges): 新增挑战功能模块及完整接口实现

- 新增挑战列表、详情、加入/退出、进度上报等 REST 接口
- 定义 Challenge / ChallengeParticipant 数据模型与状态枚举
- 提供排行榜查询与用户排名计算
- 包含接口文档与数据库初始化脚本
This commit is contained in:
richarjiang
2025-09-28 12:02:39 +08:00
parent 8e51994e71
commit 1b7132a325
12 changed files with 1003 additions and 0 deletions

236
docs/challenges-api.md Normal file
View File

@@ -0,0 +1,236 @@
# 挑战功能接口文档
> 所有接口均需携带 `Authorization: Bearer <token>`,鉴权方式与现有用户体系一致。
> 基础路径:`/challenges`
## 数据模型概述
### Challenge
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `string` | 挑战唯一标识 |
| `title` | `string` | 挑战名称 |
| `image` | `string` | 挑战展示图 URL |
| `periodLabel` | `string` | 可选展示周期文案如「21 天计划」) |
| `durationLabel` | `string` | 必填,持续时间描述 |
| `requirementLabel` | `string` | 必填,参与要求文案 |
| `status` | `"upcoming" \| "ongoing" \| "expired"` | 由服务端根据时间自动计算 |
| `participantsCount` | `number` | 当前参与人数(仅统计 active 状态) |
| `rankingDescription` | `string` | 可选,排行榜说明 |
| `highlightTitle` | `string` | 高亮标题 |
| `highlightSubtitle` | `string` | 高亮副标题 |
| `ctaLabel` | `string` | CTA 按钮文案 |
| `progress` | `ChallengeProgress` | 可选,仅当当前用户已加入时返回 |
| `isJoined` | `boolean` | 当前用户是否已加入 |
### ChallengeProgress
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `completed` | `number` | 已完成进度值 |
| `target` | `number` | 总目标值 |
| `remaining` | `number` | 剩余进度 |
| `badge` | `string` | 当前进度徽章文案 |
| `subtitle` | `string` | 可选,补充提示文案 |
### RankingItem
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `string` | 用户 ID |
| `name` | `string` | 昵称 |
| `avatar` | `string` | 头像 URL |
| `metric` | `string` | 排行榜展示文案(如 `5/21天` |
| `badge` | `string` | 可选,名次勋章(`gold`/`silver`/`bronze` |
---
## 1. 获取挑战列表
- **Method / Path**`GET /challenges`
- **描述**:获取当前所有挑战(全局共享),按开始时间升序排序。
### 请求参数
无额外 query 参数。
### 响应示例
```json
{
"code": 0,
"message": "获取挑战列表成功",
"data": [
{
"id": "f27c9a5d-8e53-4ba8-b8df-3c843f0241d2",
"title": "21 天核心燃脂计划",
"image": "https://cdn.example.com/challenges/core-21.png",
"periodLabel": "21 天",
"durationLabel": "持续 21 天",
"requirementLabel": "每日完成 1 次训练",
"status": "ongoing",
"startAt": "2024-03-01T00:00:00.000Z",
"endAt": "2024-03-21T23:59:59.000Z",
"participantsCount": 1287,
"rankingDescription": "坚持天数排行榜",
"highlightTitle": "一起塑造强壮核心",
"highlightSubtitle": "与全球用户共同挑战",
"ctaLabel": "立即加入挑战",
"progress": {
"completed": 5,
"target": 21,
"remaining": 16,
"badge": "已坚持 5天",
"subtitle": "还差 16天"
},
"isJoined": true
}
]
}
```
---
## 2. 获取挑战详情
- **Method / Path**`GET /challenges/{id}`
- **描述**:获取单个挑战的详细信息及排行榜。
### 路径参数
| 名称 | 必填 | 说明 |
| --- | --- | --- |
| `id` | 是 | 挑战 ID |
### 响应示例
```json
{
"code": 0,
"message": "获取挑战详情成功",
"data": {
"id": "f27c9a5d-8e53-4ba8-b8df-3c843f0241d2",
"title": "21 天核心燃脂计划",
"image": "https://cdn.example.com/challenges/core-21.png",
"periodLabel": "21 天",
"durationLabel": "持续 21 天",
"requirementLabel": "每日完成 1 次训练",
"summary": "21 天集中强化腹部及核心肌群,帮助塑形与燃脂。",
"rankingDescription": "坚持天数排行榜",
"highlightTitle": "连赢 7 天即可获得限量徽章",
"highlightSubtitle": "邀请好友并肩作战",
"ctaLabel": "立即加入挑战",
"participantsCount": 1287,
"progress": {
"completed": 5,
"target": 21,
"remaining": 16,
"badge": "已坚持 5天",
"subtitle": "还差 16天"
},
"rankings": [
{
"id": "user-001",
"name": "Alexa",
"avatar": "https://cdn.example.com/users/user-001.png",
"metric": "15/21天",
"badge": "gold"
},
{
"id": "user-002",
"name": "Ella",
"avatar": null,
"metric": "13/21天",
"badge": "silver"
}
],
"userRank": 57
}
}
```
---
## 3. 加入挑战
- **Method / Path**`POST /challenges/{id}/join`
- **描述**:当前用户加入挑战。若已加入会返回冲突错误。
### 响应示例
```json
{
"code": 0,
"message": "加入挑战成功",
"data": {
"completed": 0,
"target": 21,
"remaining": 21,
"badge": "已坚持 0天"
}
}
```
### 可能错误
- `404`:挑战不存在
- `400`:挑战已过期
- `409`:用户已加入或已完成(需先退出再加入)
---
## 4. 退出挑战
- **Method / Path**`POST /challenges/{id}/leave`
- **描述**:用户退出挑战,之后不再计入排行榜。可重新加入恢复进度(将重置为 0
### 响应示例
```json
{
"code": 0,
"message": "退出挑战成功",
"data": true
}
```
### 可能错误
- `404`:用户尚未加入或挑战不存在
---
## 5. 上报挑战进度
- **Method / Path**`POST /challenges/{id}/progress`
- **描述**:用户完成一次进度上报。默认增量 `1`,也可传入自定义增量,服务端会控制不超过目标值。
### 请求体
| 字段 | 类型 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `increment` | `number` | 否 | `1` | 本次增加的进度值,必须大于等于 1 |
```json
{
"increment": 2
}
```
### 响应示例
```json
{
"code": 0,
"message": "进度更新成功",
"data": {
"completed": 7,
"target": 21,
"remaining": 14,
"badge": "已坚持 7天",
"subtitle": "还差 14天"
}
}
```
### 可能错误
- `404`:挑战不存在或用户未加入
- `400`:挑战未开始 / 已过期 / 进度增量非法
---
## 错误码说明
| `code` | `message` | 场景 |
| --- | --- | --- |
| `0` | `success`/具体文案 | 请求成功 |
| `1` | 错误描述 | 业务异常,例如未加入、挑战已过期等 |
---
## 接入建议
- 列表接口可做缓存(例如 10 分钟),但需结合挑战状态实时变更。
- 排行榜为前 10 名,客户端可在详情页展示,并根据 `userRank` 显示用户当前排名。
- 进度上报建议结合业务埋点,确保重复提交时可处理幂等性(服务端会封顶到目标值)。