package voiceroomredpacket import ( "context" "errors" "net/http" "time" "chatapp3-golang/internal/model" "chatapp3-golang/internal/utils" "gorm.io/gorm" ) // GetConfig 返回 App 当前红包配置和用户今日发送次数。 func (s *Service) GetConfig(ctx context.Context, user AuthUser) (*ConfigResponse, error) { snapshot, err := s.loadConfig(ctx, user.SysOrigin) if err != nil { return nil, err } todayCount := 0 if user.UserID > 0 { location := resolveLocation(snapshot.Timezone) count, err := s.loadDailySendCount(ctx, snapshot.SysOrigin, user.UserID, dayKey(time.Now().In(location))) if err != nil { return nil, err } todayCount = count } resp := configResponse(snapshot) resp.TodaySendCount = todayCount return resp, nil } // GetAdminConfig 返回后台配置。 func (s *Service) GetAdminConfig(ctx context.Context, sysOrigin string) (*ConfigResponse, error) { snapshot, err := s.loadConfig(ctx, sysOrigin) if err != nil { return nil, err } return configResponse(snapshot), nil } // SaveAdminConfig 保存后台配置。 func (s *Service) SaveAdminConfig(ctx context.Context, req SaveConfigRequest) (*ConfigResponse, error) { sysOrigin := normalizeSysOrigin(req.SysOrigin) timezone := normalizeTimezone(req.Timezone) if _, err := time.LoadLocation(timezone); err != nil { return nil, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error()) } amountOptions := req.AmountOptions if len(amountOptions) == 0 { amountOptions = defaultAmountOptions() } countOptions := req.CountOptions if len(countOptions) == 0 { countOptions = defaultCountOptions() } dailyLimit := req.DailySendLimit if dailyLimit <= 0 { dailyLimit = defaultDailySendLimit } delaySeconds := req.DelaySeconds if delaySeconds <= 0 { delaySeconds = defaultDelaySeconds } refundAfterSeconds := req.RefundAfterSeconds if refundAfterSeconds <= 0 { refundAfterSeconds = defaultRefundAfterSeconds } var configID int64 if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { now := time.Now() var row model.VoiceRoomRedPacketConfig err := tx.Where("sys_origin = ?", sysOrigin).First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { id, idErr := utils.NextID() if idErr != nil { return idErr } row = model.VoiceRoomRedPacketConfig{ ID: id, SysOrigin: sysOrigin, CreateTime: now, } } else if err != nil { return err } row.Enabled = req.Enabled row.DailySendLimit = dailyLimit row.DelaySeconds = delaySeconds row.RefundAfterSeconds = refundAfterSeconds row.Timezone = timezone row.AmountOptions = joinInt64CSV(amountOptions) row.CountOptions = joinIntCSV(countOptions) row.UpdateTime = now if row.CreateTime.IsZero() { row.CreateTime = now } if err := tx.Save(&row).Error; err != nil { return err } configID = row.ID return nil }); err != nil { return nil, err } _ = configID return s.GetAdminConfig(ctx, sysOrigin) } func (s *Service) loadConfig(ctx context.Context, sysOrigin string) (configSnapshot, error) { sysOrigin = normalizeSysOrigin(sysOrigin) var row model.VoiceRoomRedPacketConfig err := s.db.WithContext(ctx).Where("sys_origin = ?", sysOrigin).First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return configSnapshot{ SysOrigin: sysOrigin, Enabled: false, DailySendLimit: defaultDailySendLimit, DelaySeconds: defaultDelaySeconds, RefundAfterSeconds: defaultRefundAfterSeconds, Timezone: defaultTimezone, AmountOptions: defaultAmountOptions(), CountOptions: defaultCountOptions(), }, nil } if err != nil { return configSnapshot{}, err } return configSnapshot{ ID: row.ID, SysOrigin: row.SysOrigin, Enabled: row.Enabled, DailySendLimit: positiveOrDefault(row.DailySendLimit, defaultDailySendLimit), DelaySeconds: positiveOrDefault(row.DelaySeconds, defaultDelaySeconds), RefundAfterSeconds: positiveOrDefault(row.RefundAfterSeconds, defaultRefundAfterSeconds), Timezone: normalizeTimezone(row.Timezone), AmountOptions: parseInt64CSV(row.AmountOptions, defaultAmountOptions()), CountOptions: parseIntCSV(row.CountOptions, defaultCountOptions()), Configured: true, UpdateTime: row.UpdateTime, }, nil } func configResponse(snapshot configSnapshot) *ConfigResponse { return &ConfigResponse{ Configured: snapshot.Configured, ID: snapshot.ID, SysOrigin: snapshot.SysOrigin, Enabled: snapshot.Enabled, DailySendLimit: snapshot.DailySendLimit, DelaySeconds: snapshot.DelaySeconds, RefundAfterSeconds: snapshot.RefundAfterSeconds, Timezone: snapshot.Timezone, AmountOptions: append([]int64(nil), snapshot.AmountOptions...), CountOptions: append([]int(nil), snapshot.CountOptions...), UpdateTime: formatTime(snapshot.UpdateTime), } } func positiveOrDefault(value, fallback int) int { if value <= 0 { return fallback } return value } func resolveLocation(timezone string) *time.Location { location, err := time.LoadLocation(normalizeTimezone(timezone)) if err != nil { return time.UTC } return location } func dayKey(t time.Time) string { return t.Format("20060102") } func (s *Service) loadDailySendCount(ctx context.Context, sysOrigin string, userID int64, day string) (int, error) { var row model.VoiceRoomRedPacketDailyCounter err := s.db.WithContext(ctx). Where("sys_origin = ? AND user_id = ? AND day_key = ?", sysOrigin, userID, day). First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return 0, nil } if err != nil { return 0, err } return row.SendCount, nil }