完成内容: - 5个完整的UI页面(登录、评分列表、评分详情、多场地列表、修改评分) - 完整的Mock数据展示 - 完整的业务逻辑实现 - 文档体系建立(2000+行文档) 文档包含: - 项目概述.md - 页面功能说明.md - API接口设计.md (17个接口) - 数据结构设计.md (17个接口定义) - 功能模块划分.md - 后端实现对比报告.md - 数据可行性分析报告.md (95分评估) - 保护Mock版本的实施方案.md (4层保护机制) - API对接完成度检查报告.md 此版本为Mock原型版本,所有UI功能完整,数据为硬编码Mock数据。
1108 lines
23 KiB
Markdown
1108 lines
23 KiB
Markdown
# API接口设计文档
|
||
|
||
## 接口概述
|
||
|
||
本文档定义了武术评分系统需要对接的所有后端API接口。当前项目为前端原型,所有数据均为Mock数据,需要按照本文档定义的接口规范进行后端开发和对接。
|
||
|
||
## 接口规范
|
||
|
||
### 基础信息
|
||
|
||
- **协议**: HTTPS
|
||
- **请求格式**: JSON
|
||
- **响应格式**: JSON
|
||
- **字符编码**: UTF-8
|
||
- **认证方式**: JWT Token
|
||
|
||
### 通用请求头
|
||
|
||
```
|
||
Content-Type: application/json
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
### 通用响应格式
|
||
|
||
```json
|
||
{
|
||
"code": 200, // 状态码
|
||
"message": "成功", // 提示信息
|
||
"data": {} // 业务数据
|
||
}
|
||
```
|
||
|
||
### 状态码说明
|
||
|
||
| 状态码 | 说明 |
|
||
|-------|------|
|
||
| 200 | 请求成功 |
|
||
| 400 | 请求参数错误 |
|
||
| 401 | 未授权/Token失效 |
|
||
| 403 | 权限不足 |
|
||
| 404 | 资源不存在 |
|
||
| 500 | 服务器错误 |
|
||
|
||
---
|
||
|
||
## 一、认证模块
|
||
|
||
### 1.1 用户登录
|
||
|
||
**接口地址**: `POST /api/auth/login`
|
||
|
||
**功能描述**: 通过比赛编码和邀请码进行登录认证
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"matchCode": "string", // 比赛编码,必填
|
||
"inviteCode": "string" // 邀请码 (pub/admin),必填
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "登录成功",
|
||
"data": {
|
||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // JWT Token
|
||
"userRole": "pub", // 用户角色: pub-普通评委, admin-裁判长
|
||
"matchId": "match_001", // 比赛ID
|
||
"matchName": "2025年全国武术散打锦标赛", // 比赛名称
|
||
"matchTime": "2025-06-25T09:00:00", // 比赛时间
|
||
"judgeId": "judge_001", // 评委ID
|
||
"judgeName": "张三", // 评委姓名
|
||
"venueId": "venue_001", // 场地ID (普通评委)
|
||
"venueName": "第一场地" // 场地名称 (普通评委)
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**:
|
||
|
||
```json
|
||
{
|
||
"code": 401,
|
||
"message": "邀请码错误或已失效",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 1.2 退出登录
|
||
|
||
**接口地址**: `POST /api/auth/logout`
|
||
|
||
**功能描述**: 用户退出登录,清除Token
|
||
|
||
**请求参数**: 无
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "退出成功",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 1.3 Token验证
|
||
|
||
**接口地址**: `GET /api/auth/verify`
|
||
|
||
**功能描述**: 验证Token是否有效
|
||
|
||
**请求参数**: 无(通过Header传递Token)
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "Token有效",
|
||
"data": {
|
||
"valid": true,
|
||
"expiresIn": 3600 // 剩余有效时间(秒)
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 二、比赛信息模块
|
||
|
||
### 2.1 获取比赛详情
|
||
|
||
**接口地址**: `GET /api/matches/{matchId}`
|
||
|
||
**功能描述**: 获取指定比赛的详细信息
|
||
|
||
**路径参数**:
|
||
- `matchId`: 比赛ID
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"matchId": "match_001",
|
||
"matchName": "2025年全国武术散打锦标赛暨第十七届世界武术锦标赛选拔赛",
|
||
"matchTime": "2025-06-25T09:00:00",
|
||
"matchEndTime": "2025-06-25T18:00:00",
|
||
"location": "北京体育馆",
|
||
"status": "ongoing", // ongoing-进行中, finished-已结束, upcoming-未开始
|
||
"description": "比赛说明",
|
||
"venueCount": 5, // 场地数量
|
||
"projectCount": 8, // 项目数量
|
||
"athleteCount": 150 // 参赛选手数量
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、场地管理模块
|
||
|
||
### 3.1 获取场地列表
|
||
|
||
**接口地址**: `GET /api/venues`
|
||
|
||
**功能描述**: 获取比赛的所有场地列表(裁判长专用)
|
||
|
||
**请求参数**:
|
||
```
|
||
matchId: string // 比赛ID,必填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": [
|
||
{
|
||
"venueId": "venue_001",
|
||
"venueName": "第一场地",
|
||
"order": 1, // 排序顺序
|
||
"athleteCount": 30, // 选手数量
|
||
"scoredCount": 25, // 已评分数量
|
||
"status": "active" // active-进行中, completed-已完成
|
||
},
|
||
{
|
||
"venueId": "venue_002",
|
||
"venueName": "第二场地",
|
||
"order": 2,
|
||
"athleteCount": 30,
|
||
"scoredCount": 28,
|
||
"status": "active"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、项目管理模块
|
||
|
||
### 4.1 获取项目列表
|
||
|
||
**接口地址**: `GET /api/projects`
|
||
|
||
**功能描述**: 获取比赛的所有项目列表
|
||
|
||
**请求参数**:
|
||
```
|
||
matchId: string // 比赛ID,必填
|
||
venueId: string // 场地ID,选填(裁判长查询所有场地时不传)
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": [
|
||
{
|
||
"projectId": "project_001",
|
||
"projectName": "女子组长拳",
|
||
"category": "套路", // 项目类别
|
||
"order": 1, // 排序顺序
|
||
"athleteCount": 15, // 参赛选手数
|
||
"minScore": 5.0, // 最低分
|
||
"maxScore": 10.0, // 最高分
|
||
"status": "ongoing" // ongoing-进行中, completed-已完成
|
||
},
|
||
{
|
||
"projectId": "project_002",
|
||
"projectName": "男子组陈氏太极拳",
|
||
"category": "太极拳",
|
||
"order": 2,
|
||
"athleteCount": 20,
|
||
"minScore": 5.0,
|
||
"maxScore": 10.0,
|
||
"status": "ongoing"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 4.2 获取项目详情
|
||
|
||
**接口地址**: `GET /api/projects/{projectId}`
|
||
|
||
**功能描述**: 获取指定项目的详细信息
|
||
|
||
**路径参数**:
|
||
- `projectId`: 项目ID
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"projectId": "project_001",
|
||
"projectName": "女子组长拳",
|
||
"category": "套路",
|
||
"description": "项目说明",
|
||
"rules": "评分规则",
|
||
"minScore": 5.0,
|
||
"maxScore": 10.0,
|
||
"scoreStep": 0.001, // 评分步进值
|
||
"athleteCount": 15,
|
||
"deductions": [ // 扣分项列表
|
||
{
|
||
"deductionId": "deduction_001",
|
||
"text": "起势不稳",
|
||
"score": -0.1
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、选手管理模块
|
||
|
||
### 5.1 获取选手列表(普通评委)
|
||
|
||
**接口地址**: `GET /api/athletes`
|
||
|
||
**功能描述**: 普通评委获取分配给自己的选手列表
|
||
|
||
**请求参数**:
|
||
```
|
||
matchId: string // 比赛ID,必填
|
||
venueId: string // 场地ID,必填
|
||
projectId: string // 项目ID,必填
|
||
judgeId: string // 评委ID,必填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"total": 30, // 总选手数
|
||
"scored": 25, // 已评分数
|
||
"unscored": 5, // 未评分数
|
||
"athletes": [
|
||
{
|
||
"athleteId": "athlete_001",
|
||
"name": "张三",
|
||
"gender": "male", // male-男, female-女
|
||
"idCard": "123456789000000000",
|
||
"team": "少林寺武术大学院",
|
||
"number": "123-4567898275", // 比赛编号
|
||
"order": 1, // 出场顺序
|
||
"myScore": 8.906, // 我的评分(已评分时有值)
|
||
"totalScore": 8.907, // 总分(已评分时有值)
|
||
"scored": true, // 是否已评分
|
||
"scoreTime": "2025-06-25T09:15:00", // 评分时间
|
||
"status": "finished" // waiting-等待, performing-表演中, finished-完成
|
||
},
|
||
{
|
||
"athleteId": "athlete_002",
|
||
"name": "李四",
|
||
"gender": "male",
|
||
"idCard": "123456789000000001",
|
||
"team": "武当山武术学院",
|
||
"number": "123-4567898276",
|
||
"order": 2,
|
||
"myScore": null,
|
||
"totalScore": null,
|
||
"scored": false,
|
||
"scoreTime": null,
|
||
"status": "waiting"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 获取选手列表(裁判长)
|
||
|
||
**接口地址**: `GET /api/athletes/all`
|
||
|
||
**功能描述**: 裁判长获取所有场地和项目的选手列表
|
||
|
||
**请求参数**:
|
||
```
|
||
matchId: string // 比赛ID,必填
|
||
venueId: string // 场地ID,选填
|
||
projectId: string // 项目ID,选填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"total": 150,
|
||
"scored": 120,
|
||
"athletes": [
|
||
{
|
||
"athleteId": "athlete_001",
|
||
"name": "张三",
|
||
"gender": "male",
|
||
"idCard": "123456789000000000",
|
||
"team": "少林寺武术大学院",
|
||
"number": "123-4567898275",
|
||
"order": 1,
|
||
"venueId": "venue_001",
|
||
"venueName": "第一场地",
|
||
"projectId": "project_001",
|
||
"projectName": "女子组长拳",
|
||
"totalScore": 8.907, // 总分
|
||
"judgeCount": 6, // 评分评委数
|
||
"totalJudges": 6, // 总评委数
|
||
"canModify": true, // 是否可修改
|
||
"status": "finished"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 获取选手详情
|
||
|
||
**接口地址**: `GET /api/athletes/{athleteId}`
|
||
|
||
**功能描述**: 获取选手详细信息
|
||
|
||
**路径参数**:
|
||
- `athleteId`: 选手ID
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"athleteId": "athlete_001",
|
||
"name": "张三",
|
||
"gender": "male",
|
||
"age": 25,
|
||
"idCard": "123456789000000000",
|
||
"team": "少林寺武术大学院",
|
||
"number": "123-4567898275",
|
||
"order": 1,
|
||
"projectId": "project_001",
|
||
"projectName": "女子组长拳",
|
||
"venueId": "venue_001",
|
||
"venueName": "第一场地",
|
||
"photo": "https://example.com/photo.jpg", // 选手照片URL
|
||
"defaultScore": 8.907, // 默认起评分
|
||
"status": "finished"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、评分管理模块
|
||
|
||
### 6.1 提交评分
|
||
|
||
**接口地址**: `POST /api/scores`
|
||
|
||
**功能描述**: 普通评委提交对选手的评分
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"matchId": "match_001", // 比赛ID,必填
|
||
"athleteId": "athlete_001", // 选手ID,必填
|
||
"judgeId": "judge_001", // 评委ID,必填
|
||
"projectId": "project_001", // 项目ID,必填
|
||
"venueId": "venue_001", // 场地ID,必填
|
||
"score": 8.907, // 评分,必填
|
||
"deductions": [ // 扣分项ID列表,选填
|
||
"deduction_001",
|
||
"deduction_003"
|
||
],
|
||
"note": "整体表现良好" // 备注,选填
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "评分提交成功",
|
||
"data": {
|
||
"scoreId": "score_001",
|
||
"athleteId": "athlete_001",
|
||
"score": 8.907,
|
||
"createTime": "2025-06-25T09:15:00",
|
||
"totalScore": 8.905, // 当前总分(如果有多个评委)
|
||
"judgeCount": 3 // 已评分评委数
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**:
|
||
|
||
```json
|
||
{
|
||
"code": 400,
|
||
"message": "该选手已评分,不可重复提交",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 6.2 获取评分详情
|
||
|
||
**接口地址**: `GET /api/scores/{athleteId}`
|
||
|
||
**功能描述**: 获取选手的所有评分详情(裁判长使用)
|
||
|
||
**路径参数**:
|
||
- `athleteId`: 选手ID
|
||
|
||
**请求参数**:
|
||
```
|
||
judgeId: string // 评委ID,选填(普通评委只能看自己的评分)
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"athleteId": "athlete_001",
|
||
"athleteName": "张三",
|
||
"team": "少林寺武术大学院",
|
||
"number": "123-4567898275",
|
||
"projectName": "女子组长拳",
|
||
"totalScore": 8.907,
|
||
"judgeCount": 6,
|
||
"totalJudges": 6,
|
||
"judgeScores": [
|
||
{
|
||
"scoreId": "score_001",
|
||
"judgeId": "judge_001",
|
||
"judgeName": "欧阳丽娜",
|
||
"score": 8.907,
|
||
"deductions": [
|
||
{
|
||
"deductionId": "deduction_001",
|
||
"text": "起势不稳",
|
||
"score": -0.1
|
||
}
|
||
],
|
||
"note": "整体表现良好",
|
||
"scoreTime": "2025-06-25T09:15:00"
|
||
},
|
||
{
|
||
"scoreId": "score_002",
|
||
"judgeId": "judge_002",
|
||
"judgeName": "张三",
|
||
"score": 8.901,
|
||
"deductions": [],
|
||
"note": "",
|
||
"scoreTime": "2025-06-25T09:16:00"
|
||
}
|
||
],
|
||
"modifications": [
|
||
{
|
||
"modifyId": "modify_001",
|
||
"modifierId": "admin_001",
|
||
"modifierName": "总裁判长",
|
||
"originalScore": 8.907,
|
||
"modifiedScore": 8.910,
|
||
"note": "技术动作优秀,加分",
|
||
"modifyTime": "2025-06-25T10:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 修改评分(裁判长)
|
||
|
||
**接口地址**: `PUT /api/scores/{athleteId}/modify`
|
||
|
||
**功能描述**: 裁判长修改选手的总分
|
||
|
||
**路径参数**:
|
||
- `athleteId`: 选手ID
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"modifierId": "admin_001", // 裁判长ID,必填
|
||
"originalScore": 8.907, // 原始分数,必填
|
||
"modifiedScore": 8.910, // 修改后的分数,必填
|
||
"note": "技术动作优秀,加分" // 修改原因,必填
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "修改成功",
|
||
"data": {
|
||
"modifyId": "modify_001",
|
||
"athleteId": "athlete_001",
|
||
"originalScore": 8.907,
|
||
"modifiedScore": 8.910,
|
||
"modifyTime": "2025-06-25T10:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**:
|
||
|
||
```json
|
||
{
|
||
"code": 403,
|
||
"message": "只有裁判长可以修改评分",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 6.4 获取我的评分记录
|
||
|
||
**接口地址**: `GET /api/scores/my-records`
|
||
|
||
**功能描述**: 获取当前评委的所有评分记录
|
||
|
||
**请求参数**:
|
||
```
|
||
judgeId: string // 评委ID,必填
|
||
matchId: string // 比赛ID,必填
|
||
page: number // 页码,选填,默认1
|
||
pageSize: number // 每页数量,选填,默认20
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"total": 25,
|
||
"page": 1,
|
||
"pageSize": 20,
|
||
"records": [
|
||
{
|
||
"scoreId": "score_001",
|
||
"athleteId": "athlete_001",
|
||
"athleteName": "张三",
|
||
"team": "少林寺武术大学院",
|
||
"projectName": "女子组长拳",
|
||
"score": 8.907,
|
||
"scoreTime": "2025-06-25T09:15:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 七、扣分项管理模块
|
||
|
||
### 7.1 获取扣分项列表
|
||
|
||
**接口地址**: `GET /api/deductions`
|
||
|
||
**功能描述**: 获取指定项目的扣分项列表
|
||
|
||
**请求参数**:
|
||
```
|
||
projectId: string // 项目ID,必填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": [
|
||
{
|
||
"deductionId": "deduction_001",
|
||
"text": "起势不稳",
|
||
"score": -0.1,
|
||
"category": "基础动作",
|
||
"order": 1
|
||
},
|
||
{
|
||
"deductionId": "deduction_002",
|
||
"text": "节奏不准",
|
||
"score": -0.05,
|
||
"category": "节奏控制",
|
||
"order": 2
|
||
},
|
||
{
|
||
"deductionId": "deduction_003",
|
||
"text": "力度不足",
|
||
"score": -0.1,
|
||
"category": "技术要求",
|
||
"order": 3
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、统计分析模块
|
||
|
||
### 8.1 获取比赛统计
|
||
|
||
**接口地址**: `GET /api/statistics/match/{matchId}`
|
||
|
||
**功能描述**: 获取比赛的整体统计数据(裁判长专用)
|
||
|
||
**路径参数**:
|
||
- `matchId`: 比赛ID
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"matchId": "match_001",
|
||
"totalAthletes": 150, // 总选手数
|
||
"finishedAthletes": 120, // 已完成评分选手数
|
||
"totalJudges": 30, // 总评委数
|
||
"activeJudges": 28, // 活跃评委数
|
||
"totalScores": 720, // 总评分数
|
||
"averageScore": 8.756, // 平均分
|
||
"highestScore": 9.850, // 最高分
|
||
"lowestScore": 7.200, // 最低分
|
||
"venueStats": [ // 各场地统计
|
||
{
|
||
"venueId": "venue_001",
|
||
"venueName": "第一场地",
|
||
"athleteCount": 30,
|
||
"finishedCount": 28,
|
||
"progress": 93.3
|
||
}
|
||
],
|
||
"projectStats": [ // 各项目统计
|
||
{
|
||
"projectId": "project_001",
|
||
"projectName": "女子组长拳",
|
||
"athleteCount": 15,
|
||
"finishedCount": 15,
|
||
"averageScore": 8.650,
|
||
"progress": 100
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 获取评委统计
|
||
|
||
**接口地址**: `GET /api/statistics/judges`
|
||
|
||
**功能描述**: 获取所有评委的评分统计(裁判长专用)
|
||
|
||
**请求参数**:
|
||
```
|
||
matchId: string // 比赛ID,必填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": [
|
||
{
|
||
"judgeId": "judge_001",
|
||
"judgeName": "欧阳丽娜",
|
||
"role": "pub",
|
||
"venueId": "venue_001",
|
||
"venueName": "第一场地",
|
||
"totalScores": 25, // 总评分数
|
||
"averageScore": 8.765, // 平均给分
|
||
"highestScore": 9.500, // 最高给分
|
||
"lowestScore": 7.800, // 最低给分
|
||
"lastScoreTime": "2025-06-25T15:30:00" // 最后评分时间
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 九、实时推送模块(可选)
|
||
|
||
### 9.1 WebSocket连接
|
||
|
||
**连接地址**: `wss://api.example.com/ws`
|
||
|
||
**认证方式**: URL参数传递Token
|
||
```
|
||
wss://api.example.com/ws?token={jwt_token}
|
||
```
|
||
|
||
**功能描述**: 建立WebSocket连接,接收实时评分更新
|
||
|
||
### 9.2 推送消息格式
|
||
|
||
#### 新评分推送
|
||
|
||
```json
|
||
{
|
||
"type": "new_score",
|
||
"data": {
|
||
"athleteId": "athlete_001",
|
||
"athleteName": "张三",
|
||
"judgeId": "judge_001",
|
||
"judgeName": "欧阳丽娜",
|
||
"score": 8.907,
|
||
"totalScore": 8.905,
|
||
"judgeCount": 3,
|
||
"timestamp": "2025-06-25T09:15:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 评分修改推送
|
||
|
||
```json
|
||
{
|
||
"type": "score_modified",
|
||
"data": {
|
||
"athleteId": "athlete_001",
|
||
"athleteName": "张三",
|
||
"modifierId": "admin_001",
|
||
"modifierName": "总裁判长",
|
||
"originalScore": 8.907,
|
||
"modifiedScore": 8.910,
|
||
"note": "技术动作优秀,加分",
|
||
"timestamp": "2025-06-25T10:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 选手状态更新
|
||
|
||
```json
|
||
{
|
||
"type": "athlete_status",
|
||
"data": {
|
||
"athleteId": "athlete_001",
|
||
"athleteName": "张三",
|
||
"status": "performing", // waiting-等待, performing-表演中, finished-完成
|
||
"timestamp": "2025-06-25T09:10:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、文件上传模块(扩展)
|
||
|
||
### 10.1 上传选手照片
|
||
|
||
**接口地址**: `POST /api/upload/photo`
|
||
|
||
**功能描述**: 上传选手照片
|
||
|
||
**请求格式**: `multipart/form-data`
|
||
|
||
**请求参数**:
|
||
```
|
||
file: File // 图片文件,必填
|
||
athleteId: string // 选手ID,必填
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "上传成功",
|
||
"data": {
|
||
"fileId": "file_001",
|
||
"fileName": "athlete_photo.jpg",
|
||
"fileUrl": "https://example.com/photos/athlete_photo.jpg",
|
||
"fileSize": 102400,
|
||
"uploadTime": "2025-06-25T08:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 接口调用示例
|
||
|
||
### JavaScript调用示例
|
||
|
||
```javascript
|
||
// 1. 登录
|
||
const login = async (matchCode, inviteCode) => {
|
||
try {
|
||
const response = await uni.request({
|
||
url: 'https://api.example.com/api/auth/login',
|
||
method: 'POST',
|
||
data: {
|
||
matchCode,
|
||
inviteCode
|
||
}
|
||
})
|
||
|
||
if (response.data.code === 200) {
|
||
// 保存Token
|
||
uni.setStorageSync('token', response.data.data.token)
|
||
uni.setStorageSync('userInfo', response.data.data)
|
||
return response.data.data
|
||
}
|
||
} catch (error) {
|
||
console.error('登录失败:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 2. 获取选手列表
|
||
const getAthletes = async (matchId, venueId, projectId, judgeId) => {
|
||
const token = uni.getStorageSync('token')
|
||
|
||
try {
|
||
const response = await uni.request({
|
||
url: 'https://api.example.com/api/athletes',
|
||
method: 'GET',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
data: {
|
||
matchId,
|
||
venueId,
|
||
projectId,
|
||
judgeId
|
||
}
|
||
})
|
||
|
||
return response.data.data
|
||
} catch (error) {
|
||
console.error('获取选手列表失败:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 3. 提交评分
|
||
const submitScore = async (scoreData) => {
|
||
const token = uni.getStorageSync('token')
|
||
|
||
try {
|
||
const response = await uni.request({
|
||
url: 'https://api.example.com/api/scores',
|
||
method: 'POST',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
data: scoreData
|
||
})
|
||
|
||
if (response.data.code === 200) {
|
||
uni.showToast({
|
||
title: '提交成功',
|
||
icon: 'success'
|
||
})
|
||
return response.data.data
|
||
}
|
||
} catch (error) {
|
||
console.error('提交评分失败:', error)
|
||
uni.showToast({
|
||
title: '提交失败',
|
||
icon: 'none'
|
||
})
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 4. 修改评分(裁判长)
|
||
const modifyScore = async (athleteId, modifyData) => {
|
||
const token = uni.getStorageSync('token')
|
||
|
||
try {
|
||
const response = await uni.request({
|
||
url: `https://api.example.com/api/scores/${athleteId}/modify`,
|
||
method: 'PUT',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
data: modifyData
|
||
})
|
||
|
||
if (response.data.code === 200) {
|
||
uni.showToast({
|
||
title: '修改成功',
|
||
icon: 'success'
|
||
})
|
||
return response.data.data
|
||
}
|
||
} catch (error) {
|
||
console.error('修改评分失败:', error)
|
||
uni.showToast({
|
||
title: '修改失败',
|
||
icon: 'none'
|
||
})
|
||
throw error
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 接口安全建议
|
||
|
||
### 1. 认证安全
|
||
- 使用HTTPS协议
|
||
- JWT Token设置合理过期时间(建议8小时)
|
||
- Token应包含用户角色信息
|
||
- 实现Token刷新机制
|
||
|
||
### 2. 权限控制
|
||
- 普通评委只能访问自己的数据
|
||
- 裁判长可以访问所有数据
|
||
- 每个接口都需要验证用户权限
|
||
|
||
### 3. 数据验证
|
||
- 后端必须验证所有输入参数
|
||
- 评分范围必须在5.0-10.0之间
|
||
- 评分精度必须为0.001
|
||
- 防止重复提交评分
|
||
|
||
### 4. 日志记录
|
||
- 记录所有评分操作
|
||
- 记录所有修改操作
|
||
- 记录登录和登出
|
||
- 保留操作时间戳
|
||
|
||
### 5. 错误处理
|
||
- 统一错误响应格式
|
||
- 不暴露敏感信息
|
||
- 提供友好的错误提示
|
||
|
||
---
|
||
|
||
## 接口性能优化建议
|
||
|
||
### 1. 缓存策略
|
||
- 比赛信息、场地列表、项目列表可缓存
|
||
- 扣分项列表可缓存
|
||
- 选手列表需要实时更新
|
||
|
||
### 2. 分页查询
|
||
- 选手列表支持分页
|
||
- 评分记录支持分页
|
||
- 统计数据可按需加载
|
||
|
||
### 3. 数据压缩
|
||
- 使用Gzip压缩响应数据
|
||
- 图片使用CDN加速
|
||
|
||
### 4. 并发控制
|
||
- 限制单个评委的评分频率
|
||
- 防止恶意刷接口
|
||
- 实现请求限流
|
||
|
||
---
|
||
|
||
## 接口测试建议
|
||
|
||
### 1. 单元测试
|
||
- 测试所有接口的正常流程
|
||
- 测试各种异常情况
|
||
- 测试权限验证
|
||
|
||
### 2. 压力测试
|
||
- 模拟多评委同时评分
|
||
- 测试并发修改评分
|
||
- 测试WebSocket连接数
|
||
|
||
### 3. 集成测试
|
||
- 测试完整的评分流程
|
||
- 测试跨模块的数据一致性
|
||
|
||
---
|
||
|
||
## 附录:环境配置
|
||
|
||
### 开发环境
|
||
```
|
||
BASE_URL: https://dev-api.example.com
|
||
```
|
||
|
||
### 测试环境
|
||
```
|
||
BASE_URL: https://test-api.example.com
|
||
```
|
||
|
||
### 生产环境
|
||
```
|
||
BASE_URL: https://api.example.com
|
||
```
|
||
|
||
建议在项目中创建环境配置文件:
|
||
|
||
```javascript
|
||
// config/env.js
|
||
const env = {
|
||
development: {
|
||
baseUrl: 'https://dev-api.example.com',
|
||
timeout: 10000
|
||
},
|
||
production: {
|
||
baseUrl: 'https://api.example.com',
|
||
timeout: 5000
|
||
}
|
||
}
|
||
|
||
export default env[process.env.NODE_ENV || 'development']
|
||
```
|