spark-server/internal/bootstrap/app.go
2026-09-16 19:08:06 +08:00

126 lines
3.0 KiB
Go

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
}