// Command scoreboard 是赛事积分榜系统的服务端。 package main import ( "context" "errors" "flag" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "git.yeij.top/AskaEth/Scoreboard/server/internal/config" "git.yeij.top/AskaEth/Scoreboard/server/internal/httpapi" "git.yeij.top/AskaEth/Scoreboard/server/internal/store" ) // version 由构建时注入:-ldflags "-X main.version=..." var version = "dev" func main() { configPath := flag.String("config", "config.yaml", "配置文件路径") flag.Parse() cfg, err := config.Load(*configPath) if err != nil { fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err) os.Exit(1) } logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.Log.SlogLevel()})) slog.SetDefault(logger) st, err := store.Open(cfg.DataDir) if err != nil { logger.Error("打开数据库失败", "err", err) os.Exit(1) } defer func() { _ = st.Close() }() if err := st.Migrate(); err != nil { logger.Error("数据库迁移失败", "err", err) os.Exit(1) } logger.Info("数据库就绪", "dataDir", cfg.DataDir) srv := &http.Server{ Addr: cfg.Server.Addr(), Handler: httpapi.New(httpapi.Options{Logger: logger, Version: version}), ReadHeaderTimeout: 10 * time.Second, } go func() { logger.Info("服务启动", "addr", srv.Addr, "version", version) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Error("服务异常退出", "err", err) os.Exit(1) } }() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() <-ctx.Done() logger.Info("正在关闭…") shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { logger.Error("关闭超时", "err", err) } }