713 lines
24 KiB
Go
713 lines
24 KiB
Go
package wheel
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type configBundle struct {
|
|
Config *model.WheelConfig
|
|
Categories []model.WheelPoolConfig
|
|
Rewards []model.WheelRewardConfig
|
|
}
|
|
|
|
// GetConfig returns admin wheel configuration.
|
|
func (s *Service) GetConfig(ctx context.Context, sysOrigin string) (*ConfigResponse, error) {
|
|
bundle, err := s.loadConfigBundle(ctx, sysOrigin, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bundle.Config == nil {
|
|
return defaultConfigResponse(sysOrigin), nil
|
|
}
|
|
return s.buildConfigResponse(bundle)
|
|
}
|
|
|
|
// GetAppConfig returns enabled wheel configuration for app clients.
|
|
func (s *Service) GetAppConfig(ctx context.Context, user AuthUser) (*ConfigResponse, error) {
|
|
bundle, err := s.loadConfigBundle(ctx, user.SysOrigin, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bundle.Config == nil {
|
|
return &ConfigResponse{
|
|
Configured: false,
|
|
SysOrigin: normalizeSysOrigin(user.SysOrigin),
|
|
Timezone: defaultTimezone,
|
|
ProbabilityTotal: probabilityTotal,
|
|
Categories: defaultCategoryPayloads(false),
|
|
}, nil
|
|
}
|
|
return s.buildConfigResponse(bundle)
|
|
}
|
|
|
|
// SaveConfig saves admin wheel configuration.
|
|
func (s *Service) SaveConfig(ctx context.Context, req SaveConfigRequest) (*ConfigResponse, error) {
|
|
normalized, err := normalizeSaveRequest(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configEnabled := false
|
|
for _, category := range normalized.Categories {
|
|
if category.Enabled {
|
|
configEnabled = true
|
|
break
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var configRow model.WheelConfig
|
|
if err := withWriteLock(tx).Where("sys_origin = ?", normalized.SysOrigin).First(&configRow).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
nextID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
configRow = model.WheelConfig{
|
|
ID: nextID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
CreateTime: now,
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
configRow.Enabled = configEnabled
|
|
configRow.Timezone = normalized.Timezone
|
|
configRow.UpdateTime = now
|
|
if configRow.CreateTime.IsZero() {
|
|
configRow.CreateTime = now
|
|
}
|
|
if err := tx.Save(&configRow).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
existingRewards, err := loadExistingRewardRows(ctx, tx, configRow.ID, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ?", configRow.ID).Delete(&model.WheelPoolConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ?", configRow.ID).Delete(&model.WheelRewardConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
poolRows := make([]model.WheelPoolConfig, 0, len(normalized.Categories))
|
|
rewardRows := make([]model.WheelRewardConfig, 0, 28)
|
|
for _, category := range normalized.Categories {
|
|
meta, _ := categoryMeta(category.Category)
|
|
poolID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
poolRows = append(poolRows, model.WheelPoolConfig{
|
|
ID: poolID,
|
|
ConfigID: configRow.ID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
Category: category.Category,
|
|
Enabled: category.Enabled,
|
|
PriceOneGold: category.PriceOneGold,
|
|
PriceTenGold: category.PriceTenGold,
|
|
PriceFiftyGold: category.PriceFiftyGold,
|
|
Sort: meta.Sort,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
})
|
|
|
|
for index, item := range category.Rewards {
|
|
rewardID, issuedCount, rewardIDErr := resolveRewardRowIdentity(existingRewards, item)
|
|
if rewardIDErr != nil {
|
|
return rewardIDErr
|
|
}
|
|
sortValue := item.Sort
|
|
if sortValue <= 0 {
|
|
sortValue = index + 1
|
|
}
|
|
rewardRows = append(rewardRows, model.WheelRewardConfig{
|
|
ID: rewardID,
|
|
ConfigID: configRow.ID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
Category: category.Category,
|
|
RewardType: item.RewardType,
|
|
ResourceID: resourceIDPtr(item.ResourceID),
|
|
ResourceType: item.ResourceType,
|
|
ResourceName: strings.TrimSpace(item.ResourceName),
|
|
ResourceURL: strings.TrimSpace(item.ResourceURL),
|
|
CoverURL: strings.TrimSpace(item.CoverURL),
|
|
DurationDays: item.DurationDays,
|
|
DisplayGoldAmount: item.DisplayGoldAmount,
|
|
GoldAmount: item.GoldAmount,
|
|
Probability: item.Probability,
|
|
TotalLimit: item.TotalLimit,
|
|
UserLimit: item.UserLimit,
|
|
IssuedCount: issuedCount,
|
|
Sort: sortValue,
|
|
Enabled: item.Enabled,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
})
|
|
}
|
|
}
|
|
if len(poolRows) > 0 {
|
|
if err := tx.Create(&poolRows).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(rewardRows) > 0 {
|
|
return tx.Create(&rewardRows).Error
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetConfig(ctx, normalized.SysOrigin)
|
|
}
|
|
|
|
// SaveCategoryConfig saves one admin wheel category without touching the other categories.
|
|
func (s *Service) SaveCategoryConfig(ctx context.Context, req SaveCategoryConfigRequest) (*ConfigResponse, error) {
|
|
normalized, err := normalizeSaveCategoryRequest(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := time.Now()
|
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var configRow model.WheelConfig
|
|
if err := withWriteLock(tx).Where("sys_origin = ?", normalized.SysOrigin).First(&configRow).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
nextID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
configRow = model.WheelConfig{
|
|
ID: nextID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
CreateTime: now,
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
configRow.Timezone = normalized.Timezone
|
|
configRow.UpdateTime = now
|
|
if configRow.CreateTime.IsZero() {
|
|
configRow.CreateTime = now
|
|
}
|
|
if err := tx.Save(&configRow).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
category := normalized.Category
|
|
meta, _ := categoryMeta(category.Category)
|
|
existingRewards, err := loadExistingRewardRows(ctx, tx, configRow.ID, category.Category)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ? AND category = ?", configRow.ID, category.Category).Delete(&model.WheelPoolConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ? AND category = ?", configRow.ID, category.Category).Delete(&model.WheelRewardConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
poolID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
poolRow := model.WheelPoolConfig{
|
|
ID: poolID,
|
|
ConfigID: configRow.ID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
Category: category.Category,
|
|
Enabled: category.Enabled,
|
|
PriceOneGold: category.PriceOneGold,
|
|
PriceTenGold: category.PriceTenGold,
|
|
PriceFiftyGold: category.PriceFiftyGold,
|
|
Sort: meta.Sort,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
if err := tx.Create(&poolRow).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
rewardRows := make([]model.WheelRewardConfig, 0, len(category.Rewards))
|
|
for index, item := range category.Rewards {
|
|
rewardID, issuedCount, rewardIDErr := resolveRewardRowIdentity(existingRewards, item)
|
|
if rewardIDErr != nil {
|
|
return rewardIDErr
|
|
}
|
|
sortValue := item.Sort
|
|
if sortValue <= 0 {
|
|
sortValue = index + 1
|
|
}
|
|
rewardRows = append(rewardRows, model.WheelRewardConfig{
|
|
ID: rewardID,
|
|
ConfigID: configRow.ID,
|
|
SysOrigin: normalized.SysOrigin,
|
|
Category: category.Category,
|
|
RewardType: item.RewardType,
|
|
ResourceID: resourceIDPtr(item.ResourceID),
|
|
ResourceType: item.ResourceType,
|
|
ResourceName: strings.TrimSpace(item.ResourceName),
|
|
ResourceURL: strings.TrimSpace(item.ResourceURL),
|
|
CoverURL: strings.TrimSpace(item.CoverURL),
|
|
DurationDays: item.DurationDays,
|
|
DisplayGoldAmount: item.DisplayGoldAmount,
|
|
GoldAmount: item.GoldAmount,
|
|
Probability: item.Probability,
|
|
TotalLimit: item.TotalLimit,
|
|
UserLimit: item.UserLimit,
|
|
IssuedCount: issuedCount,
|
|
Sort: sortValue,
|
|
Enabled: item.Enabled,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
})
|
|
}
|
|
if len(rewardRows) > 0 {
|
|
if err := tx.Create(&rewardRows).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var enabledPools int64
|
|
if err := tx.Model(&model.WheelPoolConfig{}).
|
|
Where("config_id = ? AND enabled = ?", configRow.ID, true).
|
|
Count(&enabledPools).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&model.WheelConfig{}).
|
|
Where("id = ?", configRow.ID).
|
|
Updates(map[string]any{
|
|
"enabled": enabledPools > 0,
|
|
"timezone": normalized.Timezone,
|
|
"update_time": now,
|
|
}).Error
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetConfig(ctx, normalized.SysOrigin)
|
|
}
|
|
|
|
func normalizeSaveRequest(req SaveConfigRequest) (SaveConfigRequest, error) {
|
|
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
if sysOrigin == "" {
|
|
return SaveConfigRequest{}, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
|
|
}
|
|
timezone := normalizeTimezone(req.Timezone)
|
|
if _, err := time.LoadLocation(timezone); err != nil {
|
|
return SaveConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error())
|
|
}
|
|
|
|
inputByCategory := make(map[string]CategoryConfigInput, len(req.Categories))
|
|
for _, category := range req.Categories {
|
|
normalizedCategory := normalizeCategory(category.Category)
|
|
if _, ok := categoryMeta(normalizedCategory); !ok {
|
|
return SaveConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_category", "category must be CLASSIC, LUXURY, or ADVANCED")
|
|
}
|
|
category.Category = normalizedCategory
|
|
inputByCategory[normalizedCategory] = category
|
|
}
|
|
|
|
categories := make([]CategoryConfigInput, 0, len(wheelCategories))
|
|
for _, meta := range wheelCategories {
|
|
category, exists := inputByCategory[meta.Category]
|
|
if !exists {
|
|
category = defaultCategoryInput(meta)
|
|
}
|
|
category.Category = meta.Category
|
|
if category.PriceOneGold <= 0 || category.PriceTenGold <= 0 || category.PriceFiftyGold <= 0 {
|
|
return SaveConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_price", fmt.Sprintf("%s draw prices must be greater than 0", meta.Category))
|
|
}
|
|
normalizedRewards, err := normalizeCategoryRewards(meta, category.Rewards, category.Enabled)
|
|
if err != nil {
|
|
return SaveConfigRequest{}, err
|
|
}
|
|
category.Rewards = normalizedRewards
|
|
categories = append(categories, category)
|
|
}
|
|
|
|
return SaveConfigRequest{
|
|
SysOrigin: sysOrigin,
|
|
Enabled: req.Enabled,
|
|
Timezone: timezone,
|
|
Categories: categories,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeSaveCategoryRequest(req SaveCategoryConfigRequest) (SaveCategoryConfigRequest, error) {
|
|
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
if sysOrigin == "" {
|
|
return SaveCategoryConfigRequest{}, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
|
|
}
|
|
timezone := normalizeTimezone(req.Timezone)
|
|
if _, err := time.LoadLocation(timezone); err != nil {
|
|
return SaveCategoryConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error())
|
|
}
|
|
|
|
category := req.Category
|
|
category.Category = normalizeCategory(category.Category)
|
|
meta, ok := categoryMeta(category.Category)
|
|
if !ok {
|
|
return SaveCategoryConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_category", "category must be CLASSIC, LUXURY, or ADVANCED")
|
|
}
|
|
if category.PriceOneGold <= 0 || category.PriceTenGold <= 0 || category.PriceFiftyGold <= 0 {
|
|
return SaveCategoryConfigRequest{}, NewAppError(http.StatusBadRequest, "invalid_price", fmt.Sprintf("%s draw prices must be greater than 0", meta.Category))
|
|
}
|
|
rewards, err := normalizeCategoryRewards(meta, category.Rewards, category.Enabled)
|
|
if err != nil {
|
|
return SaveCategoryConfigRequest{}, err
|
|
}
|
|
category.Rewards = rewards
|
|
return SaveCategoryConfigRequest{
|
|
SysOrigin: sysOrigin,
|
|
Timezone: timezone,
|
|
Category: category,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeCategoryRewards(meta CategoryMeta, rewards []RewardConfigInput, requireEnabled bool) ([]RewardConfigInput, error) {
|
|
if len(rewards) != meta.RewardCount {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_count", fmt.Sprintf("%s rewards must contain exactly %d items", meta.Category, meta.RewardCount))
|
|
}
|
|
|
|
normalized := append([]RewardConfigInput(nil), rewards...)
|
|
sortRewardInputs(normalized)
|
|
enabledProbability := 0
|
|
enabledCount := 0
|
|
for index := range normalized {
|
|
item := &normalized[index]
|
|
item.RewardType = normalizeRewardType(item.RewardType)
|
|
item.ResourceType = strings.ToUpper(strings.TrimSpace(item.ResourceType))
|
|
item.ResourceName = strings.TrimSpace(item.ResourceName)
|
|
item.ResourceURL = strings.TrimSpace(item.ResourceURL)
|
|
item.CoverURL = strings.TrimSpace(item.CoverURL)
|
|
if item.Sort <= 0 {
|
|
item.Sort = index + 1
|
|
}
|
|
|
|
switch item.RewardType {
|
|
case rewardTypeResource:
|
|
if requireEnabled && item.ResourceID.Int64() <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_resource", fmt.Sprintf("%s resource reward requires resourceId", meta.Category))
|
|
}
|
|
if requireEnabled && item.ResourceType == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_resource_type", fmt.Sprintf("%s resource reward requires resourceType", meta.Category))
|
|
}
|
|
if requireEnabled && item.DurationDays <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_duration_days", fmt.Sprintf("%s resource reward requires durationDays greater than 0", meta.Category))
|
|
}
|
|
if requireEnabled && item.DisplayGoldAmount <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_display_gold_amount", fmt.Sprintf("%s resource reward display gold amount must be greater than 0", meta.Category))
|
|
}
|
|
if item.ResourceID.Int64() > 0 && item.ResourceName == "" {
|
|
item.ResourceName = fmt.Sprintf("资源 %d", item.ResourceID.Int64())
|
|
}
|
|
item.GoldAmount = 0
|
|
case rewardTypeGold:
|
|
if requireEnabled && item.GoldAmount <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_gold_amount", fmt.Sprintf("%s gold reward requires goldAmount", meta.Category))
|
|
}
|
|
item.ResourceID = 0
|
|
item.ResourceType = ""
|
|
item.ResourceName = "金币"
|
|
item.ResourceURL = ""
|
|
item.CoverURL = ""
|
|
item.DurationDays = 0
|
|
item.DisplayGoldAmount = 0
|
|
default:
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_type", "rewardType must be RESOURCE or GOLD")
|
|
}
|
|
|
|
if item.Probability < 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_probability", "probability must be greater than or equal to 0")
|
|
}
|
|
if item.TotalLimit < 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_total_limit", "totalLimit must be greater than or equal to 0")
|
|
}
|
|
if item.UserLimit < 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_user_limit", "userLimit must be greater than or equal to 0")
|
|
}
|
|
if item.Enabled {
|
|
enabledCount++
|
|
if requireEnabled && item.Probability <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_probability", fmt.Sprintf("%s enabled reward probability must be greater than 0", meta.Category))
|
|
}
|
|
enabledProbability += item.Probability
|
|
}
|
|
}
|
|
if requireEnabled {
|
|
if enabledCount != meta.RewardCount {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_enabled_reward_count", fmt.Sprintf("%s must enable exactly %d rewards", meta.Category, meta.RewardCount))
|
|
}
|
|
if enabledProbability != probabilityTotal {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_probability_total", fmt.Sprintf("%s enabled reward probability total must equal %d", meta.Category, probabilityTotal))
|
|
}
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
func loadExistingRewardRows(ctx context.Context, tx *gorm.DB, configID int64, category string) (map[int64]model.WheelRewardConfig, error) {
|
|
rows := make([]model.WheelRewardConfig, 0)
|
|
query := tx.WithContext(ctx).Where("config_id = ?", configID)
|
|
if strings.TrimSpace(category) != "" {
|
|
query = query.Where("category = ?", normalizeCategory(category))
|
|
}
|
|
if err := query.Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[int64]model.WheelRewardConfig, len(rows))
|
|
for _, row := range rows {
|
|
result[row.ID] = row
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func resolveRewardRowIdentity(existing map[int64]model.WheelRewardConfig, item RewardConfigInput) (int64, int64, error) {
|
|
if item.ID > 0 {
|
|
if row, ok := existing[item.ID]; ok && sameRewardIdentity(row, item) {
|
|
return item.ID, row.IssuedCount, nil
|
|
}
|
|
}
|
|
nextID, err := utils.NextID()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return nextID, 0, nil
|
|
}
|
|
|
|
func sameRewardIdentity(row model.WheelRewardConfig, item RewardConfigInput) bool {
|
|
if normalizeRewardType(row.RewardType) != normalizeRewardType(item.RewardType) {
|
|
return false
|
|
}
|
|
if normalizeRewardType(item.RewardType) == rewardTypeGold {
|
|
return row.GoldAmount == item.GoldAmount
|
|
}
|
|
return int64PtrValue(row.ResourceID) == item.ResourceID.Int64() &&
|
|
strings.EqualFold(strings.TrimSpace(row.ResourceType), strings.TrimSpace(item.ResourceType)) &&
|
|
row.DurationDays == item.DurationDays
|
|
}
|
|
|
|
func (s *Service) loadConfigBundle(ctx context.Context, sysOrigin string, onlyEnabledRewards bool) (*configBundle, error) {
|
|
var bundle *configBundle
|
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
loaded, err := s.loadConfigBundleTx(ctx, tx, sysOrigin, onlyEnabledRewards, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bundle = loaded
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return bundle, nil
|
|
}
|
|
|
|
func (s *Service) loadConfigBundleTx(ctx context.Context, tx *gorm.DB, sysOrigin string, onlyEnabledRewards bool, lockRows bool) (*configBundle, error) {
|
|
sysOrigin = normalizeSysOrigin(sysOrigin)
|
|
if sysOrigin == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
|
|
}
|
|
|
|
var configRow model.WheelConfig
|
|
configQuery := tx.WithContext(ctx).Where("sys_origin = ?", sysOrigin)
|
|
if lockRows {
|
|
configQuery = withWriteLock(configQuery)
|
|
}
|
|
err := configQuery.First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return &configBundle{}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var categoryRows []model.WheelPoolConfig
|
|
categoryQuery := tx.WithContext(ctx).
|
|
Where("config_id = ?", configRow.ID).
|
|
Order("sort asc, id asc")
|
|
if lockRows {
|
|
categoryQuery = withWriteLock(categoryQuery)
|
|
}
|
|
if err := categoryQuery.Find(&categoryRows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rewardQuery := tx.WithContext(ctx).
|
|
Where("config_id = ?", configRow.ID).
|
|
Order("category asc, sort asc, id asc")
|
|
if onlyEnabledRewards {
|
|
rewardQuery = rewardQuery.Where("enabled = ?", true)
|
|
}
|
|
if lockRows {
|
|
rewardQuery = withWriteLock(rewardQuery)
|
|
}
|
|
var rewardRows []model.WheelRewardConfig
|
|
if err := rewardQuery.Find(&rewardRows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &configBundle{Config: &configRow, Categories: categoryRows, Rewards: rewardRows}, nil
|
|
}
|
|
|
|
func defaultConfigResponse(sysOrigin string) *ConfigResponse {
|
|
return &ConfigResponse{
|
|
Configured: false,
|
|
SysOrigin: normalizeSysOrigin(sysOrigin),
|
|
Enabled: false,
|
|
Timezone: defaultTimezone,
|
|
ProbabilityTotal: probabilityTotal,
|
|
Categories: defaultCategoryPayloads(false),
|
|
}
|
|
}
|
|
|
|
func defaultCategoryInput(meta CategoryMeta) CategoryConfigInput {
|
|
return CategoryConfigInput{
|
|
Category: meta.Category,
|
|
Enabled: false,
|
|
PriceOneGold: meta.DefaultPriceOneGold,
|
|
PriceTenGold: meta.DefaultPriceTenGold,
|
|
PriceFiftyGold: meta.DefaultPriceFiftyGold,
|
|
Rewards: defaultRewardInputs(meta.RewardCount),
|
|
}
|
|
}
|
|
|
|
func defaultRewardInputs(count int) []RewardConfigInput {
|
|
rewards := make([]RewardConfigInput, 0, count)
|
|
for index := 0; index < count; index++ {
|
|
rewards = append(rewards, RewardConfigInput{
|
|
Enabled: true,
|
|
Sort: index + 1,
|
|
RewardType: rewardTypeResource,
|
|
Probability: 0,
|
|
})
|
|
}
|
|
return rewards
|
|
}
|
|
|
|
func defaultCategoryPayloads(enabled bool) []CategoryConfigPayload {
|
|
result := make([]CategoryConfigPayload, 0, len(wheelCategories))
|
|
for _, meta := range wheelCategories {
|
|
rewards := make([]RewardConfigPayload, 0, meta.RewardCount)
|
|
for index := 0; index < meta.RewardCount; index++ {
|
|
rewards = append(rewards, RewardConfigPayload{
|
|
Enabled: true,
|
|
Sort: index + 1,
|
|
RewardType: rewardTypeResource,
|
|
Probability: 0,
|
|
ProbabilityPercent: "0.00",
|
|
})
|
|
}
|
|
result = append(result, CategoryConfigPayload{
|
|
Category: meta.Category,
|
|
Label: meta.Label,
|
|
Enabled: enabled,
|
|
RewardCount: meta.RewardCount,
|
|
PriceOneGold: meta.DefaultPriceOneGold,
|
|
PriceTenGold: meta.DefaultPriceTenGold,
|
|
PriceFiftyGold: meta.DefaultPriceFiftyGold,
|
|
ProbabilityTotal: probabilityTotal,
|
|
Rewards: rewards,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *Service) buildConfigResponse(bundle *configBundle) (*ConfigResponse, error) {
|
|
configRow := bundle.Config
|
|
categoryByName := make(map[string]model.WheelPoolConfig, len(bundle.Categories))
|
|
for _, row := range bundle.Categories {
|
|
categoryByName[normalizeCategory(row.Category)] = row
|
|
}
|
|
rewardsByCategory := make(map[string][]model.WheelRewardConfig, len(wheelCategories))
|
|
for _, row := range bundle.Rewards {
|
|
category := normalizeCategory(row.Category)
|
|
rewardsByCategory[category] = append(rewardsByCategory[category], row)
|
|
}
|
|
|
|
categories := make([]CategoryConfigPayload, 0, len(wheelCategories))
|
|
for _, meta := range wheelCategories {
|
|
pool, exists := categoryByName[meta.Category]
|
|
if !exists {
|
|
pool = model.WheelPoolConfig{
|
|
Category: meta.Category,
|
|
Enabled: false,
|
|
PriceOneGold: meta.DefaultPriceOneGold,
|
|
PriceTenGold: meta.DefaultPriceTenGold,
|
|
PriceFiftyGold: meta.DefaultPriceFiftyGold,
|
|
Sort: meta.Sort,
|
|
}
|
|
}
|
|
rewards := make([]RewardConfigPayload, 0, len(rewardsByCategory[meta.Category]))
|
|
for _, row := range rewardsByCategory[meta.Category] {
|
|
rewards = append(rewards, rewardPayloadFromConfig(row))
|
|
}
|
|
for len(rewards) < meta.RewardCount {
|
|
rewards = append(rewards, RewardConfigPayload{
|
|
Enabled: true,
|
|
Sort: len(rewards) + 1,
|
|
RewardType: rewardTypeResource,
|
|
Probability: 0,
|
|
ProbabilityPercent: "0.00",
|
|
})
|
|
}
|
|
if len(rewards) > meta.RewardCount {
|
|
rewards = rewards[:meta.RewardCount]
|
|
}
|
|
categories = append(categories, CategoryConfigPayload{
|
|
Category: meta.Category,
|
|
Label: meta.Label,
|
|
Enabled: pool.Enabled,
|
|
RewardCount: meta.RewardCount,
|
|
PriceOneGold: pool.PriceOneGold,
|
|
PriceTenGold: pool.PriceTenGold,
|
|
PriceFiftyGold: pool.PriceFiftyGold,
|
|
ProbabilityTotal: probabilityTotal,
|
|
Rewards: rewards,
|
|
})
|
|
}
|
|
return &ConfigResponse{
|
|
Configured: true,
|
|
ID: configRow.ID,
|
|
SysOrigin: configRow.SysOrigin,
|
|
Enabled: configRow.Enabled,
|
|
Timezone: normalizeTimezone(configRow.Timezone),
|
|
ProbabilityTotal: probabilityTotal,
|
|
Categories: categories,
|
|
UpdateTime: formatDateTime(configRow.UpdateTime),
|
|
}, nil
|
|
}
|
|
|
|
func rewardPayloadFromConfig(row model.WheelRewardConfig) RewardConfigPayload {
|
|
return RewardConfigPayload{
|
|
ID: row.ID,
|
|
Enabled: row.Enabled,
|
|
Sort: row.Sort,
|
|
RewardType: row.RewardType,
|
|
ResourceID: resourceIDValue(row.ResourceID),
|
|
ResourceType: row.ResourceType,
|
|
ResourceName: row.ResourceName,
|
|
ResourceURL: row.ResourceURL,
|
|
CoverURL: row.CoverURL,
|
|
DurationDays: row.DurationDays,
|
|
DisplayGoldAmount: row.DisplayGoldAmount,
|
|
GoldAmount: row.GoldAmount,
|
|
Probability: row.Probability,
|
|
ProbabilityPercent: probabilityPercent(row.Probability),
|
|
TotalLimit: row.TotalLimit,
|
|
UserLimit: row.UserLimit,
|
|
IssuedCount: row.IssuedCount,
|
|
}
|
|
}
|