feat: DB DI 전환 + download 하위 호환성 + race condition 수정

- middleware(Auth, Idempotency)를 클로저 팩토리 패턴으로 DI 전환
- database.DB/RDB 전역 변수 제거, ConnectMySQL/Redis 값 반환으로 변경
- download API X-API-Version 헤더 + 하위 호환성 규칙 문서화
- SaveGameData PlayTimeDelta 원자적 UPDATE (race condition 해소)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 16:58:36 +09:00
parent f4d862b47f
commit 0dfa744c16
9 changed files with 226 additions and 199 deletions

View File

@@ -12,6 +12,19 @@ import (
"github.com/gofiber/fiber/v2"
)
// Download API 하위 호환성 규칙:
// - 기존 필드 삭제 금지 (런처 바이너리가 필드에 의존)
// - 기존 필드 타입 변경 금지
// - 기존 필드명(JSON key) 변경 금지
// - 신규 필드 추가만 허용 (기존 런처는 unknown 필드를 무시)
// - 스키마 변경 시 downloadAPIVersion 값을 올릴 것
//
// 현재 /api/download/info 응답 필드 (v1):
// id, createdAt, updatedAt, url, version, fileName, fileSize,
// fileHash, launcherUrl, launcherSize, launcherHash
const downloadAPIVersion = "1"
type Handler struct {
svc *Service
baseURL string
@@ -34,6 +47,7 @@ func (h *Handler) GetInfo(c *fiber.Ctx) error {
if err != nil {
return apperror.NotFound("다운로드 정보가 없습니다")
}
c.Set("X-API-Version", downloadAPIVersion)
return c.JSON(info)
}
@@ -89,6 +103,7 @@ func (h *Handler) ServeFile(c *fiber.Ctx) error {
if info != nil && info.FileName != "" {
filename = info.FileName
}
c.Set("X-API-Version", downloadAPIVersion)
c.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
return c.SendFile(path)
}
@@ -128,6 +143,7 @@ func (h *Handler) ServeLauncher(c *fiber.Ctx) error {
if _, err := os.Stat(path); err != nil {
return apperror.NotFound("파일이 없습니다")
}
c.Set("X-API-Version", downloadAPIVersion)
c.Set("Content-Disposition", `attachment; filename="launcher.exe"`)
return c.SendFile(path)
}