243 lines
7.3 KiB
Go
243 lines
7.3 KiB
Go
package vip
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GetConfig returns the operator-managed VIP configuration for one sysOrigin.
|
|
func (s *Service) GetConfig(ctx context.Context, sysOrigin string) (*ConfigResponse, error) {
|
|
sysOrigin = normalizeSysOrigin(sysOrigin)
|
|
if sysOrigin == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
|
|
}
|
|
|
|
rows, err := s.loadConfigRows(ctx, sysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resources, err := s.loadResourceLookup(ctx, resourceIDsFromConfigRows(rows))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
levels := buildDefaultLevelPayloads()
|
|
configured := len(rows) > 0
|
|
var updateTime time.Time
|
|
for _, row := range rows {
|
|
if row.Level < 1 || row.Level > levelCount {
|
|
continue
|
|
}
|
|
levels[row.Level-1] = payloadFromConfigWithResources(row, resources)
|
|
if row.UpdateTime.After(updateTime) {
|
|
updateTime = row.UpdateTime
|
|
}
|
|
}
|
|
|
|
return &ConfigResponse{
|
|
Configured: configured,
|
|
SysOrigin: sysOrigin,
|
|
Levels: levels,
|
|
UpdateTime: formatTime(updateTime),
|
|
}, nil
|
|
}
|
|
|
|
// SaveConfig saves all five VIP levels for one sysOrigin.
|
|
func (s *Service) SaveConfig(ctx context.Context, req SaveConfigRequest) (*ConfigResponse, error) {
|
|
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
if sysOrigin == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
|
|
}
|
|
|
|
levels, err := normalizeSaveLevels(req.Levels)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := time.Now()
|
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
for _, item := range levels {
|
|
var row model.VipLevelConfig
|
|
err := tx.Where("sys_origin = ? AND level = ?", sysOrigin, item.Level).First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
nextID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
row = model.VipLevelConfig{
|
|
ID: nextID,
|
|
SysOrigin: sysOrigin,
|
|
Level: item.Level,
|
|
CreateTime: now,
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
applyLevelPayloadToConfig(&row, item, now)
|
|
if err := tx.Save(&row).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetConfig(ctx, sysOrigin)
|
|
}
|
|
|
|
func normalizeSaveLevels(levels []VipLevelPayload) ([]VipLevelPayload, error) {
|
|
if len(levels) != levelCount {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_levels", "levels must contain exactly 5 items")
|
|
}
|
|
|
|
seen := make(map[int]struct{}, levelCount)
|
|
normalized := make([]VipLevelPayload, 0, levelCount)
|
|
for _, item := range levels {
|
|
if err := validateLevel(item.Level); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, exists := seen[item.Level]; exists {
|
|
return nil, NewAppError(http.StatusBadRequest, "duplicate_level", "level must be unique")
|
|
}
|
|
seen[item.Level] = struct{}{}
|
|
if item.PriceGold < 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_price", "priceGold must be greater than or equal to 0")
|
|
}
|
|
item.LevelCode = levelCode(item.Level)
|
|
item.DisplayName = strings.TrimSpace(item.DisplayName)
|
|
if item.DisplayName == "" {
|
|
item.DisplayName = levelDisplayName(item.Level)
|
|
}
|
|
item.DurationDays = vipDurationDay
|
|
item.LongBadge = normalizeResource(firstResource(item.LongBadge, item.Badge))
|
|
item.Badge = item.LongBadge
|
|
item.ShortBadge = normalizeResource(item.ShortBadge)
|
|
item.AvatarFrame = normalizeResource(item.AvatarFrame)
|
|
item.EntryEffect = normalizeResource(item.EntryEffect)
|
|
item.ChatBubble = normalizeResource(item.ChatBubble)
|
|
item.FloatPicture = normalizeResource(item.FloatPicture)
|
|
item.BackgroundCard = normalizeResource(item.BackgroundCard)
|
|
item.EffectImage = normalizeResource(item.EffectImage)
|
|
normalized = append(normalized, item)
|
|
}
|
|
|
|
sort.Slice(normalized, func(i, j int) bool {
|
|
return normalized[i].Level < normalized[j].Level
|
|
})
|
|
return normalized, nil
|
|
}
|
|
|
|
func normalizeResource(resource VipResourcePayload) VipResourcePayload {
|
|
if resource.ResourceID < 0 {
|
|
resource.ResourceID = 0
|
|
}
|
|
resource.Name = strings.TrimSpace(resource.Name)
|
|
resource.URL = strings.TrimSpace(resource.URL)
|
|
resource.SourceURL = strings.TrimSpace(resource.SourceURL)
|
|
resource.ResourceURL = strings.TrimSpace(resource.ResourceURL)
|
|
resource.Cover = strings.TrimSpace(resource.Cover)
|
|
resource.CoverURL = strings.TrimSpace(resource.CoverURL)
|
|
if resource.URL == "" {
|
|
resource.URL = resource.SourceURL
|
|
}
|
|
if resource.URL == "" {
|
|
resource.URL = resource.ResourceURL
|
|
}
|
|
if resource.Cover == "" {
|
|
resource.Cover = resource.CoverURL
|
|
}
|
|
resource.SourceURL = resource.URL
|
|
resource.ResourceURL = resource.URL
|
|
resource.CoverURL = resource.Cover
|
|
return resource
|
|
}
|
|
|
|
func firstResource(preferred VipResourcePayload, fallback VipResourcePayload) VipResourcePayload {
|
|
if preferred.ResourceID.Int64() > 0 || strings.TrimSpace(preferred.Name) != "" ||
|
|
strings.TrimSpace(preferred.URL) != "" || strings.TrimSpace(preferred.SourceURL) != "" ||
|
|
strings.TrimSpace(preferred.ResourceURL) != "" || strings.TrimSpace(preferred.Cover) != "" ||
|
|
strings.TrimSpace(preferred.CoverURL) != "" {
|
|
return preferred
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func applyLevelPayloadToConfig(row *model.VipLevelConfig, item VipLevelPayload, now time.Time) {
|
|
row.Level = item.Level
|
|
row.LevelCode = levelCode(item.Level)
|
|
row.DisplayName = strings.TrimSpace(item.DisplayName)
|
|
row.Enabled = item.Enabled
|
|
row.DurationDays = vipDurationDay
|
|
row.PriceGold = item.PriceGold
|
|
row.BadgeResourceID = item.LongBadge.ResourceID.Int64()
|
|
row.BadgeName = ""
|
|
row.BadgeURL = ""
|
|
row.ShortBadgeResourceID = item.ShortBadge.ResourceID.Int64()
|
|
row.ShortBadgeName = ""
|
|
row.ShortBadgeURL = ""
|
|
row.AvatarFrameResourceID = item.AvatarFrame.ResourceID.Int64()
|
|
row.AvatarFrameName = ""
|
|
row.AvatarFrameURL = ""
|
|
row.EntryEffectResourceID = item.EntryEffect.ResourceID.Int64()
|
|
row.EntryEffectName = ""
|
|
row.EntryEffectURL = ""
|
|
row.ChatBubbleResourceID = item.ChatBubble.ResourceID.Int64()
|
|
row.ChatBubbleName = ""
|
|
row.ChatBubbleURL = ""
|
|
row.FloatPictureResourceID = item.FloatPicture.ResourceID.Int64()
|
|
row.FloatPictureName = ""
|
|
row.FloatPictureURL = ""
|
|
row.BackgroundCardResourceID = item.BackgroundCard.ResourceID.Int64()
|
|
row.BackgroundCardName = ""
|
|
row.BackgroundCardURL = ""
|
|
row.EffectImageResourceID = item.EffectImage.ResourceID.Int64()
|
|
row.EffectImageName = ""
|
|
row.EffectImageURL = ""
|
|
row.UpdateTime = now
|
|
if row.CreateTime.IsZero() {
|
|
row.CreateTime = now
|
|
}
|
|
}
|
|
|
|
func (s *Service) loadConfigRows(ctx context.Context, sysOrigin string) ([]model.VipLevelConfig, error) {
|
|
var rows []model.VipLevelConfig
|
|
if err := s.db.WithContext(ctx).
|
|
Where("sys_origin = ?", normalizeSysOrigin(sysOrigin)).
|
|
Order("level asc").
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (s *Service) loadConfigMap(ctx context.Context, sysOrigin string) (map[int]model.VipLevelConfig, error) {
|
|
rows, err := s.loadConfigRows(ctx, sysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configs := make(map[int]model.VipLevelConfig, len(rows))
|
|
for _, row := range rows {
|
|
configs[row.Level] = row
|
|
}
|
|
return configs, nil
|
|
}
|
|
|
|
func buildDefaultLevelPayloads() []VipLevelPayload {
|
|
levels := make([]VipLevelPayload, 0, levelCount)
|
|
for level := 1; level <= levelCount; level++ {
|
|
levels = append(levels, defaultLevelPayload(level))
|
|
}
|
|
return levels
|
|
}
|