Files
railwaysystem/RuoYi-Vue/sql/train/v2/016_api_test_data.md
T

289 lines
12 KiB
Markdown
Raw Normal View History

# 阅读机/听书机 接口测试数据手册
> 配套文件:`阅读机数据表+接口设计-v1.md`、`015_train_test_data.sql`
> 本文档中的「请求示例」全部使用 `015_train_test_data.sql` 已插入的真实数据,可直接照抄测试。
>
> 基础地址:`http://localhost:8096/api/iot/v1`(端口按你实际改)
> 数据前提:已执行 `015_train_test_data.sql`,且 `sys_user` 中存在 `userId=10001`、`status='0'`(正常)。
---
## 0. 鉴权与测试顺序
| 接口 | 鉴权 |
|---|---|
| `POST /device/login` | 免鉴权(permitAll) |
| `GET /book/list` | 免鉴权(已放开) |
| `GET /book/download` | 免鉴权(已放开) |
| 其余 6 个接口 | 需 `Authorization: Bearer <accessToken>`(来自登录返回) |
> **登录接口有强制设备签名(`DeviceHmacFilter`)**:必须带 `X-Signature` + `X-Nonce` 请求头,且 body 的 `timestamp` 距今 ≤5 分钟,否则报「缺少设备签名头 / 签名校验失败 / 时间戳超时」。
> 本地联调不想算签名时,把 `application.yml` 的 `iot.hmac.enabled` 改为 `false`(重启后端)即可跳过验签;生产环境务必保持 `true`。
> 其余接口(Bearer 鉴权)不受该 Filter 影响。
**推荐顺序**:登录 → 复制 `data.accessToken` → 在 Swagger 点 Authorize 填 `Bearer <accessToken>`(或 curl 带 `-H 'Authorization: Bearer <accessToken>'`)→ 测其余接口。
---
## 1. 设备学员登录 `POST /api/iot/v1/device/login`
请求(取自 015 设备 `READER-LIB-01` + 学员 `10001`):
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/device/login' \
-H 'Content-Type: application/json' \
-d '{
"deviceId":"READER-LIB-01",
"userId":10001,
"firmware":"1.4.2",
"appVersion":"2.1.0",
"timestamp":1767620000000,
"nonce":"a1b2c3d4e5f6"
}'
```
预期响应:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"accessToken": "<ruoyi生成的token>",
"sessionId": "<本次新建的业务会话ID>",
"heartbeatInterval": 60
}
}
```
---
## 2. 设备注销 `POST /api/iot/v1/device/logout`(需 token)
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/device/logout' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{"deviceId":"READER-LIB-01","reason":"REBOOT","timestamp":1767621000000,"nonce":"f6e5d4c3b2a1"}'
```
预期:`{"data":{"result":true,"tokenRevoked":true}}`
---
## 3. 设备心跳 `POST /api/iot/v1/device/heartbeat`(需 token)
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/device/heartbeat' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{"deviceId":"READER-LIB-01","status":"ONLINE","metrics":{"cpu":20,"mem":30,"diskFree":10240},"configVersion":"CV-17","personVersion":"PV-226118-7","timestamp":1767622000000,"nonce":"aa11bb22cc33"}'
```
预期:`{"data":{"nextInterval":60,"commands":[]}}`
---
## 4. 阅读记录单条上报 `POST /api/iot/v1/reading/record`(需 token)
**新 recordId(正常 ACCEPTED)** —— 用上架书 `BK-00012/BV-3`:
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/reading/record' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{
"deviceId":"READER-LIB-01",
"recordId":"RD-TEST-0001",
"bookId":"BK-00012",
"bookVersion":"BV-3",
"mode":"read",
"recordType":"END",
"startTime":1767620000000,
"endTime":1767622400000,
"durationSec":1420,
"pageFrom":12,
"pageTo":46,
"progress":0.35
}'
```
预期:`{"data":{"result":"ACCEPTED","studyMinutes":23,"totalMinutes":<累计/60>}}`
> 重复发送同一个 `recordId=RD-TEST-0001` → 返回 `{"result":"DUPLICATE",...}`(幂等)。
---
## 5. 阅读记录批量上报 `POST /api/iot/v1/reading/record/batch`(需 token)
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/reading/record/batch' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{
"deviceId":"READER-LIB-01",
"batchId":"BATCH-RD-001",
"timestamp":1767622500000,
"nonce":"b2b3c4d5e6f7",
"records":[
{"recordId":"RD-TEST-0002","bookId":"BK-00013","bookVersion":"BV-2","mode":"read","recordType":"END","startTime":1767620000000,"endTime":1767622400000,"durationSec":980,"pageFrom":5,"pageTo":22,"progress":0.18}
]
}'
```
预期:`{"data":{"itemResults":[{"recordId":"RD-TEST-0002","result":"ACCEPTED"}]}}`
---
## 6. 获取学员阅读汇总与续读进度 `GET /api/iot/v1/reading/progress`(需 token)
```bash
curl 'http://localhost:8096/api/iot/v1/reading/progress?deviceId=READER-LIB-01' \
-H 'Authorization: Bearer <accessToken>'
```
预期(基于 015 已插入的进度/上报数据;需存在一个 `ACTIVE` 会话,登录后即有):
```json
{
"data": {
"userId": 10001,
"totalReadSeconds": 2400,
"totalAudioSeconds": 600,
"bookRecords": [
{
"bookId": "BK-00012", "bookVersion": "BV-3",
"mainProgress": {"mode":"read","pageNo":46,"sentenceIndex":null,"progress":0.35,"updateTime":1767622400000},
"alternateProgressList": [
{"mode":"read","bookVersion":"BV-2","pageNo":22,"sentenceIndex":null,"progress":0.18,"createTime":1767610000000}
]
},
{
"bookId": "BK-00012", "bookVersion": "BV-3",
"mainProgress": {"mode":"audio","pageNo":120,"sentenceIndex":88,"progress":0.50,"updateTime":1767622500000},
"alternateProgressList": []
},
{
"bookId": "BK-00013", "bookVersion": "BV-2",
"mainProgress": {"mode":"read","pageNo":30,"sentenceIndex":null,"progress":0.20,"updateTime":1767622500000},
"alternateProgressList": []
}
]
}
}
```
> `totalReadSeconds`=1420+980(两条 read 上报累加);`totalAudioSeconds`=600(audio 上报)。
---
## 7. 获取可阅读书籍列表 `GET /api/iot/v1/book/list`(免鉴权)
```bash
# 全部上架书
curl 'http://localhost:8096/api/iot/v1/book/list?deviceId=READER-LIB-01'
# 按分类过滤(只返回上架)
curl 'http://localhost:8096/api/iot/v1/book/list?deviceId=READER-LIB-01&bookCategory=SAFETY'
```
预期(`bookCategory` 省略时 `total=3`,只返回 `is_on_shelf=1` 的书;下架的 `BK-00012/BV-2`、`BK-00015` 不返回):
```json
{
"data": {
"total": 3,
"list": [
{"bookId":"BK-00012","bookTitle":"铁路房建施工安全手册","bookCategory":"SAFETY","bookVersion":"BV-3","fileSize":2048000,"fileMd5":"c4ca4238a0b923820dcc509a6f75849b","isOnShelf":true},
{"bookId":"BK-00013","bookTitle":"铁路电力安全工作规程","bookCategory":"REGULATION","bookVersion":"BV-2","fileSize":3072000,"fileMd5":"a1b2c3d4e5f60718293a4b5c6d7e8f90a","isOnShelf":true},
{"bookId":"BK-00014","bookTitle":"高铁客运服务英语","bookCategory":"LANGUAGE","bookVersion":"BV-1","fileSize":1536000,"fileMd5":"f0e1d2c3b4a5968778695a4b3c2d1e0f1","isOnShelf":true}
]
}
}
```
> `bookCategory=SAFETY` 时 `total=1`(仅 `BK-00012/BV-3`)。
---
## 8. PDF 书籍下载 `GET /api/iot/v1/book/download`(免鉴权)
```bash
# 上架书(正常)
curl -i 'http://localhost:8096/api/iot/v1/book/download?deviceId=READER-LIB-01&bookId=BK-00012&bookVersion=BV-3'
# 支持断点续传
curl -i -H 'Range: bytes=0-1023' 'http://localhost:8096/api/iot/v1/book/download?deviceId=READER-LIB-01&bookId=BK-00012&bookVersion=BV-3'
```
- 上架书:HTTP 200(或 Range 时 206),响应头带 `Accept-Ranges: bytes`、`Content-MD5`。
- 下架书:`bookId=BK-00012&bookVersion=BV-2`(is_on_shelf=0)→ 业务报错 `{"code":500,"msg":"图书已下架,禁止下载"}`。
- 不存在的版本:`bookId=BK-00015&bookVersion=BV-9` → `图书或版本不存在`。
> ⚠️ `download_url` 支持两种形态:
> ① **相对路径**(如正式数据的 `/profile/upload/2026/10/21/铁路房建施工安全手册_20261021085854A001.pdf`,或测试用的 `/books/BK-00012/BV-3.pdf`)——后端自动在前面补上「后端 base URL(`scheme+host+port`,如 `http://localhost:8096`)」转成可访问的 http(s) 绝对地址后再流式返回;
> ② **完整 http(s) 远程地址**(如 OSS)——直接拉取。
> 两种都会自动加 `Content-Type: application/pdf` 与 `Content-Disposition`(下载头),保证浏览器可访问。
> 相对路径由后端自身静态资源处理器提供文件:`/profile/**` 指向 RuoYi 上传根目录(文件须真实存在于该磁盘目录),`/books/**` 指向 `classpath:/static/books/`(测试 PDF 已放此处)。文件不存在则返回「文件不存在」。
---
## 9. 下载完成回执上报 `POST /api/iot/v1/book/download/ack`(需 token)
```bash
curl -X POST 'http://localhost:8096/api/iot/v1/book/download/ack' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{
"deviceId":"READER-LIB-01",
"bookId":"BK-00012",
"bookVersion":"BV-3",
"fileMd5":"c4ca4238a0b923820dcc509a6f75849b",
"result":"SUCCESS",
"errMsg":"",
"timestamp":1767630000000,
"nonce":"cc55dd66ee77"
}'
```
预期:`{"data":{"deviceReady":true,"recordId":<自增主键>}}`
> `result` 可取 `SUCCESS`/`FAIL`(缺省 SUCCESS);落库 `train_book_download_record`。
---
## 10. 边界 / 异常用例矩阵(均基于 015 数据)
| 用例 | 操作 | 预期 |
|---|---|---|
| 禁用设备登录 | `device/login` 用 `deviceId=READER-LIB-03`(status=2) | `msg:"设备已禁用"` |
| 重复上报幂等 | `reading/record` 用已存在的 `recordId=RD-20261020-0001` | `result:"DUPLICATE"` |
| 冲突进度(REPLACED 仅补交) | 用 `deviceId=READER-LIB-02`(其会话 `SES-20261019-0001` 是 REPLACED)发 `reading/record` 新 recordId | `result:"CONFLICT_SAVED"`,写入 `train_reading_alternate_progress` |
| 下架书被过滤 | `book/list` 不含 `BK-00012/BV-2`、`BK-00015` | `total=3`(仅上架) |
| 下架书禁止下载 | `book/download?bookId=BK-00012&bookVersion=BV-2` | 业务错误「图书已下架,禁止下载」 |
| REPLACED 禁止进度查询 | `reading/progress?deviceId=READER-LIB-02`(需先有一个有效 token) | `msg:"当前会话状态不允许查询进度(被替换设备仅允许补交上报)"` |
| 版本强绑定 | 进度查询中 `BK-00012` 的 `read` 主进度 `BV-3` 与备选 `BV-2` 不混用页码 | 各自返回对应版本页码 |
---
## 11. 已插入测试数据速查(源自 015)
**设备 `train_iot_device`**
- `READER-LIB-01` status=1(正常)、`READER-LIB-02` status=1、`READER-LIB-03` status=2(禁用)
**图书 `train_book_snapshot`**(is_on_shelf)
- `BK-00012/BV-3` SAFETY 上架 md5=`c4ca4238a0b923820dcc509a6f75849b`
- `BK-00012/BV-2` SAFETY **下架**
- `BK-00013/BV-2` REGULATION 上架 md5=`a1b2c3d4e5f60718293a4b5c6d7e8f90a`
- `BK-00014/BV-1` LANGUAGE 上架 md5=`f0e1d2c3b4a5968778695a4b3c2d1e0f1`
- `BK-00015/BV-1` REGULATION **下架**
**会话 `train_user_session`**
- `SES-20261020-0001` user=10001 device=READER-LIB-01 status=1 ACTIVE
- `SES-20261019-0001` user=10001 device=READER-LIB-02 status=2 REPLACED(用于冲突用例)
**主进度 `train_reading_current_progress`**:10001 的 `BK-00012/read/BV-3`(46,0.35)、`BK-00012/audio/BV-3`(120,88,0.50)、`BK-00013/read/BV-2`(30,0.20)
**备选进度 `train_reading_alternate_progress`**:10001 `BK-00012/read/BV-2`(22,0.18)
**上报日志 `train_reading_duration_log`**:`RD-20261020-0001`(ACCEPTED)、`RD-20261020-0002`(ACCEPTED)、`RD-20261019-0001`(CONFLICT_SAVED)
**下载回执 `train_book_download_record`**:`BK-00012/BV-3` SUCCESS、`BK-00013/BV-2` SUCCESS、`BK-00014/BV-1` FAIL(errMsg=存储空间不足)