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:
@@ -7,6 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"a301_server/pkg/apperror"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@@ -30,7 +32,7 @@ func NewHandler(svc *Service, baseURL string) *Handler {
|
||||
func (h *Handler) GetInfo(c *fiber.Ctx) error {
|
||||
info, err := h.svc.GetInfo()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "다운로드 정보가 없습니다"})
|
||||
return apperror.NotFound("다운로드 정보가 없습니다")
|
||||
}
|
||||
return c.JSON(info)
|
||||
}
|
||||
@@ -54,17 +56,17 @@ func (h *Handler) Upload(c *fiber.Ctx) error {
|
||||
// 경로 순회 방지: 디렉토리 구분자 제거, 기본 파일명만 사용
|
||||
filename = filepath.Base(filename)
|
||||
if !strings.HasSuffix(strings.ToLower(filename), ".zip") {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zip 파일만 업로드 가능합니다"})
|
||||
return apperror.BadRequest("zip 파일만 업로드 가능합니다")
|
||||
}
|
||||
if len(filename) > 200 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "파일명이 너무 깁니다"})
|
||||
return apperror.BadRequest("파일명이 너무 깁니다")
|
||||
}
|
||||
|
||||
body := c.Request().BodyStream()
|
||||
info, err := h.svc.Upload(filename, body, h.baseURL)
|
||||
if err != nil {
|
||||
log.Printf("game upload failed: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "게임 파일 업로드에 실패했습니다"})
|
||||
return apperror.Internal("게임 파일 업로드에 실패했습니다")
|
||||
}
|
||||
return c.JSON(info)
|
||||
}
|
||||
@@ -80,7 +82,7 @@ func (h *Handler) Upload(c *fiber.Ctx) error {
|
||||
func (h *Handler) ServeFile(c *fiber.Ctx) error {
|
||||
path := h.svc.GameFilePath()
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "파일이 없습니다"})
|
||||
return apperror.NotFound("파일이 없습니다")
|
||||
}
|
||||
info, _ := h.svc.GetInfo()
|
||||
filename := "game.zip"
|
||||
@@ -108,7 +110,7 @@ func (h *Handler) UploadLauncher(c *fiber.Ctx) error {
|
||||
info, err := h.svc.UploadLauncher(body, h.baseURL)
|
||||
if err != nil {
|
||||
log.Printf("launcher upload failed: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "런처 업로드에 실패했습니다"})
|
||||
return apperror.Internal("런처 업로드에 실패했습니다")
|
||||
}
|
||||
return c.JSON(info)
|
||||
}
|
||||
@@ -124,7 +126,7 @@ func (h *Handler) UploadLauncher(c *fiber.Ctx) error {
|
||||
func (h *Handler) ServeLauncher(c *fiber.Ctx) error {
|
||||
path := h.svc.LauncherFilePath()
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "파일이 없습니다"})
|
||||
return apperror.NotFound("파일이 없습니다")
|
||||
}
|
||||
c.Set("Content-Disposition", `attachment; filename="launcher.exe"`)
|
||||
return c.SendFile(path)
|
||||
|
||||
Reference in New Issue
Block a user