2026-04-20 18:31:47 +08:00

254 lines
7.6 KiB
Go

package signinreward
import (
"context"
"errors"
"net/http"
"sort"
"strings"
"time"
"chatapp3-golang/internal/model"
"chatapp3-golang/internal/utils"
"gorm.io/gorm"
)
// GetConfig 返回后台配置页使用的签到奖励配置。
func (s *SignInRewardService) GetConfig(ctx context.Context, sysOrigin string) (*SignInRewardConfigResponse, error) {
bundle, err := s.loadConfigBundle(ctx, sysOrigin)
if err != nil {
return nil, err
}
if bundle.Config == nil {
return &SignInRewardConfigResponse{
Configured: false,
SysOrigin: normalizeSignInRewardSysOrigin(sysOrigin),
Timezone: signInRewardDefaultTimezone,
CycleDays: signInRewardCycleLength,
Days: buildDefaultSignInRewardDays(),
}, nil
}
return &SignInRewardConfigResponse{
Configured: true,
ID: bundle.Config.ID,
SysOrigin: bundle.Config.SysOrigin,
Enabled: bundle.Config.Enabled,
Timezone: bundle.Config.Timezone,
CycleDays: signInRewardCycleLength,
Days: buildAdminDayPayloads(bundle.Days),
UpdateTime: formatSignInRewardDateTime(bundle.Config.UpdateTime),
}, nil
}
// SaveConfig 保存签到奖励后台配置。
func (s *SignInRewardService) SaveConfig(ctx context.Context, req SaveSignInRewardConfigRequest) (*SignInRewardConfigResponse, error) {
sysOrigin := normalizeSignInRewardSysOrigin(req.SysOrigin)
if sysOrigin == "" {
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
}
timezone := normalizeSignInRewardTimezone(req.Timezone)
if _, err := time.LoadLocation(timezone); err != nil {
return nil, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error())
}
normalizedDays, err := normalizeSaveDays(req.Days, req.Enabled)
if err != nil {
return nil, err
}
now := time.Now()
var configID int64
if err := s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var configRow model.SignInRewardConfig
if err := tx.Where("sys_origin = ?", sysOrigin).First(&configRow).Error; errors.Is(err, gorm.ErrRecordNotFound) {
nextID, idErr := utils.NextID()
if idErr != nil {
return idErr
}
configRow = model.SignInRewardConfig{
ID: nextID,
SysOrigin: sysOrigin,
CreateTime: now,
}
} else if err != nil {
return err
}
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.SignInRewardDayConfig{}).Error; err != nil {
return err
}
rows := make([]model.SignInRewardDayConfig, 0, len(normalizedDays))
for _, item := range normalizedDays {
dayID, idErr := utils.NextID()
if idErr != nil {
return idErr
}
rows = append(rows, model.SignInRewardDayConfig{
ID: dayID,
ConfigID: configRow.ID,
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
RewardGroupName: strings.TrimSpace(item.RewardGroupName),
CreateTime: now,
UpdateTime: now,
})
}
if len(rows) > 0 {
if err := tx.Create(&rows).Error; err != nil {
return err
}
}
configID = configRow.ID
return nil
}); err != nil {
return nil, err
}
_ = configID
return s.GetConfig(ctx, sysOrigin)
}
func normalizeSaveDays(days []SignInRewardDayConfigInput, enabled bool) ([]SignInRewardDayConfigInput, error) {
if len(days) != signInRewardCycleLength {
return nil, NewAppError(http.StatusBadRequest, "invalid_days", "days must contain exactly 7 items")
}
seen := make(map[int]struct{}, len(days))
normalized := make([]SignInRewardDayConfigInput, 0, len(days))
for _, item := range days {
if item.DayIndex < 1 || item.DayIndex > signInRewardCycleLength {
return nil, NewAppError(http.StatusBadRequest, "invalid_day_index", "dayIndex must be between 1 and 7")
}
if _, exists := seen[item.DayIndex]; exists {
return nil, NewAppError(http.StatusBadRequest, "duplicate_day_index", "dayIndex must be unique")
}
seen[item.DayIndex] = struct{}{}
if item.GoldAmount < 0 {
return nil, NewAppError(http.StatusBadRequest, "invalid_gold_amount", "goldAmount must be greater than or equal to 0")
}
if enabled && item.GoldAmount <= 0 && item.RewardGroupID == nil {
return nil, NewAppError(http.StatusBadRequest, "empty_day_reward", "each enabled day must configure goldAmount or rewardGroupId")
}
normalized = append(normalized, SignInRewardDayConfigInput{
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
RewardGroupName: strings.TrimSpace(item.RewardGroupName),
})
}
sort.Slice(normalized, func(i, j int) bool {
return normalized[i].DayIndex < normalized[j].DayIndex
})
return normalized, nil
}
func (s *SignInRewardService) loadConfigBundle(ctx context.Context, sysOrigin string) (*signInRewardConfigBundle, error) {
sysOrigin = normalizeSignInRewardSysOrigin(sysOrigin)
if sysOrigin == "" {
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
}
var configRow model.SignInRewardConfig
err := s.repo.DB.WithContext(ctx).Where("sys_origin = ?", sysOrigin).First(&configRow).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return &signInRewardConfigBundle{}, nil
}
if err != nil {
return nil, err
}
var dayRows []model.SignInRewardDayConfig
if err := s.repo.DB.WithContext(ctx).
Where("config_id = ?", configRow.ID).
Order("day_index asc").
Find(&dayRows).Error; err != nil {
return nil, err
}
result := &signInRewardConfigBundle{
Config: &signInRewardConfigSnapshot{
ID: configRow.ID,
SysOrigin: configRow.SysOrigin,
Enabled: configRow.Enabled,
Timezone: normalizeSignInRewardTimezone(configRow.Timezone),
UpdateTime: configRow.UpdateTime,
},
Days: make([]signInRewardDaySnapshot, 0, len(dayRows)),
}
for _, row := range dayRows {
result.Days = append(result.Days, signInRewardDaySnapshot{
ID: row.ID,
DayIndex: row.DayIndex,
GoldAmount: row.GoldAmount,
RewardGroupID: row.RewardGroupID,
RewardGroupName: strings.TrimSpace(row.RewardGroupName),
})
}
fillMissingSignInRewardDays(&result.Days)
return result, nil
}
func buildDefaultSignInRewardDays() []SignInRewardDayPayload {
days := make([]SignInRewardDayPayload, 0, signInRewardCycleLength)
for dayIndex := 1; dayIndex <= signInRewardCycleLength; dayIndex++ {
days = append(days, SignInRewardDayPayload{
DayIndex: dayIndex,
RewardItems: []SignInRewardRewardItem{},
})
}
return days
}
func buildAdminDayPayloads(days []signInRewardDaySnapshot) []SignInRewardDayPayload {
payloads := make([]SignInRewardDayPayload, 0, len(days))
for _, item := range days {
payloads = append(payloads, SignInRewardDayPayload{
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
RewardGroupName: item.RewardGroupName,
RewardItems: []SignInRewardRewardItem{},
})
}
return payloads
}
func fillMissingSignInRewardDays(days *[]signInRewardDaySnapshot) {
dayMap := make(map[int]signInRewardDaySnapshot, len(*days))
for _, item := range *days {
dayMap[item.DayIndex] = item
}
filled := make([]signInRewardDaySnapshot, 0, signInRewardCycleLength)
for dayIndex := 1; dayIndex <= signInRewardCycleLength; dayIndex++ {
if item, ok := dayMap[dayIndex]; ok {
filled = append(filled, item)
continue
}
filled = append(filled, signInRewardDaySnapshot{DayIndex: dayIndex})
}
*days = filled
}
func formatSignInRewardDateTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format("2006-01-02 15:04:05")
}