Files
EchoChat/backend/go-service/cmd/server/main.go
2026-05-26 22:41:53 +08:00

127 lines
3.5 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 main 是 EchoChat 后端服务的入口
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
authModel "github.com/echochat/backend/app/auth/model"
"github.com/echochat/backend/app/im/model"
meetingModel "github.com/echochat/backend/app/meeting/model"
transcribeModel "github.com/echochat/backend/app/transcribe/model"
"github.com/echochat/backend/app/provider"
"github.com/echochat/backend/config"
"github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/middleware"
"github.com/echochat/backend/router"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func main() {
// 1. 加载配置(通过 CONFIG_NAME 环境变量指定配置文件名,默认 config.dev
configName := os.Getenv("CONFIG_NAME")
if configName == "" {
configName = "config.dev"
}
cfg, err := config.Load("config", configName)
if err != nil {
fmt.Printf("加载配置失败: %v\n", err)
os.Exit(1)
}
// 2. 初始化日志系统
if err := logs.Init(&cfg.Log); err != nil {
fmt.Printf("初始化日志失败: %v\n", err)
os.Exit(1)
}
defer logs.Sync()
ctx := context.Background()
logs.Info(ctx, "main", "EchoChat 服务启动中",
zap.String("mode", cfg.Server.Mode),
zap.Int("port", cfg.Server.Port),
)
// 3. 通过 Wire 初始化所有组件
app, err := provider.InitializeApp(cfg)
if err != nil {
logs.Fatal(ctx, "main", "初始化应用失败", zap.Error(err))
}
// 4. IM 数据库表自动迁移(开发阶段使用,生产环境配合 init.sql
if err := app.DB.AutoMigrate(
&model.Conversation{},
&model.ConversationMember{},
&model.Message{},
&authModel.OAuthAccount{},
// Phase B 新增:会议录制元数据表
&meetingModel.MeetingRecording{},
// Phase B 新增:会议录制语音转写结果表
&transcribeModel.Transcript{},
); err != nil {
logs.Fatal(ctx, "main", "IM 表迁移失败", zap.Error(err))
}
logs.Info(ctx, "main", "IM 表迁移完成")
// 5. 创建 Gin Engine
if cfg.Server.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
}
engine := gin.New()
// 6. 注册中间件顺序Trace → Logger → CORS → Recovery
engine.Use(
middleware.Trace(),
middleware.Logger(),
middleware.CORS(),
middleware.Recovery(),
)
// 7. 注册路由(由 router.Setup 统一汇总各模块路由)
router.Setup(engine, app)
// 7.1 启动 notify 模块过期通知清理定时任务(后台周期执行)
app.NotifyCleanupTask.Start()
defer app.NotifyCleanupTask.Stop()
// 7.2 启动 meeting 模块生命周期兜底任务Phase 2e-2 Task 8
// 内部会先 RescheduleFromRedis 恢复服务重启前残留的 host_grace / empty_ttl timer
app.MeetingCleanupTask.Start()
defer app.MeetingCleanupTask.Stop()
// 8. 启动 HTTP 服务(优雅关闭)
addr := fmt.Sprintf(":%d", cfg.Server.Port)
srv := &http.Server{
Addr: addr,
Handler: engine,
}
go func() {
logs.Info(ctx, "main", "HTTP 服务启动", zap.String("addr", addr))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logs.Fatal(ctx, "main", "HTTP 服务启动失败", zap.Error(err))
}
}()
// 等待中断信号,优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logs.Info(ctx, "main", "正在关闭服务...")
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
logs.Error(ctx, "main", "服务关闭失败", zap.Error(err))
}
logs.Info(ctx, "main", "服务已停止")
}