- server: Go 单二进制骨架,embed 内嵌前端,配置/迁移/HTTP 路由 - store: system.db + event.db 两套迁移,业务表 + 种子数据 - domain: 积分引擎(并列三口径/计分通道/流水/排名/破纪录)+ 68 个测试 - web: Vite + Vue 三入口骨架 + shared 层(类型/API/WebSocket 客户端) - console: Electron 宿主骨架(选屏/防熄屏/进程守护/托盘)
75 lines
1.9 KiB
Go
75 lines
1.9 KiB
Go
// 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)
|
|
}
|
|
}
|