幸运24-额外

This commit is contained in:
khalil
2025-06-11 22:19:38 +08:00
parent e7a4459eb2
commit a81adc0303
10 changed files with 283 additions and 11 deletions

View File

@@ -1355,6 +1355,8 @@ public enum RedisKey {
lock_lucky_24_message, // 消费送礼物消息锁
lucky_24_user_10w_stat, // 消费送礼物消息锁
lucky_24_extra_stock,
family_diamond_settlement,
room_avatar_under_review,//头像:审核中的任务

View File

@@ -22,6 +22,7 @@ public enum Lucky24PoolTypeEnum {
*/
NORMAL_POOL(2, "普通奖池"),
BLACK_POOL(3, "黑名单奖池"),
EXTRA_POOL(4, "额外奖池"),
;
/**

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Data
public class Lucky24GiftConfig {
@@ -24,6 +25,8 @@ public class Lucky24GiftConfig {
private BigDecimal totalInputOffset_K1;
private BigDecimal totalInputOffset_K2;
private Lucky24ExtraPoolConfig extraPoolConfig;
private Integer poolSize = 500;
private Integer newUserPoolCount = 0;
@@ -48,4 +51,17 @@ public class Lucky24GiftConfig {
return ratioPartitionMap.getOrDefault(partitionId, this);
}
@Data
public static class Lucky24ExtraPoolConfig{
private Boolean isOpen;
private BigDecimal storeRatio;
private Integer startHour;
private Integer endHour;
private Integer inputThreshold;
private Set<String> userRechargeLevels;
private BigDecimal todayProductionRatio;
private Long todayDiff;
private Integer twoDayCountLimit;
}
}

View File

@@ -16,4 +16,13 @@ public class SuperLuckyGiftIncomeAllot {
private BigDecimal roomOwnerTotalIncome;
private BigDecimal remainValue;
private BigDecimal extraStore;
public SuperLuckyGiftIncomeAllot(BigDecimal totalValue, BigDecimal receiveTotalIncome, Map<Long, BigDecimal> receiveIncomeMap, BigDecimal roomOwnerTotalIncome, BigDecimal remainValue) {
this.totalValue = totalValue;
this.receiveTotalIncome = receiveTotalIncome;
this.receiveIncomeMap = receiveIncomeMap;
this.roomOwnerTotalIncome = roomOwnerTotalIncome;
this.remainValue = remainValue;
}
}

View File

@@ -57,6 +57,8 @@ public class Lucky24GiftSendService {
private RocketMQService rocketMQService;
@Autowired
private JedisService jedisService;
@Autowired
private Lucky24ExtraService extraService;
public void draw(long senderUid, Integer partitionId, Room room, List<Long> receiverList,
Gift gift, int everyGiftNum, Date sendGiftTime) {
@@ -72,12 +74,43 @@ public class Lucky24GiftSendService {
log.info("[lucky24] uid {}, partitionId {}, addStockGoldNum {}, afterStock {}",
senderUid, partitionId, incomeAllot.getRemainValue(), afterStock);
Map<Long, Lucky24Record> recordMap = draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
// 增加额外库存
if (null != incomeAllot.getExtraStore()){
BigDecimal extraAfterStock = extraService.addStock(partitionId, incomeAllot.getExtraStore());
log.info("[lucky24] extraStockAdd uid {}, partitionId {}, addStockGoldNum {}, afterStock {}",
senderUid, partitionId, incomeAllot.getExtraStore(), extraAfterStock);
}
Map<Long, Lucky24Record> recordMap = draw(config, partitionConfig, senderUid, partitionId,
gift, everyGiftNum, everyoneGoldNum,
receiverList, room, sendGiftTime, null != incomeAllot.getExtraStore());
log.info("[lucky24] uid {}, totalWinGoldNum {}", senderUid, JSON.toJSONString(recordMap));
sendMq(recordMap);
}
private Map<Long, Lucky24Record> draw(Lucky24GiftConfig config, Lucky24GiftConfig partitionConfig, long senderUid, Integer partitionId,
Gift gift, int everyGiftNum, long everyoneGoldNum,
List<Long> receiverList, Room room, Date sendGiftTime, boolean extraSwitch) {
if (!extraSwitch){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig = partitionConfig.getExtraPoolConfig();
Long extraLucker = extraService.selectExtraLucker(extraPoolConfig, senderUid, partitionId, everyoneGoldNum, receiverList);
if (null == extraLucker){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Lucky24Record extraRecord = extraService.randomExtraRecord(config, senderUid, partitionId, extraLucker, gift, everyGiftNum, everyoneGoldNum, room, sendGiftTime);
List<Long> receiverUidList = new ArrayList<>(receiverList);
receiverUidList.remove(extraLucker);
Map<Long, Lucky24Record> recordMap = draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverUidList, room, sendGiftTime);
recordMap.put(extraLucker, extraRecord);
return recordMap;
}
public Map<Long, Lucky24Record> draw(Lucky24GiftConfig config, Lucky24GiftConfig partitionConfig, Long senderUid, int partitionId,
Gift gift, int everyGiftNum, long everyoneGoldNum,
List<Long> receiverList, Room room, Date sendGiftTime) {

View File

@@ -0,0 +1,144 @@
package com.accompany.business.service.lucky;
import com.accompany.business.constant.Lucky24PoolTypeEnum;
import com.accompany.business.dto.lucky.Lucky24GiftConfig;
import com.accompany.business.model.Gift;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.RandomUtil;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.model.Room;
import com.accompany.payment.service.UserRechargeLevelService;
import com.accompany.sharding.model.Lucky24Record;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class Lucky24ExtraService {
@Autowired
private Lucky24ExtraStockService stockService;
@Autowired
private UserRechargeLevelService userRechargeLevelService;
@Autowired
private Lucky24UserMetaService userMetaService;
@Autowired
private Lucky24RobotMsgService robotMsgService;
@Autowired
private Lucky24SettlementService settlementService;
@Autowired
private Lucky24RecordService recordService;
public BigDecimal addStock(Integer partitionId, BigDecimal addScore) {
return stockService.addStock(partitionId, addScore);
}
public Long selectExtraLucker(Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig, long senderUid, Integer partitionId, long everyoneGoldNum, List<Long> receiverList) {
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
ZonedDateTime zdt = DateTimeUtil.getDateTimeByZoneId(partitionEnum.getZoneId());
if (zdt.getHour() < extraPoolConfig.getStartHour() || zdt.getHour() > extraPoolConfig.getEndHour()){
return null;
}
if (everyoneGoldNum < extraPoolConfig.getInputThreshold()){
return null;
}
String userRechargeLevel = userRechargeLevelService.getLevelByUid(senderUid);
if (!extraPoolConfig.getUserRechargeLevels().contains(userRechargeLevel)){
return null;
}
RMap<String, Number> userMetaMap = userMetaService.getUserMeta(senderUid);
Map<String, Number> userMetaSnapshot = userMetaMap.readAllMap();
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayInputKey = String.join("_", today, Lucky24UserMetaService.INPUT_KEY);
long todayInput = userMetaSnapshot.getOrDefault(todayInputKey, 0L).longValue();
String todayOutputKey = String.join("_", today, Lucky24UserMetaService.OUTPUT_KEY);
long todayOutput = userMetaSnapshot.getOrDefault(todayOutputKey, 0L).longValue();
BigDecimal todayProductionRatio = todayOutput > 0L ? BigDecimal.valueOf(todayInput).divide(BigDecimal.valueOf(todayOutput), 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
if (todayProductionRatio.compareTo(extraPoolConfig.getTodayProductionRatio()) >= 0){
return null;
}
long todayDiff = todayInput - todayOutput;
if (todayDiff < extraPoolConfig.getTodayDiff()){
return null;
}
String yesterday = String.valueOf(zdt.minusDays(1L).toInstant().toEpochMilli());
String lastTwoDayKey = String.join("_", Lucky24UserMetaService.EXTRA_POOL_COUNT, yesterday);
int lastTwoDayCount = userMetaSnapshot.getOrDefault(lastTwoDayKey, 0).intValue();
if (lastTwoDayCount >= extraPoolConfig.getTwoDayCountLimit()){
return null;
}
int expectedValue = lastTwoDayCount + 1;
int result = userMetaMap.compute(lastTwoDayKey, (key, currentVal) -> {
if (currentVal == null || currentVal.intValue() == lastTwoDayCount) {
return expectedValue;
}
return currentVal; // 如果当前值不等于期望值,则返回原值,不做修改
}).intValue();
if (result != expectedValue){
//cas 失败了
return null;
}
String todayKey = String.join("_", Lucky24UserMetaService.EXTRA_POOL_COUNT, today);
int todayAfter = userMetaMap.addAndGet(todayKey, 1).intValue();
//todo log
return receiverList.get(0);
}
public Lucky24Record randomExtraRecord(Lucky24GiftConfig config, long senderUid, Integer partitionId, Long receiverUid,
Gift gift, int giftNum, long everyoneGoldNum, Room room, Date sendGiftTime) {
int random = RandomUtil.randomByRange(0, 10);
int drawMultiple = random < 5 ? 1000: random < 9 ? 500: 250;
long afterMultiple = drawMultiple;
// 平台库存
BigDecimal preWinGoldNum = BigDecimal.valueOf(afterMultiple * everyoneGoldNum);
if (!judgeStock(partitionId, preWinGoldNum, senderUid, receiverUid)){
afterMultiple = 0L;
}
long winGoldNum = afterMultiple * everyoneGoldNum;
if (winGoldNum > 0L){
settlementService.sendReward(config, senderUid, room, gift, winGoldNum, afterMultiple);
}
//todo log
return recordService.buildRecord(senderUid, partitionId, gift, giftNum, null != room? room.getUid(): null,
receiverUid, Lucky24PoolTypeEnum.EXTRA_POOL.getType(),
drawMultiple, afterMultiple, sendGiftTime);
}
private boolean judgeStock(Integer partitionId, BigDecimal winGoldNum, Long senderUid, Long receiverUid){
BigDecimal afterStock = stockService.subStock(partitionId, winGoldNum);
boolean enough = afterStock.compareTo(BigDecimal.ZERO) >= 0;
if (!enough){
log.info("[lucky24] extraStock sender {} receiver {} 产出大于库存 winGoldNum {} afterStock {}",
senderUid, receiverUid, winGoldNum, afterStock);
afterStock = stockService.addStock(partitionId, winGoldNum);
robotMsgService.pushStockNotEnough(partitionId, afterStock);
}
return enough;
}
}

View File

@@ -0,0 +1,25 @@
package com.accompany.business.service.lucky;
import com.accompany.common.redis.RedisKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class Lucky24ExtraStockService implements StockService {
@Autowired
protected RedissonClient redissonClient;
@Override
public RedissonClient getRedissonClient() {
return redissonClient;
}
@Override
public RedisKey getRedisKey() {
return RedisKey.lucky_24_extra_stock;
}
}

View File

@@ -46,10 +46,17 @@ public class Lucky24IncomeAllotService implements LuckyGiftIncomeAllotService {
// 平台抽成
BigDecimal platformGoldNum = totalValue.multiply(config.getPlatformRatio());
// 增加库存
BigDecimal addStockGoldNum = totalValue.subtract(platformGoldNum);
return new SuperLuckyGiftIncomeAllot(totalValue, receiverTotalIncome, receiverIncomeMap, BigDecimal.ZERO, addStockGoldNum);
// 额外抽成
BigDecimal extraRatio = null == config.getExtraPoolConfig() || Boolean.TRUE.equals(config.getExtraPoolConfig().getIsOpen())?
BigDecimal.ZERO: config.getExtraPoolConfig().getTodayProductionRatio();
BigDecimal extraGoldNum = extraRatio.equals(BigDecimal.ZERO)? BigDecimal.ZERO:
totalValue.multiply(config.getExtraPoolConfig().getStoreRatio());
// 增加库存
BigDecimal addStockGoldNum = totalValue.subtract(platformGoldNum).subtract(extraGoldNum);
return new SuperLuckyGiftIncomeAllot(totalValue, receiverTotalIncome, receiverIncomeMap, BigDecimal.ZERO, addStockGoldNum,
extraGoldNum.equals(BigDecimal.ZERO)? null: extraGoldNum);
}
@Override

View File

@@ -33,6 +33,25 @@ public class Lucky24RecordService extends ServiceImpl<Lucky24RecordMapper, Lucky
@Resource(name = "bizExecutor")
private ThreadPoolExecutor bizExecutor;
public Lucky24Record buildRecord(long senderUid, int partitionId, Gift gift, int giftNum, Long roomUid, long receiverUid, Integer poolType,
long drawMultiple, long afterMultiple, Date sendGiftTime) {
Lucky24Record record = new Lucky24Record();
record.setUid(senderUid);
record.setPartitionId(partitionId);
record.setReceiverUid(receiverUid);
record.setRoomUid(roomUid);
record.setGiftId(gift.getGiftId());
record.setGiftNum(giftNum);
record.setGiftGoldPrice(gift.getGoldPrice());
record.setPoolType(poolType);
record.setIsSupplement(false);
record.setDrawMultiple((int) drawMultiple);
record.setAfterMultiple((int) afterMultiple);
record.setWinGoldNum(afterMultiple * gift.getGoldPrice() * giftNum);
record.setCreateTime(sendGiftTime);
return record;
}
public Lucky24Record buildRecord(long senderUid, int partitionId, Gift gift, int giftNum, Long roomUid, long receiverUid, Integer poolId,
boolean isSupplement, long drawMultiple, long afterMultiple, Date sendGiftTime) {
Lucky24Record record = new Lucky24Record();

View File

@@ -17,6 +17,7 @@ import java.math.RoundingMode;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Set;
@Slf4j
@Service
@@ -28,6 +29,8 @@ public class Lucky24UserMetaService {
private static final String TODAY = "today";
public static final String EXTRA_POOL_COUNT = "extra_pool_count";
private static final int HISTORY_QUEUE_SIZE = 40000;
@Autowired
@@ -125,6 +128,12 @@ public class Lucky24UserMetaService {
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayInputKey = String.join("_", today, INPUT_KEY);
long todayInput = userMetaMap.addAndGet(todayInputKey, input).longValue();
String todayOutputKey = String.join("_", today, OUTPUT_KEY);
long todayOutput = userMetaMap.addAndGet(todayOutputKey, output).longValue();
long userMetaToday = userMetaMap.computeIfAbsent(TODAY, k->todayStartTimeLong).longValue();
if (userMetaToday < todayStartTimeLong
&& userMetaMap.replace(TODAY, userMetaToday, todayStartTimeLong)){
@@ -132,14 +141,21 @@ public class Lucky24UserMetaService {
String oldToday = String.valueOf(userMetaToday);
String oldTodayInputKey = String.join("_", oldToday, INPUT_KEY);
String oldTodayOutputKey = String.join("_", oldToday, OUTPUT_KEY);
userMetaMap.fastRemoveAsync(oldTodayInputKey, oldTodayOutputKey);
}
userMetaMap.fastRemove(oldTodayInputKey, oldTodayOutputKey);
String today = String.valueOf(todayStartTimeLong);
String todayInputKey = String.join("_", today, INPUT_KEY);
long todayInput = userMetaMap.addAndGet(todayInputKey, input).longValue();
String todayOutputKey = String.join("_", today, OUTPUT_KEY);
long todayOutput = userMetaMap.addAndGet(todayOutputKey, output).longValue();
// 清理额外线计数器
String todayExtraPoolKey = String.join("_", EXTRA_POOL_COUNT, today);
String yesterday = DateTimeUtil.getDateTimeByZoneId(partitionEnum.getZoneId()).minusDays(1L).format(DateTimeUtil.dateFormatter);
String yesterdayExtraPoolKey = String.join("_", EXTRA_POOL_COUNT, yesterday);
String extraPoolKeyPattern = EXTRA_POOL_COUNT + "*";
Set<String> extraPoolKeySet = userMetaMap.keySet(extraPoolKeyPattern);
for (String extraPoolKey : extraPoolKeySet){
if (!extraPoolKey.equals(todayExtraPoolKey) && !extraPoolKey.equals(yesterdayExtraPoolKey)){
userMetaMap.fastRemove(extraPoolKey);
}
}
}
log.info("[Lucky24] updateUserMeta uid {} times {} today {} todayInput {} todayOutput {}",
senderUid, times, todayStartTimeLong, todayInput, todayOutput);