refactor(tts): 添加限流器清理和多项优化

1. 为限流器增加定期清理过期请求记录的功能,减少内存占用
2. 重构httpPostStream使用context超时管理,复用全局http客户端
3. 调整synthesis函数返回结果结构,新增请求ID字段
4. 优化请求体读取错误处理,细化错误响应
5. 为TTS响应添加X-Request-Id响应头
This commit is contained in:
sun
2026-05-18 23:13:34 +08:00
parent 00460771c4
commit f3197338d4
+47 -18
View File
@@ -75,8 +75,11 @@ type RateLimiter struct {
mutex sync.Mutex
limit int
window time.Duration
lastCleanup time.Time
}
const cleanupInterval = time.Hour
type Stats struct {
totalRequests int64
successfulRequests int64
@@ -131,6 +134,11 @@ func (rl *RateLimiter) Allow(key string) bool {
now := time.Now()
cutoff := now.Add(-rl.window)
if now.Sub(rl.lastCleanup) > cleanupInterval {
rl.cleanup()
rl.lastCleanup = now
}
timestamps := rl.requests[key]
valid := make([]time.Time, 0, len(timestamps))
for _, ts := range timestamps {
@@ -149,6 +157,23 @@ func (rl *RateLimiter) Allow(key string) bool {
return true
}
func (rl *RateLimiter) cleanup() {
cutoff := time.Now().Add(-rl.window)
for k, v := range rl.requests {
valid := make([]time.Time, 0, len(v))
for _, ts := range v {
if ts.After(cutoff) {
valid = append(valid, ts)
}
}
if len(valid) == 0 {
delete(rl.requests, k)
} else {
rl.requests[k] = valid
}
}
}
func initTTSConfig() error {
apiKey := os.Getenv("BYTEDANCE_TTS_API_KEY")
resourceId := os.Getenv("BYTEDANCE_TTS_RESOURCE_ID")
@@ -242,20 +267,12 @@ func httpPostStream(url string, headers map[string]string, body []byte, timeout
req.Header.Set(key, value)
}
client := globalHTTPClient
if timeout != 0 {
client = &http.Client{
Timeout: timeout,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
},
}
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return client.Do(req)
req = req.WithContext(ctx)
return globalHTTPClient.Do(req)
}
func convertSpeedToSpeechRate(speed float64) int {
@@ -268,7 +285,12 @@ func convertSpeedToSpeechRate(speed float64) int {
return int((speed - 1.0) * 100)
}
func synthesis(text string, speed float64) ([]byte, error) {
type SynthesisResult struct {
AudioData []byte
ReqID string
}
func synthesis(text string, speed float64) (*SynthesisResult, error) {
reqID := uuid.NewString()
speechRate := convertSpeedToSpeechRate(speed)
@@ -336,6 +358,8 @@ func synthesis(text string, speed float64) ([]byte, error) {
if v3Resp.Usage != nil {
log.Printf("TTS synthesis completed, usage: %+v", v3Resp.Usage)
}
for scanner.Scan() {
}
break
}
@@ -365,7 +389,7 @@ func synthesis(text string, speed float64) ([]byte, error) {
return nil, fmt.Errorf("no audio data received")
}
return audioData, nil
return &SynthesisResult{AudioData: audioData, ReqID: reqID}, nil
}
func validateAPIKey(r *http.Request) bool {
@@ -478,7 +502,11 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, MAX_REQUEST_BODY_SIZE))
if err != nil {
if strings.Contains(err.Error(), "request body too large") {
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
} else {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
}
return
}
@@ -510,7 +538,7 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
}
ttsStart := time.Now()
audioData, err := synthesis(req.Input, speed)
result, err := synthesis(req.Input, speed)
duration := time.Since(ttsStart)
if err != nil {
@@ -522,9 +550,10 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
addRequestStats(true, duration, "")
w.Header().Set("Content-Type", "audio/wav")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(audioData)))
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(result.AudioData)))
w.Header().Set("X-Request-Id", result.ReqID)
w.WriteHeader(http.StatusOK)
w.Write(audioData)
w.Write(result.AudioData)
}
func addRequestStats(success bool, responseTime time.Duration, errMsg string) {