package game import ( "bindbox-game/internal/pkg/logger" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" "context" "fmt" "time" "go.uber.org/zap" "gorm.io/gorm" "gorm.io/gorm/clause" ) // TicketService 游戏资格服务 type TicketService interface { // GrantTicket 发放游戏资格 GrantTicket(ctx context.Context, userID int64, gameCode string, amount int, source string, sourceID int64, remark string) error // UseTicket 使用游戏资格 UseTicket(ctx context.Context, userID int64, gameCode string) error // GetUserTickets 获取用户资格 GetUserTickets(ctx context.Context, userID int64) (map[string]int, error) // GetUserTicketByGame 获取用户指定游戏资格 GetUserTicketByGame(ctx context.Context, userID int64, gameCode string) (*model.UserGameTickets, error) // GetTicketLogs 获取用户资格变动日志 GetTicketLogs(ctx context.Context, userID int64, page, pageSize int) ([]*model.GameTicketLogs, int64, error) } type ticketService struct { logger logger.CustomLogger readDB *dao.Query writeDB *dao.Query repo mysql.Repo } // NewTicketService 创建资格服务 func NewTicketService(l logger.CustomLogger, db mysql.Repo) TicketService { return &ticketService{ logger: l, readDB: dao.Use(db.GetDbR()), writeDB: dao.Use(db.GetDbW()), repo: db, } } // GrantTicket 发放游戏资格 func (s *ticketService) GrantTicket(ctx context.Context, userID int64, gameCode string, amount int, source string, sourceID int64, remark string) error { if amount <= 0 { return fmt.Errorf("amount must be positive") } return s.repo.GetDbW().Transaction(func(tx *gorm.DB) error { // Upsert user_game_tickets ticket := &model.UserGameTickets{ UserID: userID, GameCode: gameCode, Available: int32(amount), TotalEarned: int32(amount), TotalUsed: 0, } err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "user_id"}, {Name: "game_code"}}, DoUpdates: clause.Assignments(map[string]interface{}{ "available": gorm.Expr("available + ?", amount), "total_earned": gorm.Expr("total_earned + ?", amount), "updated_at": time.Now(), }), }).Create(ticket).Error if err != nil { return err } // 查询变动后余额 var balance int32 tx.Model(&model.UserGameTickets{}). Where("user_id = ? AND game_code = ?", userID, gameCode). Pluck("available", &balance) // 记录日志 log := &model.GameTicketLogs{ UserID: userID, GameCode: gameCode, ChangeType: 1, // 获得 Amount: int32(amount), Balance: balance, Source: source, SourceID: sourceID, Remark: remark, } return tx.Create(log).Error }) } // UseTicket 使用游戏资格 func (s *ticketService) UseTicket(ctx context.Context, userID int64, gameCode string) error { return s.repo.GetDbW().Transaction(func(tx *gorm.DB) error { // 检查并扣减 result := tx.Model(&model.UserGameTickets{}). Where("user_id = ? AND game_code = ? AND available > 0", userID, gameCode). Updates(map[string]interface{}{ "available": gorm.Expr("available - 1"), "total_used": gorm.Expr("total_used + 1"), "updated_at": time.Now(), }) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return fmt.Errorf("insufficient game tickets") } // 查询变动后余额 var balance int32 tx.Model(&model.UserGameTickets{}). Where("user_id = ? AND game_code = ?", userID, gameCode). Pluck("available", &balance) // 记录日志 log := &model.GameTicketLogs{ UserID: userID, GameCode: gameCode, ChangeType: 2, // 使用 Amount: 1, Balance: balance, Source: "game_enter", Remark: "进入游戏", } return tx.Create(log).Error }) } // GetUserTickets 获取用户所有游戏资格 func (s *ticketService) GetUserTickets(ctx context.Context, userID int64) (map[string]int, error) { var tickets []*model.UserGameTickets err := s.repo.GetDbR().WithContext(ctx). Where("user_id = ?", userID). Find(&tickets).Error if err != nil { s.logger.Error("GetUserTickets failed", zap.Int64("user_id", userID), zap.Error(err)) return nil, err } result := make(map[string]int) for _, t := range tickets { result[t.GameCode] = int(t.Available) } return result, nil } // GetUserTicketByGame 获取用户指定游戏资格 func (s *ticketService) GetUserTicketByGame(ctx context.Context, userID int64, gameCode string) (*model.UserGameTickets, error) { var ticket model.UserGameTickets err := s.repo.GetDbR().WithContext(ctx). Where("user_id = ? AND game_code = ?", userID, gameCode). First(&ticket).Error if err != nil { return nil, err } return &ticket, nil } // GetTicketLogs 获取用户游戏资格变动日志 func (s *ticketService) GetTicketLogs(ctx context.Context, userID int64, page, pageSize int) ([]*model.GameTicketLogs, int64, error) { var logs []*model.GameTicketLogs var total int64 db := s.repo.GetDbR().WithContext(ctx).Model(&model.GameTicketLogs{}).Where("user_id = ?", userID) if err := db.Count(&total).Error; err != nil { return nil, 0, err } if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 20 } offset := (page - 1) * pageSize if err := db.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil { return nil, 0, err } return logs, total, nil }