repair-agent c219ec2fcf
Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 4m28s
feat(hw-ws-service): 将 Go WebSocket 服务纳入 CI/CD 并通过 Traefik 统一入口
## 变更内容

### k8s/ingress.yaml
- 新增 /xiaozhi/v1/ 路径规则,将 WebSocket 流量路由到 hw-ws-svc:8888
- Traefik 最长前缀优先,/xiaozhi/v1/ 不影响 / 下的 Django 路由

### hw_service_go/k8s/service.yaml
- Service 类型由 LoadBalancer 改为 ClusterIP
- 移除阿里云 SLB 注解(通过 Traefik Ingress 统一暴露,不再需要独立公网 IP)

### hw_service_go/k8s/deployment.yaml
- 镜像地址改为 ${CI_REGISTRY_IMAGE}/hw-ws-service:latest 占位符
- CI/CD 部署时统一通过 sed 替换为华为云 SWR 实际地址

### hw_service_go/internal/server/server.go
- 新增 GET /xiaozhi/v1/healthz 接口,返回 {"status":"ok","active_connections":N}
- 用于部署后验证服务存活及当前连接数

### .gitea/workflows/deploy.yaml
- 新增 Build and Push HW WebSocket Service 步骤,构建并推送 hw_service_go 镜像
- 部署步骤新增 kubectl apply hw_service_go/k8s/deployment.yaml 和 service.yaml
- 新增 kubectl rollout restart deployment/hw-ws-service

### run.sh
- 本地同时启动 Django + hw_service_go 的开发脚本

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 17:16:26 +08:00

74 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"context"
"log"
"time"
"github.com/qy/hw-ws-service/internal/audio"
"github.com/qy/hw-ws-service/internal/connection"
"github.com/qy/hw-ws-service/internal/rtcclient"
)
// HandleStory 处理硬件发来的 {"type":"story"} 指令。
// 在独立 goroutine 中调用,不阻塞消息读取循环。
func HandleStory(conn *connection.Connection, client *rtcclient.Client) {
tag := "[story][" + conn.DeviceID + "]"
// 整个故事播放流程最长允许 10 分钟
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// 1. 通知硬件TTS 开始
if err := conn.SendJSON(map[string]string{"type": "tts", "state": "start"}); err != nil {
log.Printf("%s send start failed: %v", tag, err)
return
}
// 确保异常退出时也发送 stop避免硬件卡住
defer func() {
conn.StopPlayback()
conn.SendJSON(map[string]string{"type": "tts", "state": "stop"}) //nolint:errcheck
}()
// 2. 调用 RTC 后端获取故事
story, err := client.FetchStoryByMAC(ctx, conn.DeviceID)
if err != nil {
log.Printf("%s fetch story error: %v", tag, err)
return
}
if story == nil {
log.Printf("%s no story available", tag)
return
}
log.Printf("%s playing: %s", tag, story.Title)
// 3. 下载 MP3 并转码为 Opus 帧CPU 密集,在当前 goroutine 中执行)
frames, err := audio.MP3URLToOpusFrames(ctx, story.AudioURL)
if err != nil {
log.Printf("%s audio convert error: %v", tag, err)
return
}
log.Printf("%s converted %d frames (~%.1fs)", tag, len(frames),
float64(len(frames)*audio.FrameDurationMs)/1000)
// 4. 通知硬件:句子开始(发送故事标题)
if err := conn.SendJSON(map[string]any{
"type": "tts",
"state": "sentence_start",
"text": story.Title,
}); err != nil {
log.Printf("%s send sentence_start failed: %v", tag, err)
return
}
// 5. 开始播放,获取打断 channel
abortCh := conn.StartPlayback()
// 6. 流控推送 Opus 帧
SendOpusStream(conn, frames, abortCh)
log.Printf("%s playback finished", tag)
// defer 会发送 stop 并调用 StopPlayback
}