feat: 보상 재시도 + TX 확정 대기 + 에러 포맷 통일 + 품질 고도화
- 보상 지급 실패 시 즉시 재시도(3회 backoff) + DB 기록 + 백그라운드 워커 재시도 - WaitForTx 폴링으로 블록체인 TX 확정 대기, SendTxAndWait 편의 메서드 - chain 트랜잭션 코드 중복 제거 (userTx/operatorTx 헬퍼, 50% 감소) - AppError 기반 에러 응답 포맷 통일 (8개 코드, 전 핸들러 마이그레이션) - TX 에러 분류 + 한국어 사용자 메시지 매핑 (11가지 패턴) - player 서비스 테스트 20개 + chain WaitForTx 테스트 10개 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
23
main.go
23
main.go
@@ -18,6 +18,7 @@ import (
|
||||
_ "a301_server/docs" // swagger docs
|
||||
|
||||
"github.com/tolelom/tolchain/core"
|
||||
"a301_server/pkg/apperror"
|
||||
"a301_server/pkg/config"
|
||||
"a301_server/pkg/database"
|
||||
"a301_server/pkg/metrics"
|
||||
@@ -56,7 +57,7 @@ func main() {
|
||||
log.Println("MySQL 연결 성공")
|
||||
|
||||
// AutoMigrate
|
||||
if err := database.DB.AutoMigrate(&auth.User{}, &announcement.Announcement{}, &download.Info{}, &chain.UserWallet{}, &bossraid.BossRoom{}, &bossraid.DedicatedServer{}, &bossraid.RoomSlot{}, &player.PlayerProfile{}); err != nil {
|
||||
if err := database.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)
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ func main() {
|
||||
return c.IP()
|
||||
},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{"error": "요청이 너무 많습니다. 잠시 후 다시 시도해주세요"})
|
||||
return apperror.ErrRateLimited
|
||||
},
|
||||
})
|
||||
|
||||
@@ -182,7 +183,7 @@ func main() {
|
||||
return c.IP()
|
||||
},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{"error": "요청이 너무 많습니다. 잠시 후 다시 시도해주세요"})
|
||||
return apperror.ErrRateLimited
|
||||
},
|
||||
})
|
||||
|
||||
@@ -215,7 +216,7 @@ func main() {
|
||||
return "chain_ip:" + c.IP()
|
||||
},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{"error": "트랜잭션 요청이 너무 많습니다. 잠시 후 다시 시도해주세요"})
|
||||
return apperror.ErrRateLimited
|
||||
},
|
||||
})
|
||||
|
||||
@@ -230,12 +231,26 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// Background: retry failed rewards
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user