refactor: 重构项目架构,拆分代码到模块化目录
将单文件tts_server.go重构为模块化项目结构,拆分出common、dto、middleware、router、controller、service、adapter、setting等目录,优化代码组织提升可维护性
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var validAPIKeys []string
|
||||
|
||||
func InitAPIKeys() {
|
||||
apiKey := os.Getenv("OPENAI_TTS_API_KEY")
|
||||
if apiKey != "" {
|
||||
validAPIKeys = strings.Split(apiKey, ",")
|
||||
for i, k := range validAPIKeys {
|
||||
validAPIKeys[i] = strings.TrimSpace(k)
|
||||
}
|
||||
log.Printf("已配置 %d 个有效的API密钥", len(validAPIKeys))
|
||||
} else {
|
||||
log.Println("警告: OPENAI_TTS_API_KEY 环境变量未设置,将允许所有请求")
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateAPIKey(r *http.Request) bool {
|
||||
if len(validAPIKeys) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return false
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
for _, validKey := range validAPIKeys {
|
||||
if token == validKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SendJSONError(w http.ResponseWriter, statusCode int, message string, errType string, code string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"error": map[string]interface{}{
|
||||
"message": message,
|
||||
"type": errType,
|
||||
"code": code,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
allowedOrigins []string
|
||||
allowAllOrigins bool
|
||||
corsMaxAgeHeader = "86400"
|
||||
)
|
||||
|
||||
func normalizeOrigin(origin string) string {
|
||||
origin = strings.TrimSpace(origin)
|
||||
origin = strings.TrimRight(origin, "/")
|
||||
return strings.ToLower(origin)
|
||||
}
|
||||
|
||||
func InitCORSConfig() {
|
||||
origins := os.Getenv("ALLOWED_ORIGINS")
|
||||
if origins == "" {
|
||||
log.Println("警告: ALLOWED_ORIGINS 环境变量未设置")
|
||||
log.Println("出于安全考虑,跨域请求将被拒绝。如需开放跨域请配置 ALLOWED_ORIGINS")
|
||||
log.Println("开发环境可设置 ALLOWED_ORIGINS=* 允许所有来源(不可与凭据共用)")
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(origins, ",")
|
||||
for _, p := range parts {
|
||||
o := strings.TrimSpace(p)
|
||||
if o == "" {
|
||||
continue
|
||||
}
|
||||
if o == "*" {
|
||||
allowAllOrigins = true
|
||||
continue
|
||||
}
|
||||
allowedOrigins = append(allowedOrigins, normalizeOrigin(o))
|
||||
}
|
||||
|
||||
if allowAllOrigins {
|
||||
log.Println("警告: ALLOWED_ORIGINS=*,将允许所有来源跨域请求(不携带凭据)")
|
||||
}
|
||||
if len(allowedOrigins) > 0 {
|
||||
log.Printf("已配置 %d 个允许的跨域来源白名单", len(allowedOrigins))
|
||||
}
|
||||
}
|
||||
|
||||
func isValidOrigin(origin string) bool {
|
||||
if origin == "" || origin == "null" || origin == "nil" {
|
||||
return false
|
||||
}
|
||||
if !strings.HasPrefix(origin, "http://") && !strings.HasPrefix(origin, "https://") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func matchOrigin(origin string) (string, bool) {
|
||||
if !isValidOrigin(origin) {
|
||||
return "", false
|
||||
}
|
||||
if allowAllOrigins {
|
||||
return "*", true
|
||||
}
|
||||
normalized := normalizeOrigin(origin)
|
||||
for _, allowed := range allowedOrigins {
|
||||
if allowed == normalized {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func CORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
if origin != "" {
|
||||
allowOrigin, matched := matchOrigin(origin)
|
||||
if matched {
|
||||
w.Header().Set("Access-Control-Allow-Origin", allowOrigin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.Header().Set("Access-Control-Expose-Headers", "X-Request-Id")
|
||||
w.Header().Set("Access-Control-Max-Age", corsMaxAgeHeader)
|
||||
if allowOrigin != "*" {
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
vary := w.Header().Get("Vary")
|
||||
if vary == "" {
|
||||
w.Header().Set("Vary", "Origin")
|
||||
} else if !strings.Contains(vary, "Origin") {
|
||||
w.Header().Set("Vary", vary+", Origin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
if origin != "" {
|
||||
if _, matched := matchOrigin(origin); !matched {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type statusRecorder struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (rec *statusRecorder) WriteHeader(code int) {
|
||||
rec.statusCode = code
|
||||
rec.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/health" {
|
||||
start := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
log.Printf("%s %s %s %v", r.Method, r.RequestURI, r.RemoteAddr, time.Since(start))
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
rec := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
next.ServeHTTP(rec, r)
|
||||
duration := time.Since(start)
|
||||
|
||||
log.Printf("%s %s %s %d %v", r.Method, r.RequestURI, r.RemoteAddr, rec.statusCode, duration)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/volcano-tts/tts-api/common"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
requests map[string][]time.Time
|
||||
mutex sync.Mutex
|
||||
limit int
|
||||
window time.Duration
|
||||
lastCleanup time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
GlobalRateLimiter *RateLimiter
|
||||
ConcurrencySem chan struct{}
|
||||
)
|
||||
|
||||
func InitRateLimiter() {
|
||||
GlobalRateLimiter = &RateLimiter{
|
||||
requests: make(map[string][]time.Time),
|
||||
limit: common.RateLimitRequests,
|
||||
window: common.RateLimitWindow,
|
||||
}
|
||||
ConcurrencySem = make(chan struct{}, common.MaxConcurrentRequests)
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Allow(key string) bool {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-rl.window)
|
||||
|
||||
if now.Sub(rl.lastCleanup) > common.CleanupInterval {
|
||||
rl.cleanup()
|
||||
rl.lastCleanup = now
|
||||
}
|
||||
|
||||
timestamps := rl.requests[key]
|
||||
valid := make([]time.Time, 0, len(timestamps))
|
||||
for _, ts := range timestamps {
|
||||
if ts.After(cutoff) {
|
||||
valid = append(valid, ts)
|
||||
}
|
||||
}
|
||||
|
||||
if len(valid) >= rl.limit {
|
||||
rl.requests[key] = valid
|
||||
return false
|
||||
}
|
||||
|
||||
valid = append(valid, now)
|
||||
rl.requests[key] = valid
|
||||
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 GetClientIP(r *http.Request) string {
|
||||
xForwardedFor := r.Header.Get("X-Forwarded-For")
|
||||
if xForwardedFor != "" {
|
||||
ips := strings.Split(xForwardedFor, ",")
|
||||
if len(ips) > 0 {
|
||||
return strings.TrimSpace(ips[0])
|
||||
}
|
||||
}
|
||||
|
||||
xRealIP := r.Header.Get("X-Real-IP")
|
||||
if xRealIP != "" {
|
||||
return xRealIP
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
Reference in New Issue
Block a user