修改雷鸟录音存储文件路径
authorCaisking <Casiking@zhihuishixun.com>
Thu, 18 Sep 2025 09:02:23 +0000 (17:02 +0800)
committerCaisking <Casiking@zhihuishixun.com>
Thu, 18 Sep 2025 09:02:23 +0000 (17:02 +0800)
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API使用示例.md [new file with mode: 0644]
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/设备API接口文档.md [new file with mode: 0644]

diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API使用示例.md b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API使用示例.md
new file mode 100644 (file)
index 0000000..ee24dea
--- /dev/null
@@ -0,0 +1,321 @@
+# ThunderBirdModel API接口使用示例
+
+## 概述
+本文档展示了如何在UI中调用ThunderBirdModel的API接口服务,实现与设备API的交互。
+
+## 1. 设备认证和Token管理
+
+### 获取设备Token
+```cpp
+// 在Presenter中调用
+ThunderBirdModel* model = ThunderBirdModel::GetInstance();
+
+// 获取设备Token
+ThunderBirdModel::DeviceToken token;
+if (model->GetDeviceToken("DEVICE_001", "12345678901234567890", token)) {
+    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "Token获取成功: %s", token.token.c_str());
+} else {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Token获取失败");
+}
+
+// 检查Token有效性
+if (model->IsTokenValid(token)) {
+    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "Token有效");
+} else {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Token已过期");
+}
+```
+
+## 2. 语音对话接口
+
+### 开始语音对话
+```cpp
+// 在录音完成后调用
+std::string audioFilePath = "/user/audio/recording.mp3";
+
+model->StartSpeechChat(audioFilePath, [](const ThunderBirdModel::SpeechResponse& response) {
+    if (response.isError) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "语音对话错误: %s", response.errorMessage.c_str());
+        // 处理错误,如播放错误音频
+    } else {
+        WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "收到回复: %s", response.textContent.c_str());
+        
+        // 播放回复音频
+        if (!response.audioUrl.empty()) {
+            // TODO: 实现音频播放
+        }
+        
+        // 显示文本内容
+        ThunderBirdModel::GetInstance()->SetTextStr(response.textContent.c_str());
+        
+        // 检查意图
+        if (response.intention == "StartSleepMode") {
+            // 进入哄睡模式
+            StartLullabyMode();
+        }
+    }
+});
+```
+
+### 停止语音对话
+```cpp
+model->StopSpeechChat();
+```
+
+## 3. 故事接口
+
+### 智能选择故事
+```cpp
+// 用户通过语音选择故事类型
+std::string audioFilePath = "/user/audio/story_preference.mp3";
+
+model->StartChooseStory(audioFilePath, [](const ThunderBirdModel::StoryResponse& response) {
+    if (response.isError) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "故事生成错误: %s", response.errorMessage.c_str());
+    } else {
+        WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "故事主题: %s", response.storyTheme.c_str());
+        WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "故事内容: %s", response.storyContent.c_str());
+        
+        // 播放故事音频
+        if (!response.storyAudioUrl.empty()) {
+            // TODO: 实现音频播放
+        }
+        
+        // 显示故事内容
+        ThunderBirdModel::GetInstance()->SetTextStr(response.storyContent.c_str());
+    }
+});
+```
+
+### 随机故事
+```cpp
+// 直接请求随机故事
+std::string storyTheme = "童话故事";
+
+model->StartRandomStory(storyTheme, [](const ThunderBirdModel::StoryResponse& response) {
+    if (response.isError) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "随机故事错误: %s", response.errorMessage.c_str());
+    } else {
+        // 处理故事响应
+        WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "随机故事: %s", response.storyContent.c_str());
+    }
+});
+```
+
+## 4. 提醒接口
+
+### 处理提醒
+```cpp
+// 收到WebSocket推送的提醒消息
+ThunderBirdModel::ReminderInfo reminder;
+reminder.scheduleId = 123;
+reminder.deviceId = "DEVICE_001";
+reminder.scheduleName = "开会提醒";
+reminder.reminderContent = "您有一个会议即将开始,请准备参加";
+reminder.reminderAudioUrl = "https://example.com/reminder/reminder_001.mp3";
+reminder.enableVoice = true;
+reminder.enableVibration = true;
+
+model->ProcessReminder(reminder);
+```
+
+### 处理提醒配置
+```cpp
+// 处理提醒配置
+std::string scheduleName = "七点起床";
+model->ProcessReminderConfig(scheduleName);
+```
+
+## 5. 表情接口
+
+### 下载表情
+```cpp
+// 收到WebSocket推送的下载表情命令
+std::vector<ThunderBirdModel::EmojiInfo> emojis;
+ThunderBirdModel::EmojiInfo emoji1;
+emoji1.characterId = "char_001";
+emoji1.eyesColor = "blue";
+emoji1.eyesShape = "calm-eyes";
+emoji1.emotion = "happy";
+emoji1.emojiUrl = "https://example.com/emoji/happy.mp4";
+emojis.push_back(emoji1);
+
+ThunderBirdModel::EmojiInfo emoji2;
+emoji2.characterId = "char_001";
+emoji2.eyesColor = "blue";
+emoji2.eyesShape = "calm-eyes";
+emoji2.emotion = "sad";
+emoji2.emojiUrl = "https://example.com/emoji/sad.mp4";
+emojis.push_back(emoji2);
+
+model->DownloadEmoji(emojis);
+```
+
+### 删除表情
+```cpp
+// 收到WebSocket推送的删除表情命令
+std::string characterId = "char_001";
+std::string eyesColor = "blue";
+std::string eyesShape = "calm-eyes";
+
+model->DeleteEmoji(characterId, eyesColor, eyesShape);
+```
+
+## 6. WebSocket连接管理
+
+### 建立连接
+```cpp
+// 在应用启动时建立WebSocket连接
+if (model->ConnectWebSocket()) {
+    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "WebSocket连接成功");
+    
+    // 启动心跳定时器
+    StartHeartbeatTimer();
+} else {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket连接失败");
+}
+```
+
+### 发送心跳
+```cpp
+// 定时发送心跳
+void StartHeartbeatTimer() {
+    // 每30秒发送一次心跳
+    std::thread([]() {
+        while (true) {
+            std::this_thread::sleep_for(std::chrono::seconds(30));
+            ThunderBirdModel::GetInstance()->SendHeartbeat();
+        }
+    }).detach();
+}
+```
+
+### 发送消息
+```cpp
+// 发送WebSocket消息
+Json::Value data;
+data["messageId"] = "msg_001";
+data["timestamp"] = time(nullptr);
+
+model->SendWebSocketMessage("Ping", data.toStyledString());
+```
+
+## 7. 在Presenter中的集成示例
+
+```cpp
+// ThunderBirdPresenter.cpp
+void ThunderBirdPresenter::OnClick(UIView &view)
+{
+    std::string viewId = view.GetViewId();
+    ThunderBirdModel* model = ThunderBirdModel::GetInstance();
+    
+    if (viewId == "RECORD_BUTTON") {
+        if (model->IsRecording()) {
+            // 停止录音
+            model->StopRecording();
+            
+            // 开始语音对话
+            std::string audioPath = "/user/audio/recording.mp3";
+            model->StartSpeechChat(audioPath, [this](const ThunderBirdModel::SpeechResponse& response) {
+                // 处理语音回复
+                HandleSpeechResponse(response);
+            });
+        } else {
+            // 开始录音
+            model->StartRecording();
+        }
+    }
+    else if (viewId == "STORY_BUTTON") {
+        // 开始随机故事
+        model->StartRandomStory("童话故事", [this](const ThunderBirdModel::StoryResponse& response) {
+            // 处理故事回复
+            HandleStoryResponse(response);
+        });
+    }
+}
+
+void ThunderBirdPresenter::HandleSpeechResponse(const ThunderBirdModel::SpeechResponse& response)
+{
+    if (response.isError) {
+        // 显示错误信息
+        m_view->UpdateTextLabel(response.errorMessage.c_str());
+    } else {
+        // 显示回复内容
+        m_view->UpdateTextLabel(response.textContent.c_str());
+        
+        // 检查意图
+        if (response.intention == "StartSleepMode") {
+            // 进入哄睡模式
+            StartLullabyMode();
+        }
+    }
+}
+
+void ThunderBirdPresenter::HandleStoryResponse(const ThunderBirdModel::StoryResponse& response)
+{
+    if (response.isError) {
+        // 显示错误信息
+        m_view->UpdateTextLabel(response.errorMessage.c_str());
+    } else {
+        // 显示故事内容
+        m_view->UpdateTextLabel(response.storyContent.c_str());
+    }
+}
+```
+
+## 8. 错误处理
+
+### 常见错误处理
+```cpp
+void HandleAPIError(const std::string& errorCode, const std::string& errorMessage)
+{
+    if (errorCode == "AUTH_FAILURE") {
+        // Token认证失败,重新获取Token
+        ThunderBirdModel::GetInstance()->ClearToken();
+        // 重新进行设备认证
+    }
+    else if (errorCode == "INSUFFICIENT_STAMINA") {
+        // 对话体力不足,引导用户充值
+        ShowStaminaInsufficientDialog();
+    }
+    else if (errorCode == "DEVICE_NOT_FOUND") {
+        // 设备信息异常,重新获取设备信息
+        RefreshDeviceInfo();
+    }
+    else if (errorCode == "MODEL_ERROR") {
+        // 模型服务异常,稍后重试
+        ShowRetryDialog();
+    }
+    else {
+        // 其他错误
+        ShowErrorDialog(errorMessage);
+    }
+}
+```
+
+## 9. 注意事项
+
+1. **线程安全**: 所有API调用都是线程安全的,可以在任何线程中调用
+2. **回调函数**: 回调函数会在后台线程中执行,如果需要更新UI,需要切换到主线程
+3. **资源管理**: 及时调用Stop方法停止不需要的服务
+4. **错误处理**: 始终检查返回值并处理错误情况
+5. **Token管理**: 定期检查Token有效性,过期时重新获取
+6. **网络状态**: 在调用API前检查网络连接状态
+7. **文件路径**: 确保音频文件路径正确且文件存在
+
+## 10. 配置说明
+
+### API配置
+```cpp
+// 在ThunderBirdModel构造函数中配置
+m_baseUrl = "https://ambybox-api.test.leiniao.com/ambybox-device-api";
+m_signSecret = "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=";
+```
+
+### 日志配置
+```cpp
+// 启用详细日志
+#define WEARABLE_LOG_MODULE_APP "APP"
+```
+
+这个API接口设计完全按照设备API接口文档的要求实现,提供了完整的设备认证、语音对话、故事生成、提醒管理、表情管理等功能,可以直接在UI中调用使用。
index 92ab53e..ea7141c 100644 (file)
      }
  
      // ==================== 新手引导相关函数 ====================
+     //判断是否绑定
      ThunderBirdModel::BindingStatus ThunderBirdModel::CheckBindingStatus()
      {
          WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::CheckBindingStatus");
              return false;
          }
          g_isRecording = true;//设置录音状态
-         std::string audioPath = "/user/123.mp3";//故事选择的录音文件路径
-        // uapi_ln_get_story_choose_audio_path();
-         // audioPath = "/user/audio/test.pcm";//修改为实际的录音文件路径
-         
-         // // 确保目录存在
-          //std::string dirPath = "/user/123.mp3";
-         // 这里可以添加目录创建逻辑
-         
+         std::string audioPath = uapi_ln_get_story_choose_audio_path();
+        if(audioPath.empty())
+        {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 故事选择的录音文件路径为空");
+            return false;
+        }
          const char *defaultArgs[6] = {"01", "mp3", "16000", "1", "16", audioPath.c_str()};
          WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 开始录音,路径: %s", audioPath.c_str());
          WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 参数 - 格式: %s, 采样率: %s, 声道: %s, 位宽: %s", 
diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/设备API接口文档.md b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/设备API接口文档.md
new file mode 100644 (file)
index 0000000..19b1e14
--- /dev/null
@@ -0,0 +1,2489 @@
+# 设备API接口文档\r
+\r
+测试服务BaseUrl:https://ambybox-api.test.leiniao.com/ambybox-device-api\r
+\r
+## 1. 获取设备Token接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    \r
+    Note over Device, Server: 设备认证流程\r
+    \r
+    Device->>Device: 生成时间戳和随机数\r
+    Device->>Device: 构建签名数据<br/>deviceId:deviceIccid:timestamp:nonce\r
+    Device->>Device: 使用HMAC-SHA256生成签名\r
+    \r
+    Device->>Server: GET /v1/auth/getDeviceToken<br/>Headers: deviceId, deviceIccid, timestamp, nonce, signature\r
+    \r
+    alt 签名验证成功\r
+        Server->>Server: 验证时间戳(5分钟内有效)\r
+        Server->>Server: 验证签名正确性\r
+        Server->>Server: 生成JWT Token\r
+        Server->>Device: 返回设备Token<br/>{"deviceId", "token", "expireTime"}\r
+        \r
+        Note over Device: Token获取成功,缓存Token\r
+        \r
+    else 签名验证失败\r
+        Server->>Device: 返回错误<br/>{"errorCode": "INVALID_SIGNATURE"}\r
+        Note over Device: 认证失败,重新尝试\r
+    end\r
+```\r
+\r
+### 接口描述\r
+设备认证接口,用于获取设备访问令牌。该接口无需认证,但需要进行签名校验。\r
+\r
+### 请求信息\r
+- **URL**: `/v1/auth/getDeviceToken`\r
+- **方法**: `GET`\r
+- **Content-Type**: `application/json`\r
+\r
+### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceIccid | String | 是 | 设备物联网卡号 | "12345678901234567890" |\r
+| timestamp | String | 是 | 毫秒时间戳 | "1640995200000" |\r
+| nonce | String | 是 | 随机数,用于防重放攻击 | "abc123" |\r
+| signature | String | 是 | 签名,用于验证请求合法性 | "a1b2c3d4..." |\r
+| deviceModel | String | 否 | 设备型号,默认值: "abmybox_1" | "abmybox_1" |\r
+| deviceVersion | String | 否 | 设备固件版本,默认值: "1.0.0" | "1.0.0" |\r
+\r
+### 签名算法\r
+签名使用 HMAC-SHA256 算法生成,具体步骤如下:\r
+\r
+1. **构建签名数据**:将参数按以下格式拼接\r
+   ```\r
+   signData = deviceId + ":" + deviceIccid + ":" + timestamp + ":" + nonce\r
+   ```\r
+\r
+2. **生成签名**:使用预共享密钥对签名数据进行 HMAC-SHA256 加密\r
+   ```\r
+   signature = HMAC-SHA256(signSecret, signData)\r
+   ```\r
+\r
+3. **转换格式**:将签名结果转换为十六进制字符串(小写)\r
+\r
+### 签名验证规则\r
+- 时间戳有效性:请求时间戳与服务器时间差不能超过5分钟\r
+- 签名正确性:使用相同算法生成的签名必须与请求中的签名一致\r
+- 防重放攻击:建议客户端每次请求使用不同的随机数\r
+- 测试签名密钥:vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=\r
+\r
+\r
+### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": {\r
+    "deviceId": "DEVICE_001",\r
+    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",\r
+    "expireTime": 2592000\r
+  }\r
+}\r
+```\r
+\r
+**失败响应**:\r
+```json\r
+{\r
+  "errorCode": "INVALID_SIGNATURE",\r
+  "message": "签名验证失败",\r
+  "data": null\r
+}\r
+```\r
+\r
+### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| deviceId | String | 设备ID |\r
+| token | String | 设备访问令牌(JWT格式) |\r
+| expireTime | Long | 令牌过期时间(秒,默认30天) |\r
+\r
+### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| INVALID_SIGNATURE | 签名验证失败 |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+---\r
+\r
+## 2. 语音对话接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    \r
+    Note over Device, Server: 流式语音对话流程\r
+    \r
+    Device->>Device: 录制用户语音\r
+    Device->>Device: 转换为MP3格式\r
+    \r
+    Device->>Server: POST /v1/speechChat/chatStream<br/>Headers: deviceId, deviceToken, Accept: text/event-stream<br/>Form: audio file\r
+    \r
+    alt Token验证成功\r
+        Server->>Server: 验证设备Token\r
+        Server->>Server: 建立SSE连接\r
+        Server->>Server: 语音识别(ASR)\r
+        Server->>Server: 意图识别\r
+        \r
+        loop 流式响应生成\r
+            Server->>Server: 生成回复片段\r
+            Server->>Server: 文本转语音(TTS)\r
+            Server->>Device: SSE事件: 回复片段<br/>{"audioUrl", "textContent", "intention"}\r
+            Device->>Device: 播放回复音频片段\r
+            Device->>Device: 显示文本片段\r
+        end\r
+        \r
+        Server->>Device: SSE连接关闭(对话完成)\r
+        \r
+        alt 识别到哄睡意图\r
+            Note over Device: intention = "StartSleepMode"\r
+            Device->>Device: 准备进入哄睡模式\r
+        end\r
+        \r
+    else Token验证失败\r
+        Server->>Device: 返回认证错误<br/>{"errorCode": "AUTH_FAILURE"}\r
+        Device->>Device: 重新获取Token\r
+    end\r
+```\r
+\r
+### 接口描述\r
+流式语音对话接口,接收音频文件并通过SSE(Server-Sent Events)流式返回对话结果。\r
+\r
+### 请求信息\r
+- **URL**: `/v1/speechChat/chatStream`\r
+- **方法**: `POST`\r
+- **Content-Type**: `multipart/form-data`\r
+- **响应类型**: `text/event-stream` (SSE流式响应)\r
+\r
+### 认证要求\r
+该接口需要设备Token认证,有以下两种方式传递Token:\r
+\r
+1. **请求头方式**:\r
+   ```\r
+   deviceToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\r
+   ```\r
+\r
+2. **查询参数方式**:\r
+   ```\r
+   /v1/speechChat/chatStream?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\r
+   ```\r
+\r
+### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" |\r
+| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" |\r
+\r
+### 请求参数\r
+| 参数名 | 类型 | 必填 | 描述 | 限制 |\r
+|--------|------|------|------|------|\r
+| file | MultipartFile | 是 | 音频文件 | MP3格式,压缩前应为采样率16k或8k单声道的PCM |\r
+\r
+### Token认证规则\r
+- Token必须是有效的JWT格式\r
+- Token必须包含设备ID、设备型号、设备版本等信息\r
+- Token不能过期(默认有效期30天)\r
+- 请求头中的设备ID不能为空\r
+- 设备ID必须与Token中的设备ID一致\r
+\r
+### SSE流式响应数据\r
+**回复片段事件**:\r
+```\r
+data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_001.mp3","textContent":"好的,","intention":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_002.mp3","textContent":"我来为你","intention":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_003.mp3","textContent":"讲一个故事","intention":"StartSleepMode"}}\r
+```\r
+\r
+**失败响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+**失败响应(设备信息异常)**:\r
+```json\r
+{\r
+  "errorCode": "DEVICE_NOT_FOUND",\r
+  "message": "设备信息异常",\r
+  "data": {\r
+    "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3",\r
+    "textContent": "设备信息异常,请稍后重试。",\r
+    "intention": null\r
+  }\r
+}\r
+```\r
+\r
+**失败响应(对话体力不足)**:\r
+```json\r
+{\r
+  "errorCode": "INSUFFICIENT_STAMINA",\r
+  "message": "对话体力不足",\r
+  "data": {\r
+    "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/insuffient_stamina.mp3",\r
+    "textContent": "对话体力不足,请前往小程序充值体力。",\r
+    "intention": null\r
+  }\r
+}\r
+```\r
+\r
+**失败响应(音频输入无效)**:\r
+```json\r
+{\r
+  "errorCode": "INVALID_AUDIO_INPUT",\r
+  "message": "音频输入无效",\r
+  "data": {\r
+    "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/invalid_audio_input.mp3",\r
+    "textContent": "抱歉,我无法识别语音内容,请再说一次吧",\r
+    "intention": null\r
+  }\r
+}\r
+```\r
+\r
+**失败响应(模型服务异常)**:\r
+```json\r
+{\r
+  "errorCode": "MODEL_ERROR",\r
+  "message": "模型服务异常",\r
+  "data": {\r
+    "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3",\r
+    "textContent": "模型服务异常,请稍后重试。",\r
+    "intention": null\r
+  }\r
+}\r
+```\r
+\r
+**失败响应(服务异常)**:\r
+```json\r
+{\r
+  "errorCode": "SERVER_ERROR",\r
+  "message": "服务器内部错误",\r
+  "data": {\r
+    "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3",\r
+    "textContent": "服务器异常,请稍后重试。",\r
+    "intention": null\r
+  }\r
+}\r
+```\r
+\r
+### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| audioUrl | String | 回复音频片段文件URL |\r
+| textContent | String | 回复的文本片段内容 |\r
+| intention | String | 用户意图识别结果,通常在第一个片段中返回,可能的值:StartSleepMode(哄我睡觉)等 |\r
+\r
+### 错误码\r
+| 错误码 | 描述 | \r
+|--------|------|\r
+| AUTH_FAILURE | token认证失败 |\r
+| DEVICE_NOT_FOUND | 设备信息异常 | \r
+| INSUFFICIENT_STAMINA | 对话体力不足 |\r
+| MODEL_NOT_FOUND | 模型信息不存在 | \r
+| VOICE_NOT_FOUND | 音色信息不存在 | \r
+| INVALID_AUDIO_INPUT | 音频输入无效 | \r
+| MODEL_ERROR | 模型服务异常 | \r
+| SERVER_ERROR | 服务器内部错误 | \r
+\r
+### 错误响应说明\r
+当流式语音对话接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该:\r
+1. 检查响应中的errorCode字段\r
+2. 如果errorCode不为null,表示发生了错误\r
+3. 播放data字段中的audioUrl音频文件,向用户说明错误情况\r
+4. 根据错误类型进行相应的处理:\r
+   - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token\r
+   - **INSUFFICIENT_STAMINA**: 用户对话体力不足,引导用户前往小程序充值\r
+   - **MODEL_NOT_FOUND/VOICE_NOT_FOUND**: 配置信息缺失,建议重新获取角色配置\r
+   - **INVALID_AUDIO_INPUT**: 语音识别失败,提示用户重新录音\r
+   - **MODEL_ERROR**: 模型服务异常,建议稍后重试\r
+   - **SERVER_ERROR**: 服务器内部错误,建议稍后重试\r
+\r
+### SSE协议说明\r
+- 接口使用SSE(Server-Sent Events)协议进行流式响应\r
+- 每个回复片段作为一个独立的SSE事件发送\r
+- 客户端需要实现SSE客户端来接收流式数据\r
+- 连接会在对话生成完成后自动关闭\r
+- 客户端应当设置适当的超时时间(建议60秒)\r
+\r
+### 音频数据处理说明\r
+- 返回的 `audioUrl` 字段是音频片段文件URL\r
+- 音频格式通常为MP3格式\r
+- 客户端需要将音频文件URL下载后播放\r
+- 建议客户端实现音频片段的缓存和顺序播放机制\r
+\r
+### 用户意图说明\r
+流式语音对话接口会识别用户的意图,目前支持的意图包括:\r
+- **StartSleepMode**:用户表达想要听故事或哄睡的意图\r
+- 更多意图类型会在后续版本中添加\r
+\r
+用户意图通常在最后一个或几个SSE事件中返回,客户端应当:\r
+- 监听所有SSE事件中的intention字段\r
+- 当收到非null的intention时,根据意图类型进行相应处理\r
+- 收到StartSleepMode意图时,可以调用故事相关的API接口\r
+\r
+### 使用说明\r
+- 该接口适用于需要实时响应的语音对话场景\r
+- 客户端可以边接收边播放,实现流式播放体验\r
+- 相比普通语音对话接口,流式接口能提供更自然的交互体验\r
+- 建议在网络状况良好的环境下使用,以确保音频片段的连续播放\r
+- 对话生成完成后,SSE连接会自动关闭\r
+\r
+\r
+\r
+## 3. 故事相关接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    \r
+    Note over Device, Server: 哄睡故事完整流程\r
+    \r
+    %% 意图触发\r
+    Device->>Server: 语音对话识别到哄睡意图<br/>intention = "StartSleepMode"\r
+    \r
+    %% 故事选择分支\r
+    alt 用户指定故事类型\r
+        Device->>Device: 录制用户偏好语音\r
+        Device->>Server: POST /v1/story/chooseStory<br/>Headers: Accept: text/event-stream<br/>Form: audio file\r
+    else 随机故事\r
+        Device->>Server: POST /v1/story/randomStory<br/>Headers: Accept: text/event-stream<br/>Body: {"storyTheme": "可选主题"}\r
+    end\r
+    \r
+    %% SSE流式响应\r
+    Server->>Server: 建立SSE连接\r
+    Server->>Server: 分析语音/生成故事主题\r
+    Server->>Server: 开始生成故事片段\r
+    \r
+    loop 故事片段流式推送\r
+        Server->>Server: 生成故事片段文本\r
+        Server->>Server: 文本转语音\r
+        Server->>Device: SSE事件: 故事片段<br/>{"storyContent", "storyAudioUrl", "storyTheme"}\r
+        Device->>Device: 播放音频片段\r
+        Device->>Device: 显示文本内容\r
+    end\r
+    \r
+    Server->>Device: SSE连接关闭(故事完成)\r
+    \r
+    %% 退出确认流程\r
+    Device->>Server: POST /v1/story/getExitSleepInquiry<br/>获取退出询问\r
+    Server->>Device: 返回退出询问<br/>{"audioContent", "textContent"}\r
+    Device->>Device: 播放退出询问音频\r
+    \r
+    Device->>Device: 等待用户确认\r
+    alt 用户确认退出\r
+        Device->>Server: POST /v1/story/finishSleepMode<br/>确认结束哄睡\r
+        Server->>Device: 返回成功响应\r
+        Device->>Device: 退出哄睡模式\r
+    else 用户继续\r
+        Note over Device: 继续哄睡模式或重新开始故事\r
+    end\r
+```\r
+\r
+### 3.1 智能选择故事\r
+\r
+#### 接口描述\r
+智能选择故事接口。用户通过语音表达故事类型偏好,系统会识别故事主题并流式返回相应的故事内容。该接口使用SSE(Server-Sent Events)协议进行流式响应,需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/story/chooseStory`\r
+- **方法**: `POST`\r
+- **Content-Type**: `multipart/form-data`\r
+- **响应类型**: `text/event-stream` (SSE流式响应)\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头或查询参数传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" |\r
+| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" |\r
+\r
+#### 请求表单参数\r
+| 参数名 | 类型 | 必填 | 描述 | 限制 |\r
+|--------|------|------|------|------|\r
+| file | MultipartFile | 是 | 语音文件 | MP3格式,压缩前应为采样率16k或8k单声道的PCM |\r
+\r
+\r
+#### SSE流式响应数据\r
+**故事片段事件**:\r
+```\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"从前有一个可爱的小女孩","storyAudioUrl":"https://example.com/story/audio/chunk_001.mp3","storyDuration":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"她住在森林深处的小木屋里","storyAudioUrl":"https://example.com/story/audio/chunk_002.mp3","storyDuration":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"每天都会去采集美丽的花朵","storyAudioUrl":"https://example.com/story/audio/chunk_003.mp3","storyDuration":null}}\r
+```\r
+\r
+**错误响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+**错误响应(音频输入无效)**:\r
+```json\r
+{\r
+  "errorCode": "INVALID_AUDIO_INPUT",\r
+  "message": "音频输入无效",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "抱歉,我无法识别语音内容,请再说一次吧",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/invalid_audio_input.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(设备信息异常)**:\r
+```json\r
+{\r
+  "errorCode": "DEVICE_NOT_FOUND",\r
+  "message": "设备信息异常",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "设备信息异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(模型服务异常)**:\r
+```json\r
+{\r
+  "errorCode": "MODEL_ERROR",\r
+  "message": "模型服务异常",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "模型服务异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(故事生成失败)**:\r
+```json\r
+{\r
+  "errorCode": "SERVER_ERROR",\r
+  "message": "服务器内部错误",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "服务器异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| storyTheme | String | 故事主题 |\r
+| storyContent | String | 故事片段文本内容 |\r
+| storyAudioUrl | String | 故事片段音频文件URL |\r
+| storyDuration | Integer | 故事片段时长(秒) |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | Token无效 |\r
+| INVALID_AUDIO_INPUT | 音频输入无效 |\r
+| DEVICE_NOT_FOUND | 设备信息异常 |\r
+| MODEL_NOT_FOUND | 模型信息不存在 |\r
+| MODEL_ERROR | 模型服务异常 |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+#### 错误响应说明\r
+当智能选择故事接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该:\r
+1. 检查响应中的errorCode字段\r
+2. 如果errorCode不为null,表示发生了错误\r
+3. 播放data字段中的storyAudioUrl音频文件,向用户说明错误情况\r
+4. 根据错误类型进行相应的处理:\r
+   - **INVALID_AUDIO_INPUT**: 语音识别失败,提示用户重新录音表达故事偏好\r
+   - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token\r
+   - **MODEL_NOT_FOUND**: 故事生成模型配置缺失,建议重新获取配置或联系技术支持\r
+   - **MODEL_ERROR**: 故事生成服务异常,建议稍后重试\r
+   - **SERVER_ERROR**: 服务器内部错误,建议稍后重试\r
+\r
+#### SSE协议说明\r
+- 接口使用SSE(Server-Sent Events)协议进行流式响应\r
+- 每个故事片段作为一个独立的SSE事件发送\r
+- 客户端需要实现SSE客户端来接收流式数据\r
+- 连接会在故事生成完成后自动关闭\r
+- 客户端应当设置适当的超时时间(建议120秒)\r
+\r
+#### 使用说明\r
+- 用户可以通过语音表达故事偏好,如"我想听童话故事"、"讲个冒险故事吧"等\r
+- 系统会分析语音内容,识别故事主题,并实时生成故事片段\r
+- 每个故事片段包含文本内容和对应的音频URL\r
+- 客户端可以边接收边播放,实现流式播放体验\r
+- 故事生成完成后,SSE连接会自动关闭\r
+\r
+### 3.2 随机播放故事\r
+\r
+#### 接口描述\r
+随机故事接口。系统会随机生成一个故事并流式返回给用户。该接口使用SSE(Server-Sent Events)协议进行流式响应,需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/story/randomStory`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+- **响应类型**: `text/event-stream` (SSE流式响应)\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头或查询参数传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" |\r
+| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" |\r
+\r
+\r
+#### 请求体参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| storyTheme | String | 否 | 故事主题 | "爱情故事" |\r
+\r
+\r
+示例:\r
+```json\r
+{\r
+  "storyTheme": "爱情故事"\r
+}\r
+```\r
+\r
+\r
+#### SSE流式响应数据\r
+**故事片段事件**:\r
+```\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"在一个阳光明媚的早晨","storyAudioUrl":"https://example.com/story/audio/chunk_001.mp3","storyDuration":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"小兔子决定去探险","storyAudioUrl":"https://example.com/story/audio/chunk_002.mp3","storyDuration":null}}\r
+\r
+data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"它背上小书包,踏上了未知的旅程","storyAudioUrl":"https://example.com/story/audio/chunk_003.mp3","storyDuration":null}}\r
+```\r
+\r
+**错误响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+**错误响应(设备信息异常)**:\r
+```json\r
+{\r
+  "errorCode": "DEVICE_NOT_FOUND",\r
+  "message": "设备信息异常",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "设备信息异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(模型信息不存在)**:\r
+```json\r
+{\r
+  "errorCode": "MODEL_NOT_FOUND",\r
+  "message": "模型信息不存在",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "设备模型信息异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_notfound.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(模型服务异常)**:\r
+```json\r
+{\r
+  "errorCode": "MODEL_ERROR",\r
+  "message": "模型服务异常",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "模型服务异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+**错误响应(故事生成失败)**:\r
+```json\r
+{\r
+  "errorCode": "SERVER_ERROR",\r
+  "message": "服务器内部错误",\r
+  "data": {\r
+    "storyTheme": "",\r
+    "storyContent": "服务器异常,请稍后重试。",\r
+    "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3",\r
+    "storyDuration": 0\r
+  }\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| storyTheme | String | 故事主题 |\r
+| storyContent | String | 故事片段文本内容 |\r
+| storyAudioUrl | String | 故事片段音频文件URL |\r
+| storyDuration | Integer | 故事片段时长(秒) |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 | \r
+|--------|------|\r
+| AUTH_FAILURE | Token无效 | \r
+| DEVICE_NOT_FOUND | 设备信息异常 | \r
+| MODEL_NOT_FOUND | 模型信息不存在 | \r
+| MODEL_ERROR | 模型服务异常 | \r
+| SERVER_ERROR | 服务器内部错误 | \r
+\r
+#### 错误响应说明\r
+当随机故事接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该:\r
+1. 检查响应中的errorCode字段\r
+2. 如果errorCode不为null,表示发生了错误\r
+3. 播放data字段中的storyAudioUrl音频文件,向用户说明错误情况\r
+4. 根据错误类型进行相应的处理:\r
+   - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token\r
+   - **MODEL_NOT_FOUND**: 故事生成模型配置缺失,建议重新获取配置或联系技术支持\r
+   - **MODEL_ERROR**: 故事生成服务异常,建议稍后重试\r
+   - **SERVER_ERROR**: 服务器内部错误,建议稍后重试\r
+\r
+#### SSE协议说明\r
+- 接口使用SSE(Server-Sent Events)协议进行流式响应\r
+- 每个故事片段作为一个独立的SSE事件发送\r
+- 客户端需要实现SSE客户端来接收流式数据\r
+- 连接会在故事生成完成后自动关闭\r
+- 客户端应当设置适当的超时时间(建议120秒)\r
+\r
+#### 使用说明\r
+- 该接口不需要任何参数,系统会自动生成随机主题的故事\r
+- 适用于用户没有特定偏好,想要听任意故事的场景\r
+- 可以作为默认的故事推荐方式\r
+- 客户端可以边接收边播放,实现流式播放体验\r
+- 故事生成完成后,SSE连接会自动关闭\r
+\r
+### 3.3 获取退出哄睡询问\r
+\r
+#### 接口描述\r
+获取退出哄睡询问接口。在用户要结束哄睡前,返回询问内容用于确认用户是否真的要退出哄睡模式。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/story/getExitSleepInquiry`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头或查询参数传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 请求参数\r
+无请求参数\r
+\r
+#### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": {\r
+    "audioUrl": "https://example.com/audio/response.mp3",\r
+    "textContent": "你确定要退出哄睡模式吗?如果确定要退出,请说确定",\r
+    "intention": "EXIT_SLEEP_MODE"\r
+  }\r
+}\r
+```\r
+\r
+**失败响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| audioUrl | String | 询问音频文件URL |\r
+| textContent | String | 询问的文本内容 |\r
+| intention | String | 用户意图类型,此接口固定返回"EXIT_SLEEP_MODE" |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+#### 使用说明\r
+- 该接口用于获取退出哄睡模式的确认询问\r
+- 返回的音频和文本内容用于向用户确认是否真的要退出哄睡模式\r
+- 设备播放询问内容后,应等待用户的确认回复\r
+- 如果用户确认退出,再调用`finishSleepMode`接口正式结束哄睡模式\r
+\r
+### 3.4 确认结束哄睡模式\r
+\r
+#### 接口描述\r
+结束哄睡模式接口。当用户完成哄睡故事播放或主动结束哄睡模式时,调用此接口通知服务器。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/story/finishSleepMode`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头或查询参数传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 请求参数\r
+无请求参数\r
+\r
+#### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": ""\r
+}\r
+```\r
+\r
+**失败响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| data | String |  |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+#### 使用说明\r
+- 该接口用于通知服务器哄睡模式已结束\r
+- 服务器可以记录哄睡会话的结束时间和统计信息\r
+- 调用此接口后,服务器可能会停止推送哄睡相关的消息\r
+- 建议在故事播放完成或用户主动停止时调用此接口\r
+\r
+### 故事接口使用流程\r
+1. **意图识别**:用户通过语音对话接口表达哄我睡觉的意图,系统返回`StartSleepMode`意图\r
+2. **故事选择**:\r
+   - 如果用户有特定偏好,调用`chooseStory`接口,传入用户的语音描述,获取对应主题的故事流\r
+   - 如果用户没有特定偏好,调用`randomStory`接口获取随机故事流\r
+3. **SSE连接建立**:客户端建立SSE连接,准备接收流式故事数据\r
+4. **流式故事播放**:\r
+   - 接收到每个故事片段事件后,立即播放对应的音频文件\r
+   - 可以同时显示文本内容,实现边听边看的体验\r
+   - 继续接收后续故事片段,直到故事完整播放完毕\r
+5. **连接关闭**:故事生成完成后,SSE连接自动关闭\r
+6. **退出询问**:故事播放完成或用户主动结束时,调用`getExitSleepInquiry`获取二次确认询问\r
+7. **播放询问**:设备播放退出询问的音频内容,等待用户确认\r
+8. **确认结束**:用户确认后,调用`finishSleepMode`接口通知服务器正式结束哄睡模式\r
+\r
+### SSE客户端实现要点\r
+- **请求头设置**:必须设置`Accept: text/event-stream`和`Cache-Control: no-cache`\r
+- **连接超时**:建议设置120秒超时时间,因为故事生成可能需要较长时间\r
+- **事件解析**:正确解析SSE事件格式,提取`data:`字段中的JSON数据\r
+- **错误处理**:处理连接失败、超时、认证失败等异常情况\r
+- **资源管理**:连接完成后正确释放资源,避免内存泄漏\r
+\r
+\r
+---\r
+\r
+## 4. 设备信息接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    \r
+    Note over Device, Server: 设备信息管理流程\r
+    \r
+    %% 获取设备角色信息\r
+    Device->>Server: GET /v1/device/getCharacter<br/>Headers: deviceId, deviceToken\r
+    \r
+    alt 首次开机\r
+        Server->>Device: 返回默认角色信息<br/>{"characterId", "characterName", "characterAvatar", "emotionEmojis"}\r
+        Device->>Device: 应用默认角色配置\r
+    else 已有角色配置\r
+        Server->>Device: 返回用户选择的角色信息\r
+        Device->>Device: 应用角色配置\r
+    end\r
+    \r
+    %% 检查用户绑定状态\r
+    Device->>Server: GET /v1/device/getUserId<br/>获取绑定用户ID\r
+    \r
+    alt 设备已绑定用户\r
+        Server->>Device: 返回用户ID<br/>{"data": "user_001"}\r
+        Device->>Device: 正常运行模式\r
+    else 设备未绑定\r
+        Server->>Device: 返回空值<br/>{"data": null}\r
+        Device->>Device: 显示绑定二维码\r
+        \r
+        %% 生成绑定二维码\r
+        Device->>Server: POST /v1/qrcode/generate/image<br/>{"content": "绑定链接", "width": 300, "height": 300}\r
+        Server->>Device: 返回二维码图片(PNG)\r
+        Device->>Device: 显示二维码等待绑定\r
+    end\r
+```\r
+\r
+### 4.1 获取设备角色信息\r
+\r
+#### 接口描述\r
+获取设备角色信息接口。首次开机时返回默认角色,否则返回小程序选择的角色信息。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/device/getCharacter`\r
+- **方法**: `GET`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": {\r
+    "characterId": "char_001",\r
+    "characterName": "小艾",\r
+    "characterAvatar": {\r
+      "hairColor": "blue",\r
+      "eyesColor": "green",\r
+      "hairStyle": "side-parted-comma",\r
+      "eyesShape": "calm-eyes",\r
+      "screenImage": "https://example.com/avatar.jpg",\r
+      "screenImageAsEmoji": false\r
+    }\r
+  }\r
+}\r
+```\r
+\r
+**失败响应**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| characterId | String | 角色ID |\r
+| characterName | String | 角色名称 |\r
+| characterAvatar | Object | 角色形象配置 |\r
+\r
+**characterAvatar字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 | 可选值 |\r
+|--------|------|------|--------|\r
+| eyesShape | String | 眼型 | calm-eyes, round-eyes, upturned-eyes |\r
+| hairColor | String | 发色 | light-brown, dark-brown, black, white, gray, blue, purple, gold |\r
+| eyesColor | String | 瞳色 | red, yellow, blue, green, purple, brown, purple-orange-gradient, blue-pink-gradient, yellow-green-gradient |\r
+| hairStyle | String | 发型 | side-parted-comma, m-shaped-bangs, diagonal-bangs, center-parted-bangs, straight-bangs, side-parted-texture |\r
+| screenImage | String | 屏保图片URL | |\r
+| screenImageAsEmoji | Boolean | 屏保图片是否作为对话表情 | 默认false |\r
+\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+\r
+### 4.2 获取设备绑定用户ID\r
+\r
+#### 接口描述\r
+获取设备绑定的用户ID接口。用于检查设备是否已绑定用户,如果未绑定则需要弹出二维码供用户绑定。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/device/getUserId`\r
+- **方法**: `GET`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 响应数据\r
+**成功响应(已绑定用户)**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": "user_001"\r
+}\r
+```\r
+\r
+**成功响应(未绑定用户)**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": null\r
+}\r
+```\r
+\r
+**失败响应**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| data | String | 用户ID,如果设备未绑定用户则返回null |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+\r
+---\r
+\r
+## 5. OTA升级任务接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    \r
+    Note over Device, Server: OTA升级流程\r
+    \r
+    %% 设备启动检查升级\r
+    Device->>Device: 设备启动\r
+    Device->>Device: 获取当前固件版本\r
+    \r
+    Device->>Server: GET /v1/upgrade/getTask<br/>?deviceId=xxx&sourceVersion=1.0.0\r
+    \r
+    alt 有可用升级\r
+        Server->>Server: 检查升级策略\r
+        Server->>Server: 查找可用升级包\r
+        Server->>Device: 返回升级任务<br/>{"updateId", "targetVersion", "packageUrl", "packageMd5", "updateType"}\r
+        \r
+        Device->>Device: 检查升级类型\r
+        \r
+        alt 强制升级 (updateType=1)\r
+            Device->>Device: 立即开始升级\r
+        else 可选升级 (updateType=0)\r
+            Device->>Device: 提示用户选择\r
+            alt 用户同意升级\r
+                Device->>Device: 开始升级\r
+            else 用户拒绝\r
+                Device->>Device: 继续正常运行\r
+            end\r
+        end\r
+        \r
+        %% 执行升级\r
+        opt 开始升级流程\r
+            Device->>Device: 下载升级包\r
+            Device->>Server: 下载 packageUrl\r
+            Server->>Device: 返回升级包文件\r
+            \r
+            Device->>Device: 校验MD5\r
+            alt MD5校验成功\r
+                Device->>Device: 执行固件升级\r
+                Device->>Device: 重启设备\r
+                Device->>Device: 升级完成,版本更新\r
+                Device->>Server: 重新获取设备Token<br/>(新版本)\r
+            else MD5校验失败\r
+                Device->>Device: 升级失败,重新下载  \r
+            end\r
+        end\r
+        \r
+    else 无可用升级\r
+        Server->>Device: 返回无升级<br/>{"errorCode": "UPGRADE_NOT_FOUND"}\r
+        Device->>Device: 继续正常运行\r
+    end\r
+```\r
+\r
+### 接口描述\r
+获取OTA升级任务接口。设备可通过此接口查询是否有可用的固件升级任务,如果有则返回升级包的详细信息。\r
+\r
+### 请求信息\r
+- **URL**: `/v1/upgrade/getTask`\r
+- **方法**: `GET`\r
+- **Content-Type**: `application/json`\r
+\r
+### 请求参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| sourceVersion | String | 是 | 设备当前固件版本 | "1.0.0" |\r
+\r
+### 响应数据\r
+**成功响应(有升级任务)**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": {\r
+    "updateId": 123,\r
+    "sourceVersion": "1.0.0",\r
+    "targetVersion": "1.1.0",\r
+    "updateType": 1,\r
+    "packageId": 456,\r
+    "packageUrl": "https://example.com/firmware/update_1.1.0.bin",\r
+    "packageMd5": "a1b2c3d4e5f6789012345678901234567890",\r
+    "packageSize": 1048576,\r
+    "description": "升级描述"\r
+  }\r
+}\r
+```\r
+\r
+**成功响应(无升级任务)**:\r
+```json\r
+{\r
+  "errorCode": "UPGRADE_NOT_FOUND",\r
+  "message": "已是最新版本",\r
+  "data": null\r
+}\r
+```\r
+\r
+**失败响应**:\r
+```json\r
+{\r
+  "errorCode": "SERVER_ERROR",\r
+  "message": "服务器异常",\r
+  "data": null\r
+}\r
+```\r
+\r
+### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| updateId | Integer | 升级任务ID |\r
+| sourceVersion | String | 源版本号 |\r
+| targetVersion | String | 目标版本号 |\r
+| updateType | Integer | 升级类型,0:紧急修复,1:重要更新,2:普通更新 |\r
+| packageId | Integer | 升级包ID |\r
+| packageUrl | String | 升级包下载URL |\r
+| packageMd5 | String | 升级包MD5校验值 |\r
+| packageSize | Long | 升级包大小(字节) |\r
+| description | String | 升级描述 |\r
+\r
+### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| UPGRADE_NOT_FOUND | 已是最新版本 |\r
+| SERVER_ERROR | 服务器异常 |\r
+\r
+### 升级流程说明\r
+1. 设备启动时,调用此接口检查是否有可用的升级任务\r
+2. 如果有升级任务,获取升级包URL和校验信息\r
+3. 设备下载升级包,使用MD5校验文件完整性\r
+4. 校验通过后,设备执行固件升级操作\r
+5. 升级完成后,设备重启并重新获取设备token\r
+\r
+---\r
+\r
+## 6. WebSocket连接端点\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    participant MiniProgram as 小程序端\r
+    \r
+    Note over Device,MiniProgram: WebSocket通信流程\r
+    \r
+    %% 建立连接\r
+    Device->>Server: WebSocket连接请求<br/>Headers: deviceId, deviceToken\r
+    \r
+    alt Token验证成功\r
+        Server->>Device: 连接成功(101)\r
+        Server->>Server: 记录设备会话\r
+        \r
+        %% 心跳保持\r
+        loop 心跳循环\r
+            Device->>Server: ping\r
+            Server->>Device: pong\r
+        end\r
+        \r
+        %% 业务消息推送示例\r
+        par 语音对话分片传输\r
+            Device->>Device: 用户语音输入\r
+            Device->>Device: 音频数据分片(8KB)\r
+            loop 分片传输\r
+                Device->>Server: WebSocket发送<br/>{"command": "VoiceChatRequest", "data": {"audioBase64": "...", "chatId": "...", "finished": false}}\r
+            end\r
+            Device->>Server: WebSocket发送<br/>{"command": "VoiceChatRequest", "data": {"finished": true}}\r
+            \r
+            Server->>Server: 重组音频数据\r
+            Server->>Server: 语音识别和处理\r
+            loop 流式响应\r
+                Server->>Device: WebSocket推送<br/>{"command": "VoiceChatResponse", "data": {"chatId": "...", "finished": false, "data": {...}}}\r
+                Device->>Device: 播放响应音频片段\r
+            end\r
+            Server->>Device: WebSocket推送<br/>{"command": "VoiceChatResponse", "data": {"finished": true}}\r
+            \r
+        and 角色切换\r
+            MiniProgram->>Server: 用户在小程序切换角色\r
+            Server->>Device: WebSocket推送<br/>{"command": "CharacterChange", "data": {...}}\r
+            Device->>Device: 应用新角色配置\r
+        and 哄睡模式控制\r
+            MiniProgram->>Server: 用户启动哄睡模式\r
+            Server->>Device: WebSocket推送<br/>{"command": "SleepModeStart", "data": {"storyTheme": "..."}}\r
+            Device->>Device: 启动哄睡模式\r
+        and 设备绑定通知\r
+            MiniProgram->>Server: 用户绑定设备\r
+            Server->>Device: WebSocket推送<br/>{"command": "DeviceBind", "data": {"userId": "...", "userName": "..."}}\r
+            Device->>Device: 更新绑定状态\r
+        and 日程提醒\r
+            Server->>Device: WebSocket推送<br/>{"command": "ScheduleReminder", "data": {"scheduleName": "...", "reminderContent": "..."}}\r
+            Device->>Device: 播放提醒内容\r
+        end\r
+        \r
+    else Token验证失败\r
+        Server->>Device: 连接拒绝(401)\r
+        Device->>Device: 重新获取Token后重连\r
+    end\r
+    \r
+    %% 连接断开\r
+    alt 正常断开\r
+        Device->>Server: 关闭连接\r
+        Server->>Server: 清理设备会话\r
+    else 异常断开\r
+        Server->>Server: 检测到连接断开\r
+        Server->>Server: 清理设备会话\r
+        Device->>Device: 自动重连机制\r
+    end\r
+```\r
+\r
+### 接口描述\r
+WebSocket连接端点,用于设备与服务器建立实时双向通信连接。该连接需要设备Token认证。\r
+\r
+### 连接信息\r
+- **URL**: `wss://ambybox-api.test.leiniao.com/ambybox-device-api/v1/websocket`\r
+- **协议**: `WebSocket`\r
+- **认证**: 设备Token认证\r
+\r
+### 连接认证\r
+WebSocket连接需要设备Token认证,有以下两种方式传递Token:\r
+\r
+1. **请求头方式**:\r
+   ```\r
+   deviceId: DEVICE_001\r
+   deviceToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\r
+   reconnect: 0\r
+   ```\r
+\r
+2. **查询参数方式**:\r
+   ```\r
+   wss://ambybox-api.test.leiniao.com/ambybox-device-api/v1/websocket?deviceToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\r
+   ```\r
+\r
+### 连接参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符(请求头) | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+| reconnect | Integer | 否 | 重连次数,设备启动时设置为0,断线重连递增 | 0 |\r
+\r
+### 连接状态码\r
+| 状态码 | 描述 |\r
+|--------|------|\r
+| 400 | 设备ID为空 |\r
+| 401 | 设备Token无效或过期 |\r
+| 101 | 正常连接 |\r
+\r
+\r
+### 连接流程\r
+1. 客户端携带设备Token发起WebSocket连接\r
+2. 服务器验证Token有效性和设备ID匹配性\r
+3. 连接建立成功,设备会话被管理器记录\r
+4. 客户端可发送心跳消息保持连接活跃\r
+5. 连接断开时,设备会话从管理器中移除\r
+\r
+### 错误处理\r
+- **认证失败**:Token无效或设备ID不匹配,连接被拒绝\r
+- **传输错误**:连接出现异常时,服务器会主动关闭连接\r
+- **心跳超时**:长时间无消息交互时,连接可能被服务器关闭\r
+\r
+\r
+\r
+\r
+### 客户端业务消息\r
+\r
+客户端可以通过WebSocket连接发送业务消息给服务端,目前支持心跳、语音对话分片传输、语音选择故事和随机故事请求。\r
+\r
+#### 心跳请求 (Ping)\r
+**命令描述**:客户端发送心跳请求,保持WebSocket连接活跃\r
+**协议说明**:\r
+- 建议每30秒发送一次心跳请求\r
+- 服务端收到心跳请求后会立即响应Pong消息\r
+- 心跳机制用于检测连接状态和保持连接活跃\r
+\r
+**消息格式**:\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID",\r
+  "command": "Ping",\r
+  "data": null\r
+}\r
+```\r
+\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "ping_001",\r
+  "command": "Ping",\r
+  "data": null\r
+}\r
+```\r
+\r
+**服务端响应**:\r
+服务端收到心跳请求后,会立即通过WebSocket推送Pong响应:\r
+\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995201,\r
+  "messageId": "pong_001",\r
+  "command": "Pong",\r
+  "data": null\r
+}\r
+```\r
+\r
+**协议说明**:\r
+- 客户端发送Ping消息后应等待服务端的Pong响应\r
+- 如果长时间未收到Pong响应,说明连接可能异常,建议重新连接\r
+- 心跳间隔建议设置为30秒,过于频繁会增加服务器负载\r
+\r
+#### 语音对话分片传输 (VoiceChatRequest)\r
+**命令描述**:客户端发送语音对话请求,支持大音频文件的分片传输\r
+**协议说明**:\r
+- 大于8KB的音频数据应分片传输\r
+- 每个分片应包含相同的`chatId`用于服务端重组,建议使用UUIDv4算法生成\r
+- 最后一个分片应设置`finished=true`,否则设置为`finished=false`\r
+\r
+**消息格式**:\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID",\r
+  "command": "VoiceChatRequest",\r
+  "data": {\r
+    "audioBase64": "音频数据Base64编码(分片)",\r
+    "chatId": "对话ID,用于标识本次对话",\r
+    "finished": false\r
+  }\r
+}\r
+```\r
+\r
+**分片传输示例**:\r
+```json\r
+// 第一个分片\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_001",\r
+  "command": "VoiceChatRequest",\r
+  "data": {\r
+    "audioBase64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA...",\r
+    "chatId": "chat_12345",\r
+    "finished": false\r
+  }\r
+}\r
+\r
+// 第二个分片\r
+{\r
+  "deviceId": "DEVICE_001", \r
+  "timestamp": 1640995201,\r
+  "messageId": "msg_002",\r
+  "command": "VoiceChatRequest",\r
+  "data": {\r
+    "audioBase64": "AAEAAQABAAEAAQABAAEAAQABAAEAAQAB...",\r
+    "chatId": "chat_12345",\r
+    "finished": false\r
+  }\r
+}\r
+\r
+// 最后一个分片\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995202, \r
+  "messageId": "msg_003",\r
+  "command": "VoiceChatRequest",\r
+  "data": {\r
+    "audioBase64": "BAAEAAQABAAEAAQABAAEAAQABAAEAAAA...",\r
+    "chatId": "chat_12345",\r
+    "finished": true\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| audioBase64 | String | 音频数据Base64编码,单个分片不超过8KB原始数据 |\r
+| chatId | String | 对话ID,用于标识本次对话,建议使用UUID |\r
+| finished | Boolean | 是否为最后一个分片,true表示传输完成 |\r
+\r
+**分片传输规则**:\r
+1. 原始音频数据按8KB分片\r
+2. 每个分片转换为Base64编码后发送\r
+3. 所有分片使用相同的chatId\r
+4. 只有最后一个分片设置finished=true\r
+\r
+**服务端响应**:\r
+服务端收到完整音频后,会通过WebSocket推送VoiceChatResponse消息:\r
+\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995203,\r
+  "messageId": "msg_004", \r
+  "command": "VoiceChatResponse",\r
+  "data": {\r
+    "chatId": "chat_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "textContent": "你好,我听到了你的话",\r
+      "audioUrl": "https://example.com/response_audio.mp3"\r
+    }\r
+  }\r
+}\r
+```\r
+\r
+#### 语音选择故事请求 (ChooseStoryRequest)\r
+**命令描述**:客户端发送语音选择故事请求,用户通过语音表达故事偏好,系统根据语音内容选择相应故事\r
+**协议说明**:\r
+- 支持音频数据分片传输,大于8KB的音频数据应分片传输\r
+- 每个分片应包含相同的`chatId`用于服务端重组,建议使用UUIDv4算法生成\r
+- 最后一个分片应设置`finished=true`,否则设置为`finished=false`\r
+- 服务端会通过WebSocket推送ChooseStoryResponse消息流式返回故事内容\r
+\r
+**消息格式**:\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID",\r
+  "command": "ChooseStoryRequest",\r
+  "data": {\r
+    "audioBase64": "音频数据Base64编码(分片)",\r
+    "chatId": "对话ID,用于标识本次故事请求",\r
+    "finished": false\r
+  }\r
+}\r
+```\r
+\r
+**分片传输示例**:\r
+```json\r
+// 第一个分片\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "story_001",\r
+  "command": "ChooseStoryRequest",\r
+  "data": {\r
+    "audioBase64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA...",\r
+    "chatId": "story_12345",\r
+    "finished": false\r
+  }\r
+}\r
+\r
+// 最后一个分片\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995202,\r
+  "messageId": "story_002",\r
+  "command": "ChooseStoryRequest",\r
+  "data": {\r
+    "audioBase64": "BAAEAAQABAAEAAQABAAEAAQABAAEAAAA...",\r
+    "chatId": "story_12345",\r
+    "finished": true\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| audioBase64 | String | 音频数据Base64编码,单个分片不超过8KB原始数据 |\r
+| chatId | String | 对话ID,用于标识本次故事请求,建议使用UUID |\r
+| finished | Boolean | 是否为最后一个分片,true表示传输完成 |\r
+\r
+**服务端响应**:\r
+服务端收到完整音频后,会通过WebSocket推送ChooseStoryResponse消息流式返回故事内容:\r
+\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995203,\r
+  "messageId": "story_response_001",\r
+  "command": "ChooseStoryResponse",\r
+  "data": {\r
+    "chatId": "story_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "storyTheme": "童话故事",\r
+      "storyContent": "从前有一个可爱的小女孩",\r
+      "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3",\r
+      "storyDuration": null\r
+    },\r
+    "errorCode": null\r
+  }\r
+}\r
+```\r
+\r
+#### 随机故事请求 (RandomStoryRequest)\r
+**命令描述**:客户端发送随机故事请求,系统会根据指定主题(可选)生成随机故事\r
+**协议说明**:\r
+- 无需音频数据,仅通过文本参数指定故事主题\r
+- 故事主题为可选参数,不指定时系统会随机选择主题\r
+- 服务端会通过WebSocket推送RandomStoryResponse消息流式返回故事内容\r
+\r
+**消息格式**:\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID",\r
+  "command": "RandomStoryRequest",\r
+  "data": {\r
+    "chatId": "对话ID,用于标识本次故事请求",\r
+    "storyTheme": "故事主题(可选)"\r
+  }\r
+}\r
+```\r
+\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "random_story_001",\r
+  "command": "RandomStoryRequest",\r
+  "data": {\r
+    "chatId": "random_story_12345",\r
+    "storyTheme": "冒险"\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| chatId | String | 对话ID,用于标识本次故事请求,建议使用UUID |\r
+| storyTheme | String | 故事主题,可选参数。如"童话"、"冒险"、"科幻"等,不指定时系统随机选择 |\r
+\r
+**服务端响应**:\r
+服务端收到随机故事请求后,会通过WebSocket推送RandomStoryResponse消息流式返回故事内容:\r
+\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995201,\r
+  "messageId": "random_story_response_001",\r
+  "command": "RandomStoryResponse",\r
+  "data": {\r
+    "chatId": "random_story_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "storyTheme": "冒险故事",\r
+      "storyContent": "在一个阳光明媚的早晨",\r
+      "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3",\r
+      "storyDuration": null\r
+    },\r
+    "errorCode": null\r
+  }\r
+}\r
+```\r
+\r
+### 服务端业务消息\r
+\r
+服务端通过WebSocket连接推送业务消息给设备端,消息采用JSON格式,消息结构如下:\r
+\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID,用于设备去重防重放",\r
+  "command": "命令名称",\r
+  "data": {\r
+    // 具体的消息数据,根据不同命令名称而变化\r
+  }\r
+}\r
+```\r
+\r
+#### 消息结构说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| deviceId | String | 设备唯一标识符 |\r
+| timestamp | Long | 时间戳,单位:秒 |\r
+| messageId | String | 消息ID,用于设备去重防重放 |\r
+| command | String | 命令名称,见下方支持的命令列表 |\r
+| data | Object | 消息体,不同命令对应不同的数据结构 |\r
+\r
+支持的命令名称如下:\r
+\r
+#### 1. 心跳响应 (Pong)\r
+**命令描述**:服务端响应客户端的心跳请求\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995201,\r
+  "messageId": "pong_001",\r
+  "command": "Pong",\r
+  "data": null\r
+}\r
+```\r
+\r
+**data字段说明**:无数据字段\r
+\r
+#### 2. 语音对话响应 (VoiceChatResponse)\r
+**命令描述**:服务端推送语音对话响应,流式返回对话结果\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995203,\r
+  "messageId": "chat_response_001",\r
+  "command": "VoiceChatResponse",\r
+  "data": {\r
+    "chatId": "chat_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "textContent": "你好,我听到了你的话",\r
+      "audioUrl": "https://example.com/response_audio.mp3",\r
+      "intention": null\r
+    },\r
+    "errorCode": null\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| chatId | String | 对话ID,与客户端请求中的chatId对应 |\r
+| finished | Boolean | 是否为最后一个响应片段 |\r
+| data | Object | 响应数据,包含文本内容、音频URL等 |\r
+| errorCode | String | 错误码,正常情况下为null |\r
+\r
+**data.data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| textContent | String | 回复的文本内容 |\r
+| audioUrl | String | 回复音频文件URL |\r
+| intention | String | 用户意图识别结果,如"StartSleepMode"等 |\r
+\r
+#### 3. 语音选择故事响应 (ChooseStoryResponse)\r
+**命令描述**:服务端推送语音选择故事响应,流式返回故事内容\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995203,\r
+  "messageId": "story_response_001",\r
+  "command": "ChooseStoryResponse",\r
+  "data": {\r
+    "chatId": "story_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "storyTheme": "童话故事",\r
+      "storyContent": "从前有一个可爱的小女孩",\r
+      "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3",\r
+      "storyDuration": null\r
+    },\r
+    "errorCode": null\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| chatId | String | 对话ID,与客户端请求中的chatId对应 |\r
+| finished | Boolean | 是否为最后一个故事片段 |\r
+| data | Object | 故事数据,包含主题、内容、音频等 |\r
+| errorCode | String | 错误码,正常情况下为null |\r
+\r
+**data.data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| storyTheme | String | 故事主题 |\r
+| storyContent | String | 故事片段文本内容 |\r
+| storyAudioUrl | String | 故事片段音频文件URL |\r
+| storyDuration | Integer | 故事片段时长(秒) |\r
+\r
+#### 4. 随机故事响应 (RandomStoryResponse)\r
+**命令描述**:服务端推送随机故事响应,流式返回故事内容\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995201,\r
+  "messageId": "random_story_response_001",\r
+  "command": "RandomStoryResponse",\r
+  "data": {\r
+    "chatId": "random_story_12345",\r
+    "finished": false,\r
+    "data": {\r
+      "storyTheme": "冒险故事",\r
+      "storyContent": "在一个阳光明媚的早晨",\r
+      "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3",\r
+      "storyDuration": null\r
+    },\r
+    "errorCode": null\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:与语音选择故事响应相同\r
+\r
+#### 5. 角色切换 (CharacterChange)\r
+**命令描述**:通知设备切换到指定角色\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_001",\r
+  "command": "CharacterChange",\r
+  "data": {\r
+    "characterId": "char_001",\r
+    "characterName": "小艾",\r
+    "characterAvatar": {\r
+      "eyesShape": "upturned-eyes",\r
+      "hairColor": "blue",\r
+      "eyesColor": "green",\r
+      "hairStyle": "side-parted",\r
+      "screenImage": "https://example.com/avatar.jpg",\r
+      "screenImageAsEmoji": false\r
+    }\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| characterId | String | 角色ID |\r
+| characterName | String | 角色名称 |\r
+| characterAvatar | Object | 角色形象配置 |\r
+\r
+**characterAvatar字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 | 可选值 |\r
+|--------|------|------|--------|\r
+| eyesShape | String | 眼型 | calm-eyes, round-eyes, upturned-eyes |\r
+| eyesColor | String | 瞳色 | red, yellow, blue, green, purple, brown, purple-orange-gradient, blue-pink-gradient, yellow-green-gradient |\r
+| hairColor | String | 发色 | light-brown, dark-brown, black, white, gray, blue, purple, gold |\r
+| hairStyle | String | 发型 | side-parted-comma, m-shaped-bangs, diagonal-bangs, center-parted-bangs, straight-bangs, side-parted-texture |\r
+| screenImage | String | 屏保图片URL | |\r
+| screenImageAsEmoji | Boolean | 屏保图片是否作为对话表情 | 默认false |\r
+\r
+**枚举说明**:\r
+\r
+眼型\r
+| 值 | 描述 |\r
+|--------|------|\r
+| calm-eyes | 平缓眼 |\r
+| round-eyes | 圆眼 |\r
+| upturned-eyes | 上挑眼 |\r
+\r
+瞳色\r
+| 值 | 描述 |\r
+|--------|------|\r
+| red | 红 |\r
+| yellow | 黄 |\r
+| blue | 蓝 |\r
+| green | 绿 |\r
+| purple | 紫 |\r
+| brown | 棕 |\r
+| purple-orange-gradient | 紫橙渐变 |\r
+| blue-pink-gradient | 蓝粉渐变 |\r
+| yellow-green-gradient | 黄绿渐变 |\r
+\r
+发色\r
+| 值 | 描述 |\r
+|--------|------|\r
+| light-brown | 浅棕色 |\r
+| dark-brown | 深棕色 |\r
+| black | 黑色 |\r
+| white | 白色 |\r
+| gray | 灰色 |\r
+| blue | 蓝色 |\r
+| purple | 紫色 |\r
+| gold | 金色 |\r
+\r
+发型\r
+| 值 | 描述 |\r
+|--------|------|\r
+| side-parted-comma | 偏分逗号 |\r
+| m-shaped-bangs | M形刘海 |\r
+| diagonal-bangs | 斜刘海 |\r
+| center-parted-bangs | 中分刘海 |\r
+| straight-bangs | 齐刘海 |\r
+| side-parted-texture | 偏分纹理 |\r
+\r
+\r
+#### 6. 角色配置更改 (CharacterConfigChange)\r
+**命令描述**:更新当前角色的配置信息\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_002",\r
+  "command": "CharacterConfigChange",\r
+  "data": {\r
+    "characterId": "char_001",\r
+    "characterName": "小艾",\r
+    "characterAvatar": {\r
+      "eyesShape": "upturned-eyes",\r
+      "hairColor": "blue",\r
+      "eyesColor": "green",\r
+      "hairStyle": "side-parted-comma",\r
+      "screenImage": "https://example.com/new_avatar.jpg",\r
+      "screenImageAsEmoji": true\r
+    }\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:与角色切换命令相同\r
+\r
+#### 7. 哄睡模式开始 (SleepModeStart)\r
+**命令描述**:通知设备启动哄睡模式\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_003",\r
+  "command": "SleepModeStart",\r
+  "data": {\r
+    "storyTheme": "爱情故事"\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| storyTheme | String | 故事主题 |\r
+\r
+设备收到此命令后,使用推送的主题调用randomStory接口,播放故事。\r
+\r
+\r
+#### 8. 哄睡模式结束 (SleepModeEnd)\r
+**命令描述**:通知设备结束哄睡模式\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_004",\r
+  "command": "SleepModeEnd",\r
+  "data": {}\r
+}\r
+```\r
+\r
+**data字段说明**:无特殊字段\r
+\r
+设备收到改命令后,需要停止播放故事。\r
+\r
+\r
+#### 9. 哄睡模式故事内容\r
+\r
+设备收到`SleepModeStart` 命令后,需要调用`/v1/story/randomStory`接口获取故事内容。\r
+\r
+\r
+\r
+#### 10. 设备绑定 (DeviceBind)\r
+**命令描述**:通知设备已被用户绑定\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_007",\r
+  "command": "DeviceBind",\r
+  "data": {\r
+    "userId": "user_001",\r
+    "userName": "张三",\r
+    "bindTime": 1640995200\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| userId | String | 用户ID |\r
+| userName | String | 用户名称 |\r
+| bindTime | Long | 绑定时间戳,单位:秒 |\r
+\r
+#### 11. 设备解绑 (DeviceUnbind)\r
+**命令描述**:通知设备已被用户解绑\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_008",\r
+  "command": "DeviceUnbind",\r
+  "data": {\r
+    "userId": "user_001",\r
+    "reason": "用户主动解绑",\r
+    "unbindTime": 1640995200\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| userId | String | 用户ID |\r
+| reason | String | 解绑原因 |\r
+| unbindTime | Long | 解绑时间戳,单位:秒 |\r
+\r
+#### 12. 日程提醒 (ScheduleReminder)\r
+**命令描述**:推送日程提醒信息\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_009",\r
+  "command": "ScheduleReminder",\r
+  "data": {\r
+    "scheduleId": 123,\r
+    "deviceId": "DEVICE_001",\r
+    "scheduleName": "开会提醒",\r
+    "reminderContent": "您有一个会议即将开始,请准备参加",\r
+    "reminderAudioUrl": "https://example.com/reminder/reminder_001.mp3",\r
+    "enableVoice": 1,\r
+    "enableVibration": 1\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| scheduleId | Integer | 日程ID |\r
+| deviceId | String | 设备ID |\r
+| scheduleName | String | 日程名称 |\r
+| reminderContent | String | 提醒内容 |\r
+| reminderAudioUrl | String | 提醒音频文件URL |\r
+| enableVoice | Integer | 是否启用语音,1:启用,0:未启用 |\r
+| enableVibration | Integer | 是否启用震动,1:启用,0:未启用 |\r
+\r
+#### 13. 日程提醒配置 (ScheduleReminderConfig)\r
+**命令描述**:配置日程提醒相关设置\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_010",\r
+  "command": "ScheduleReminderConfig",\r
+  "data": {\r
+      "scheduleName": "七点起床"\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| scheduleName | String | 日程名称 |\r
+\r
+#### 14. 下载表情 (DownloadEmoji)\r
+**命令描述**:通知设备下载指定角色的表情包\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_011",\r
+  "command": "DownloadEmoji",\r
+  "data": {\r
+    "characterId": "char_001",\r
+    "eyesColor": "blue",\r
+    "eyesShape": "calm-eyes",\r
+    "emotionEmojis": [\r
+      {\r
+        "emotion": "happy",\r
+        "emojiUrl": "https://example.com/emoji/happy.mp4"\r
+      },\r
+      {\r
+        "emotion": "sad",\r
+        "emojiUrl": "https://example.com/emoji/sad.mp4"\r
+      }\r
+    ]\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| characterId | String | 角色ID |\r
+| eyesColor | String | 瞳色,用于标识表情包组合 |\r
+| eyesShape | String | 眼型,用于标识表情包组合 |\r
+| emotionEmojis | Array | 表情列表 |\r
+\r
+**emotionEmojis字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| emotion | String | 心情类型,如"happy"、"sad"、"angry"等 |\r
+| emojiUrl | String | 表情动画文件URL,通常为mp4格式 |\r
+\r
+**设备处理说明**:\r
+- 设备收到此命令后,应下载所有emotionEmojis中的表情文件\r
+- 下载完成后,调用`/v1/emoji/reportEmoji`接口上报下载状态\r
+- 表情文件建议缓存到本地,以便快速显示\r
+\r
+#### 15. 删除表情 (DeleteEmoji)\r
+**命令描述**:通知设备删除指定角色的表情包\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_012",\r
+  "command": "DeleteEmoji",\r
+  "data": {\r
+    "characterId": "char_001",\r
+    "eyesColor": "blue",\r
+    "eyesShape": "calm-eyes"\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| characterId | String | 角色ID |\r
+| eyesColor | String | 瞳色,用于标识要删除的表情包组合 |\r
+| eyesShape | String | 眼型,用于标识要删除的表情包组合 |\r
+\r
+**设备处理说明**:\r
+- 设备收到此命令后,应删除对应角色和瞳色、眼型组合的所有表情文件\r
+- 删除完成后,调用`/v1/emoji/reportEmoji`接口上报删除状态\r
+- 删除操作应清理本地缓存的表情文件\r
+\r
+\r
+## 7. 表情管理接口\r
+\r
+### 时序图\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    participant MiniProgram as 小程序端\r
+    \r
+    Note over Device,MiniProgram: 表情管理流程\r
+    \r
+    %% 表情下载流程\r
+    rect rgb(240, 248, 255)\r
+        Note over MiniProgram, Device: 1. 表情下载流程\r
+        MiniProgram->>Server: 用户在小程序选择下载表情\r
+        Server->>Device: WebSocket推送<br/>{"command": "DownloadEmoji", "data": {...}}\r
+        Device->>Device: 下载表情文件到本地\r
+        Device->>Server: POST /v1/emoji/reportEmoji<br/>上报下载状态\r
+        Server->>MiniProgram: 同步表情状态到小程序\r
+    end\r
+    \r
+    %% 表情删除流程\r
+    rect rgb(255, 250, 240)\r
+        Note over MiniProgram, Device: 2. 表情删除流程\r
+        MiniProgram->>Server: 用户在小程序删除表情\r
+        Server->>Device: WebSocket推送<br/>{"command": "DeleteEmoji", "data": {...}}\r
+        Device->>Device: 删除本地表情文件\r
+        Device->>Server: POST /v1/emoji/reportEmoji<br/>上报删除状态\r
+        Server->>MiniProgram: 同步表情状态到小程序\r
+    end\r
+    \r
+    %% 定时同步流程\r
+    rect rgb(245, 255, 250)\r
+        Note over Device, Server: 3. 定时同步流程\r
+        Device->>Device: 每日0点触发\r
+        Device->>Server: POST /v1/emoji/reportAllEmoji<br/>上报所有表情状态\r
+        Server->>Server: 更新设备表情状态\r
+    end\r
+```\r
+\r
+### 7.1 上报角色表情组下载状态\r
+\r
+#### 接口描述\r
+上报角色表情组下载状态接口。设备收到删除表情指令或者下载表情指令,执行成功后调用该接口,用以同步表情下载状态到小程序端。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/emoji/reportEmoji`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 请求体参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| characterId | String | 是 | 角色ID | "char_001" |\r
+| eyesColor | String | 是 | 瞳色 | "blue" |\r
+| eyesShape | String | 是 | 眼型 | "calm-eyes" |\r
+| status    | String | 是 | 状态 | "download_success", "download_fail", "delete_success", "delete_fail" |\r
+\r
+#### 请求示例\r
+```json\r
+{\r
+  "characterId": "char_001",\r
+  "eyesColor": "blue",\r
+  "eyesShape": "calm-eyes",\r
+  "status": "download_success"\r
+}\r
+```\r
+\r
+#### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": "ok"\r
+}\r
+```\r
+\r
+**失败响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| data | String | 操作结果,成功时返回"ok" |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+| MISSING_PARAMETER | 必填参数缺失 |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+#### 使用说明\r
+- 该接口用于设备向服务器同步表情下载/删除状态\r
+- 设备执行表情下载或删除操作成功后,必须调用此接口\r
+- 服务器会根据上报的信息更新小程序端的表情状态显示\r
+- characterId、eyesColor、eyesShape三个字段组合标识一个唯一的表情组\r
+\r
+### 7.2 上报设备所有表情组状态\r
+\r
+#### 接口描述\r
+上报设备已下载的所有非预设表情组接口。需每天0点上报一次,同步表情下载状态到小程序端。该接口需要设备Token认证。\r
+\r
+#### 请求信息\r
+- **URL**: `/v1/emoji/reportAllEmoji`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+\r
+#### 认证要求\r
+该接口需要设备Token认证,Token通过请求头传递。\r
+\r
+#### 请求头参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" |\r
+| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |\r
+\r
+#### 请求体参数\r
+| 参数名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| emojiGroups | Array | 是 | 已下载的表情组列表 | 见下方示例 |\r
+\r
+#### EmojiGroup对象说明\r
+| 字段名 | 类型 | 必填 | 描述 | 示例 |\r
+|--------|------|------|------|------|\r
+| characterId | String | 是 | 角色ID | "char_001" |\r
+| eyesColor | String | 是 | 瞳色 | "blue" |\r
+| eyesShape | String | 是 | 眼型 | "calm-eyes" |\r
+| status | String | 是 | 状态 | "download_success", "download_fail", "delete_success", "delete_fail" |\r
+\r
+#### 请求示例\r
+```json\r
+{\r
+  "emojiGroups": [\r
+    {\r
+      "characterId": "char_001",\r
+      "eyesColor": "blue",\r
+      "eyesShape": "calm-eyes",\r
+      "status": "download_success"\r
+    },\r
+    {\r
+      "characterId": "char_001",\r
+      "eyesColor": "green",\r
+      "eyesShape": "round-eyes",\r
+      "status": "download_success"\r
+    },\r
+    {\r
+      "characterId": "char_002",\r
+      "eyesColor": "red",\r
+      "eyesShape": "upturned-eyes",\r
+      "status": "download_success"\r
+    }\r
+  ]\r
+}\r
+```\r
+\r
+#### 响应数据\r
+**成功响应**:\r
+```json\r
+{\r
+  "errorCode": null,\r
+  "message": null,\r
+  "data": "ok"\r
+}\r
+```\r
+\r
+**失败响应(认证失败)**:\r
+```json\r
+{\r
+  "errorCode": "AUTH_FAILURE",\r
+  "message": "设备Token无效",\r
+  "data": null\r
+}\r
+```\r
+\r
+#### 响应字段说明\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| data | String | 操作结果,成功时返回"ok" |\r
+\r
+#### 错误码\r
+| 错误码 | 描述 |\r
+|--------|------|\r
+| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) |\r
+| MISSING_PARAMETER | 必填参数缺失 |\r
+| SERVER_ERROR | 服务器内部错误 |\r
+\r
+#### 使用说明\r
+- 该接口用于设备定时向服务器同步所有已下载的表情组状态\r
+- 建议每天0点自动调用一次,确保服务器端数据与设备端一致\r
+- emojiGroups数组包含设备当前已下载的所有非预设表情组\r
+- 空数组表示设备没有下载任何非预设表情组\r
+- 服务器会根据上报的信息更新小程序端的表情状态显示\r
+\r
+### 表情管理接口使用流程\r
+1. **表情下载**:小程序端触发表情下载 → 服务器通过WebSocket推送DownloadEmoji命令 → 设备下载表情文件 → 设备调用reportEmoji接口上报状态\r
+2. **表情删除**:小程序端触发表情删除 → 服务器通过WebSocket推送DeleteEmoji命令 → 设备删除表情文件 → 设备调用reportEmoji接口上报状态\r
+3. **状态同步**:设备每日0点调用reportAllEmoji接口 → 服务器更新设备表情状态 → 小程序端显示最新状态\r
+\r
+### 表情包标识说明\r
+表情包通过三个字段的组合进行唯一标识:\r
+- **characterId**:角色ID,标识表情属于哪个角色\r
+- **eyesColor**:瞳色\r
+- **eyesShape**:眼型\r
+\r
+相同characterId、eyesColor、eyesShape组合的所有表情文件构成一个表情组,设备需要统一管理这些表情文件的下载和删除。\r
+\r
+---\r
+\r
+## 8. 二维码生成接口\r
+\r
+### 接口描述\r
+生成二维码图片接口,根据提供的内容生成PNG格式的二维码图片并直接返回图片二进制数据。该接口无需认证。\r
+\r
+### 请求信息\r
+- **URL**: `/v1/qrcode/generate/image`\r
+- **方法**: `POST`\r
+- **Content-Type**: `application/json`\r
+\r
+### 请求参数\r
+| 参数名 | 类型 | 必填 | 描述 | 限制 | 默认值 |\r
+|--------|------|------|------|------|--------|\r
+| content | String | 是 | 二维码内容 | 不能为空 | - |\r
+| width | Integer | 否 | 图片宽度 | 100-1000像素 | 300 |\r
+| height | Integer | 否 | 图片高度 | 100-1000像素 | 300 |\r
+| withLogo | Boolean | 否 | 是否包含Logo | - | false |\r
+\r
+### 请求示例\r
+```json\r
+{\r
+  "content": "https://example.com/device/bind?deviceId=DEVICE_001",\r
+  "width": 300,\r
+  "height": 300,\r
+  "withLogo": false\r
+}\r
+```\r
+\r
+### 响应数据\r
+**成功响应**:\r
+- **Content-Type**: `image/png`\r
+- **Content-Disposition**: `inline; filename=qrcode.png`\r
+- **响应体**: PNG格式的二维码图片二进制数据\r
+\r
+**失败响应**:\r
+- **400 Bad Request**: 请求参数无效(如content为空)\r
+- **500 Internal Server Error**: 服务器内部错误\r
+\r
+### 响应头说明\r
+| 头字段名 | 值 | 描述 |\r
+|---------|-----|------|\r
+| Content-Type | image/png | 响应内容类型为PNG图片 |\r
+| Content-Length | 图片字节数 | 图片文件大小 |\r
+| Content-Disposition | inline; filename=qrcode.png | 内联显示,文件名为qrcode.png |\r
+\r
+### 参数验证规则\r
+- **content**: 不能为空或空字符串\r
+- **width**: 如果提供,必须在100-1000像素范围内,否则使用默认值300\r
+- **height**: 如果提供,必须在100-1000像素范围内,否则使用默认值300\r
+- **withLogo**: 可选参数,默认为false\r
+\r
+### 使用场景\r
+此接口主要用于:\r
+1. 设备绑定:生成包含设备ID的绑定二维码\r
+2. 设备信息展示:生成设备相关信息的二维码\r
+3. 通用二维码生成:根据任意文本内容生成二维码\r
+\r
+### 客户端处理说明\r
+- 响应为二进制图片数据,可直接显示或保存\r
+- 建议客户端设置合理的超时时间\r
+- 可通过Content-Length头获取图片大小信息\r
+\r
+---\r
+\r
+## 9. 系统整体时序图\r
+\r
+### 设备完整生命周期流程\r
+\r
+```mermaid\r
+sequenceDiagram\r
+    participant Device as 设备端\r
+    participant Server as 服务端\r
+    participant User as 用户\r
+    participant MiniProgram as 小程序端\r
+    \r
+    Note over Device,MiniProgram: 设备完整生命周期流程\r
+    \r
+    %% 设备启动和认证\r
+    rect rgb(240, 248, 255)\r
+        Note over Device, Server: 1. 设备启动和认证阶段\r
+        Device->>Device: 设备开机启动\r
+        Device->>Server: 获取设备Token (签名认证)\r
+        Server->>Device: 返回JWT Token\r
+        Device->>Server: 建立WebSocket连接\r
+        Server->>Device: 连接成功\r
+    end\r
+    \r
+    %% 设备初始化\r
+    rect rgb(245, 255, 250)\r
+        Note over Device, Server: 2. 设备初始化阶段\r
+        Device->>Server: 检查OTA升级\r
+        alt 有升级\r
+            Server->>Device: 返回升级包信息\r
+            Device->>Device: 执行升级\r
+        else 无升级\r
+            Server->>Device: 无升级任务\r
+        end\r
+        \r
+        Device->>Server: 获取设备角色信息\r
+        Server->>Device: 返回角色配置\r
+        Device->>Device: 应用角色设置\r
+        \r
+        Device->>Server: 检查用户绑定状态\r
+        alt 未绑定\r
+            Server->>Device: 返回未绑定\r
+            Device->>Server: 生成绑定二维码\r
+            Server->>Device: 返回二维码图片\r
+            Device->>Device: 显示二维码等待绑定\r
+        else 已绑定\r
+            Server->>Device: 返回用户ID\r
+            Device->>Device: 进入正常运行模式\r
+        end\r
+    end\r
+    \r
+    %% 用户交互阶段\r
+    rect rgb(255, 250, 240)\r
+        Note over Device, MiniProgram: 3. 用户交互阶段\r
+        \r
+        par 语音对话\r
+            User->>Device: 语音输入\r
+            Device->>Server: 语音对话接口\r
+            Server->>Device: 返回回复和意图识别\r
+            Device->>User: 播放回复音频\r
+            \r
+            alt 识别到哄睡意图\r
+                Device->>Server: 请求故事接口(SSE)\r
+                loop 故事流式推送\r
+                    Server->>Device: 推送故事片段\r
+                    Device->>User: 播放故事音频\r
+                end\r
+                Device->>Server: 结束哄睡模式\r
+            end\r
+            \r
+        and 小程序控制\r
+            MiniProgram->>Server: 角色切换/哄睡控制\r
+            Server->>Device: WebSocket推送命令\r
+            Device->>Device: 执行相应操作\r
+            \r
+        and 系统提醒\r
+            Server->>Device: WebSocket推送日程提醒\r
+            Device->>User: 播放提醒内容\r
+        end\r
+    end\r
+    \r
+    %% 运行维护\r
+    rect rgb(248, 248, 255)\r
+        Note over Device, Server: 4. 运行维护阶段\r
+        loop 持续运行\r
+            Device->>Server: 心跳保持\r
+            Server->>Device: 心跳响应\r
+        end\r
+        \r
+        opt 定期检查升级\r
+            Device->>Server: 检查OTA升级\r
+            Server->>Device: 升级任务响应\r
+        end\r
+    end\r
+```\r
+\r
+## 10. 通用说明\r
+\r
+### 设备认证流程\r
+1. 设备首次启动时,使用签名算法调用获取设备Token接口\r
+2. 获取到Token后,后续所有API调用都需要携带该Token\r
+3. Token过期后,需要重新获取新的Token\r
+\r
+### 安全注意事项\r
+- 签名密钥和Token密钥必须安全保存,不能泄露\r
+- 时间戳验证可以防止重放攻击\r
+- 使用HTTPS协议传输,确保数据安全\r
+- WebSocket连接建议使用WSS协议确保通信安全\r
+\r
+### 开发调试\r
+- 签名算法实现时注意字符串拼接的顺序和分隔符\r
+- 时间戳必须是毫秒级别\r
+- WebSocket连接测试时注意Token的有效性\r
+- 建议实现心跳机制保持连接活跃\r
+\r
+### 时序图说明\r
+本文档包含以下时序图:\r
+1. **设备认证流程**:展示设备如何获取访问令牌\r
+2. **语音对话流程**:展示语音交互的完整过程\r
+3. **哄睡故事流程**:展示从意图识别到故事播放的完整流程\r
+4. **设备信息管理**:展示设备角色配置和用户绑定流程\r
+5. **WebSocket通信**:展示实时通信和命令推送流程\r
+6. **OTA升级流程**:展示固件升级的完整过程\r
+7. **表情管理流程**:展示表情下载、删除和状态同步的完整过程\r
+8. **系统整体流程**:展示设备完整生命周期的综合时序图\r
+\r
+每个时序图都详细描述了参与方之间的交互序列,有助于开发者理解系统架构和实现细节。\r