2026-06-12 17:34:43 +08:00
package main
import (
2026-06-13 15:18:03 +08:00
"context"
"errors"
"net/http"
"os/signal"
2026-06-13 21:31:58 +08:00
"strings"
2026-06-13 15:18:03 +08:00
"syscall"
2026-06-12 17:34:43 +08:00
"time"
"github.com/gin-gonic/gin"
2026-06-13 16:22:53 +08:00
"github.com/hhs/camtalk/internal/api"
2026-06-14 17:26:50 +08:00
"github.com/hhs/camtalk/internal/auth"
2026-06-13 16:13:22 +08:00
"github.com/hhs/camtalk/internal/ai/llm"
"github.com/hhs/camtalk/internal/ai/stt"
"github.com/hhs/camtalk/internal/ai/tts"
2026-06-13 15:18:03 +08:00
"github.com/hhs/camtalk/internal/config"
"github.com/hhs/camtalk/internal/logger"
2026-06-13 16:13:22 +08:00
"github.com/hhs/camtalk/internal/orchestrator"
2026-06-13 15:31:15 +08:00
"github.com/hhs/camtalk/internal/session"
2026-06-14 16:43:07 +08:00
"github.com/hhs/camtalk/internal/store"
2026-06-12 17:34:43 +08:00
"github.com/hhs/camtalk/internal/ws"
)
2026-06-14 11:55:10 +08:00
// Version 通过构建时 -ldflags 注入,如:
// go build -ldflags "-X main.Version=v1.0.0" ./cmd/server
var Version string
2026-06-12 17:34:43 +08:00
var startTime = time . Now ( )
func main ( ) {
2026-06-13 15:18:03 +08:00
// 加载配置
cfg , err := config . Load ( )
if err != nil {
panic ( "failed to load config: " + err . Error ( ) )
}
// 初始化日志
logger . Init ( cfg . Log . Level , cfg . Log . Format )
defer logger . Sync ( )
logger . Log . Infow ( "config loaded" ,
"env" , cfg . App . Env ,
"addr" , cfg . Server . Addr ( ) ,
)
2026-06-14 16:43:07 +08:00
// 初始化存储层(条件初始化 PostgreSQL)
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
if cfg . Storage . Driver == "postgres" {
pool , err := store . NewPostgresPool ( ctx , cfg . Storage . DSN )
if err != nil {
logger . Log . Fatalw ( "failed to connect to postgres" , "error" , err )
}
defer pool . Close ( )
logger . Log . Infow ( "postgres connected" , "driver" , cfg . Storage . Driver )
// TODO: Phase 2 - 初始化 UserRepository 和 MessageRepository
_ = pool
}
2026-06-14 17:26:50 +08:00
// 初始化 UserRepository( 内存模式用于无 DB 场景)
var userRepo store . UserRepository
userRepo = store . NewMemUserRepository ( )
2026-06-13 15:31:15 +08:00
// 初始化 Session Manager( MVP 默认内存实现)
var sessionMgr session . Manager
// TODO: 当 Redis 配置非空时切换为 RedisManager
2026-06-14 11:55:10 +08:00
sessionMgr = session . NewMemoryManager (
time . Duration ( cfg . Session . TTL ) * time . Minute ,
cfg . Session . MaxHistory ,
)
2026-06-13 15:31:15 +08:00
defer sessionMgr . ( * session . MemoryManager ) . Stop ( )
2026-06-13 16:13:22 +08:00
// 初始化 AI 服务
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "initializing AI services" ,
"stt.provider" , cfg . AI . STT . Provider ,
"stt.model" , cfg . AI . STT . Model ,
"llm.provider" , cfg . AI . LLM . Provider ,
"llm.model" , cfg . AI . LLM . Model ,
"tts.provider" , cfg . AI . TTS . Provider ,
"tts.model" , cfg . AI . TTS . Model ,
"tts.voice" , cfg . AI . TTS . Voice ,
)
2026-06-13 21:31:58 +08:00
var sttService stt . Service
switch strings . ToLower ( cfg . AI . STT . Provider ) {
case "mimo" , "xiaomi" :
2026-06-14 11:55:10 +08:00
sttService = stt . NewMiMoService ( cfg . AI . STT . APIKey , cfg . AI . STT . Model , cfg . AI . STT . Endpoint , cfg . AI . STT . Timeout , logger . Log )
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "STT service initialized" , "provider" , "mimo" , "model" , cfg . AI . STT . Model , "endpoint" , cfg . AI . STT . Endpoint )
2026-06-13 21:31:58 +08:00
default :
2026-06-14 11:55:10 +08:00
sttService = stt . NewDeepgramService ( cfg . AI . STT . APIKey , cfg . AI . STT . Model , cfg . AI . STT . Endpoint , cfg . AI . STT . Timeout , logger . Log )
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "STT service initialized" , "provider" , "deepgram" , "model" , cfg . AI . STT . Model )
2026-06-13 21:31:58 +08:00
}
2026-06-14 11:55:10 +08:00
llmService := llm . NewOpenAIService ( cfg . AI . LLM . APIKey , cfg . AI . LLM . Model , cfg . AI . LLM . Endpoint , cfg . AI . LLM . Timeout , cfg . AI . LLM . HTTPClientTimeout , logger . Log )
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "LLM service initialized" , "provider" , cfg . AI . LLM . Provider , "model" , cfg . AI . LLM . Model , "endpoint" , cfg . AI . LLM . Endpoint , "timeout" , cfg . AI . LLM . Timeout )
2026-06-14 10:05:56 +08:00
var ttsService tts . Service
switch strings . ToLower ( cfg . AI . TTS . Provider ) {
case "mimo" , "xiaomi" :
2026-06-14 11:55:10 +08:00
ttsService = tts . NewMiMoService ( cfg . AI . TTS . APIKey , cfg . AI . TTS . Model , cfg . AI . TTS . Voice , cfg . AI . TTS . Endpoint , cfg . AI . TTS . Timeout , cfg . AI . TTS . HTTPClientTimeout , logger . Log )
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "TTS service initialized" , "provider" , "mimo" , "model" , cfg . AI . TTS . Model , "voice" , cfg . AI . TTS . Voice , "endpoint" , cfg . AI . TTS . Endpoint )
2026-06-14 10:05:56 +08:00
default :
2026-06-14 11:55:10 +08:00
ttsService = tts . NewOpenAIService ( cfg . AI . TTS . APIKey , cfg . AI . TTS . Model , cfg . AI . TTS . Voice , cfg . AI . TTS . Endpoint , cfg . AI . TTS . Speed , cfg . AI . TTS . Timeout , cfg . AI . TTS . HTTPClientTimeout , logger . Log )
2026-06-14 10:18:48 +08:00
logger . Log . Infow ( "TTS service initialized" , "provider" , "openai" , "model" , cfg . AI . TTS . Model , "voice" , cfg . AI . TTS . Voice , "speed" , cfg . AI . TTS . Speed )
2026-06-14 10:05:56 +08:00
}
2026-06-13 16:13:22 +08:00
// 初始化 Orchestrator
2026-06-14 11:55:10 +08:00
orch := orchestrator . New ( sttService , llmService , ttsService , sessionMgr , cfg )
2026-06-13 16:13:22 +08:00
2026-06-14 17:26:50 +08:00
// 初始化认证服务
tokenMgr := auth . NewTokenManager (
cfg . Auth . JWTSecret ,
time . Duration ( cfg . Auth . AccessTTL ) * time . Minute ,
time . Duration ( cfg . Auth . RefreshTTL ) * time . Minute ,
)
authService := auth . NewAuthService ( tokenMgr , userRepo )
2026-06-13 15:18:03 +08:00
// Gin 模式
if cfg . App . Env == "prod" {
gin . SetMode ( gin . ReleaseMode )
}
r := gin . New ( )
r . Use ( gin . Recovery ( ) )
2026-06-12 17:34:43 +08:00
// REST API
2026-06-13 16:22:53 +08:00
apiGroup := r . Group ( "/api" )
2026-06-12 17:34:43 +08:00
{
2026-06-14 11:55:10 +08:00
apiGroup . GET ( "/health" , healthHandler ( sessionMgr , cfg ) )
2026-06-12 17:34:43 +08:00
}
2026-06-13 16:22:53 +08:00
// Session REST 端点
sessionHandler := api . NewSessionHandler ( sessionMgr )
sessionHandler . RegisterRoutes ( apiGroup )
2026-06-14 17:26:50 +08:00
// Auth REST 端点
authHandler := api . NewAuthHandler ( authService , tokenMgr )
authHandler . RegisterRoutes ( apiGroup )
2026-06-12 17:34:43 +08:00
// WebSocket
2026-06-14 11:55:10 +08:00
r . GET ( "/ws" , ws . ServeWS ( sessionMgr , orch , cfg ) )
2026-06-12 17:34:43 +08:00
2026-06-13 15:18:03 +08:00
// HTTP Server
srv := & http . Server {
Addr : cfg . Server . Addr ( ) ,
Handler : r ,
ReadTimeout : time . Duration ( cfg . Server . ReadTimeout ) * time . Second ,
WriteTimeout : time . Duration ( cfg . Server . WriteTimeout ) * time . Second ,
}
// Graceful shutdown
ctx , stop := signal . NotifyContext ( context . Background ( ) , syscall . SIGINT , syscall . SIGTERM )
defer stop ( )
go func ( ) {
logger . Log . Infow ( "server starting" , "addr" , srv . Addr )
if err := srv . ListenAndServe ( ) ; err != nil && ! errors . Is ( err , http . ErrServerClosed ) {
logger . Log . Fatalw ( "listen failed" , "error" , err )
}
} ( )
<- ctx . Done ( )
logger . Log . Info ( "shutting down..." )
2026-06-14 11:55:10 +08:00
shutdownCtx , cancel := context . WithTimeout ( context . Background ( ) , time . Duration ( cfg . Server . ShutdownTimeout ) * time . Second )
2026-06-13 15:18:03 +08:00
defer cancel ( )
if err := srv . Shutdown ( shutdownCtx ) ; err != nil {
logger . Log . Errorw ( "shutdown error" , "error" , err )
2026-06-12 17:34:43 +08:00
}
2026-06-13 15:18:03 +08:00
logger . Log . Info ( "server stopped" )
2026-06-12 17:34:43 +08:00
}
// healthHandler 健康检查。
2026-06-14 11:55:10 +08:00
func healthHandler ( sessionMgr session . Manager , cfg * config . Config ) gin . HandlerFunc {
2026-06-13 15:31:15 +08:00
return func ( c * gin . Context ) {
2026-06-14 11:55:10 +08:00
version := Version
if version == "" {
version = cfg . App . Version
}
2026-06-13 15:31:15 +08:00
c . JSON ( 200 , gin . H {
"status" : "ok" ,
2026-06-14 11:55:10 +08:00
"version" : version ,
2026-06-13 16:21:53 +08:00
"uptime_seconds" : int ( time . Since ( startTime ) . Seconds ( ) ) ,
2026-06-13 15:31:15 +08:00
"active_sessions" : sessionMgr . ActiveCount ( ) ,
} )
}
2026-06-12 17:34:43 +08:00
}