# 终端 API 参考

更新：2026-09-14 · HTTPS / JSON · 示例数据均为占位值。

## 快速开始

1. 使用系统管理员账号在后台 [申请 API Key](/admin/terminal-api-config)，准备所属系统的学员与音频。
2. 登记设备并完成学员绑定，查询设备状态取得 `access_token`。
3. 使用 Token 获取本周作业，下载音频并上报听音事件。

可先用 [在线测试](/admin/apipost-simulator) 验证接口；C / ESP-IDF 接入见 [C 语言指南](/developers/c-terminal/)。

## 鉴权与约定

家长和教师可通过 [独立 H5](/admin/h5/login) 使用移动端。家长直接使用学员账号；家长子账号已下线，旧账号返回 `401 PARENT_SUBACCOUNT_DISABLED`，客户端应移除相关入口。

基址：`https://saas.example.com`，替换为你的服务域名。

| 接口 | 请求头 |
| --- | --- |
| `/api/terminal/*` | `X-API-Key: <apiKey>` |
| `/api/assignments/*` | `Authorization: Bearer <accessToken>` |
| JSON 写请求 | `Content-Type: application/json` |

- 时间使用 ISO 8601，时长单位为秒，流量单位为字节，进度为 `0..1`。
- 设备码为 1–120 个字母、数字或 `._:-`；组织/校区使用业务编码，学员/班级/作业使用返回的 ID。
- 每个系统管理员拥有独立音频库，名下组织共享。Key、设备和学员必须属于同一系统；跨系统请求返回 `403`，无系统归属或失效的 Key 返回 `401`。
- 系统管理员的数据访问限于本人代理组织，正式与试用账号采用相同的数据隔离规则。
- Token 有效期为 24 小时；Bearer 接口返回 `401` 时重新查询设备状态。不要保存或输出真实凭证、个人数据及签名 URL 到日志。
- 音频 URL 有效期为 15 分钟，失效后重新获取作业。仅下载当前响应中的音轨，不回退历史 URL。
- 仅使用本文接口；旧资源 API 返回 `410`。

## 设备

### POST /api/terminal/devices/register/campus

登记设备。鉴权：API Key。必填：`deviceId`（string）。重复登记不覆盖已有绑定。

```json
{"deviceId":"EXAMPLE-DEVICE-001"}
```

响应关键字段：`created`、`status`、`data.deviceCode`、`data.studentId`。首次登记 `created=true`，重复登记为 `false`。

### GET /api/terminal/devices/{deviceCode}

查询绑定状态并获取 Token。鉴权：API Key。路径参数：`deviceCode`（string）。

| 响应 | 处理 |
| --- | --- |
| `404 + DEVICE_NOT_BOUND` | 先登记设备 |
| `200 + status=DEVICE_NOT_BOUND` | 等待绑定，低频轮询 |
| `200 + data.access_token` | 保存 Token，读取 `data.student.id` |
| `403 + DEVICE_DISABLED / STUDENT_DISABLED` | 停止轮询，联系管理员 |

绑定后的最小响应示意：

```json
{"status":"BOUND","data":{"deviceCode":"EXAMPLE-DEVICE-001","student":{"id":"student_uuid"},"access_token":"<accessToken>"}}
```

## 作业与音频

### GET /api/assignments/my-weekly

获取本周作业。鉴权：Bearer Token。可选查询参数：`date`（`YYYY-MM-DD`，参考周日期）。

| 响应字段 | 含义 |
| --- | --- |
| `week / startDate / endDate / count` | 周期与作业数量 |
| `assignments[].id / title / dueDate` | 作业标识、标题与截止时间 |
| `assignments[].tracks[].id / audioId / title` | 音轨与资源标识 |
| `tracks[].url / duration / sortOrder` | 完整下载地址、时长与顺序 |
| `tracks[].progress` | 状态、听音次数、时长及完成时间 |

音轨数量取 `tracks.length`。直接下载 `tracks[].url`，**不携带 Key 或 Token**；URL 失效返回 `403` 时重新获取作业。音频 `duration=0` 时可在播放端解析时长，不应直接跳过。

### POST /api/assignments/{assignmentId}/progress

上报单条进度。鉴权：Bearer Token。必填：`trackId`（string）、`listenDuration`（非负整数）。为保证有效时长和重试幂等，建议同时传时间区间与事件标识。

```json
{
  "trackId":"track_uuid",
  "listenDuration":60,
  "startTime":"2026-01-01T12:00:00Z",
  "endTime":"2026-01-01T12:01:00Z",
  "clientEventId":"example-event-001",
  "idempotencyKey":"example-event-001",
  "progress":1,
  "status":"COMPLETED"
}
```

可选：`audioId`、`durationSeconds`、`sessionId`、`trafficBytes`、`clientMeta`。`status` 为 `PENDING / IN_PROGRESS / COMPLETED`。有效时长与完成度以服务端结果为准。

### POST /api/assignments/{assignmentId}/audio-download-failures/batch

批量记录下载失败。鉴权：Bearer Token。仅在同批作业部分下载失败时上报；必填：`failures` 数组及每项的 `trackId`。

```json
{"failures":[{"trackId":"track_uuid","errorType":"DOWNLOAD_FAILED","downloadBatchResult":"PARTIAL_FAILED","errorMessage":"HTTP 403"}]}
```

可选：`audioId`、`audioTitle`、`audioUrl`、`clientMeta`。版本元数据使用 `appVersionName / appVersionCode`，不要使用旧字段 `appVersion`；错误信息须脱敏。

## 听音与诊断

### GET /api/terminal/listening-rules

获取有效听音时段及统计规则。鉴权：API Key。无需参数；启动或规则缓存过期时读取。旧奖励字段固定为停用值，不参与计算。

### POST /api/terminal/listening-events/batch

批量同步听音事件。鉴权：API Key。最小请求：

```json
{
  "deviceCode":"EXAMPLE-DEVICE-001",
  "batchId":"example-batch-001",
  "events":[{
    "clientEventId":"example-event-001",
    "idempotencyKey":"example-event-001",
    "startTime":"2026-01-01T12:00:00Z",
    "endTime":"2026-01-01T12:01:00Z",
    "durationSeconds":60
  }]
}
```

| 字段 | 约束 |
| --- | --- |
| `deviceCode / batchId / events` | 必填；每批 1–500 条事件 |
| `clientEventId / idempotencyKey` | 每项必填，非空且最长 128 字符 |
| `startTime / endTime / durationSeconds` | 每项必填；ISO 8601 时间与非负整数时长 |
| `studentId` | 可选；若提供，必须与设备当前绑定学员一致 |
| `timezone / generatedAt / clientMeta` | 可选批次信息 |
| `audioId / assignmentId / trackId` | 可选事件信息；作业听音建议携带 |
| `sessionId / audioTitle / clientCreatedAt` | 可选事件信息 |
| `eventType` | 可选：`SESSION_END / PROGRESS_REPORT / BATCH_SYNC` |
| `totalDurationSeconds / trafficBytes / progress / clientMeta` | 可选事件信息；计数非负，进度 `0..1` |

响应：`accepted / duplicates / failed / total / isDuplicateBatch / serverTime`。补传时保持原批次、事件幂等键与请求内容，成功或明确重复后再移除本地队列。

### GET /api/terminal/listening-events/sync-status/{deviceCode}

查询最近同步结果。鉴权：API Key。响应：`hasData / latestBatch / latestEvent / serverTime`；`latestBatch` 包含最近批次及接受、重复、失败数量。

### POST /api/terminal/diagnostics/daily

上报设备状态。鉴权：API Key。最小请求中的字段均必填：

```json
{
  "deviceCode":"EXAMPLE-DEVICE-001",
  "isBound":true,"isOnline":true,
  "pendingListeningLogs":0,"pendingFocusRewards":0,
  "favoritesCount":0,"homeworkCacheCount":0,
  "generatedAt":"2026-01-01T12:01:00Z"
}
```

常用可选字段：`appVersion / appVersionCode`、`model`、`networkType`、`batteryPercent`（0–100）、`storageUsedBytes / storageFreeBytes`、`lastError`、`lastSyncTime`。`reportSource` 为 `AUTO_DAILY / MANUAL`；历史字段 `pendingFocusRewards` 固定填 `0`。

## 可选目录接口

以下均使用 API Key，仅返回授权范围内数据。路径前缀均为 `/api/terminal`。

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/organizations/{orgCode}/campuses` | 校区列表 |
| GET | `/organizations/{orgCode}/campuses/{campusCode}/students` | 学员列表 |
| GET | `/organizations/{orgCode}/teachers` | 教师及班级 |
| GET | `/organizations/{orgCode}/campuses/{campusCode}/classes` | 班级列表 |
| GET | `/organizations/{orgCode}/campuses/{campusCode}/students/{studentId}/assignments` | 指定学员作业 |
| GET | `/students/{studentId}/classes` | 学员班级 |
| GET | `/classes/{classId}/statistics` | 班级排行；可选 `startDate / endDate`，格式 `YYYY-MM-DD` |

旧固件的 `POST /api/terminal/assignment-progress` 仅保留兼容。新集成统一使用上面的听音事件与作业进度接口。

## 错误处理

| HTTP 状态 | 客户端处理 |
| --- | --- |
| 2xx | 按业务 `status / success` 判断结果 |
| 400 | 检查必填字段、类型与设备绑定 |
| 401 | 检查 Key；Bearer 请求重新获取 Token |
| 403 | 检查账号/设备状态；音频下载则刷新签名 URL |
| 404 | 检查资源；设备未登记时先登记 |
| 409 | 清除旧学员缓存，重新确认绑定 |
| 429 | 等待响应头 `Retry-After` 指定的秒数 |
| 5xx | 保留请求内容，指数退避后重试 |

`503 LISTENING_SYNC_BUSY` 表示同步暂时繁忙，使用原幂等键重试。建议退避 5、15、30、60 秒，并加入随机抖动；诊断时可记录 `X-Request-ID`。

配套客户端使用正确密码可直接登录，无需首次强制改密。登录后可调用 `POST /api/auth/password-reminder/claim`（Bearer JWT，无参数）；收到 `{ "showReminder": true }` 时显示一次可关闭的改密建议，false 时不提醒，请求失败不阻断使用。提醒跨设备共享，设备 Token 不消耗提醒；用户可随时在个人设置中自愿修改密码。

学员 Web/H5/小程序会话遵循单账号单活，收到 `401 SESSION_REPLACED` 时重新登录；设备 Token 独立按设备绑定校验，不随网页登录切换而替换。

服务端可能在完成回执覆盖和一致性核对后，将历史同步状态读取切换到窄回执；终端接口路径、鉴权、幂等键、响应字段和重试规则保持不变，客户端不得依赖服务端原始明细表的保留时长。
