package service import ( "fmt" "time" "github.com/topfans/backend/pkg/logger" "github.com/topfans/backend/pkg/models" "github.com/topfans/backend/services/assetService/repository" "go.uber.org/zap" "gorm.io/gorm" ) type AssetLevelService interface { GetOrCreateRecord(assetID int64) (*models.AssetLevelRecord, error) GetRecordByAssetID(assetID int64) (*models.AssetLevelRecord, error) GetLevelConfig(level string) (*models.AssetLevel, error) GetAllLevels() ([]*models.AssetLevel, error) AddExhibitionHours(assetID int64, hours int) (string, bool, error) AddLikes(assetID int64, count int) (string, bool, error) RemoveLikes(assetID int64, count int) (string, bool, error) CalculateRevenue(assetID int64, likeCount int, startTime, endTime int64, revenueBoostBps int) (int64, error) SeasonReset(seasonID string) error GetCurrentSeason() (*models.Season, error) GetChangeLogs(assetID int64, page, pageSize int) ([]*models.AssetLevelChangeLog, error) } type assetLevelService struct { levelRepo *repository.AssetLevelRepository seasonRepo *repository.SeasonRepository decayConfigRepo *repository.SeasonDecayConfigRepository assetRepo repository.AssetRepository // 用于同步等级到AssetRegistry.Grade(可选) } func NewAssetLevelService( levelRepo *repository.AssetLevelRepository, seasonRepo *repository.SeasonRepository, decayConfigRepo *repository.SeasonDecayConfigRepository, ) AssetLevelService { return &assetLevelService{ levelRepo: levelRepo, seasonRepo: seasonRepo, decayConfigRepo: decayConfigRepo, } } func (s *assetLevelService) GetOrCreateRecord(assetID int64) (*models.AssetLevelRecord, error) { record, err := s.levelRepo.GetByAssetID(assetID) if err == nil { return record, nil } if err == gorm.ErrRecordNotFound { record = &models.AssetLevelRecord{ AssetID: assetID, CurrentLevel: models.LevelN, } if err := s.levelRepo.Create(record); err != nil { return nil, err } return record, nil } return nil, err } func (s *assetLevelService) GetRecordByAssetID(assetID int64) (*models.AssetLevelRecord, error) { return s.levelRepo.GetByAssetID(assetID) } func (s *assetLevelService) GetLevelConfig(level string) (*models.AssetLevel, error) { return s.levelRepo.GetLevelConfig(level) } func (s *assetLevelService) GetAllLevels() ([]*models.AssetLevel, error) { return s.levelRepo.GetAllLevels() } func (s *assetLevelService) GetCurrentSeason() (*models.Season, error) { return s.seasonRepo.GetActiveSeason() } func (s *assetLevelService) getDefaultSeason() *models.Season { return &models.Season{ ID: "season_1", Name: "第一赛季", DurationDays: 84, ResetStrategy: "percentage_decay", ResetLevel: true, Status: "active", } } // CheckUpgrade 检查是否可以升级(使用赛季内累计) func (s *assetLevelService) CheckUpgrade(record *models.AssetLevelRecord) (string, bool) { levels, err := s.GetAllLevels() if err != nil { return record.CurrentLevel, false } currentOrder := models.LevelOrderMap[record.CurrentLevel] for i := len(levels) - 1; i >= 0; i-- { level := levels[i] if level.LevelOrder <= currentOrder { continue } if record.SeasonExhibitionHours >= level.RequireHours && record.SeasonLikes >= level.RequireLikes { return level.Level, true } } return record.CurrentLevel, false } // CheckDowngrade 检查是否需要降级 func (s *assetLevelService) CheckDowngrade(record *models.AssetLevelRecord) (string, bool) { levels, err := s.GetAllLevels() if err != nil { return record.CurrentLevel, false } currentOrder := models.LevelOrderMap[record.CurrentLevel] for _, level := range levels { if level.LevelOrder != currentOrder { continue } if record.SeasonExhibitionHours >= level.RequireHours && record.SeasonLikes >= level.RequireLikes { return record.CurrentLevel, false } } newLevel := models.LevelN for _, level := range levels { if record.SeasonExhibitionHours >= level.RequireHours && record.SeasonLikes >= level.RequireLikes { newLevel = level.Level } } return newLevel, newLevel != record.CurrentLevel } func (s *assetLevelService) AddExhibitionHours(assetID int64, hours int) (string, bool, error) { record, err := s.GetOrCreateRecord(assetID) if err != nil { return "", false, err } oldLevel := record.CurrentLevel if record.SeasonID == "" { season, err := s.GetCurrentSeason() if err != nil { season = s.getDefaultSeason() } record.SeasonID = season.ID } record.SeasonExhibitionHours += hours record.LifetimeExhibitionHours += hours newLevel, upgraded := s.CheckUpgrade(record) if upgraded { record.CurrentLevel = newLevel } if err := s.levelRepo.Save(record); err != nil { return "", false, err } if upgraded && newLevel != oldLevel { s.logLevelChange(record.AssetID, oldLevel, newLevel, "exhibition_complete", record.SeasonExhibitionHours, record.SeasonLikes, fmt.Sprintf("展出完成,时长+%d小时", hours)) // 同步等级到AssetRegistry.Grade s.syncGradeToAssetRegistry(record.AssetID, newLevel) } return newLevel, upgraded, nil } func (s *assetLevelService) AddLikes(assetID int64, count int) (string, bool, error) { record, err := s.GetOrCreateRecord(assetID) if err != nil { return "", false, err } oldLevel := record.CurrentLevel if record.SeasonID == "" { season, err := s.GetCurrentSeason() if err != nil { season = s.getDefaultSeason() } record.SeasonID = season.ID } record.SeasonLikes += count record.LifetimeLikes += count newLevel, upgraded := s.CheckUpgrade(record) if upgraded { record.CurrentLevel = newLevel } if err := s.levelRepo.Save(record); err != nil { return "", false, err } if upgraded && newLevel != oldLevel { s.logLevelChange(record.AssetID, oldLevel, newLevel, "like_update", record.SeasonExhibitionHours, record.SeasonLikes, fmt.Sprintf("点赞数达到%d触发升级", record.SeasonLikes)) // 同步等级到AssetRegistry.Grade s.syncGradeToAssetRegistry(record.AssetID, newLevel) } return newLevel, upgraded, nil } func (s *assetLevelService) RemoveLikes(assetID int64, count int) (string, bool, error) { record, err := s.GetOrCreateRecord(assetID) if err != nil { return "", false, err } oldLevel := record.CurrentLevel record.SeasonLikes -= count if record.SeasonLikes < 0 { record.SeasonLikes = 0 } record.LifetimeLikes -= count if record.LifetimeLikes < 0 { record.LifetimeLikes = 0 } newLevel, downgraded := s.CheckDowngrade(record) if downgraded { record.CurrentLevel = newLevel } if err := s.levelRepo.Save(record); err != nil { return "", false, err } if downgraded && newLevel != oldLevel { s.logLevelChange(record.AssetID, oldLevel, newLevel, "like_remove", record.SeasonExhibitionHours, record.SeasonLikes, fmt.Sprintf("取消点赞,点赞数降至%d触发降级", record.SeasonLikes)) // 同步等级到AssetRegistry.Grade s.syncGradeToAssetRegistry(record.AssetID, newLevel) } return newLevel, downgraded, nil } // syncGradeToAssetRegistry 将等级同步到AssetRegistry.Grade // 直接使用gorm.DB更新,不依赖repository层 func (s *assetLevelService) syncGradeToAssetRegistry(assetID int64, level string) { if s.assetRepo == nil { // 直接使用levelRepo的db进行更新 grade := models.LevelToGrade(level) db := s.levelRepo.GetDB() if db != nil { if err := db.Table("public.asset_registry"). Where("asset_id = ?", assetID). Update("grade", grade).Error; err != nil { logger.Logger.Warn("syncGradeToAssetRegistry failed", zap.Int64("asset_id", assetID), zap.String("level", level), zap.Int("grade", grade), zap.Error(err)) } else { logger.Logger.Info("syncGradeToAssetRegistry success", zap.Int64("asset_id", assetID), zap.String("level", level), zap.Int("grade", grade)) } } return } grade := models.LevelToGrade(level) if err := s.assetRepo.UpdateGradeByAssetID(assetID, int32(grade)); err != nil { logger.Logger.Warn("syncGradeToAssetRegistry failed", zap.Int64("asset_id", assetID), zap.String("level", level), zap.Int("grade", grade), zap.Error(err)) } else { logger.Logger.Info("syncGradeToAssetRegistry success", zap.Int64("asset_id", assetID), zap.String("level", level), zap.Int("grade", grade)) } } func (s *assetLevelService) CalculateRevenue(assetID int64, likeCount int, startTime, endTime int64, revenueBoostBps int) (int64, error) { record, err := s.GetRecordByAssetID(assetID) if err != nil || record == nil { return s.calculateDefaultRevenue(likeCount, startTime, endTime, revenueBoostBps) } levelConfig, err := s.GetLevelConfig(record.CurrentLevel) if err != nil { return s.calculateDefaultRevenue(likeCount, startTime, endTime, revenueBoostBps) } T := (endTime - startTime) / 3600000 if T <= 0 { T = 1 } R0 := int64(levelConfig.HourlyRevenue) baseRevenue := R0 * T buff := CalculateBuff(likeCount) buffedRevenue := baseRevenue * (100 + int64(buff)) / 100 if revenueBoostBps > 0 { boost := buffedRevenue * int64(revenueBoostBps) / 10000 buffedRevenue += boost } return buffedRevenue, nil } func (s *assetLevelService) calculateDefaultRevenue(likeCount int, startTime, endTime int64, revenueBoostBps int) (int64, error) { R0 := int64(5) T := (endTime - startTime) / 3600000 if T <= 0 { T = 1 } baseRevenue := R0 * T buff := CalculateBuff(likeCount) buffedRevenue := baseRevenue * (100 + int64(buff)) / 100 if revenueBoostBps > 0 { boost := buffedRevenue * int64(revenueBoostBps) / 10000 buffedRevenue += boost } return buffedRevenue, nil } // CalculateBuff 根据点赞数计算Buff百分比 func CalculateBuff(likeCount int) int { switch { case likeCount >= 30: return 30 case likeCount >= 10: return 20 case likeCount >= 5: return 10 default: return 0 } } func (s *assetLevelService) SeasonReset(seasonID string) error { season, err := s.seasonRepo.GetByID(seasonID) if err != nil || season == nil { return fmt.Errorf("season not found: %s", seasonID) } if !season.ResetLevel { return nil } decayConfigs, err := s.decayConfigRepo.GetBySeason(seasonID) if err != nil { return err } decayPercentMap := make(map[string]int) for _, cfg := range decayConfigs { decayPercentMap[cfg.Level] = cfg.PreservePercent } records, err := s.levelRepo.GetBySeason(seasonID) if err != nil { return err } nextSeasonID := s.calculateNextSeasonID(seasonID) _, err = s.seasonRepo.GetOrCreate(nextSeasonID, season.DurationDays, season.ResetStrategy) if err != nil { return err } for _, record := range records { oldLevel := record.CurrentLevel preservePercent := decayPercentMap[record.CurrentLevel] if preservePercent <= 0 { preservePercent = 0 } else if preservePercent >= 100 { preservePercent = 100 } record.SeasonExhibitionHours = record.LifetimeExhibitionHours * preservePercent / 100 record.SeasonLikes = record.LifetimeLikes * preservePercent / 100 newLevel := s.recalculateLevelAfterDecay(record) record.CurrentLevel = newLevel record.SeasonID = nextSeasonID if err := s.levelRepo.Save(record); err != nil { logger.Logger.Error("SeasonReset: failed to update record", zap.Int64("asset_id", record.AssetID), zap.Error(err)) continue } if oldLevel != newLevel { s.logLevelChange(record.AssetID, oldLevel, newLevel, "season_decay", record.SeasonExhibitionHours, record.SeasonLikes, fmt.Sprintf("赛季%s降序,保留%d%%", seasonID, preservePercent)) // 同步等级到AssetRegistry.Grade s.syncGradeToAssetRegistry(record.AssetID, newLevel) } } season.Status = "ended" return s.seasonRepo.Save(season) } func (s *assetLevelService) calculateNextSeasonID(currentSeasonID string) string { if len(currentSeasonID) > 0 && currentSeasonID[:7] == "season_" { num := 0 fmt.Sscanf(currentSeasonID[7:], "%d", &num) return fmt.Sprintf("season_%d", num+1) } return currentSeasonID + "_next" } func (s *assetLevelService) recalculateLevelAfterDecay(record *models.AssetLevelRecord) string { levels, err := s.GetAllLevels() if err != nil { return record.CurrentLevel } newLevel := models.LevelN for _, level := range levels { if record.SeasonExhibitionHours >= level.RequireHours && record.SeasonLikes >= level.RequireLikes { newLevel = level.Level } } return newLevel } func (s *assetLevelService) logLevelChange(assetID int64, fromLevel, toLevel, triggerType string, triggerHours, triggerLikes int, reason string) { log := &models.AssetLevelChangeLog{ AssetID: assetID, FromLevel: fromLevel, ToLevel: toLevel, TriggerType: triggerType, TriggerHours: triggerHours, TriggerLikes: triggerLikes, ChangeReason: reason, CreatedAt: time.Now().UnixMilli(), } if err := s.levelRepo.CreateChangeLog(log); err != nil { logger.Logger.Error("Failed to create level change log", zap.Int64("asset_id", assetID), zap.Error(err)) } } func (s *assetLevelService) GetChangeLogs(assetID int64, page, pageSize int) ([]*models.AssetLevelChangeLog, error) { offset := (page - 1) * pageSize return s.levelRepo.GetChangeLogs(assetID, pageSize, offset) }