diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/game/VipLevelRule.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/game/VipLevelRule.java index b04e7d07..5a620acd 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/game/VipLevelRule.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/game/VipLevelRule.java @@ -4,6 +4,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.util.Random; /** * VIP等级规则枚举(按周区分) @@ -12,44 +15,133 @@ import java.math.BigDecimal; @AllArgsConstructor public enum VipLevelRule { - // 第一周规则 WEEK1_V3(1, 3, new BigDecimal("500"), null), WEEK1_V2(1, 2, new BigDecimal("100"), new BigDecimal("499")), - WEEK1_V1(1, 1, new BigDecimal("1"), new BigDecimal("99")), + WEEK1_V1(1, 1, new BigDecimal("0.99"), new BigDecimal("99.99")), - // 第二周规则 WEEK2_V3(2, 3, new BigDecimal("700"), null), WEEK2_V2(2, 2, new BigDecimal("500"), new BigDecimal("699")), - WEEK2_V1(2, 1, new BigDecimal("1"), new BigDecimal("499")), + WEEK2_V1(2, 1, new BigDecimal("0.99"), new BigDecimal("499")), - // 第三周规则 WEEK3_V3(3, 3, new BigDecimal("1000"), null), WEEK3_V2(3, 2, new BigDecimal("700"), new BigDecimal("999")), - WEEK3_V1(3, 1, new BigDecimal("1"), new BigDecimal("699")); + WEEK3_V1(3, 1, new BigDecimal("0.99"), new BigDecimal("699")); - private final int week; // 周(1/2/3) - private final int vipLevel; // VIP等级 0-3 - private final BigDecimal minAmount; // 最低金额 - private final BigDecimal maxAmount; // 最高金额(null表示无上限) + private final int week; + private final int vipLevel; + private final BigDecimal minAmount; + private final BigDecimal maxAmount; /** - * 根据周、充值金额计算VIP等级 + * 计算用户的周期配置(基于userId + 首充日期哈希) + * 返回 [week1Days, week2Days, week3Days],总和固定21天 */ - public static int calculateVipLevel(int week, BigDecimal rechargeAmount) { - if (rechargeAmount == null || rechargeAmount.compareTo(BigDecimal.ZERO) <= 0) { - return 0; // 未充值 + public static int[] calculateWeekDurations(Long userId, LocalDate firstRechargeDate) { + long seed = userId + firstRechargeDate.toEpochDay(); + Random random = new Random(seed); + + int week1 = 5 + random.nextInt(5); + int week2 = 5 + random.nextInt(5); + int week3 = 21 - week1 - week2; + + if (week3 < 5) { + week1 -= (5 - week3); + week3 = 5; + } else if (week3 > 9) { + week1 += (week3 - 9); + week3 = 9; } + return new int[]{week1, week2, week3}; + } + + /** + * 带继承金额的等级判定(继承金额用于降低门槛) + * + * @param week 当前周(1/2/3) + * @param effectiveAmount 有效金额(21天累计/周数*0.5) + * @param inheritedAmount 上周继承金额(用于降低门槛) + * @return VIP等级 + */ + public static int calculateVipLevelWithInheritance( + int week, + BigDecimal effectiveAmount, + BigDecimal inheritedAmount) { + + if (effectiveAmount == null || effectiveAmount.compareTo(BigDecimal.ZERO) <= 0) { + effectiveAmount = BigDecimal.ZERO; + } + + if (inheritedAmount == null || inheritedAmount.compareTo(BigDecimal.ZERO) <= 0) { + inheritedAmount = BigDecimal.ZERO; + } + + BigDecimal v3Threshold = getThreshold(week, 3); + BigDecimal v2Threshold = getThreshold(week, 2); + BigDecimal v1Threshold = getThreshold(week, 1); + + v3Threshold = v3Threshold.subtract(inheritedAmount); + v2Threshold = v2Threshold.subtract(inheritedAmount); + v1Threshold = v1Threshold.subtract(inheritedAmount); + + if (effectiveAmount.compareTo(v3Threshold) >= 0) { + return 3; + } + + if (v2Threshold.compareTo(BigDecimal.ZERO) <= 0 + || effectiveAmount.compareTo(v2Threshold) >= 0) { + return 2; + } + + if (v1Threshold.compareTo(BigDecimal.ZERO) <= 0 + || effectiveAmount.compareTo(v1Threshold) >= 0) { + return 1; + } + + return 0; + } + + /** + * 计算本周产生的继承金额 + * 继承金额 = 本周充值 - 下一档门槛(如果有超出) + * + * @param week 当前周(1/2/3) + * @param currentLevel 本周达到的等级 + * @param thisWeekRecharge 本周实际充值 + * @return 继承金额 + */ + public static BigDecimal calculateThisWeekInheritance( + int week, + int currentLevel, + BigDecimal thisWeekRecharge) { + + if (thisWeekRecharge == null || thisWeekRecharge.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO; + } + + int nextLevel = currentLevel + 1; + if (nextLevel > 3) { + return BigDecimal.ZERO; + } + + BigDecimal nextThreshold = getThreshold(week, nextLevel); + if (nextThreshold == null) { + return BigDecimal.ZERO; + } + + BigDecimal excess = thisWeekRecharge.subtract(nextThreshold); + return excess.compareTo(BigDecimal.ZERO) > 0 ? excess : BigDecimal.ZERO; + } + + /** + * 获取指定周、指定等级的门槛 + */ + public static BigDecimal getThreshold(int week, int level) { for (VipLevelRule rule : values()) { - if (rule.week == week) { - if (rechargeAmount.compareTo(rule.minAmount) >= 0) { - if (rule.maxAmount == null || rechargeAmount.compareTo(rule.maxAmount) <= 0) { - return rule.vipLevel; - } - } + if (rule.week == week && rule.vipLevel == level) { + return rule.minAmount; } } - - return 0; // 默认V0 + return null; } -} \ No newline at end of file +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java index c7a64bef..04586643 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java @@ -40,6 +40,7 @@ import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheSe import com.red.circle.other.infra.database.cache.service.other.GameListCacheService; import com.red.circle.other.infra.database.rds.entity.sys.GameListConfig; import com.red.circle.other.infra.database.rds.service.activity.UserActivityRechargeService; +import com.red.circle.other.infra.database.rds.dto.game.WeeklyVipInfo; import com.red.circle.other.infra.database.rds.service.game.GameVipLevelWeeklyService; import com.red.circle.other.infra.database.rds.service.sys.GameListConfigService; import com.red.circle.other.inner.enums.config.EnumConfigKey; @@ -61,6 +62,7 @@ import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO; import com.red.circle.wallet.inner.model.enums.GoldOrigin; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZonedDateTime; @@ -145,7 +147,7 @@ public class HotGameServiceImpl implements HotGameService { String cacheKey = VIP_LEVEL_CACHE_KEY + userId; Object cachedLevel = redisService.redisTemplate().opsForValue().get(cacheKey); if (cachedLevel != null) { - return Integer.parseInt(cachedLevel.toString()); + //return Integer.parseInt(cachedLevel.toString()); } LocalDate firstRechargeDate = userActivityRechargeService.getFirstRechargeDate(userId, VIP_ACTIVITY_ID); @@ -154,33 +156,74 @@ public class HotGameServiceImpl implements HotGameService { return 0; } + int[] weekDurations = VipLevelRule.calculateWeekDurations(userId, firstRechargeDate); + int week1Days = weekDurations[0]; + int week2Days = weekDurations[1]; + int week3Days = weekDurations[2]; + LocalDate today = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); - long daysBetween = ChronoUnit.DAYS.between(firstRechargeDate, today); - int currentWeek = (int) (daysBetween / 7) + 1; + long daysSinceFirst = ChronoUnit.DAYS.between(firstRechargeDate, today); + long completedCycles = daysSinceFirst / 21; + long dayInCycle = daysSinceFirst % 21; - int normalizedWeek = ((currentWeek - 1) % 3) + 1; + int normalizedWeek; + int weekInCycle; + LocalDate weekStartDate; + LocalDate weekEndDate; - LocalDate weekStartDate = firstRechargeDate.plusWeeks(currentWeek - 1); - LocalDate weekEndDate = weekStartDate.plusDays(6); + if (dayInCycle < week1Days) { + normalizedWeek = 1; + weekInCycle = 1; + weekStartDate = firstRechargeDate.plusDays(completedCycles * 21); + weekEndDate = weekStartDate.plusDays(week1Days - 1); + } else if (dayInCycle < week1Days + week2Days) { + normalizedWeek = 2; + weekInCycle = 2; + weekStartDate = firstRechargeDate.plusDays(completedCycles * 21 + week1Days); + weekEndDate = weekStartDate.plusDays(week2Days - 1); + } else { + normalizedWeek = 3; + weekInCycle = 3; + weekStartDate = firstRechargeDate.plusDays(completedCycles * 21 + week1Days + week2Days); + weekEndDate = weekStartDate.plusDays(week3Days - 1); + } - int lastWeekLevel = gameVipLevelWeeklyService.getLastWeekFinalLevel(userId, VIP_ACTIVITY_ID, weekStartDate); - int startLevel = Math.max(0, lastWeekLevel - 1); + LocalDate cycleStartDate = firstRechargeDate.plusDays(completedCycles * 21); + LocalDate cycleEndDate = cycleStartDate.plusDays(20); + + BigDecimal cycleTotalRecharge = userActivityRechargeService.getRechargeAmountByDateRange( + userId, VIP_ACTIVITY_ID, cycleStartDate, cycleEndDate + ); + + BigDecimal effectiveAmount = cycleTotalRecharge + .divide(new BigDecimal(weekInCycle), 2, RoundingMode.HALF_UP); + + WeeklyVipInfo lastWeekInfo = gameVipLevelWeeklyService.getLastWeekInfo(userId, VIP_ACTIVITY_ID, weekStartDate); + BigDecimal lastWeekInherited = (lastWeekInfo != null && lastWeekInfo.getInheritedAmount() != null) + ? lastWeekInfo.getInheritedAmount() + : BigDecimal.ZERO; + + int displayLevel = VipLevelRule.calculateVipLevelWithInheritance( + normalizedWeek, effectiveAmount, lastWeekInherited + ); BigDecimal thisWeekRecharge = userActivityRechargeService.getRechargeAmountByDateRange( userId, VIP_ACTIVITY_ID, weekStartDate, weekEndDate ); - int rechargeLevel = VipLevelRule.calculateVipLevel(normalizedWeek, thisWeekRecharge); - - int finalLevel = Math.max(startLevel, rechargeLevel); - - gameVipLevelWeeklyService.saveOrUpdateWeeklyLevel( - userId, VIP_ACTIVITY_ID, currentWeek, weekStartDate, weekEndDate, finalLevel, thisWeekRecharge + BigDecimal thisWeekInherited = VipLevelRule.calculateThisWeekInheritance( + normalizedWeek, displayLevel, thisWeekRecharge ); - redisService.redisTemplate().opsForValue().set(cacheKey, finalLevel, 1, TimeUnit.DAYS); + gameVipLevelWeeklyService.saveOrUpdateWeeklyLevel( + userId, VIP_ACTIVITY_ID, (int) (completedCycles * 3 + normalizedWeek), + weekStartDate, weekEndDate, displayLevel, thisWeekRecharge, + normalizedWeek, thisWeekInherited + ); - return finalLevel; + redisService.redisTemplate().opsForValue().set(cacheKey, displayLevel, 1, TimeUnit.DAYS); + + return displayLevel; } catch (Exception e) { log.error("获取VIP等级异常, userId:{}, error:{}", userId, e.getMessage(), e); diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dto/game/WeeklyVipInfo.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dto/game/WeeklyVipInfo.java new file mode 100644 index 00000000..7a4dd549 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dto/game/WeeklyVipInfo.java @@ -0,0 +1,20 @@ +package com.red.circle.other.infra.database.rds.dto.game; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 每周VIP信息 + */ +@Data +public class WeeklyVipInfo { + + private Integer finalLevel; + + private BigDecimal rechargeAmount; + + private Integer normalizedWeek; + + private BigDecimal inheritedAmount; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/GameVipLevelWeekly.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/GameVipLevelWeekly.java index 51c046d6..363ce373 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/GameVipLevelWeekly.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/GameVipLevelWeekly.java @@ -48,6 +48,12 @@ public class GameVipLevelWeekly implements Serializable { @TableField("recharge_amount") private BigDecimal rechargeAmount; + @TableField("normalized_week") + private Integer normalizedWeek; + + @TableField("inherited_amount") + private BigDecimal inheritedAmount; + @TableField("created_at") private Timestamp createdAt; diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/activity/impl/UserActivityRechargeServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/activity/impl/UserActivityRechargeServiceImpl.java index 41aa2326..6fdd800b 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/activity/impl/UserActivityRechargeServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/activity/impl/UserActivityRechargeServiceImpl.java @@ -36,14 +36,15 @@ public class UserActivityRechargeServiceImpl implements UserActivityRechargeServ public void recordRecharge(Long userId, BigDecimal amount, MonthlyRechargeType type) { LocalDate today = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); String rechargeDate = today.toString(); - - int result = userActivityRechargeDAO.incrAmount( - userId, - FIXED_ACTIVITY_ID, - rechargeDate, - type.name(), - amount - ); + + int result = 0; +// int result = userActivityRechargeDAO.incrAmount( +// userId, +// FIXED_ACTIVITY_ID, +// rechargeDate, +// type.name(), +// amount +// ); userActivityRechargeDAO.incrAmount( userId, diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/GameVipLevelWeeklyService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/GameVipLevelWeeklyService.java index cc25f104..f742963d 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/GameVipLevelWeeklyService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/GameVipLevelWeeklyService.java @@ -1,5 +1,7 @@ package com.red.circle.other.infra.database.rds.service.game; +import com.red.circle.other.infra.database.rds.dto.game.WeeklyVipInfo; + import java.math.BigDecimal; import java.time.LocalDate; @@ -7,7 +9,10 @@ public interface GameVipLevelWeeklyService { Integer getLastWeekFinalLevel(Long userId, Long activityId, LocalDate currentWeekStartDate); + WeeklyVipInfo getLastWeekInfo(Long userId, Long activityId, LocalDate currentWeekStartDate); + void saveOrUpdateWeeklyLevel(Long userId, Long activityId, Integer weekNumber, LocalDate weekStartDate, LocalDate weekEndDate, - Integer finalLevel, BigDecimal rechargeAmount); + Integer finalLevel, BigDecimal rechargeAmount, + Integer normalizedWeek, BigDecimal inheritedAmount); } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/impl/GameVipLevelWeeklyServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/impl/GameVipLevelWeeklyServiceImpl.java index baa48123..fffc766f 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/impl/GameVipLevelWeeklyServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/game/impl/GameVipLevelWeeklyServiceImpl.java @@ -1,6 +1,7 @@ package com.red.circle.other.infra.database.rds.service.game.impl; import com.red.circle.other.infra.database.rds.dao.game.GameVipLevelWeeklyDAO; +import com.red.circle.other.infra.database.rds.dto.game.WeeklyVipInfo; import com.red.circle.other.infra.database.rds.entity.game.GameVipLevelWeekly; import com.red.circle.other.infra.database.rds.service.game.GameVipLevelWeeklyService; import lombok.RequiredArgsConstructor; @@ -24,15 +25,33 @@ public class GameVipLevelWeeklyServiceImpl implements GameVipLevelWeeklyService return lastWeek != null ? lastWeek.getFinalLevel() : 0; } + @Override + public WeeklyVipInfo getLastWeekInfo(Long userId, Long activityId, LocalDate currentWeekStartDate) { + GameVipLevelWeekly lastWeek = gameVipLevelWeeklyDAO.getLastWeekLevel(userId, activityId, currentWeekStartDate); + if (lastWeek == null) { + return null; + } + + WeeklyVipInfo info = new WeeklyVipInfo(); + info.setFinalLevel(lastWeek.getFinalLevel()); + info.setRechargeAmount(lastWeek.getRechargeAmount()); + info.setNormalizedWeek(lastWeek.getNormalizedWeek()); + info.setInheritedAmount(lastWeek.getInheritedAmount()); + return info; + } + @Override public void saveOrUpdateWeeklyLevel(Long userId, Long activityId, Integer weekNumber, LocalDate weekStartDate, LocalDate weekEndDate, - Integer finalLevel, BigDecimal rechargeAmount) { + Integer finalLevel, BigDecimal rechargeAmount, + Integer normalizedWeek, BigDecimal inheritedAmount) { GameVipLevelWeekly existing = gameVipLevelWeeklyDAO.getByUserAndWeek(userId, activityId, weekStartDate); if (existing != null) { existing.setFinalLevel(finalLevel); existing.setRechargeAmount(rechargeAmount); + existing.setNormalizedWeek(normalizedWeek); + existing.setInheritedAmount(inheritedAmount); existing.setUpdatedAt(new Timestamp(System.currentTimeMillis())); gameVipLevelWeeklyDAO.updateById(existing); } else { @@ -44,6 +63,8 @@ public class GameVipLevelWeeklyServiceImpl implements GameVipLevelWeeklyService record.setWeekEndDate(weekEndDate); record.setFinalLevel(finalLevel); record.setRechargeAmount(rechargeAmount); + record.setNormalizedWeek(normalizedWeek); + record.setInheritedAmount(inheritedAmount); Timestamp now = new Timestamp(System.currentTimeMillis()); record.setCreatedAt(now); record.setUpdatedAt(now); diff --git a/rc-service/rc-service-other/other-start/src/test/java/GameVipLevelTest.java b/rc-service/rc-service-other/other-start/src/test/java/GameVipLevelTest.java index ba00f8a8..55fcb467 100644 --- a/rc-service/rc-service-other/other-start/src/test/java/GameVipLevelTest.java +++ b/rc-service/rc-service-other/other-start/src/test/java/GameVipLevelTest.java @@ -27,7 +27,7 @@ public class GameVipLevelTest { private GameVipLevelWeeklyService gameVipLevelWeeklyService; private static final Long TEST_USER_ID = 1957345312961527809L; - private static final Long ACTIVITY_ID = 2005571533988298753L; + private static final Long ACTIVITY_ID = 1235571533988298753L; @Test public void testGetVipLevel() {