feat: 인프라 개선 — 헬스체크, 로깅, 보안, CI 검증
- /health + /ready 엔드포인트 추가 (DB/Redis 상태 확인) - RequestID 미들웨어 + 구조화 JSON 로깅 - 체인 트랜잭션 per-user rate limit (20 req/min) - DB 커넥션 풀 설정 (MaxOpen 25, MaxIdle 10, MaxLifetime 5m) - Graceful Shutdown 시 Redis/MySQL 연결 정리 - Dockerfile HEALTHCHECK 추가 - CI에 go vet + 빌드 검증 단계 추가 (deploy 전 실행) - 보스 레이드 클라이언트 입장 API (JWT 인증) - Player 프로필 모듈 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,21 +1,41 @@
|
||||
package bossraid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/tolelom/tolchain/core"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
rewardGrant func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error
|
||||
const (
|
||||
// entryTokenTTL is the TTL for boss raid entry tokens in Redis.
|
||||
entryTokenTTL = 5 * time.Minute
|
||||
// entryTokenPrefix is the Redis key prefix for entry token → {username, sessionName}.
|
||||
entryTokenPrefix = "bossraid:entry:"
|
||||
// pendingEntryPrefix is the Redis key prefix for username → {sessionName, entryToken}.
|
||||
pendingEntryPrefix = "bossraid:pending:"
|
||||
)
|
||||
|
||||
// entryTokenData is stored in Redis for each entry token.
|
||||
type entryTokenData struct {
|
||||
Username string `json:"username"`
|
||||
SessionName string `json:"sessionName"`
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
rdb *redis.Client
|
||||
rewardGrant func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error
|
||||
}
|
||||
|
||||
func NewService(repo *Repository, rdb *redis.Client) *Service {
|
||||
return &Service{repo: repo, rdb: rdb}
|
||||
}
|
||||
|
||||
// SetRewardGranter sets the callback for granting rewards via blockchain.
|
||||
@@ -213,3 +233,108 @@ func (s *Service) FailRaid(sessionName string) (*BossRoom, error) {
|
||||
func (s *Service) GetRoom(sessionName string) (*BossRoom, error) {
|
||||
return s.repo.FindBySessionName(sessionName)
|
||||
}
|
||||
|
||||
// generateToken creates a cryptographically random hex token.
|
||||
func generateToken() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// GenerateEntryTokens creates entry tokens for all players in a room
|
||||
// and stores them in Redis. Returns a map of username → entryToken.
|
||||
func (s *Service) GenerateEntryTokens(sessionName string, usernames []string) (map[string]string, error) {
|
||||
ctx := context.Background()
|
||||
tokens := make(map[string]string, len(usernames))
|
||||
|
||||
for _, username := range usernames {
|
||||
token, err := generateToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("토큰 생성 실패: %w", err)
|
||||
}
|
||||
tokens[username] = token
|
||||
|
||||
// Store entry token → {username, sessionName}
|
||||
data, _ := json.Marshal(entryTokenData{
|
||||
Username: username,
|
||||
SessionName: sessionName,
|
||||
})
|
||||
entryKey := entryTokenPrefix + token
|
||||
if err := s.rdb.Set(ctx, entryKey, string(data), entryTokenTTL).Err(); err != nil {
|
||||
return nil, fmt.Errorf("Redis 저장 실패: %w", err)
|
||||
}
|
||||
|
||||
// Store pending entry: username → {sessionName, entryToken}
|
||||
pendingData, _ := json.Marshal(map[string]string{
|
||||
"sessionName": sessionName,
|
||||
"entryToken": token,
|
||||
})
|
||||
pendingKey := pendingEntryPrefix + username
|
||||
if err := s.rdb.Set(ctx, pendingKey, string(pendingData), entryTokenTTL).Err(); err != nil {
|
||||
return nil, fmt.Errorf("Redis 저장 실패: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// ValidateEntryToken validates and consumes a one-time entry token.
|
||||
// Returns the username and sessionName if valid.
|
||||
func (s *Service) ValidateEntryToken(token string) (username, sessionName string, err error) {
|
||||
ctx := context.Background()
|
||||
key := entryTokenPrefix + token
|
||||
|
||||
val, err := s.rdb.GetDel(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return "", "", fmt.Errorf("유효하지 않거나 만료된 입장 토큰입니다")
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("토큰 검증 실패: %w", err)
|
||||
}
|
||||
|
||||
var data entryTokenData
|
||||
if err := json.Unmarshal([]byte(val), &data); err != nil {
|
||||
return "", "", fmt.Errorf("토큰 데이터 파싱 실패: %w", err)
|
||||
}
|
||||
|
||||
return data.Username, data.SessionName, nil
|
||||
}
|
||||
|
||||
// GetMyEntryToken returns the pending entry token for a username.
|
||||
func (s *Service) GetMyEntryToken(username string) (sessionName, entryToken string, err error) {
|
||||
ctx := context.Background()
|
||||
key := pendingEntryPrefix + username
|
||||
|
||||
val, err := s.rdb.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return "", "", fmt.Errorf("대기 중인 입장 토큰이 없습니다")
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("토큰 조회 실패: %w", err)
|
||||
}
|
||||
|
||||
var data map[string]string
|
||||
if err := json.Unmarshal([]byte(val), &data); err != nil {
|
||||
return "", "", fmt.Errorf("토큰 데이터 파싱 실패: %w", err)
|
||||
}
|
||||
|
||||
return data["sessionName"], data["entryToken"], nil
|
||||
}
|
||||
|
||||
// RequestEntryWithTokens creates a boss room and generates entry tokens for all players.
|
||||
// Returns the room and a map of username → entryToken.
|
||||
func (s *Service) RequestEntryWithTokens(usernames []string, bossID int) (*BossRoom, map[string]string, error) {
|
||||
room, err := s.RequestEntry(usernames, bossID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tokens, err := s.GenerateEntryTokens(room.SessionName, usernames)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("입장 토큰 생성 실패: %w", err)
|
||||
}
|
||||
|
||||
return room, tokens, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user