Fiber 앱 설정, 미들웨어, rate limiter를 server 패키지로 추출. main.go는 DB 연결, DI, 서버 시작, graceful shutdown만 담당. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
193 lines
6.0 KiB
Go
193 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"a301_server/internal/announcement"
|
|
"a301_server/internal/auth"
|
|
"a301_server/internal/bossraid"
|
|
"a301_server/internal/chain"
|
|
"a301_server/internal/download"
|
|
"a301_server/internal/player"
|
|
"a301_server/internal/server"
|
|
|
|
_ "a301_server/docs" // swagger docs
|
|
|
|
"github.com/tolelom/tolchain/core"
|
|
"a301_server/pkg/config"
|
|
"a301_server/pkg/database"
|
|
"a301_server/pkg/middleware"
|
|
"a301_server/routes"
|
|
)
|
|
|
|
// @title One of the Plans API
|
|
// @version 1.0
|
|
// @description 멀티플레이어 보스 레이드 게임 플랫폼 백엔드 API
|
|
// @host a301.api.tolelom.xyz
|
|
// @BasePath /
|
|
|
|
// @securityDefinitions.apikey BearerAuth
|
|
// @in header
|
|
// @name Authorization
|
|
// @description JWT Bearer 토큰 (예: Bearer eyJhbGci...)
|
|
|
|
// @securityDefinitions.apikey ApiKeyAuth
|
|
// @in header
|
|
// @name X-API-Key
|
|
// @description 내부 API 키 (게임 서버 ↔ API 서버 통신용)
|
|
|
|
func main() {
|
|
config.Load()
|
|
config.WarnInsecureDefaults()
|
|
|
|
db, err := database.ConnectMySQL()
|
|
if err != nil {
|
|
log.Fatalf("MySQL 연결 실패: %v", err)
|
|
}
|
|
log.Println("MySQL 연결 성공")
|
|
|
|
// AutoMigrate
|
|
if err := db.AutoMigrate(&auth.User{}, &announcement.Announcement{}, &download.Info{}, &chain.UserWallet{}, &bossraid.BossRoom{}, &bossraid.DedicatedServer{}, &bossraid.RoomSlot{}, &bossraid.RewardFailure{}, &player.PlayerProfile{}); err != nil {
|
|
log.Fatalf("AutoMigrate 실패: %v", err)
|
|
}
|
|
|
|
rdb, err := database.ConnectRedis()
|
|
if err != nil {
|
|
log.Fatalf("Redis 연결 실패: %v", err)
|
|
}
|
|
log.Println("Redis 연결 성공")
|
|
|
|
// ── 의존성 주입 ──────────────────────────────────────────────────
|
|
|
|
authRepo := auth.NewRepository(db)
|
|
authSvc := auth.NewService(authRepo, rdb)
|
|
authHandler := auth.NewHandler(authSvc)
|
|
|
|
chainClient := chain.NewClient(config.C.ChainNodeURL)
|
|
chainRepo := chain.NewRepository(db)
|
|
chainSvc, err := chain.NewService(chainRepo, chainClient, config.C.ChainID, config.C.OperatorKeyHex, config.C.WalletEncryptionKey)
|
|
if err != nil {
|
|
log.Fatalf("chain service init failed: %v", err)
|
|
}
|
|
chainHandler := chain.NewHandler(chainSvc)
|
|
|
|
userResolver := func(username string) (uint, error) {
|
|
user, err := authRepo.FindByUsername(username)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return user.ID, nil
|
|
}
|
|
chainSvc.SetUserResolver(userResolver)
|
|
|
|
authSvc.SetWalletCreator(func(userID uint) error {
|
|
_, err := chainSvc.CreateWallet(userID)
|
|
return err
|
|
})
|
|
|
|
playerRepo := player.NewRepository(db)
|
|
playerSvc := player.NewService(playerRepo)
|
|
playerSvc.SetUserResolver(userResolver)
|
|
playerHandler := player.NewHandler(playerSvc)
|
|
|
|
authSvc.SetProfileCreator(func(userID uint) error {
|
|
return playerSvc.CreateProfile(userID)
|
|
})
|
|
|
|
if err := authSvc.EnsureAdmin(config.C.AdminUsername, config.C.AdminPassword); err != nil {
|
|
log.Printf("admin 계정 생성 실패: %v", err)
|
|
} else {
|
|
log.Printf("admin 계정 확인 완료: %s", config.C.AdminUsername)
|
|
}
|
|
|
|
brRepo := bossraid.NewRepository(db)
|
|
brSvc := bossraid.NewService(brRepo, rdb)
|
|
brSvc.SetRewardGranter(func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error {
|
|
_, err := chainSvc.GrantRewardByUsername(username, tokenAmount, assets)
|
|
return err
|
|
})
|
|
brSvc.SetExpGranter(func(username string, exp int) error {
|
|
return playerSvc.GrantExperienceByUsername(username, exp)
|
|
})
|
|
brHandler := bossraid.NewHandler(brSvc)
|
|
|
|
if config.C.InternalAPIKey == "" {
|
|
log.Println("WARN: INTERNAL_API_KEY not set — /api/internal/* endpoints are disabled")
|
|
}
|
|
|
|
annRepo := announcement.NewRepository(db)
|
|
annSvc := announcement.NewService(annRepo)
|
|
annHandler := announcement.NewHandler(annSvc)
|
|
|
|
dlRepo := download.NewRepository(db)
|
|
dlSvc := download.NewService(dlRepo, config.C.GameDir)
|
|
dlHandler := download.NewHandler(dlSvc, config.C.BaseURL)
|
|
|
|
// ── 서버 + 라우트 설정 ───────────────────────────────────────────
|
|
|
|
app := server.New()
|
|
|
|
authMw := middleware.Auth(rdb, config.C.JWTSecret)
|
|
serverAuthMw := middleware.ServerAuth(config.C.InternalAPIKey)
|
|
idempotencyReqMw := middleware.IdempotencyRequired(rdb)
|
|
|
|
routes.Register(app, authHandler, annHandler, dlHandler, chainHandler, brHandler, playerHandler,
|
|
server.AuthLimiter(), server.APILimiter(), server.HealthCheck(), server.ReadyCheck(db, rdb),
|
|
server.ChainUserLimiter(), authMw, serverAuthMw, idempotencyReqMw)
|
|
|
|
// ── 백그라운드 워커 ──────────────────────────────────────────────
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(15 * time.Second)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
brSvc.CheckStaleSlots()
|
|
}
|
|
}()
|
|
|
|
rewardWorker := bossraid.NewRewardWorker(
|
|
brRepo,
|
|
func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error {
|
|
_, err := chainSvc.GrantRewardByUsername(username, tokenAmount, assets)
|
|
return err
|
|
},
|
|
func(username string, exp int) error {
|
|
return playerSvc.GrantExperienceByUsername(username, exp)
|
|
},
|
|
)
|
|
rewardWorker.Start()
|
|
|
|
// ── Graceful shutdown ────────────────────────────────────────────
|
|
|
|
go func() {
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
sig := <-sigCh
|
|
log.Printf("수신된 시그널: %v — 서버 종료 중...", sig)
|
|
rewardWorker.Stop()
|
|
if err := app.ShutdownWithTimeout(10 * time.Second); err != nil {
|
|
log.Printf("서버 종료 실패: %v", err)
|
|
}
|
|
if rdb != nil {
|
|
if err := rdb.Close(); err != nil {
|
|
log.Printf("Redis 종료 실패: %v", err)
|
|
} else {
|
|
log.Println("Redis 연결 종료 완료")
|
|
}
|
|
}
|
|
if sqlDB, err := db.DB(); err == nil {
|
|
if err := sqlDB.Close(); err != nil {
|
|
log.Printf("MySQL 종료 실패: %v", err)
|
|
} else {
|
|
log.Println("MySQL 연결 종료 완료")
|
|
}
|
|
}
|
|
}()
|
|
|
|
log.Fatal(app.Listen(":" + config.C.AppPort))
|
|
}
|