288 lines
8.8 KiB
Go
288 lines
8.8 KiB
Go
package rechargereward
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GetConfig 返回充值奖励后台配置;未配置时返回空档位草稿。
|
|
func (s *Service) GetConfig(ctx context.Context, sysOrigin string) (*RechargeRewardConfigResponse, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
|
|
var configRow model.RechargeRewardConfig
|
|
err := s.db.WithContext(ctx).
|
|
Where("sys_origin = ?", sysOrigin).
|
|
First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return &RechargeRewardConfigResponse{
|
|
Configured: false,
|
|
SysOrigin: sysOrigin,
|
|
Enabled: false,
|
|
Timezone: s.defaultTimezone(),
|
|
LevelConfigs: []RechargeRewardLevelPayload{},
|
|
}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
levels, err := s.loadLevels(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rewardItems := s.loadRewardItemsMap(ctx, configRow.SysOrigin, levels)
|
|
return &RechargeRewardConfigResponse{
|
|
Configured: true,
|
|
ID: configRow.ID,
|
|
SysOrigin: configRow.SysOrigin,
|
|
Enabled: configRow.Enabled,
|
|
Timezone: normalizeRechargeRewardTimezone(configRow.Timezone),
|
|
UpdateTime: formatDateTime(configRow.UpdateTime),
|
|
LevelConfigs: buildAdminLevelPayloads(levels, rewardItems),
|
|
}, nil
|
|
}
|
|
|
|
// SaveConfig 保存充值奖励后台配置;同一系统只保留一份当前配置。
|
|
func (s *Service) SaveConfig(ctx context.Context, req SaveRechargeRewardConfigRequest) (*RechargeRewardConfigResponse, error) {
|
|
requestID := req.ID.Int64()
|
|
sysOrigin := s.normalizeSysOrigin(req.SysOrigin)
|
|
timezone := normalizeRechargeRewardTimezone(req.Timezone)
|
|
if _, err := time.LoadLocation(timezone); err != nil {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error())
|
|
}
|
|
|
|
levels, err := s.normalizeLevelInputs(ctx, sysOrigin, req.LevelConfigs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if req.Enabled && !hasEnabledLevel(levels) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_level_configs", "enabled config must contain at least one enabled level")
|
|
}
|
|
|
|
var savedConfigID int64
|
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
now := time.Now()
|
|
var configRow model.RechargeRewardConfig
|
|
if requestID > 0 {
|
|
err := tx.Where("id = ?", requestID).First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return NewAppError(http.StatusNotFound, "recharge_reward_config_not_found", "config not found")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(req.SysOrigin) == "" {
|
|
sysOrigin = configRow.SysOrigin
|
|
}
|
|
} else {
|
|
err := tx.Where("sys_origin = ?", sysOrigin).First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
configID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
configRow = model.RechargeRewardConfig{
|
|
ID: configID,
|
|
SysOrigin: sysOrigin,
|
|
CreateTime: now,
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
configRow.SysOrigin = sysOrigin
|
|
configRow.Enabled = req.Enabled
|
|
configRow.Timezone = timezone
|
|
configRow.UpdateTime = now
|
|
if configRow.CreateTime.IsZero() {
|
|
configRow.CreateTime = now
|
|
}
|
|
|
|
if err := tx.Save(&configRow).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ?", configRow.ID).Delete(&model.RechargeRewardLevel{}).Error; err != nil {
|
|
return err
|
|
}
|
|
for index := range levels {
|
|
levels[index].ConfigID = configRow.ID
|
|
levels[index].CreateTime = now
|
|
levels[index].UpdateTime = now
|
|
}
|
|
if len(levels) > 0 {
|
|
if err := tx.Create(&levels).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
savedConfigID = configRow.ID
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetConfig(ctx, rechargeRewardSysOriginBySavedID(ctx, s.db, savedConfigID, sysOrigin))
|
|
}
|
|
|
|
func (s *Service) normalizeLevelInputs(ctx context.Context, sysOrigin string, inputs []RechargeRewardLevelInput) ([]model.RechargeRewardLevel, error) {
|
|
if len(inputs) == 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_level_configs", "levelConfigs is required")
|
|
}
|
|
|
|
seenLevels := make(map[int]struct{}, len(inputs))
|
|
seenAmounts := make(map[int64]struct{}, len(inputs))
|
|
rows := make([]model.RechargeRewardLevel, 0, len(inputs))
|
|
for _, item := range inputs {
|
|
if item.Level <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_level", "level must be greater than 0")
|
|
}
|
|
amountCents := item.RechargeAmount.Cents()
|
|
if amountCents == 0 && item.RechargeAmountCents > 0 {
|
|
amountCents = item.RechargeAmountCents
|
|
}
|
|
if amountCents <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_recharge_amount", "rechargeAmount must be greater than 0")
|
|
}
|
|
rewardGroupID := s.resolveRewardGroupID(ctx, sysOrigin, item.RewardGroupID.Int64Ptr(), item.RewardGroupName)
|
|
if item.RewardGold <= 0 && (rewardGroupID == nil || *rewardGroupID <= 0) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward", "rewardGold or rewardGroupId is required")
|
|
}
|
|
if _, exists := seenLevels[item.Level]; exists {
|
|
return nil, NewAppError(http.StatusBadRequest, "duplicate_level", "levelConfigs must not contain duplicate levels")
|
|
}
|
|
if _, exists := seenAmounts[amountCents]; exists {
|
|
return nil, NewAppError(http.StatusBadRequest, "duplicate_recharge_amount", "levelConfigs must not contain duplicate rechargeAmount")
|
|
}
|
|
seenLevels[item.Level] = struct{}{}
|
|
seenAmounts[amountCents] = struct{}{}
|
|
id, err := utils.NextID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows = append(rows, model.RechargeRewardLevel{
|
|
ID: id,
|
|
Level: item.Level,
|
|
RechargeAmountCents: amountCents,
|
|
RewardGold: item.RewardGold,
|
|
RewardGroupID: normalizeRewardGroupID(rewardGroupID),
|
|
RewardGroupName: strings.TrimSpace(item.RewardGroupName),
|
|
Enabled: item.Enabled,
|
|
})
|
|
}
|
|
|
|
sort.Slice(rows, func(i, j int) bool {
|
|
if rows[i].RechargeAmountCents == rows[j].RechargeAmountCents {
|
|
return rows[i].Level < rows[j].Level
|
|
}
|
|
return rows[i].RechargeAmountCents < rows[j].RechargeAmountCents
|
|
})
|
|
return rows, nil
|
|
}
|
|
|
|
func (s *Service) resolveRewardGroupID(ctx context.Context, sysOrigin string, rewardGroupID *int64, rewardGroupName string) *int64 {
|
|
normalized := normalizeRewardGroupID(rewardGroupID)
|
|
if normalized == nil || s.java == nil || strings.TrimSpace(rewardGroupName) == "" {
|
|
return normalized
|
|
}
|
|
if detail, err := s.java.GetRewardGroupDetail(ctx, *normalized); err == nil && rewardGroupDetailHasItems(detail) {
|
|
return normalized
|
|
}
|
|
detail, err := s.java.FindRewardGroupDetail(ctx, sysOrigin, rewardGroupName)
|
|
if err != nil || int64(detail.ID) <= 0 {
|
|
return normalized
|
|
}
|
|
resolved := int64(detail.ID)
|
|
return &resolved
|
|
}
|
|
|
|
func (s *Service) loadConfigBundle(ctx context.Context, sysOrigin string) (*rechargeRewardConfigBundle, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
|
|
var configRow model.RechargeRewardConfig
|
|
err := s.db.WithContext(ctx).
|
|
Where("sys_origin = ?", sysOrigin).
|
|
First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return &rechargeRewardConfigBundle{}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
levels, err := s.loadLevels(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &rechargeRewardConfigBundle{
|
|
Config: refConfigSnapshot(configSnapshotFromModel(configRow)),
|
|
Levels: levels,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) loadLevels(ctx context.Context, configID int64) ([]rechargeRewardLevelSnapshot, error) {
|
|
var rows []model.RechargeRewardLevel
|
|
if configID <= 0 {
|
|
return []rechargeRewardLevelSnapshot{}, nil
|
|
}
|
|
if err := s.db.WithContext(ctx).
|
|
Where("config_id = ?", configID).
|
|
Order("recharge_amount_cents asc").
|
|
Order("level asc").
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
levels := make([]rechargeRewardLevelSnapshot, 0, len(rows))
|
|
for _, row := range rows {
|
|
levels = append(levels, levelSnapshotFromModel(row))
|
|
}
|
|
sortLevels(levels)
|
|
return levels, nil
|
|
}
|
|
|
|
func (s *Service) defaultTimezone() string {
|
|
timezone := strings.TrimSpace(s.cfg.RechargeReward.Timezone)
|
|
if timezone == "" {
|
|
timezone = defaultRechargeRewardTimezone
|
|
}
|
|
return timezone
|
|
}
|
|
|
|
func hasEnabledLevel(levels []model.RechargeRewardLevel) bool {
|
|
for _, level := range levels {
|
|
if level.Enabled {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizeRewardGroupID(id *int64) *int64 {
|
|
if id == nil || *id <= 0 {
|
|
return nil
|
|
}
|
|
return id
|
|
}
|
|
|
|
func refConfigSnapshot(value rechargeRewardConfigSnapshot) *rechargeRewardConfigSnapshot {
|
|
return &value
|
|
}
|
|
|
|
func rechargeRewardSysOriginBySavedID(ctx context.Context, db rechargeRewardDB, configID int64, fallback string) string {
|
|
if configID <= 0 {
|
|
return fallback
|
|
}
|
|
var row model.RechargeRewardConfig
|
|
if err := db.WithContext(ctx).Select("sys_origin").Where("id = ?", configID).First(&row).Error; err != nil {
|
|
return fallback
|
|
}
|
|
return row.SysOrigin
|
|
}
|