Files
Cyrene/backend/gateway/internal/handler/voice_handler.go
T
AskaEth 71f0a1abdb feat: Go模块路径迁移 + Docker生产部署适配 + ethend Docker兼容
- 所有Go模块路径从 github.com/yourname/cyrene-ai 迁移到 git.yeij.top/AskaEth/Cyrene
- 5个Go Dockerfile添加 GOPROXY=https://goproxy.cn,direct 解决国内构建问题
- ai-core go.mod 添加 pkg/plugins replace 指令
- Caddyfile 简化为 http:// 通配 + handle 保留 /api 前缀
- ethend Dockerfile 适配 (npm install + 仅 COPY package.json)
- ethend 新增 RUNNING_IN_DOCKER 环境变量,健康检查改用Docker服务名
- ethend 数据库状态检查支持Docker hostname (postgres/redis/qdrant/minio)
- process-manager 新增 CONTAINER_SVC_MAP + Docker模式自动检测
- 统一 docker-compose.dev.db.yml 卷名 (pg_data/redis_data/qdrant_data/minio_data)
- docker-compose.yml ethend服务挂载docker.sock + 端口变量化
- 清理 .env 统一后的残留文件与提示信息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:43:22 +08:00

180 lines
5.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 (
"bytes"
"encoding/json"
"io"
"git.yeij.top/AskaEth/Cyrene/pkg/logger"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// VoiceHandler 语音处理器 — 代理到 voice-service
type VoiceHandler struct {
voiceServiceURL string
client *http.Client
}
// NewVoiceHandler 创建语音处理器
func NewVoiceHandler(voiceServiceURL string) *VoiceHandler {
return &VoiceHandler{
voiceServiceURL: voiceServiceURL,
client: &http.Client{},
}
}
// Transcribe POST /api/v1/voice/transcribe
// 代理 multipart/form-data 请求到 voice-service
func (h *VoiceHandler) Transcribe(c *gin.Context) {
// 限制上传大小 (10MB)
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10<<20)
// 读取原始请求体
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败或文件过大,最大支持 10MB"})
return
}
// 构建代理请求
url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/transcribe"
proxyReq, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "构建代理请求失败"})
return
}
proxyReq.Header.Set("Content-Type", c.GetHeader("Content-Type"))
resp, err := h.client.Do(proxyReq)
if err != nil {
logger.Printf("[voice] Voice-Service 不可达 (Transcribe): %v", err)
c.JSON(http.StatusBadGateway, gin.H{
"error": "Voice-Service 不可达: " + err.Error(),
"errorType": "voice_service_unreachable",
"hint": "Voice-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Voice-Service",
})
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
// 透传状态码和响应
c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody)
}
// TTSSynthesize POST /api/v1/voice/tts
// 代理 JSON 请求到 voice-service TTS 合成
func (h *VoiceHandler) TTSSynthesize(c *gin.Context) {
// 读取 JSON 请求体
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"})
return
}
// 构建代理请求
url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/synthesize"
proxyReq, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "构建代理请求失败"})
return
}
proxyReq.Header.Set("Content-Type", "application/json")
resp, err := h.client.Do(proxyReq)
if err != nil {
logger.Printf("[voice] Voice-Service 不可达 (TTS): %v", err)
c.JSON(http.StatusBadGateway, gin.H{
"error": "Voice-Service 不可达: " + err.Error(),
"errorType": "voice_service_unreachable",
"hint": "Voice-Service 服务未启动或不可达",
})
return
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取 Voice-Service 响应失败"})
return
}
// 透传状态码、Content-Type 和响应体
c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody)
}
// TTSVoices GET /api/v1/voice/tts/voices
// 代理请求到 voice-service 获取可用语音列表
func (h *VoiceHandler) TTSVoices(c *gin.Context) {
url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/voices"
resp, err := h.client.Get(url)
if err != nil {
logger.Printf("[voice] Voice-Service 不可达 (Voices): %v", err)
c.JSON(http.StatusBadGateway, gin.H{
"error": "Voice-Service 不可达: " + err.Error(),
"errorType": "voice_service_unreachable",
})
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
// 解析并透传
var data interface{}
json.Unmarshal(respBody, &data)
c.JSON(resp.StatusCode, data)
}
// TTSStatus GET /api/v1/voice/tts/status
// 代理请求到 voice-service 获取 TTS 状态
func (h *VoiceHandler) TTSStatus(c *gin.Context) {
url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/status"
resp, err := h.client.Get(url)
if err != nil {
logger.Printf("[voice] Voice-Service 不可达 (TTS Status): %v", err)
c.JSON(http.StatusBadGateway, gin.H{
"error": "Voice-Service 不可达: " + err.Error(),
"errorType": "voice_service_unreachable",
})
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var data interface{}
json.Unmarshal(respBody, &data)
c.JSON(resp.StatusCode, data)
}
// VoiceStatus GET /api/v1/voice/status
// 代理请求到 voice-service 获取完整状态(STT + TTS
func (h *VoiceHandler) VoiceStatus(c *gin.Context) {
url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/status"
resp, err := h.client.Get(url)
if err != nil {
logger.Printf("[voice] Voice-Service 不可达 (Status): %v", err)
c.JSON(http.StatusBadGateway, gin.H{
"error": "Voice-Service 不可达: " + err.Error(),
"errorType": "voice_service_unreachable",
})
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var data interface{}
json.Unmarshal(respBody, &data)
c.JSON(resp.StatusCode, data)
}