## 主要变更 ### 文档重构(减少 1199 行,-23%) - 01-架构设计.md: 503→369 行 (-27%),删除 DDL/配置示例,精简鉴权/存储描述 - 02-接口文档.md: 1313→570 行 (-57%),删除 Go 接口/Orchestrator 实现/配置管理 - 07-成本控制.md: 65→59 行 (-9%),代码块替换为文件引用 ### 文档编号规范化 - 08-功能创意.md → 删除(内容整合到 README.md "功能扩展方向") - 10-Eino框架与编排设计.md → 08-Eino框架与编排设计.md - 情景切换.md → 09-情景切换.md - 12-鉴权体系.md → 10-鉴权体系.md - 13-令牌桶限流.md → 11-令牌桶限流.md ### 交叉引用更新 - 01-架构设计.md: 更新对鉴权体系/令牌桶限流的引用为新编号 - README.md: 更新文档索引表、推荐阅读顺序、新增功能扩展方向 ### 删除过时文档 - 09-技术名词解释.md(内容已整合到 03-技术选型.md) - 10-Eino重构方案.md(历史记录,已完成) - 11-Eino框架技术文档.md(已合并到 08) - 情景切换功能完整文档.md(已规范化为 09) ## 重构原则 - 架构文档聚焦系统结构,移除实现细节 - 接口文档保留纯契约,删除内部实现 - 编号连续(01-11),语义清晰 - 通过交叉引用连接相关文档,避免重复
558 lines
13 KiB
Markdown
558 lines
13 KiB
Markdown
# 接口文档
|
||
|
||
## 概述
|
||
|
||
前后端通信接口契约。WebSocket 承载实时对话,REST API 支撑基础运维。
|
||
|
||
**设计原则**:
|
||
- WebSocket 为主:所有对话数据走 WebSocket
|
||
- REST 为辅:仅用于健康检查、认证、对话管理等低频操作
|
||
- 接口先行:先定义契约,再填充实现——前后端可并行开发
|
||
|
||
## 接口全景
|
||
|
||
```
|
||
浏览器 Go Gateway :8080
|
||
WebSocket Client <--> /ws?token=<jwt> (实时对话,需 JWT 认证)
|
||
HTTP Client --> GET /api/health (健康检查)
|
||
HTTP Client <--> POST /api/auth/* (注册/登录/刷新/登出)
|
||
HTTP Client <--> GET/POST/PATCH/DELETE (对话 CRUD)
|
||
/api/conversations/*
|
||
HTTP Client <--> GET /api/conversations/:id (历史消息)
|
||
/messages
|
||
```
|
||
|
||
---
|
||
|
||
## 一、WebSocket 协议
|
||
|
||
连接地址:`ws://localhost:8080/ws?token=<access_token>&conversation_id=<uuid>`
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
|------|------|------|
|
||
| `token` | 是 | JWT access_token,缺失或无效时返回 401 |
|
||
| `conversation_id` | 否 | 恢复已有对话;省略则创建新对话 |
|
||
|
||
### 消息格式约定
|
||
|
||
所有 WebSocket 消息均为 JSON 文本帧,统一结构:
|
||
|
||
```typescript
|
||
interface WsMessage {
|
||
type: string; // 消息类型,必填
|
||
request_id?: string; // 可选,用于请求-响应关联
|
||
timestamp?: number; // 可选,毫秒时间戳
|
||
[key: string]: any; // 类型特定字段
|
||
}
|
||
```
|
||
|
||
### 客户端 → 服务端消息
|
||
|
||
#### `query` — 发起一次视觉对话
|
||
|
||
```typescript
|
||
interface QueryMessage {
|
||
type: "query";
|
||
request_id: string; // 客户端生成的 UUID
|
||
image: string; // Base64 编码的 JPEG 图像(不含 data: 前缀)
|
||
audio: string; // Base64 编码的音频片段(PCM 16kHz),文本输入时为空字符串
|
||
text?: string; // 用户手动输入的文本(有值时跳过 STT,直接使用此文本)
|
||
mime_type?: string; // 音频格式,默认 "audio/pcm"
|
||
}
|
||
```
|
||
|
||
#### `config` — 更新会话配置
|
||
|
||
```typescript
|
||
interface ConfigMessage {
|
||
type: "config";
|
||
payload: {
|
||
tts_enabled?: boolean; // 是否开启语音合成,默认 true
|
||
detail_level?: "low" | "high"; // 图像精度,默认 "low"
|
||
language?: string; // 交互语言,默认 "zh-CN"
|
||
scenario?: string; // 场景模式:free_chat / interviewer / english_teacher / debate / interpreter
|
||
};
|
||
}
|
||
```
|
||
|
||
#### `interrupt` — 打断当前回复
|
||
|
||
```typescript
|
||
interface InterruptMessage {
|
||
type: "interrupt";
|
||
request_id?: string; // 可选,当前实现不使用此字段,服务端始终取消当前活跃请求
|
||
}
|
||
```
|
||
|
||
#### `ping` — 心跳保活
|
||
|
||
```typescript
|
||
interface PingMessage {
|
||
type: "ping";
|
||
}
|
||
```
|
||
|
||
### 服务端 → 客户端消息
|
||
|
||
#### `connected` — 连接建立确认
|
||
|
||
```typescript
|
||
interface ConnectedMessage {
|
||
type: "connected";
|
||
session_id: string;
|
||
conversation_id: string;
|
||
config: {
|
||
tts_enabled: boolean;
|
||
detail_level: "low" | "high";
|
||
language: string;
|
||
scenario: string;
|
||
};
|
||
}
|
||
```
|
||
|
||
#### `stt_result` — 语音识别结果
|
||
|
||
```typescript
|
||
interface SttResultMessage {
|
||
type: "stt_result";
|
||
request_id: string;
|
||
text: string; // 识别出的文本
|
||
is_final: boolean; // 当前实现始终为 true
|
||
}
|
||
```
|
||
|
||
#### `llm_chunk` — LLM 流式响应片段
|
||
|
||
```typescript
|
||
interface LlmChunkMessage {
|
||
type: "llm_chunk";
|
||
request_id: string;
|
||
content: string; // 当前 token 片段
|
||
}
|
||
```
|
||
|
||
#### `llm_done` — LLM 响应完成
|
||
|
||
```typescript
|
||
interface LlmDoneMessage {
|
||
type: "llm_done";
|
||
request_id: string;
|
||
full_text: string; // 完整响应文本
|
||
}
|
||
```
|
||
|
||
#### `tts_audio` — TTS 音频片段
|
||
|
||
音频流式推送,每个消息携带一个句子的音频数据。
|
||
|
||
```typescript
|
||
interface TtsAudioMessage {
|
||
type: "tts_audio";
|
||
request_id: string;
|
||
audio: string; // Base64 编码的音频数据
|
||
format: string; // 音频格式
|
||
sample_rate: number; // 采样率(Hz)
|
||
sequence: number; // 句子序号,从 0 开始递增
|
||
is_final: boolean; // 是否为最后一个句子
|
||
}
|
||
```
|
||
|
||
**音频格式约束**:
|
||
|
||
| 字段 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| `format` | `"pcm"` | 线性 PCM,小端序 |
|
||
| `sample_rate` | `24000` | 24kHz 采样率 |
|
||
| 位深度 | 16-bit | 单声道 |
|
||
| 句子划分 | 按标点符号(。!?;:)分割 | 服务端按句分割 LLM 响应,并行合成 |
|
||
|
||
#### `error` — 错误通知
|
||
|
||
```typescript
|
||
interface ErrorMessage {
|
||
type: "error";
|
||
request_id?: string; // 关联的请求 ID,全局错误时为空
|
||
code: string; // 错误码,见下文错误码表
|
||
message: string; // 人类可读的错误描述
|
||
details?: any; // 可选的详细错误信息
|
||
}
|
||
```
|
||
|
||
#### `pong` — 心跳响应
|
||
|
||
```typescript
|
||
interface PongMessage {
|
||
type: "pong";
|
||
}
|
||
```
|
||
|
||
### 连接管理
|
||
|
||
- **心跳机制**:客户端每 30 秒发送 `ping`,服务端回复 `pong`;60 秒无活动则服务端断开连接
|
||
- **重连策略**:客户端断线后指数退避重连(1s → 2s → 4s → ... → 最大 30s)
|
||
- **并发控制**:同一连接同时只能有一个活跃的 `query` 请求;新请求到来时自动取消旧请求
|
||
|
||
---
|
||
|
||
## 二、REST API
|
||
|
||
所有 REST 端点均使用 JSON 格式。
|
||
|
||
### 2.1 健康检查
|
||
|
||
#### `GET /api/health`
|
||
|
||
检查服务健康状态。
|
||
|
||
**响应**:
|
||
```json
|
||
{
|
||
"status": "healthy",
|
||
"timestamp": "2024-01-15T10:30:00Z",
|
||
"dependencies": {
|
||
"database": "healthy",
|
||
"redis": "healthy"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 认证 API
|
||
|
||
#### `POST /api/auth/register` — 用户注册
|
||
|
||
**请求**:
|
||
```json
|
||
{
|
||
"username": "alice",
|
||
"email": "alice@example.com",
|
||
"password": "SecurePass123!"
|
||
}
|
||
```
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"user": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"username": "alice",
|
||
"email": "alice@example.com",
|
||
"created_at": "2024-01-15T10:30:00Z"
|
||
},
|
||
"access_token": "eyJhbGc...",
|
||
"refresh_token": "eyJhbGc...",
|
||
"expires_in": 7200
|
||
}
|
||
```
|
||
|
||
**错误**:
|
||
- `400 INVALID_INPUT`: 参数验证失败
|
||
- `409 USER_EXISTS`: 用户名或邮箱已存在
|
||
|
||
#### `POST /api/auth/login` — 用户登录
|
||
|
||
**请求**:
|
||
```json
|
||
{
|
||
"username": "alice",
|
||
"password": "SecurePass123!"
|
||
}
|
||
```
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"user": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"username": "alice",
|
||
"email": "alice@example.com"
|
||
},
|
||
"access_token": "eyJhbGc...",
|
||
"refresh_token": "eyJhbGc...",
|
||
"expires_in": 7200
|
||
}
|
||
```
|
||
|
||
**错误**:
|
||
- `400 INVALID_INPUT`: 参数缺失
|
||
- `401 INVALID_CREDENTIALS`: 用户名或密码错误
|
||
|
||
#### `POST /api/auth/refresh` — 刷新 Access Token
|
||
|
||
**请求头**:
|
||
```
|
||
Authorization: Bearer <refresh_token>
|
||
```
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"access_token": "eyJhbGc...",
|
||
"refresh_token": "eyJhbGc...",
|
||
"expires_in": 7200
|
||
}
|
||
```
|
||
|
||
**错误**:
|
||
- `401 INVALID_TOKEN`: Refresh Token 无效或过期
|
||
|
||
#### `POST /api/auth/logout` — 用户登出
|
||
|
||
**请求头**:
|
||
```
|
||
Authorization: Bearer <access_token>
|
||
```
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"message": "Logged out successfully"
|
||
}
|
||
```
|
||
|
||
### 2.3 对话管理 API
|
||
|
||
所有端点均需 JWT 认证(`Authorization: Bearer <access_token>`)。
|
||
|
||
#### `GET /api/conversations` — 获取对话列表
|
||
|
||
**查询参数**:
|
||
- `page`: 页码,从 1 开始,默认 1
|
||
- `page_size`: 每页条数,默认 20,最大 100
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"conversations": [
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"title": "关于植物的对话",
|
||
"created_at": "2024-01-15T10:30:00Z",
|
||
"updated_at": "2024-01-15T11:45:00Z",
|
||
"message_count": 12
|
||
}
|
||
],
|
||
"total": 42,
|
||
"page": 1,
|
||
"page_size": 20
|
||
}
|
||
```
|
||
|
||
#### `POST /api/conversations` — 创建新对话
|
||
|
||
**请求**:
|
||
```json
|
||
{
|
||
"title": "新的对话"
|
||
}
|
||
```
|
||
|
||
**响应**(201 Created):
|
||
```json
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"title": "新的对话",
|
||
"created_at": "2024-01-15T10:30:00Z",
|
||
"updated_at": "2024-01-15T10:30:00Z",
|
||
"message_count": 0
|
||
}
|
||
```
|
||
|
||
#### `GET /api/conversations/:id` — 获取对话详情
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"title": "关于植物的对话",
|
||
"created_at": "2024-01-15T10:30:00Z",
|
||
"updated_at": "2024-01-15T11:45:00Z",
|
||
"message_count": 12
|
||
}
|
||
```
|
||
|
||
**错误**:
|
||
- `404 NOT_FOUND`: 对话不存在或无权访问
|
||
|
||
#### `PATCH /api/conversations/:id` — 更新对话
|
||
|
||
**请求**:
|
||
```json
|
||
{
|
||
"title": "修改后的标题"
|
||
}
|
||
```
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"title": "修改后的标题",
|
||
"created_at": "2024-01-15T10:30:00Z",
|
||
"updated_at": "2024-01-15T12:00:00Z",
|
||
"message_count": 12
|
||
}
|
||
```
|
||
|
||
#### `DELETE /api/conversations/:id` — 删除对话
|
||
|
||
**响应**(204 No Content):无响应体
|
||
|
||
**错误**:
|
||
- `404 NOT_FOUND`: 对话不存在或无权访问
|
||
|
||
#### `GET /api/conversations/:id/messages` — 获取对话消息
|
||
|
||
**查询参数**:
|
||
- `page`: 页码,从 1 开始,默认 1
|
||
- `page_size`: 每页条数,默认 50,最大 100
|
||
|
||
**响应**(200 OK):
|
||
```json
|
||
{
|
||
"messages": [
|
||
{
|
||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"role": "user",
|
||
"content": "这是什么植物?",
|
||
"image_url": "/api/images/abc123.jpg",
|
||
"created_at": "2024-01-15T10:30:00Z"
|
||
},
|
||
{
|
||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"role": "assistant",
|
||
"content": "这是一株向日葵...",
|
||
"created_at": "2024-01-15T10:30:15Z"
|
||
}
|
||
],
|
||
"total": 12,
|
||
"page": 1,
|
||
"page_size": 50
|
||
}
|
||
```
|
||
|
||
**消息字段说明**:
|
||
- `role`: `"user"` 或 `"assistant"`
|
||
- `image_url`: 仅 `user` 消息可能包含,指向存储的图像
|
||
- `content`: 消息文本内容
|
||
|
||
---
|
||
|
||
## 三、错误码表
|
||
|
||
所有错误均使用以下格式:
|
||
|
||
```json
|
||
{
|
||
"code": "ERROR_CODE",
|
||
"message": "Human-readable error description",
|
||
"details": {}
|
||
}
|
||
```
|
||
|
||
### WebSocket 错误码
|
||
|
||
| 错误码 | 说明 | HTTP 状态码(若适用)|
|
||
|--------|------|---------------------|
|
||
| `INVALID_MESSAGE` | 消息格式错误或缺少必填字段 | - |
|
||
| `SESSION_NOT_FOUND` | 会话不存在 | - |
|
||
| `RATE_LIMITED` | 请求频率过高 | 429 |
|
||
| `IMAGE_TOO_LARGE` | 图像超过大小限制(5MB)| - |
|
||
| `AUDIO_TOO_LARGE` | 音频超过大小限制(10MB)| - |
|
||
| `STT_ERROR` | 语音识别服务错误 | - |
|
||
| `LLM_ERROR` | LLM 服务错误 | - |
|
||
| `LLM_TIMEOUT` | LLM 响应超时(60 秒)| - |
|
||
| `TTS_ERROR` | 语音合成服务错误 | - |
|
||
| `CONCURRENT_REQUEST` | 同一连接已有进行中的请求 | - |
|
||
| `INTERNAL_ERROR` | 服务器内部错误 | 500 |
|
||
|
||
### REST API 错误码
|
||
|
||
| 错误码 | 说明 | HTTP 状态码 |
|
||
|--------|------|-------------|
|
||
| `INVALID_INPUT` | 请求参数验证失败 | 400 |
|
||
| `INVALID_TOKEN` | JWT Token 无效或过期 | 401 |
|
||
| `INVALID_CREDENTIALS` | 用户名或密码错误 | 401 |
|
||
| `UNAUTHORIZED` | 未认证或认证失败 | 401 |
|
||
| `FORBIDDEN` | 无权访问资源 | 403 |
|
||
| `NOT_FOUND` | 资源不存在 | 404 |
|
||
| `USER_EXISTS` | 用户名或邮箱已存在 | 409 |
|
||
| `RATE_LIMITED` | 请求频率过高 | 429 |
|
||
| `INTERNAL_ERROR` | 服务器内部错误 | 500 |
|
||
| `SERVICE_UNAVAILABLE` | 依赖服务不可用 | 503 |
|
||
|
||
---
|
||
|
||
## 四、数据模型
|
||
|
||
### 用户(User)
|
||
|
||
```typescript
|
||
interface User {
|
||
id: string; // UUID
|
||
username: string; // 用户名,唯一
|
||
email: string; // 邮箱,唯一
|
||
created_at: string; // ISO 8601 时间戳
|
||
updated_at: string; // ISO 8601 时间戳
|
||
}
|
||
```
|
||
|
||
### 对话(Conversation)
|
||
|
||
```typescript
|
||
interface Conversation {
|
||
id: string; // UUID
|
||
user_id: string; // 所属用户 ID
|
||
title: string; // 对话标题
|
||
created_at: string; // ISO 8601 时间戳
|
||
updated_at: string; // ISO 8601 时间戳
|
||
message_count: number; // 消息数量
|
||
}
|
||
```
|
||
|
||
### 消息(Message)
|
||
|
||
```typescript
|
||
interface Message {
|
||
id: string; // UUID
|
||
conversation_id: string; // 所属对话 ID
|
||
role: "user" | "assistant";
|
||
content: string; // 消息文本内容
|
||
image_url?: string; // 可选,用户消息的关联图像 URL
|
||
created_at: string; // ISO 8601 时间戳
|
||
}
|
||
```
|
||
|
||
### JWT Token 载荷
|
||
|
||
**Access Token**(有效期 120 分钟):
|
||
```json
|
||
{
|
||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"username": "alice",
|
||
"type": "access",
|
||
"exp": 1705318200,
|
||
"iat": 1705311000
|
||
}
|
||
```
|
||
|
||
**Refresh Token**(有效期 7 天):
|
||
```json
|
||
{
|
||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"type": "refresh",
|
||
"exp": 1705915800,
|
||
"iat": 1705311000
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 附录:版本历史
|
||
|
||
- **v1.0**(2024-01-15):初始版本,定义 WebSocket 协议和 REST API
|
||
- **v1.1**(2024-01-20):新增文本输入模式(`query.text` 字段)
|
||
- **v1.2**(2024-01-25):新增场景模式配置(`config.scenario` 字段)
|
||
- **v2.0**(2026-06-21):重构为纯接口契约规范,移除实现细节
|