// Package main 是 notification service 的入口程序。 // // 设计要点(参考 socialService/main.go 的结构): // - 通过 flag + 环境变量注入运行参数(端口、DB 等)。 // - 启动顺序:logger -> DB -> AutoMigrate -> service -> provider -> Dubbo server。 // - 使用 Dubbo Triple 协议暴露 notification.proto 中的 NotificationService。 // - 优雅关闭:监听 SIGINT/SIGTERM,关停 health server 与 DB 连接。 package main import ( "flag" "fmt" "os" "os/signal" "strconv" "syscall" "dubbo.apache.org/dubbo-go/v3/protocol" "dubbo.apache.org/dubbo-go/v3/server" _ "dubbo.apache.org/dubbo-go/v3/imports" "github.com/topfans/backend/pkg/database" "github.com/topfans/backend/pkg/health" "github.com/topfans/backend/pkg/logger" notifPb "github.com/topfans/backend/pkg/proto/notification" "github.com/topfans/backend/services/notificationService/model" "github.com/topfans/backend/services/notificationService/provider" "github.com/topfans/backend/services/notificationService/service" ) var ( port = flag.Int("port", getEnvInt("PORT", 20010), "Dubbo service port") dbHost = flag.String("db-host", getEnv("DB_HOST", "localhost"), "Database host") dbPort = flag.Int("db-port", getEnvInt("DB_PORT", 5432), "Database port") dbUser = flag.String("db-user", getEnv("DB_USER", "postgres"), "Database user") dbPassword = flag.String("db-password", getEnv("DB_PASSWORD", ""), "Database password") dbName = flag.String("db-name", getEnv("DB_NAME", "top-fans"), "Database name") healthHndl *health.Handler ) func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func getEnvInt(key string, fallback int) int { if v := os.Getenv(key); v != "" { if n, err := strconv.Atoi(v); err == nil { return n } } return fallback } func main() { flag.Parse() // 1) 初始化日志(必须在最前面,便于后续捕获启动错误) env := os.Getenv("ENV") if env == "" { env = "development" } if err := logger.Init(logger.Config{ ServiceName: "notification-service", Environment: env, LogLevel: os.Getenv("LOG_LEVEL"), }); err != nil { panic(fmt.Sprintf("Failed to initialize logger: %v", err)) } defer logger.Sync() logger.Sugar.Info("Starting Notification Service...") // 2) 初始化数据库 if err := initDatabase(); err != nil { logger.Sugar.Fatalf("Failed to initialize database: %v", err) } // 3) 自动迁移数据库表(notification + notification_stats) if err := autoMigrate(); err != nil { logger.Sugar.Fatalf("Failed to migrate database: %v", err) } // 4) 启动 Dubbo server + 注册 NotificationService if err := initDubboService(); err != nil { logger.Sugar.Fatalf("Failed to initialize Dubbo service: %v", err) } // 5) 等待退出信号(优雅关闭) logger.Sugar.Info("Notification service started successfully. Press Ctrl+C to exit.") gracefulShutdown() } // initDatabase 初始化数据库连接(复用 socialService 的写法)。 func initDatabase() error { config := database.Config{ Host: *dbHost, Port: *dbPort, User: *dbUser, Password: *dbPassword, DBName: *dbName, SSLMode: "disable", TimeZone: "Asia/Shanghai", } return database.Init(config) } // autoMigrate 自动迁移 notification 相关表。 func autoMigrate() error { db := database.GetDB() if db == nil { return fmt.Errorf("database is not initialized") } tables := []interface{}{ &model.Notification{}, &model.NotificationStats{}, } for _, table := range tables { if err := db.AutoMigrate(table); err != nil { return fmt.Errorf("failed to migrate table: %w", err) } } logger.Sugar.Info("Database migration completed successfully") return nil } // gracefulShutdown 优雅关闭(health server + DB)。 func gracefulShutdown() { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit logger.Sugar.Info("Shutting down server...") if healthHndl != nil { healthHndl.Stop() } if err := database.Close(); err != nil { logger.Sugar.Errorf("Error closing database: %v", err) } logger.Sugar.Info("Server exited") } // initDubboService 初始化 Dubbo-go 服务。 // // 启动健康检查 HTTP server(端口 = dubbo port + 1000,例如 21010), // 然后构造 service / provider,注册到 Triple 协议的 Dubbo server。 func initDubboService() error { // 健康检查 HTTP server healthPort := *port + 1000 // e.g., 20010 -> 21010 healthHndl = health.NewHandler("notification-service", healthPort) healthHndl.Start() db := database.GetDB() if db == nil { return fmt.Errorf("database is not initialized") } // 业务层(service 只依赖 DB;repository 内部自行 New) notifService := service.NewNotificationService(db) // RPC Provider notifProvider := provider.NewNotificationProvider(notifService) // Dubbo Server(Triple 协议) srv, err := server.NewServer( server.WithServerProtocol( protocol.WithPort(*port), protocol.WithTriple(), ), ) if err != nil { return fmt.Errorf("failed to create Dubbo server: %w", err) } // 注册 NotificationServiceHandler(来自 notification.triple.go) if err := notifPb.RegisterNotificationServiceHandler(srv, notifProvider); err != nil { return fmt.Errorf("failed to register NotificationService handler: %w", err) } logger.Sugar.Info("Dubbo-go notification provider registered successfully", "service", notifPb.NotificationServiceName, "port", *port, ) // 后台启动 Dubbo server(阻塞当前 goroutine 时 main 会卡在 srv.Serve 上, // 所以放 goroutine 里,由 gracefulShutdown 通过 os.Signal 触发退出)。 go func() { if err := srv.Serve(); err != nil { logger.Sugar.Fatalf("Failed to serve Dubbo: %v", err) } }() return nil }