# 优谷语音测评 — SDK 对接契约（单一事实来源 / Source of Truth）

> 本文件是五端 SDK 与服务端唯一对齐基准。所有 SDK **必须**严格按此实现请求/签名/解析。
> 字段、签名算法、协议帧均取自后端真实代码（voice-platform-service），不得臆造。
> 基址（生产）：`https://ygyx.dragonai.tech`（REST 经 nginx 反代到后端 :8080）。
> WebSocket 基址：`wss://ygyx.dragonai.tech`。

---

## 0. 鉴权（两选一，全部接口通用）

### 0.1 Bearer Token（JWT）—— 面向已登录用户/控制台
请求头：`Authorization: Bearer <jwt>`
获取：登录接口或免注册试用接口返回的 token。

### 0.2 声通风格签名（sig）—— 面向 API Key 接入方（推荐用于服务端/SDK）
请求头：
| Header | 含义 |
|---|---|
| `X-App-Key` | appKey（密钥对的公钥部分） |
| `X-Timestamp` | 当前 Unix 时间戳（**秒**，非毫秒） |
| `X-Nonce` | 随机串（可选，但建议；服务端 300s 内去重防重放） |
| `X-Signature` | 签名值，算法见下 |

**签名算法（与后端 `SignatureUtil.signHmacSha256` 完全一致）：**
1. 取本次请求的**业务参数**（query/form 参数；不含 header、不含文件二进制）。
2. 丢弃 value 为 `null` 或空字符串的项。
3. 按 key 的字典序（`TreeMap` 自然序）升序排序。
4. 拼成 `key1=value1&key2=value2&...`（**不做 URL 编码**，不追加 secret 在尾部）。
5. `signature = Base64( HMAC_SHA256( payload_utf8, secretKey_utf8 ) )`。
6. 时间窗：服务端校验 `|now_sec - X-Timestamp| ≤ 300`，超出拒绝。

> 例：params={coreType:"sent.eval.cn", refText:"北京你好", language:"zh-CN"}
> payload = `coreType=sent.eval.cn&language=zh-CN&refText=北京你好`
> X-Signature = Base64(HMAC_SHA256(payload, secretKey))

> ⚠️ 对 `multipart/form-data` 评测请求：被签名的是**表单字段**（coreType/refText/language/refPinyin…），音频文件不参与签名。
> ⚠️ WS 握手用签名：见 §3.3，业务参数取自 **query string**（去掉 signature 本身）。

---

## 1. 原生 REST：评测  `POST /api/v1/evaluate`
`Content-Type: multipart/form-data`，三个 part：
| part | 类型 | 说明 |
|---|---|---|
| `audio` | file | wav/mp3，≥16kHz，单/双声道，16bit |
| `image` | file（可选） | 仅 coreType=open 且 taskType=picture 看图说话时上传 |
| `config` | **application/json 文本 part** | EvaluateConfigDTO，见下 |

**EvaluateConfigDTO（config part 的 JSON）字段：**
| 字段 | 类型 | 必填 | 取值/默认 |
|---|---|---|---|
| `coreType` | string | ✅ | `word`/`sentence`/`passage`/`connected`/`open`/`alpha`/`pinyin` |
| `referenceText` | string | ✅ | 参考文本（open 时为题目提示）；≤1000 |
| `language` | string |  | `en-US`(默认)/`en-GB`/`zh-CN` |
| `includeReport` | bool |  | 是否返回字/音素级详报，默认 false |
| `includeStandardAudio` | bool |  | 是否返回标准示范音 URL，默认 false |
| `includeAsrText` | bool |  | 是否返回 ASR 识别文本，默认 false |
| `slack` | double |  | 松紧度 [-1,1]，默认 0 |
| `scale` | int |  | 分制 (0,100]，默认 100 |
| `precision` | double |  | 精度 (0,1]，默认 1 |
| `agegroup` | int |  | 1=学前 2=小学 3=>12岁（默认 3） |
| `toneWeight` | double |  | 中文声调占比 [0,1]，默认 0.2 |
| `refPinyin` | string |  | 拼音（多音字/pinyin 题），如 `chong2 qing4` |
| `phonemeOutput` | bool |  | 是否音素级输出 |
| `taskType` | string |  | open 题型：`picture`/`situational`/`free` |
| `paragraphNeedWordScore` | int |  | passage 是否返回逐字详分 1/0 |

**响应（SpeechEvaluationResult，直接返回，非 Result 包裹）：**
```json
{
  "recordId":"eval_xxx","eof":1,
  "result":{ "overall":85,"pronunciation":88,"tone":78,"fluency":90,"rhythm":82,
             "integrity":100,"speed":135,"rear_tone":"fall","duration":"4.10","warning":[],
             "words":[{"word":"北","pinyin":"bei","symbolpinyin":"běi","tone":"tone3",
                       "scores":{"overall":100,"pronunciation":100,"tone":100,"overall_pron":100,"prominence":0},
                       "span":{"start":120,"end":280},
                       "phonemes":[{"phoneme":"B","pronunciation":100,"span":{"start":120,"end":150}}]}] },
  "report":{ "summary":"...","dimensions":{...},"suggestions":["..."] },
  "asrText":{ "text":"北京你好","alignment":[{"char":"北","index":0,"read_status":"correct",
              "start_time":12,"end_time":28,"asr_pinyin":"bei3","asr_tone":3,"gop_score":0.9}] },
  "standardAudio":{ "url":"/audio/standard/xxx.wav","format":"wav","duration":"3.50" },
  "warnings":[1002]
}
```
`read_status` ∈ correct / mispronounced / skipped / inserted。时间单位 10ms。

---

## 2. 原生 REST：TTS / 报告
### 2.1 TTS  `POST /api/v1/tts/generate`  `Content-Type: application/json`
```json
{ "text":"你好世界", "language":"zh-CN", "voice":"xiaoyan", "format":"mp3",
  "speed":50, "pitch":50, "volume":50, "style":null }
```
- `voice`：英语 `female`/`male`；中文 `xiaoyan`(女)/`xiaofeng`(男)。
- `format`：`mp3`(默认)/`wav`/`ogg`。`speed`/`pitch`/`volume` ∈ [0,100]，默认 50。
- 响应：`{ "code":0,"message":"操作成功","data":{ "audioUrl":"...","duration":"2.5","format":"mp3" } }`
  音频地址以 `/audio/` 开头时，按基址拼成 `https://ygyx.dragonai.tech/tts<audioUrl>`（nginx 已反代 `/tts/`）。

### 2.2 报告  `GET /api/v1/report/{recordId}`
响应：`Result<ReportDetailVO>`，`{ "code":0,"data":{...评分与报告...} }`。

---

## 3. WebSocket 流式评测
### 3.1 原生 WS  `wss://host/api/v1/ws/evaluate`
连接后服务端先发 `{"event":"connected"}`。客户端帧（文本 JSON）：
1. `{"cmd":"start","coreType":"sentence","referenceText":"今天天气很好","language":"zh-CN"}` → `{"event":"started"}`
2. 音频：二进制帧（**推荐 640 bytes/帧 = 20ms@16kHz**），或 `{"cmd":"audio","data":"<base64>"}`
3. `{"cmd":"end"}` → `{"event":"result","recordId":...,"eof":1,"result":{...},"report":{...},"asrText":{...},"warnings":[...]}`
错误：`{"event":"error","message":"..."}`。

### 3.2 声通兼容 WS  `wss://host/{coreType}`  ← 本次新增
`{coreType}` 取声通命名，已注册路径：
`word.eval`、`word.eval.pro`、`sent.eval`、`sent.eval.pro`、`para.eval`、`alpha.eval`、
`word.eval.cn`、`sent.eval.cn`、`para.eval.cn`、`pinyin`。
连接后服务端发 `{"event":"connected","coreType":"<path>"}`。客户端帧：
1. **参数帧**（文本 JSON，开始即发）：`{"refText":"北京你好","language":"zh-CN","refPinyin":"...","realtime_feedback":true}`（也接受 `text` 代替 `refText`）→ 服务端回 `{"event":"started","coreType":"sent.eval.cn"}`
2. 音频：二进制帧（推荐 640B/20ms）或 `{"cmd":"audio","data":"<base64>"}`
3. 结束：`{"cmd":"end"}`（或 `{"end":true}`）→ 服务端回 **声通风格** `{"recordId":...,"eof":1,"result":{...}}`
4. 若参数帧 `realtime_feedback=true`：服务端在收音过程中下发进度中间帧 `{"eof":0,"result":{"bytes":<已收字节>}}`（节流），终评仍为 `eof:1`。
错误：`{"event":"error","message":"..."}`。

### 3.3 WS 鉴权（声通兼容 WS）
WS 握手无法自定义 header，凭证走 **query string**（可选；不传则放行，与原生 WS 一致；传则校验，失败拒绝握手）：
- Token 模式：`?token=<jwt>`
- 签名模式：`?appKey=..&timestamp=<秒>&signature=<sig>[&nonce=..]`
  被签名参数 = 除 `signature` 外的**全部 query 参数**（含 appKey/timestamp/nonce 及业务参数），规则同 §0.2。

---

## 4. 音频质量警告码（result.warning / 顶层 warnings）
| code | message |
|---|---|
| 1001 | No valid audio detected! |
| 1002 | Audio volume too low! |
| 1003 | Audio volume too high! |
| 1004 | Audio noisy! |
| 1005 | Audio not complete! |

---

## 5. SDK 统一约定（五端一致）
- **配置项**：`baseUrl`（默认 `https://ygyx.dragonai.tech`）、`wsBaseUrl`（默认 `wss://ygyx.dragonai.tech`）、鉴权（`token` 或 `appKey`+`secretKey`）。
- **能力**：①整段评测（REST `/api/v1/evaluate`）②TTS ③报告查询 ④实时流式评测（原生 WS，移动端/小程序优先用原生；服务端 SDK 额外支持声通兼容 WS）⑤声通兼容 REST `POST /{coreType}`（带 sig）。
- **结果解析**：统一解析为 `EvalResult{ overall, dims{integrity,accuracy/pronunciation,fluency,tone,rhythm,emotion}, words[], asrText, report, warnings[] }`。
- **签名实现**：所有端都要内置 §0.2 的 HMAC-SHA256 签名器，并提供单测对一组固定输入产出固定 base64（跨端一致性校验向量见 §6）。
- **录音**：移动/小程序/Web 端要能采集 16kHz/16bit/单声道 PCM/WAV；流式按 640B/帧推。

## 6. 跨端签名一致性测试向量（所有 SDK 单测必须命中）
```
secret  = "test_secret_key_123"
params  = { "coreType":"sent.eval.cn", "language":"zh-CN", "refText":"北京你好" }
payload = "coreType=sent.eval.cn&language=zh-CN&refText=北京你好"
expected X-Signature = Base64(HMAC_SHA256(payload, secret))
              = "A+6uVB/D7khxQEt8tzgCNjMUC1QtQQd1UF+NCYVYZqE="   ← 唯一正确值，所有 SDK 单测必须命中
```
（服务端 `SignatureUtil` 为裁判；上面 base64 由其同算法产出，跨端一致。）
