game getVipLevel新增第二版

This commit is contained in:
tianfeng 2026-01-12 15:02:08 +08:00
parent 421411c99c
commit 5952097305
8 changed files with 238 additions and 50 deletions

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
}

View File

@ -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);

View File

@ -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() {