game getVipLevel新增第二版
This commit is contained in:
parent
421411c99c
commit
5952097305
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user