package bootstrap import ( "context" "crypto/tls" "errors" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "spark-server/internal/infrastructure/id" "spark-server/internal/infrastructure/logger" "spark-server/internal/infrastructure/mysql" redisx "spark-server/internal/infrastructure/redis" httpx "spark-server/internal/transport/http" tcp "spark-server/internal/transport/tcp" "spark-server/internal/transport/tcp/session" ) type App struct { cfg *Config log *slog.Logger http *httpx.Server tcp *tcp.Server pool tcp.TaskPool mysql *mysql.DB redis *redisx.Client idgen *id.Generator sessions session.Manager } func New(cfg *Config) (*App, error) { log := logger.New() idgen, err := id.New(cfg.ID.SnowflakeWorkerID) if err != nil { return nil, err } db, err := mysql.Open(cfg.MySQL.DSN) if err != nil { return nil, err } rdb, err := redisx.Open(cfg.Redis.Addr) if err != nil { _ = db.Close() return nil, err } pool := tcp.NewTaskPool(cfg.TaskPool.Size, cfg.TaskPool.Queue) sessions := session.NewManager() tlsConf, err := loadTLS(cfg.Server.TCP.TLS) if err != nil { return nil, err } tcpSrv := tcp.NewServer(log, pool, sessions, &tcp.Options{ Addr: cfg.Server.TCP.Addr, Multicore: cfg.Server.TCP.Multicore, KeepAlive: cfg.Server.TCP.KeepAlive, RecvBuffer: cfg.Server.TCP.RecvBuffer, SendBuffer: cfg.Server.TCP.SendBuffer, MaxFrameSize: cfg.Server.TCP.MaxFrameSize, TLS: tlsConf, ProxyProtocol: cfg.Server.TCP.ProxyProtocol, IdleTimeout: cfg.Server.TCP.IdleTimeout, OutboundSize: cfg.Session.OutboundQueueSize, RatePerSec: cfg.RateLimit.MessagePerSecond, CompressMin: cfg.Server.TCP.CompressMinSize, IDs: idgen, }) return &App{ cfg: cfg, log: log, http: httpx.New(log, cfg.Server.HTTP.Addr), tcp: tcpSrv, pool: pool, mysql: db, redis: rdb, idgen: idgen, sessions: sessions, }, nil } func loadTLS(t TLSConfig) (*tls.Config, error) { if t.CertFile == "" || t.KeyFile == "" { return nil, nil } cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile) if err != nil { return nil, fmt.Errorf("tls: %w", err) } return &tls.Config{MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cert}}, nil } func (a *App) Run() error { errCh := make(chan error, 2) go func() { errCh <- a.http.Start() }() go func() { errCh <- a.tcp.Start() }() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) select { case err := <-errCh: return err case sig := <-sigCh: a.log.Info("shutdown", "signal", sig.String()) return a.Shutdown() } } func (a *App) Shutdown() error { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() var err error if e := a.http.Stop(ctx); e != nil && !errors.Is(e, http.ErrServerClosed) { err = errors.Join(err, e) } if e := a.tcp.Stop(ctx); e != nil { err = errors.Join(err, e) } a.pool.Close() err = errors.Join(err, a.redis.Close(), a.mysql.Close()) return err }