幸运25-giftSendService策略入口

This commit is contained in:
khalil
2025-05-04 13:53:38 +08:00
parent 90c88ac0f2
commit 8a1721706a
35 changed files with 1719 additions and 36 deletions

View File

@@ -597,6 +597,7 @@ public class Constant {
* Bravo礼物
*/
public static final byte BRAVO_GIFT = GiftTypeEnum.BRAVO.getType();
public static final byte LUCKY_25 = GiftTypeEnum.LUCKY_25.getType();
}
public static class PayloadSkiptype {
@@ -1431,6 +1432,10 @@ public class Constant {
public static final String BRAVO_GIFT_CONFIG = "bravo_gift_config";
public static final String GUILD_USD_WITHDRAW_RATE = "guild_usd_withdraw_rate";
public static final String LUCKY_NUMBER_ACT_CONFIG = "lucky_number_act_config";
public static final String LUCKY_25_GIFT_CONFIG = "lucky_25_gift_config";
}
public static class WithDrawStatus {

View File

@@ -23,10 +23,12 @@ public enum GiftTypeEnum {
LUCKY_BAG_LINEAR((byte) 15, "线性福袋"),
SUPER_LUCKY((byte) 16, "幸运礼物"),
COUNTRY((byte) 17, "国家"),
LUCKY_24((byte) 18, "幸运礼物"),
LUCKY_24((byte) 18, "幸运24礼物"),
CP((byte) 19, "cp"),
CUSTOM((byte) 20, "大R定制"),
BRAVO((byte) 21, "Bravo");
BRAVO((byte) 21, "Bravo"),
LUCKY_25((byte) 22, "幸运25礼物"),
;
private byte type;
private String desc;

View File

@@ -1441,6 +1441,17 @@ public enum RedisKey {
gusd_withdraw_limit,//公会长提现次数限制
bravo_banner_queue, // bravo轮播队列
//幸运25
lucky_25_stock,
lucky_25_user_meta,
lucky_25_user_pool,
lucky_25_user_history,
lucky_25_user_lock,
lucky_25_robot_push_msg,
lucky_25_status, // 礼物消息的状态
lock_lucky_25_message, // 消费送礼物消息锁
lucky_25_user_10w_stat, // 消费送礼物消息锁
;
public String getKey() {

View File

@@ -233,8 +233,9 @@ public enum BillObjTypeEnum {
LUCKY_NUM_JACKPOT_INPUT_GOLD(155, "幸运数字奖池金币投入", BillTypeEnum.OUT, CurrencyEnum.DIAMOND, BillDomainTypeEnum.ACTIVITY),
LUCKY_NUM_JACKPOT_OUTPUT_GOLD(156, "幸运数字奖池金币瓜分", BillTypeEnum.IN, CurrencyEnum.DIAMOND, BillDomainTypeEnum.ACTIVITY),
LUCKY_24_GIFT_PAY( 157, "幸运礼物支出", BillTypeEnum.OUT, CurrencyEnum.DIAMOND, BillDomainTypeEnum.SEND_GIFT),
LUCKY_24_GIFT_PAY( 157, "幸运24礼物支出", BillTypeEnum.OUT, CurrencyEnum.DIAMOND, BillDomainTypeEnum.SEND_GIFT),
BRAVO_GIFT_PAY( 158, "Bravo礼物支出", BillTypeEnum.OUT, CurrencyEnum.DIAMOND, BillDomainTypeEnum.SEND_GIFT),
LUCKY_25_GIFT_PAY( 159, "幸运25礼物支出", BillTypeEnum.OUT, CurrencyEnum.DIAMOND, BillDomainTypeEnum.SEND_GIFT),
;
BillObjTypeEnum(int value, String desc, BillTypeEnum type, CurrencyEnum currency, BillDomainTypeEnum domain) {

View File

@@ -0,0 +1,8 @@
package com.accompany.sharding.model;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName(value = "lucky_25_record")
public class Lucky25Record extends Lucky24Record {
}

View File

@@ -0,0 +1,11 @@
package com.accompany.sharding.vo;
import io.swagger.annotations.ApiModel;
@ApiModel
public class Lucky25PersonalStat extends Lucky24PersonalStat {
public Lucky25PersonalStat(String date, Integer partitionId, Long uid, Long erbanNo) {
super(date, partitionId, uid, erbanNo);
}
}

View File

@@ -0,0 +1,11 @@
package com.accompany.sharding.vo;
import io.swagger.annotations.ApiModel;
@ApiModel
public class Lucky25PlatformStat extends Lucky24PlatformStat {
public Lucky25PlatformStat(String date, Integer partitionId) {
super(date, partitionId);
}
}

View File

@@ -0,0 +1,22 @@
package com.accompany.sharding.mapper;
import com.accompany.sharding.model.Lucky25Record;
import com.accompany.sharding.vo.Lucky25PersonalStat;
import com.accompany.sharding.vo.Lucky25PlatformStat;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
public interface Lucky25RecordMapper extends BaseMapper<Lucky25Record> {
List<Lucky25PlatformStat> listPlatform(@Param("partitionId") Integer partitionId, @Param("startTime") Date startTime, @Param("endTime") Date endTime,
@Param("zoneIdHour") long zoneIdHour);
List<Lucky25PersonalStat> listPersonal(@Param("partitionId") Integer partitionId,
@Param("uid") Long uid,
@Param("startTime") Date startTime, @Param("endTime") Date endTime,
@Param("zoneIdHour") long zoneIdHour);
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.accompany.sharding.mapper.Lucky25RecordMapper">
<select id="listPlatform" resultType="com.accompany.sharding.vo.Lucky25PlatformStat">
select `date`, partition_id,
sum(`totalInput`) `totalInput`, sum(`totalOutput`) `totalOutput`,
sum(`totalOutput`) / sum(`totalInput`) `productionRatio`,
count(*) `count`, count((IF(`maxOutput` > 0, 1, null))) `winCount`,
sum(`num`) `num`, sum(`winNum`) `winNum`, sum(`winNum`) / sum(`num`) `winRate`
from (
select date(date_add(r.create_time, INTERVAL #{zoneIdHour} HOUR)) `date`,
partition_id,
sum(gift_num * gift_gold_price) `totalInput`,
sum(win_gold_num) `totalOutput`,
count(*) `num`,
count((case when win_gold_num > 0 then 1 end)) `winNum`,
max(win_gold_num) `maxOutput`
from lucky_24_record r
where r.create_time >= #{startTime} and r.create_time &lt;= #{endTime}
and r.partition_id = #{partitionId}
group by `date`, uid) l
group by `date`
</select>
<select id="listPersonal" resultType="com.accompany.sharding.vo.Lucky25PersonalStat">
select date(date_add(r.create_time, INTERVAL #{zoneIdHour} HOUR)) `date`, partition_id,
r.uid,
sum(gift_num * gift_gold_price) `totalInput`,
sum(win_gold_num) `totalOutput`,
sum(gift_num * gift_gold_price) - sum(win_gold_num) `production`,
sum(win_gold_num) / sum(gift_num * gift_gold_price) `productionRatio`,
sum(gift_num * gift_gold_price) / count(*) `avgInput`,
count(*) `num`,
count((case when win_gold_num > 0 then r.uid else null end)) `winNum`,
ifnull(count((case when win_gold_num > 0 then r.uid else null end)) / count(*),0) `winRate`
from lucky_24_record r
where r.create_time >= #{startTime} and r.create_time &lt;= #{endTime}
<if test="null != uid">
and r.uid = #{uid}
</if>
and r.partition_id = #{partitionId}
group by `date`, r.uid
</select>
</mapper>

View File

@@ -0,0 +1,43 @@
package com.accompany.business.constant;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.exception.ServiceException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Optional;
@AllArgsConstructor
@Getter
public enum Lucky25PoolTypeEnum {
/**
* 普通奖品池
*/
NEW_USER_POOL(1, "新人奖池"),
/**
* 偏差宝箱奖池
*/
NORMAL_POOL(2, "普通奖池"),
BLACK_POOL(3, "黑名单奖池"),
HIGH_POOL(4, "保底奖池"),
LOW_POOL(5, "衰减奖池"),
;
/**
* value
*/
private int type;
private String name;
public static Lucky25PoolTypeEnum get(int type) {
Optional<Lucky25PoolTypeEnum> result = Arrays.stream(Lucky25PoolTypeEnum.values()).filter(prizePoolTypeEnum ->
prizePoolTypeEnum.type == type).findFirst();
if (result.isPresent()) {
return result.get();
}
throw new ServiceException(BusiStatus.PARAMETERILLEGAL);
}
}

View File

@@ -0,0 +1,51 @@
package com.accompany.business.dto.lucky;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Data
public class Lucky25GiftConfig {
private BigDecimal platformRatio;
private BigDecimal receiverRatio;
//n
private BigDecimal productionRatio;
private Map<Integer, Lucky25GiftConfig> ratioPartitionMap;
private Long highInput_A;
private BigDecimal highInputExpect_D;
private Long preJudgeValue_H;
private BigDecimal totalInput_J;
private BigDecimal totalInputOffset_K1;
private BigDecimal totalInputOffset_K2;
private Integer poolSize = 500;
private Integer newUserPoolCount = 0;
private BigDecimal todayProductionRatio;
private Integer specialTipMulti;
private Long allRoomChatToastValue;
private Long specialFloatMulti;
private Long specialFloatValue;
private Long warnMulti;
private List<Long> followUidList;
private Map<Long, BigDecimal> whiteUidProductionRatioMap;
private List<Long> blackUidList;
private String diamondIcon;
public Lucky25GiftConfig getRatioByPartitionId(Integer partitionId){
return ratioPartitionMap.getOrDefault(partitionId, this);
}
}

View File

@@ -0,0 +1,12 @@
package com.accompany.business.dto.lucky;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class Lucky25Result extends Lucky24Result {
public Lucky25Result(Integer poolId, Long input, long output, Boolean isSupplement) {
super(poolId, input, output, isSupplement);
}
}

View File

@@ -0,0 +1,24 @@
package com.accompany.business.message;
import lombok.Data;
/**
* 礼物消息
*/
@Data
public class Lucky25Message extends Lucky24Message {
private Integer partitionId;
private Long uid;
private Long receiverUid;
private Long roomUid;
private Integer giftId;
private Long giftGoldPrice;
private Integer giftNum;
private Integer poolId;
private Boolean isSupplement;
private Integer drawMultiple;
private Integer afterMultiple;
private Long winGoldNum;
private Long createTime;
private String messId;
}

View File

@@ -0,0 +1,10 @@
package com.accompany.business.model.lucky;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
@ApiModel
@TableName(value = "lucky_25_pool", autoResultMap = true)
public class Lucky25Pool extends Lucky24Pool {
}

View File

@@ -0,0 +1,7 @@
package com.accompany.business.mybatismapper.lucky;
import com.accompany.business.model.lucky.Lucky25Pool;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface Lucky25PoolMapper extends BaseMapper<Lucky25Pool> {
}

View File

@@ -0,0 +1,21 @@
package com.accompany.business.mybatismapper.lucky;
import com.accompany.sharding.vo.Lucky25PersonalStat;
import com.accompany.sharding.vo.Lucky25PlatformStat;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface Lucky25StatMapper {
int savePlatform(@Param("item") Lucky25PlatformStat item);
int savePersonal(@Param("item") Lucky25PersonalStat item);
List<Lucky25PlatformStat> listPlatformStat(@Param("partitionId") Integer partitionId, @Param("startDate") String startDate, @Param("endDate") String endDate);
List<Lucky25PersonalStat> listPersonalStat(@Param("partitionId") Integer partitionId,
@Param("uid") Long uid,
@Param("startDate") String startDate, @Param("endDate") String endDate);
}

View File

@@ -86,6 +86,8 @@ public class GiftMessageService extends BaseService {
outEnum = BillObjTypeEnum.LUCKY_24_GIFT_PAY;
} else if (giftMessage.getGiftType() == GiftTypeEnum.BRAVO.getType()) {
outEnum = BillObjTypeEnum.BRAVO_GIFT_PAY;
} else if (giftMessage.getGiftType() == GiftTypeEnum.LUCKY_25.getType()) {
outEnum = BillObjTypeEnum.LUCKY_25_GIFT_PAY;
}
Date giftSendTime = new Date(giftMessage.getMessTime());

View File

@@ -61,7 +61,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@@ -106,32 +105,9 @@ public class GiftSendService extends BaseService {
private Lucky24GiftSendService lucky24GiftSendService;
@Autowired
private BravoGiftSendService bravoGiftSendService;
@Autowired
private Lucky25GiftSendService lucky25GiftSendService;
private List<LuckyBagGiftPrizePoolFilter> filters;
@PostConstruct
public void init() {
// 注意filter的顺序
List<LuckyBagGiftPrizePoolFilter> filters =
Lists.newArrayList(
applicationContext.getBean(LuckyBagGiftDeviatePrizePoolFilter.class),
applicationContext.getBean(LuckyBagGiftNormalPrizePoolFilter.class)
);
setFilters(filters);
}
/**
* 方法隔离级别声明为public只保证子类能访问
*
* @param filters
*/
public void setFilters(List<LuckyBagGiftPrizePoolFilter> filters) {
this.filters = filters;
}
public List<LuckyBagGiftPrizePoolFilter> getFilters() {
return this.filters;
}
/**
* 根据礼物id获取有效的礼物
@@ -498,6 +474,8 @@ public class GiftSendService extends BaseService {
lucky24GiftSendService.draw(sendUid, u.getPartitionId(), room, Arrays.asList(recvUids), gift, giftNum, sendGiftTime);
} else if (gift.getGiftType() == Constant.GiftType.BRAVO_GIFT) {
bravoGiftSendService.draw(sendUid, u.getPartitionId(), room, Arrays.asList(recvUids), gift, giftNum, sendGiftTime);
} else if (gift.getGiftType() == Constant.GiftType.LUCKY_25) {
lucky25GiftSendService.draw(sendUid, u.getPartitionId(), room, Arrays.asList(recvUids), gift, giftNum, sendGiftTime);
}
double everyGiftValue = calGiftValueByGiftType(everyGoldNum, gift.getGiftType(), u.getPartitionId());
@@ -517,6 +495,9 @@ public class GiftSendService extends BaseService {
} else if (giftType == Constant.GiftType.BRAVO_GIFT) {
return bravoGiftSendService.getConfig().getRatioByPartitionId(partitionId).getReceiverRatio()
.multiply(everyGoldNumB).doubleValue();
} else if (giftType == Constant.GiftType.LUCKY_25) {
return lucky25GiftSendService.getConfig().getRatioByPartitionId(partitionId).getReceiverRatio()
.multiply(everyGoldNumB).doubleValue();
}
return everyGoldNum.doubleValue();
}
@@ -529,6 +510,7 @@ public class GiftSendService extends BaseService {
BillObjTypeEnum objTypeEnum =
GiftTypeEnum.LUCKY_24.getType() == gift.getGiftType()? BillObjTypeEnum.LUCKY_24_GIFT_PAY:
GiftTypeEnum.BRAVO.getType() == gift.getGiftType()? BillObjTypeEnum.BRAVO_GIFT_PAY:
GiftTypeEnum.LUCKY_25.getType() == gift.getGiftType()? BillObjTypeEnum.LUCKY_25_GIFT_PAY:
null != room? BillObjTypeEnum.GIFT_ROOM_PAY: BillObjTypeEnum.GIFT_PERSON_PAY;
UserPurse after = reduceStockV5(sender.getUid(), sender.getPartitionId(), gift.getGiftId(),
totalGoldNum, totalGiftNum, giftSource, objTypeEnum);
@@ -937,7 +919,13 @@ public class GiftSendService extends BaseService {
* @return
*/
private Map<Long, List<LuckyBagRecord>> luckyBagDrawV2(long sendUid, int giftNum, Gift luckyBag, Date giftSendTime, List<Long> receiveUids) {
LuckyBagGiftPrizePoolFilterChain filterChain = new LuckyBagGiftPrizePoolFilterChain(getFilters());
// 注意filter的顺序
List<LuckyBagGiftPrizePoolFilter> filters =
Lists.newArrayList(
applicationContext.getBean(LuckyBagGiftDeviatePrizePoolFilter.class),
applicationContext.getBean(LuckyBagGiftNormalPrizePoolFilter.class)
);
LuckyBagGiftPrizePoolFilterChain filterChain = new LuckyBagGiftPrizePoolFilterChain(filters);
LuckyBagGiftPrizePoolRequest request =
new LuckyBagGiftPrizePoolRequest(sendUid, giftNum * receiveUids.size(), giftSendTime, luckyBag.getGiftId(),
luckyBag.getOpenDeviate(), luckyBag.getGiftType());

View File

@@ -0,0 +1,260 @@
package com.accompany.business.service.gift;
import com.accompany.business.dto.lucky.*;
import com.accompany.business.message.Lucky24Message;
import com.accompany.business.message.Lucky25Message;
import com.accompany.business.model.Gift;
import com.accompany.business.service.lucky.*;
import com.accompany.business.service.mq.RocketMQService;
import com.accompany.common.constant.Constant;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.RandomUtil;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Room;
import com.accompany.core.service.SysConfService;
import com.accompany.core.service.common.JedisService;
import com.accompany.sharding.model.Lucky25Record;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@Slf4j
@Service
public class Lucky25GiftSendService {
@Autowired
private SysConfService sysConfService;
@Autowired
private Lucky25StockService stockService;
@Autowired
private Lucky25UserMetaService userMetaService;
@Autowired
private Lucky25PoolService poolService;
@Autowired
private Lucky25RecordService recordService;
@Autowired
private Lucky25IncomeAllotService incomeAllotService;
@Autowired
private Lucky25SettlementService settlementService;
@Autowired
private Lucky25RobotMsgService robotMsgService;
@Autowired
private RocketMQService rocketMQService;
@Autowired
private JedisService jedisService;
public void draw(long senderUid, Integer partitionId, Room room, List<Long> receiverList,
Gift gift, int everyGiftNum, Date sendGiftTime) {
long everyoneGoldNum = everyGiftNum * gift.getGoldPrice();
Lucky25GiftConfig config = getConfig();
Lucky25GiftConfig partitionConfig = config.getRatioByPartitionId(partitionId);
SuperLuckyGiftIncomeAllot incomeAllot = incomeAllotService.calculate(partitionConfig, gift, everyGiftNum, receiverList);
// 增加库存
BigDecimal afterStock = stockService.addStock(partitionId, incomeAllot.getRemainValue());
log.info("[lucky25] uid {}, partitionId {}, addStockGoldNum {}, afterStock {}",
senderUid, partitionId, incomeAllot.getRemainValue(), afterStock);
Map<Long, Lucky25Record> recordMap = draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
log.info("[lucky25] uid {}, totalWinGoldNum {}", senderUid, JSON.toJSONString(recordMap));
sendMq(recordMap);
}
public Map<Long, Lucky25Record> draw(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, Long senderUid, int partitionId,
Gift gift, int everyGiftNum, long everyoneGoldNum,
List<Long> receiverList, Room room, Date sendGiftTime) {
if (everyoneGoldNum < partitionConfig.getHighInput_A()
|| (!CollectionUtils.isEmpty(config.getBlackUidList()) && config.getBlackUidList().contains(senderUid))){
log.error("[lucky25] uid {} totalInput {} configHighInput {} 原", senderUid, everyoneGoldNum, partitionConfig.getHighInput_A());
return receiverList.parallelStream()
.collect(Collectors.toMap(receiverUid-> receiverUid,
receiverUid-> drawMultiple(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, receiverUid, everyoneGoldNum, room, sendGiftTime, null)));
}
BigDecimal totalInput_A = BigDecimal.valueOf(everyoneGoldNum);
BigDecimal historyInput = BigDecimal.ZERO;
BigDecimal historyOutput = BigDecimal.ZERO;
RMap<String, Number> userStatCacheMap = userMetaService.getUser10wStat(senderUid);
Map<String, Number> userStatMap = userStatCacheMap.readAllMap();
if (userStatMap.isEmpty()){
List<Lucky25Result> historyResult = userMetaService.getUserLast10wHistory(senderUid);
historyInput = BigDecimal.valueOf(historyResult.stream().mapToLong(Lucky25Result::getInput).sum());
historyOutput = BigDecimal.valueOf(historyResult.stream().mapToLong(Lucky25Result::getOutput).sum());
userStatCacheMap.fastPut(Lucky25UserMetaService.INPUT_KEY, historyInput);
userStatCacheMap.fastPut(Lucky25UserMetaService.OUTPUT_KEY, historyOutput);
userStatCacheMap.expire(Duration.of(10, ChronoUnit.SECONDS));
} else {
historyInput = BigDecimal.valueOf(userStatMap.getOrDefault(Lucky25UserMetaService.INPUT_KEY, 0L).longValue());
historyOutput = BigDecimal.valueOf(userStatMap.getOrDefault(Lucky25UserMetaService.OUTPUT_KEY, 0L).longValue());
}
AtomicBoolean space1000flag = new AtomicBoolean(false);
AtomicBoolean space100flag = new AtomicBoolean(false);
BigDecimal productionRatio_B = historyOutput.compareTo(BigDecimal.ZERO) != 0 ?
historyOutput.divide(historyInput,2, RoundingMode.HALF_UP): BigDecimal.ZERO;
BigDecimal historyDiff_C = historyInput.subtract(historyOutput);
BigDecimal expect_D = partitionConfig.getHighInputExpect_D();
BigDecimal X = productionRatio_B.compareTo(BigDecimal.ONE) != 0 ?
historyDiff_C.divide(BigDecimal.ONE.subtract(productionRatio_B), 2, RoundingMode.HALF_UP): BigDecimal.ZERO;
BigDecimal water = X.multiply(BigDecimal.ONE.subtract(expect_D).add(BigDecimal.valueOf(0.01D)));
BigDecimal space1000 = (historyDiff_C.subtract(water)).divide(BigDecimal.valueOf(1000L), 2, RoundingMode.HALF_UP);
if (space1000.compareTo(totalInput_A) > 0){
return receiverList.parallelStream()
.collect(Collectors.toMap(receiverUid-> receiverUid,
receiverUid-> {
//left
int randomIndex = RandomUtil.randomByRange(0, 10);
long drawMultiple = randomIndex < 1? 1000L: randomIndex < 2? 500L: randomIndex < 5? 100L: 0L;
log.info("[lucky25] uid {} space1000 {} totalInput {} left receiverUid {} randomIndex {} drawMultiple {}",
senderUid, space1000, totalInput_A, receiverUid, randomIndex, drawMultiple);
if (drawMultiple > 0L && !space1000flag.compareAndSet(false, true)){
drawMultiple = 0L;
log.info("[lucky25] uid {} space1000 cas false {} totalInput {} left receiverUid {} randomIndex {} drawMultiple {}",
senderUid, space1000, totalInput_A, receiverUid, randomIndex, drawMultiple);
}
Lucky25Result result = new Lucky25Result(null, everyoneGoldNum, drawMultiple, false);
return updateMeta(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, receiverUid, everyoneGoldNum, room, sendGiftTime, null, result);
}));
}
BigDecimal space100 = (historyDiff_C.subtract(water)).divide(BigDecimal.valueOf(100L), 2, RoundingMode.HALF_UP);
if (space100.compareTo(totalInput_A) > 0){
return receiverList.parallelStream()
.collect(Collectors.toMap(receiverUid-> receiverUid,
receiverUid-> {
//middle
int randomIndex = RandomUtil.randomByRange(0, 100);
long drawMultiple = randomIndex < 25? 100L: randomIndex < 55? 5L: 0L;
log.info("[lucky25] uid {} space100 {} totalInput {} middle receiver {} randomIndex {} drawMultiple {}",
senderUid, space100, totalInput_A, receiverUid, randomIndex, drawMultiple);
if (drawMultiple > 0L && !space100flag.compareAndSet(false, true)){
drawMultiple = 0L;
log.info("[lucky25] uid {} space100 cas false {} totalInput {} left receiverUid {} randomIndex {} drawMultiple {}",
senderUid, space1000, totalInput_A, receiverUid, randomIndex, drawMultiple);
}
Lucky25Result result = new Lucky25Result(null, everyoneGoldNum, drawMultiple, false);
return updateMeta(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, receiverUid, everyoneGoldNum, room, sendGiftTime, null, result);
}));
}
log.info("[lucky25] uid {} space100 {} totalInput {} 原来 10L", senderUid, space1000, totalInput_A);
return receiverList.parallelStream()
.collect(Collectors.toMap(receiverUid-> receiverUid,
receiverUid-> drawMultiple(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, receiverUid, everyoneGoldNum, room, sendGiftTime, null)));
}
private void sendMq(Map<Long, Lucky25Record> recordMap) {
Map<String, String> caches = new HashMap<>(recordMap.size());
List<Lucky25Message> messageList = new ArrayList<>();
DefaultIdentifierGenerator idGenerator = DefaultIdentifierGenerator.getInstance();
for (Lucky25Record record: recordMap.values()){
String id = idGenerator.nextUUID(null);
Lucky25Message message = new Lucky25Message();
message.setMessId(id);
message.setPartitionId(record.getPartitionId());
message.setUid(record.getUid());
message.setReceiverUid(record.getReceiverUid());
message.setRoomUid(record.getRoomUid());
message.setGiftId(record.getGiftId());
message.setGiftGoldPrice(record.getGiftGoldPrice());
message.setGiftNum(record.getGiftNum());
message.setPoolId(record.getPoolId());
message.setIsSupplement(record.getIsSupplement());
message.setDrawMultiple(record.getDrawMultiple());
message.setAfterMultiple(record.getAfterMultiple());
message.setWinGoldNum(record.getWinGoldNum());
message.setCreateTime(record.getCreateTime().getTime());
messageList.add(message);
caches.put(id, JSON.toJSONString(message));
}
jedisService.hwrite(RedisKey.lucky_25_status.getKey(), caches);
rocketMQService.sendBatchLucky25Message(messageList);
}
public Lucky25Record drawMultiple(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, long senderUid, int partitionId,
Gift gift, int giftNum, long receiverUid, long everyoneGoldNum, Room room, Date sendGiftTime,
Long maxMultiple) {
Lucky25Result drawResult = poolService.drawMultipleFromPool(config, senderUid, partitionId);
return updateMeta(config, partitionConfig, senderUid, partitionId, gift, giftNum, receiverUid, everyoneGoldNum, room, sendGiftTime, maxMultiple, drawResult);
}
private Lucky25Record updateMeta(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, long senderUid, int partitionId,
Gift gift, int giftNum, long receiverUid, long everyoneGoldNum, Room room, Date sendGiftTime,
Long maxMultiple, Lucky25Result drawResult){
long drawMultiple = drawResult.getOutput();
long afterMultiple = drawMultiple;
if (null != maxMultiple) {
afterMultiple = Math.min(afterMultiple, maxMultiple);
}
// 个人库存
if (afterMultiple > 0L){
long preWinGoldNum = afterMultiple * everyoneGoldNum;
if (!userMetaService.judgePersonalStock(partitionConfig, senderUid, receiverUid, everyoneGoldNum, preWinGoldNum)){
afterMultiple = 0L;
}
}
// 平台库存
if (afterMultiple > 0L){
BigDecimal preWinGoldNum = BigDecimal.valueOf(afterMultiple * everyoneGoldNum);
if (!judgeStock(partitionId, preWinGoldNum, senderUid, receiverUid)){
afterMultiple = 0L;
}
}
long winGoldNum = afterMultiple * everyoneGoldNum;
userMetaService.updateUserMeta(senderUid, partitionId, everyoneGoldNum, winGoldNum);
if (winGoldNum > 0L){
settlementService.syncSendReward(config, senderUid, room, gift, winGoldNum, afterMultiple);
}
return recordService.buildRecord(senderUid, partitionId, gift, giftNum, null != room? room.getUid(): null,
receiverUid, drawResult.getPoolId(), Boolean.TRUE.equals(drawResult.getIsSupplement()),
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("[lucky25] drawMultiple sender {} receiver {} 产出大于库存 winGoldNum {} afterStock {}",
senderUid, receiverUid, winGoldNum, afterStock);
afterStock = stockService.addStock(partitionId, winGoldNum);
robotMsgService.pushStockNotEnough(partitionId, afterStock);
}
return enough;
}
public Lucky25GiftConfig getConfig() {
String configStr = sysConfService.getSysConfValueById(Constant.SysConfId.LUCKY_25_GIFT_CONFIG);
if (!StringUtils.hasText(configStr)) {
throw new ServiceException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
}
return JSON.parseObject(configStr, Lucky25GiftConfig.class);
}
}

View File

@@ -0,0 +1,114 @@
package com.accompany.business.service.gift;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.SuperLuckyGiftIncomeAllot;
import com.accompany.business.message.Lucky25Message;
import com.accompany.business.model.Gift;
import com.accompany.business.service.lucky.*;
import com.accompany.business.service.lucky.rank.Lucky24SendWeekRankService;
import com.accompany.business.service.room.RoomService;
import com.accompany.common.redis.RedisKey;
import com.accompany.core.model.Room;
import com.accompany.core.service.base.BaseService;
import com.accompany.sharding.model.Lucky25Record;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.Date;
@Slf4j
@Service
public class Lucky25MessageService extends BaseService {
@Autowired
private Lucky25RecordService recordService;
@Autowired
private Lucky25IncomeAllotService incomeAllotService;
@Autowired
private SuperLuckyGiftSendService superLuckyGiftSendService;
@Autowired
private Lucky25RobotMsgService robotMsgService;
@Autowired
private Lucky25GiftSendService sendService;
@Autowired
private RoomService roomService;
@Autowired
private GiftService giftService;
@Autowired
private Lucky24SendWeekRankService lucky24SendWeekRankService;
public void handleGiftMessage(Lucky25Message giftMessage) {
// 防止消息被重复消费
if (!jedisLockService.isExist(RedisKey.lock_lucky_25_message.getKey(giftMessage.getMessId()), 30)) {
logger.warn("handleLucky25Message giftMessage had handle, mess: " + giftMessage);
return;
}
if (!jedisService.hexists(RedisKey.lucky_25_status.getKey(), giftMessage.getMessId())){
logger.warn("handleLucky25Message giftMessage had handle, mess: " + giftMessage);
return;
}
logger.info("【处理Lucky25 mq】 开始处理 giftMessage: {}", JSON.toJSONString(giftMessage));
Room room = null != giftMessage.getRoomUid()? roomService.getRoomByUid(giftMessage.getRoomUid()): null;
Gift gift = giftService.getGiftById(giftMessage.getGiftId());
Date createTime = new Date(giftMessage.getCreateTime());
Lucky25Record record = insertRecord(giftMessage);
log.info("【处理Lucky25 mq】 record 插入成功 messId:{} recordId:{} record:{}",
giftMessage.getMessId(), record.getId(), JSON.toJSONString(record));
// 收礼者收益
Lucky25GiftConfig config = sendService.getConfig();
Lucky25GiftConfig partitionConfig = config.getRatioByPartitionId(giftMessage.getPartitionId());
SuperLuckyGiftIncomeAllot receiverIncomeAllot = incomeAllotService.calculate(partitionConfig, gift, giftMessage.getGiftNum(), Collections.singletonList(record.getReceiverUid()));
superLuckyGiftSendService.syncSettlement(giftMessage.getUid(), gift, giftMessage.getGiftNum(), giftMessage.getGiftNum(), room, receiverIncomeAllot, createTime);
logger.info("【处理Lucky25 mq】 收礼收益已发放 messId: {} incomeAllot: {}", giftMessage.getMessId(), JSON.toJSONString(receiverIncomeAllot));
// 后面都是异步发消息
// if (record.getAfterMultiple() >= config.getWarnMulti()){
// long totalGoldNum = giftMessage.getGiftNum() * giftMessage.getGiftGoldPrice();
// robotMsgService.pushSuperMulti(record.getUid(), record.getReceiverUid(), record.getAfterMultiple(), totalGoldNum, record.getWinGoldNum(),
// record.getRoomUid());
// }
if (CollectionUtils.isEmpty(config.getFollowUidList()) && config.getFollowUidList().contains(record.getUid())){
robotMsgService.pushFollowUser(record.getUid(), record.getReceiverUid(), record.getRoomUid());
}
lucky24SendWeekRankService.updateRank(record);
// 删除该标识,表示消息已经消费过
jedisService.hdel(RedisKey.lucky_25_status.getKey(), giftMessage.getMessId());
}
private Lucky25Record insertRecord(Lucky25Message giftMessage) {
Lucky25Record record = new Lucky25Record();
record.setMessId(giftMessage.getMessId());
record.setPartitionId(giftMessage.getPartitionId());
record.setUid(giftMessage.getUid());
record.setReceiverUid(giftMessage.getReceiverUid());
record.setRoomUid(giftMessage.getRoomUid());
record.setGiftId(giftMessage.getGiftId());
record.setGiftGoldPrice(giftMessage.getGiftGoldPrice());
record.setGiftNum(giftMessage.getGiftNum());
record.setPoolId(giftMessage.getPoolId());
record.setIsSupplement(giftMessage.getIsSupplement());
record.setDrawMultiple(giftMessage.getDrawMultiple());
record.setAfterMultiple(giftMessage.getAfterMultiple());
record.setWinGoldNum(giftMessage.getWinGoldNum());
record.setCreateTime(new Date(giftMessage.getCreateTime()));
recordService.insertRecord(record);
return record;
}
}

View File

@@ -0,0 +1,62 @@
package com.accompany.business.service.lucky;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.SuperLuckyGiftIncomeAllot;
import com.accompany.business.model.Gift;
import com.accompany.business.service.purse.UserPurseService;
import com.accompany.business.service.record.BillRecordService;
import com.accompany.core.enumeration.BillObjTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class Lucky25IncomeAllotService implements LuckyGiftIncomeAllotService {
@Autowired
private UserPurseService userPurseService;
@Autowired
private BillRecordService billRecordService;
public SuperLuckyGiftIncomeAllot calculate(Lucky25GiftConfig config, Gift gift, int giftNum, List<Long> receiverUids) {
BigDecimal giftNumB = BigDecimal.valueOf(giftNum);
BigDecimal totalNum = BigDecimal.valueOf(receiverUids.size()).multiply(giftNumB);
// 单价
BigDecimal giftValue = BigDecimal.valueOf(gift.getGoldPrice());
// 总价
BigDecimal everyTotalValue = giftValue.multiply(giftNumB);
BigDecimal totalValue = giftValue.multiply(totalNum);
// 收礼者收益单价
BigDecimal receiverIncome = everyTotalValue.multiply(config.getReceiverRatio());
BigDecimal receiverTotalIncome = totalValue.multiply(config.getReceiverRatio());
Map<Long, BigDecimal> receiverIncomeMap = new HashMap<>();
for (Long receiver: receiverUids){
receiverIncomeMap.put(receiver, receiverIncome);
}
// 平台抽成
BigDecimal platformGoldNum = totalValue.multiply(config.getPlatformRatio());
// 增加库存
BigDecimal addStockGoldNum = totalValue.subtract(platformGoldNum);
return new SuperLuckyGiftIncomeAllot(totalValue, receiverTotalIncome, receiverIncomeMap, BigDecimal.ZERO, addStockGoldNum);
}
@Override
public void addIncome(Long receiverUid, double income, BillObjTypeEnum billObjTypeEnum, Long senderUid, Long roomUid, Integer giftId, Integer everyGiftNum, Long giftTotalGoldNum, Date sendGiftTime) {
//userPurseService.addGoldWithoutTx(receiverUid, income.doubleValue(), billObjTypeEnum,
userPurseService.addGold(receiverUid, income, billObjTypeEnum,
(userPurse)-> billRecordService.insertGiftSendBillRecord(receiverUid, senderUid, roomUid, null, billObjTypeEnum, income,
giftId, everyGiftNum, giftTotalGoldNum, sendGiftTime, userPurse));
}
}

View File

@@ -0,0 +1,301 @@
package com.accompany.business.service.lucky;
import com.accompany.business.constant.Lucky25PoolTypeEnum;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.Lucky25Result;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.Lucky25Result;
import com.accompany.business.model.lucky.Lucky25Pool;
import com.accompany.business.mybatismapper.lucky.Lucky25PoolMapper;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.RandomUtil;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.exception.ServiceException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
public class Lucky25PoolService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private Lucky25UserMetaService userMetaService;
@Autowired
private Lucky25PoolMapper poolMapper;
public Lucky25Result drawMultipleFromPool(Lucky25GiftConfig config, Long uid, int partitionId){
RQueue<Lucky25Result> userPool = getUserPool(uid);
for (int i = 0; i < 3; i++) {
Lucky25Result result = userPool.poll();
if (null != result) {
userPool.expireAsync(Duration.of(14, ChronoUnit.DAYS));
return result;
}
genPool(config, uid, partitionId, userPool);
}
throw new ServiceException(BusiStatus.SERVERBUSY);
}
private void genPool(Lucky25GiftConfig config, Long uid, int partitionId, RQueue<Lucky25Result> userPool) {
boolean locked = false;
RLock lock = redissonClient.getLock(RedisKey.lucky_25_user_lock.getKey(uid.toString()));
try {
locked = lock.tryLock(5,3, TimeUnit.SECONDS);
if (!userPool.isEmpty()){
log.info("[lucky25] genPool uid {} 已生成pool无需再生成", uid);
return;
}
if (!locked) {
throw new ServiceException(BusiStatus.SERVERBUSY);
}
Lucky25PoolTypeEnum poolType = selectPoolType(config, uid);
List<Lucky25Pool> poolList = poolMapper.selectList(null);
if (CollectionUtils.isEmpty(poolList)){
throw new ServiceException(BusiStatus.SEIZE_TREASURE_POOL_CONFIG_ERROR);
}
Lucky25GiftConfig partitionConfig = config.getRatioByPartitionId(partitionId);
Lucky25Pool pool = selectPool(config, partitionConfig, uid, partitionId, poolType, poolList);
buildPool(config, uid, userPool, pool);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (locked) {
lock.unlock();
}
}
}
private void buildPool(Lucky25GiftConfig config, Long uid, RQueue<Lucky25Result> userPool, Lucky25Pool pool) {
List<Lucky25Pool.Lucky24PoolItem> poolItemList = pool.getItemList().stream().filter(item->item.getNum()>0).collect(Collectors.toList());
List<Integer> winList = buildWinList(poolItemList);
int[] poolArray = new int[config.getPoolSize()];
for (int i = 0; i < config.getPoolSize(); i++) {
poolArray[i] = 0;
}
if (!winList.isEmpty()){
List<Integer> winIndexList = new ArrayList<>();
int winDistance = config.getPoolSize() / winList.size();
for (int i = 0; i < winList.size(); i++) {
int nextWinIndex = config.getPoolSize() - 1 - i * winDistance;
// 确保索引不会超出超集范围
if (nextWinIndex > 0) {
poolArray[nextWinIndex] = winList.get(i);
winIndexList.add(nextWinIndex);
}
}
log.info("[lucky25] genPool buildPool uid {}, winIndexList {}", uid, JSON.toJSONString(winIndexList));
}
List<Lucky25Result> poolList = Arrays.stream(poolArray).boxed().map(output->{
Lucky25Result result = new Lucky25Result();
result.setPoolId(pool.getId());
result.setOutput(output);
return result;
}).collect(Collectors.toList());
userPool.addAll(poolList);
}
private List<Integer> buildWinList(List<Lucky25Pool.Lucky24PoolItem> poolItemList) {
List<Integer> resultList = new ArrayList<>();
for (Lucky25Pool.Lucky24PoolItem item : poolItemList) {
int multi = item.getMulti();
int num = item.getNum();
// 使用循环生成 num 个 multi 值,并添加到 resultList 中
for (int i = 0; i < num; i++) {
resultList.add(multi);
}
}
Collections.shuffle(resultList);
return resultList;
}
private Lucky25Pool selectPool(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, Long uid, Integer partitionId, Lucky25PoolTypeEnum poolType, List<Lucky25Pool> poolList) {
if (Lucky25PoolTypeEnum.NEW_USER_POOL.equals(poolType)){
poolList = poolList.stream()
.filter(pool->pool.getType() == Lucky25PoolTypeEnum.NEW_USER_POOL.getType())
.collect(Collectors.toList());
int randomIndex = RandomUtil.randomByRange(0, poolList.size());
Lucky25Pool randomPool = poolList.get(randomIndex);
log.info("[lucky25] genPool selectPool type {}, pooList {}, randomIndex {}, expect {}",
poolType, JSON.toJSONString(poolList.stream().map(Lucky25Pool::getExpect).collect(Collectors.toList())), randomIndex, randomPool.getExpect());
return randomPool;
}
// 黑名单
if (!CollectionUtils.isEmpty(config.getBlackUidList())
&& config.getBlackUidList().contains(uid)){
List<Lucky25Pool> balckPoolList = poolList.stream()
.filter(pool->pool.getType() == Lucky25PoolTypeEnum.BLACK_POOL.getType())
.sorted(Comparator.comparing(Lucky25Pool::getExpect))
.limit(1L)
.collect(Collectors.toList());
int randomIndex = RandomUtil.randomByRange(0, balckPoolList.size());
Lucky25Pool randomPool = balckPoolList.get(randomIndex);
log.info("[lucky25] genPool selectPool type {}, blackPooList {}, randomIndex {}, expect {}",
poolType, JSON.toJSONString(balckPoolList.stream().map(Lucky25Pool::getExpect).collect(Collectors.toList())), randomIndex, randomPool.getExpect());
return randomPool;
}
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
long today = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String todayStartTimeStr = String.valueOf(today);
String todayInputKey = String.join("_", todayStartTimeStr, Lucky25UserMetaService.INPUT_KEY);
String todayOutputKey = String.join("_", todayStartTimeStr, Lucky25UserMetaService.OUTPUT_KEY);
RMap<String, Number> userMetaMap = userMetaService.getUserMeta(uid);
BigDecimal input = BigDecimal.valueOf(userMetaMap.getOrDefault(todayInputKey, 0L).longValue());
BigDecimal output = BigDecimal.valueOf(userMetaMap.getOrDefault(todayOutputKey, 0L).longValue());
BigDecimal todayProductionRatio = BigDecimal.ZERO.equals(output)? BigDecimal.ZERO: output.divide(input,2, RoundingMode.HALF_UP);
if (input.compareTo(BigDecimal.valueOf(300000)) >= 0 && todayProductionRatio.compareTo(partitionConfig.getTodayProductionRatio()) >= 0){
List<Lucky25Pool> excludePoolList = poolList.stream()
.filter(pool->pool.getType() == Lucky25PoolTypeEnum.NORMAL_POOL.getType())
.filter(pool-> pool.getItemList().stream().noneMatch(poolItem->poolItem.getMulti() == 1000 && poolItem.getNum() > 0))
.filter(pool-> partitionConfig.getProductionRatio().compareTo(pool.getExpect()) > 0)
.collect(Collectors.toList());
int randomIndex = RandomUtil.randomByRange(0, excludePoolList.size());
Lucky25Pool randomPool = excludePoolList.get(randomIndex);
log.info("[lucky25] genPool selectPool type {}, excludePooList {}, randomIndex {}, expect {}",
poolType, JSON.toJSONString(excludePoolList.stream().map(Lucky25Pool::getExpect).collect(Collectors.toList())), randomIndex, randomPool.getExpect());
//SpringContextHolder.getBean(Lucky25RobotMsgService.class).pushTodayProduction(uid, input, output, todayProductionRatio, config.getTodayProductionRatio());
return randomPool;
}
BigDecimal thisExpect = calExpect(config, partitionConfig, uid);
List<Lucky25Pool> expectPoolList = BigDecimal.ZERO.compareTo(thisExpect) < 0 ?
poolList.stream().filter(pool->pool.getType() == Lucky25PoolTypeEnum.NORMAL_POOL.getType())
.filter(pool->pool.getExpect().compareTo(thisExpect) > 0).collect(Collectors.toList()):
poolList.stream().filter(pool->pool.getType() == Lucky25PoolTypeEnum.NORMAL_POOL.getType())
.filter(pool->thisExpect.negate().compareTo(pool.getExpect()) >= 0).collect(Collectors.toList());
if (CollectionUtils.isEmpty(expectPoolList)){
expectPoolList = poolList.stream().filter(pool->pool.getType() == Lucky25PoolTypeEnum.NEW_USER_POOL.getType())
.collect(Collectors.toList());
}
int randomIndex = RandomUtil.randomByRange(0, expectPoolList.size());
Lucky25Pool randomPool = expectPoolList.get(randomIndex);
log.info("[lucky25] genPool selectPool type {}, pooList {}, randomIndex {}, expect {}",
poolType, JSON.toJSONString(expectPoolList.stream().map(Lucky25Pool::getExpect).collect(Collectors.toList())), randomIndex, randomPool.getExpect());
return randomPool;
}
private BigDecimal calExpect(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, Long uid) {
BigDecimal n = !CollectionUtils.isEmpty(config.getWhiteUidProductionRatioMap()) && config.getWhiteUidProductionRatioMap().containsKey(uid) ?
config.getWhiteUidProductionRatioMap().get(uid): partitionConfig.getProductionRatio();
BigDecimal userLast2000ProductionRatio = userMetaService.getUserLast2000ProductionRatio(uid);
if (userLast2000ProductionRatio.compareTo(n) <= 0){
BigDecimal thisExpect = n.add(n).subtract(userLast2000ProductionRatio);
log.info("[lucky25] genPool calExpect uid {}, userLast2000ProductionRatio {} 小于等于配置基准 {},修正后期望值 {}",
uid, userLast2000ProductionRatio, n, thisExpect);
return thisExpect;
}
BigDecimal userProductionRatio = userMetaService.getUserProductionRatio(uid);
if (userProductionRatio.compareTo(n) <= 0){
BigDecimal thisExpect = n.add(n).subtract(userProductionRatio);
log.info("[lucky25] genPool calExpect uid {}, userTotalProductionRatio {} 小于等于配置基准 {},修正后期望值 {}",
uid, userProductionRatio, n, thisExpect);
return thisExpect;
}
BigDecimal thisExpect = n;
log.info("[lucky25] genPool calExpect uid {}, userLast2000ProductionRation {}, userTotalProductionRatio {} 都大于配置基准 {},修正后期望值 {}",
uid, userLast2000ProductionRatio, userProductionRatio, n, thisExpect);
return thisExpect.negate();
}
//todo deviceId
private Lucky25PoolTypeEnum selectPoolType(Lucky25GiftConfig config, long senderUid) {
if (!CollectionUtils.isEmpty(config.getBlackUidList()) && config.getBlackUidList().contains(senderUid)){
log.info("[lucky25] genPool selectPoolType uid {}, 黑名单", senderUid);
return Lucky25PoolTypeEnum.BLACK_POOL;
}
if (config.getNewUserPoolCount() <= 0){
log.info("[lucky25] genPool selectPoolType uid {}, 配置里的newUserPoolCount小于等于0", senderUid);
return Lucky25PoolTypeEnum.NORMAL_POOL;
}
long userTimes = userMetaService.getTimes(senderUid);
Lucky25PoolTypeEnum typeEnum = userTimes < (long) config.getPoolSize() * config.getNewUserPoolCount()?
Lucky25PoolTypeEnum.NEW_USER_POOL:
Lucky25PoolTypeEnum.NORMAL_POOL;
log.info("[lucky25] genPool selectPoolType uid {}, userTimes {}, type {}",
senderUid, userTimes, typeEnum);
return typeEnum;
}
private RDeque<Lucky25Result> getUserPool(Long uid) {
return redissonClient.getDeque(RedisKey.lucky_25_user_pool.getKey(uid.toString()));
}
public void updateUserMulti(Long uid) {
RDeque<Lucky25Result> userPool = getUserPool(uid);
if (userPool.isEmpty()){
log.info("[lucky25] updateUserMulti uid {} 未生成pool", uid);
throw new ServiceException(BusiStatus.SERVERBUSY, "用户未生成奖池");
}
boolean locked = false;
RLock lock = redissonClient.getLock(RedisKey.lucky_25_user_lock.getKey(uid.toString()));
try {
locked = lock.tryLock(5,3, TimeUnit.SECONDS);
if (!locked) {
throw new ServiceException(BusiStatus.SERVERBUSY);
}
if (userPool.isEmpty()){
log.info("[lucky25] updateUserMulti uid {} 未生成pool", uid);
throw new ServiceException(BusiStatus.SERVERBUSY, "用户未生成奖池");
}
List<Lucky25Result> list = userPool.poll(11);
for (int i = list.size() - 1; i >= 0; i--) {
Lucky25Result result = list.get(i);
if (i >= list.size() - 1) {
result.setIsSupplement(Boolean.TRUE);
result.setOutput(1000);
}
userPool.addFirst(result);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (locked) {
lock.unlock();
}
}
}
}

View File

@@ -0,0 +1,98 @@
package com.accompany.business.service.lucky;
import com.accompany.business.model.Gift;
import com.accompany.business.mybatismapper.lucky.Lucky25StatMapper;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.core.exception.ServiceException;
import com.accompany.sharding.mapper.Lucky25RecordMapper;
import com.accompany.sharding.model.Lucky25Record;
import com.accompany.sharding.vo.Lucky24PersonalStat;
import com.accompany.sharding.vo.Lucky24PlatformStat;
import com.accompany.sharding.vo.Lucky25PersonalStat;
import com.accompany.sharding.vo.Lucky25PlatformStat;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Service
public class Lucky25RecordService extends ServiceImpl<Lucky25RecordMapper, Lucky25Record> {
@Autowired
private Lucky25StatMapper statMapper;
@Resource(name = "bizExecutor")
private ThreadPoolExecutor bizExecutor;
public Lucky25Record buildRecord(long senderUid, int partitionId, Gift gift, int giftNum, Long roomUid, long receiverUid, Integer poolId,
boolean isSupplement, long drawMultiple, long afterMultiple, Date sendGiftTime) {
Lucky25Record record = new Lucky25Record();
record.setUid(senderUid);
record.setPartitionId(partitionId);
record.setReceiverUid(receiverUid);
record.setRoomUid(roomUid);
record.setGiftId(gift.getGiftId());
record.setGiftNum(giftNum);
record.setGiftGoldPrice(gift.getGoldPrice());
record.setPoolId(poolId);
record.setIsSupplement(isSupplement);
record.setDrawMultiple((int) drawMultiple);
record.setAfterMultiple((int) afterMultiple);
record.setWinGoldNum(afterMultiple * gift.getGoldPrice() * giftNum);
record.setCreateTime(sendGiftTime);
return record;
}
public void insertRecord(Lucky25Record record) {
for (int i = 0; i < 3; i++) {
try {
record.setId(null);
this.baseMapper.insert(record);
return;
} catch (DuplicateKeyException ignore) {
log.error("[insertLucky25Record] 插入送礼记录失败", ignore);
}
}
log.error(String.format("[insertLucky25Record] 插入送礼记录3次都失败 %s", JSON.toJSONString(record)));
throw new ServiceException(BusiStatus.SERVERBUSY);
}
public void statDate(Integer partitionId, Date startTime, Date endTime, long zoneIdHour) {
List<Lucky25PlatformStat> platformList = this.baseMapper.listPlatform(partitionId, startTime, endTime, zoneIdHour);
log.info("[Lucky25RecordStat] platform partitionId {} startTime {} endTime {} zoneIdHour {} platformList: {}",
partitionId, DateTimeUtil.convertDate(startTime, DateTimeUtil.DEFAULT_DATETIME_PATTERN),
DateTimeUtil.convertDate(endTime, DateTimeUtil.DEFAULT_DATETIME_PATTERN), zoneIdHour, JSON.toJSONString(platformList));
if (!CollectionUtils.isEmpty(platformList)) {
bizExecutor.execute(() -> {
for (Lucky25PlatformStat platformStat : platformList) {
statMapper.savePlatform(platformStat);
}
});
}
List<Lucky25PersonalStat> personalList = this.baseMapper.listPersonal(partitionId, null, startTime, endTime, zoneIdHour);
log.info("[Lucky25RecordStat] personal partitionId {} startTime {} endTime {} zoneIdHour {} personalList: {}",
partitionId, DateTimeUtil.convertDate(startTime, DateTimeUtil.DEFAULT_DATETIME_PATTERN),
DateTimeUtil.convertDate(endTime, DateTimeUtil.DEFAULT_DATETIME_PATTERN), zoneIdHour, JSON.toJSONString(personalList));
if (!CollectionUtils.isEmpty(personalList)) {
List<List<Lucky25PersonalStat>> list = Lists.partition(personalList, 20);
for (List<Lucky25PersonalStat> personalStatList : list) {
bizExecutor.execute(() -> {
for (Lucky25PersonalStat personalStat : personalStatList) {
statMapper.savePersonal(personalStat);
}
});
}
}
}
}

View File

@@ -0,0 +1,133 @@
package com.accompany.business.service.lucky;
import com.accompany.business.service.user.UsersService;
import com.accompany.common.config.WebSecurityConfig;
import com.accompany.common.constant.AppEnum;
import com.accompany.common.push.MarkdownMessage;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.model.PartitionInfo;
import com.accompany.core.model.Users;
import com.accompany.core.service.message.MessageRobotPushService;
import com.accompany.core.service.partition.PartitionInfoService;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service
public class Lucky25RobotMsgService {
@Autowired
private WebSecurityConfig webSecurityConfig;
@Autowired
private MessageRobotPushService messageRobotPushService;
@Autowired
private UsersService usersService;
@Autowired
private RedissonClient redissonClient;
@Autowired
private PartitionInfoService partitionInfoService;
@Async
public void pushStockNotEnough(int partitionId, BigDecimal afterStock) {
RBucket<MarkdownMessage> stockCache = redissonClient.getBucket(RedisKey.lucky_25_robot_push_msg.getKey("stock"));
if (stockCache.isExists()){
return;
}
PartitionInfo partitionInfo = partitionInfoService.getById(partitionId);
MarkdownMessage markdownMessage = new MarkdownMessage();
markdownMessage.addTitle(AppEnum.getCurApp().getValue() + "幸运24库存告急");
markdownMessage.add("当前库存:" + afterStock.toPlainString());
if (null != partitionInfo){
markdownMessage.add("分区:" + partitionInfo.getDesc());
}
messageRobotPushService.pushMessageByKey(webSecurityConfig.getLucky24StockWarningPushKey(), markdownMessage, false);
stockCache.set(markdownMessage, 10, TimeUnit.MINUTES);
}
@Async
public void pushSuperMulti(long senderUid, long receiverUid, long multi, long input, long output, Long roomUid) {
RBucket<MarkdownMessage> multiCache = redissonClient.getBucket(RedisKey.lucky_25_robot_push_msg.getKey("multi", String.valueOf(senderUid)));
if (multiCache.isExists()){
return;
}
Users sender = usersService.getNotNullUsersByUid(senderUid);
PartitionInfo partitionInfo = partitionInfoService.getById(sender.getPartitionId());
Users receiver = usersService.getNotNullUsersByUid(receiverUid);
Users room = null != roomUid? usersService.getNotNullUsersByUid(roomUid) : null;
MarkdownMessage markdownMessage = new MarkdownMessage();
markdownMessage.addTitle(AppEnum.getCurApp().getValue() + "幸运1000通知");
markdownMessage.add("送礼用户:" + String.format("%s(%d)%s", sender.getNick(), sender.getErbanNo(), partitionInfo.getDesc()));
markdownMessage.add("收礼用户:" + String.format("%s(%d)", receiver.getNick(), receiver.getErbanNo()));
markdownMessage.add("出发倍数:" + multi);
markdownMessage.add("进入:" + input);
markdownMessage.add("返回:" + output);
if (null != room) {
markdownMessage.add("房间id" + room.getErbanNo());
}
if (null != partitionInfo) {
markdownMessage.add("分区:" + partitionInfo.getDesc());
}
messageRobotPushService.pushMessageByKey(webSecurityConfig.getLucky24StockWarningPushKey(), markdownMessage, false);
multiCache.set(markdownMessage, 30, TimeUnit.MINUTES);
}
@Async
public void pushFollowUser(long senderUid, long receiverUid, Long roomUid) {
RBucket<MarkdownMessage> followCache = redissonClient.getBucket(RedisKey.lucky_25_robot_push_msg.getKey("follow"));
if (followCache.isExists()){
return;
}
Users sender = usersService.getNotNullUsersByUid(senderUid);
PartitionInfo partitionInfo = partitionInfoService.getById(sender.getPartitionId());
Users receiver = usersService.getNotNullUsersByUid(receiverUid);
Users room = null != roomUid? usersService.getNotNullUsersByUid(roomUid) : null;
MarkdownMessage markdownMessage = new MarkdownMessage();
markdownMessage.addTitle(AppEnum.getCurApp().getValue() + "幸运25用户上线通知");
markdownMessage.add("送礼用户:" + String.format("%s(%d)%s", sender.getNick(), sender.getErbanNo(), partitionInfo.getDesc()));
markdownMessage.add("收礼用户:" + String.format("%s(%d)", receiver.getNick(), receiver.getErbanNo()));
markdownMessage.add("上线时间:" + DateTimeUtil.convertDate(new Date(), DateTimeUtil.DEFAULT_DATETIME_PATTERN));
if (null != room) {
markdownMessage.add("房间id" + room.getErbanNo());
}
markdownMessage.add("分区:" + partitionInfo.getDesc());
messageRobotPushService.pushMessageByKey(webSecurityConfig.getLucky24StockWarningPushKey(), markdownMessage, false);
followCache.set(markdownMessage, 30, TimeUnit.MINUTES);
}
@Async
public void pushTodayProduction(long senderUid,
BigDecimal input, BigDecimal output, BigDecimal productionRatio, BigDecimal todayProductionRatioConfig) {
Users sender = usersService.getNotNullUsersByUid(senderUid);
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(sender.getPartitionId());
MarkdownMessage markdownMessage = new MarkdownMessage();
markdownMessage.addTitle(AppEnum.getCurApp().getValue() + "幸运25拦截1000数组");
markdownMessage.add("送礼用户:" + String.format("%s(%d)%s", sender.getNick(), sender.getErbanNo(), partitionEnum.getDesc()));
markdownMessage.add("今天总进入:" + input.toPlainString());
markdownMessage.add("今天总退出:" + output.toPlainString());
markdownMessage.add("今天退出率:" + productionRatio.toPlainString());
markdownMessage.add("配置退出率:" + todayProductionRatioConfig.toPlainString());
messageRobotPushService.pushMessageByKey(webSecurityConfig.getLucky24StockWarningPushKey(), markdownMessage, false);
}
}

View File

@@ -0,0 +1,59 @@
package com.accompany.business.service.lucky;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.SuperLuckyGiftIncomeAllot;
import com.accompany.business.model.Gift;
import com.accompany.business.service.gift.SuperLuckyGiftSendService;
import com.accompany.business.service.purse.UserPurseService;
import com.accompany.core.enumeration.BillObjTypeEnum;
import com.accompany.core.model.Room;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
@Slf4j
@Service
public class Lucky25SettlementService {
@Autowired
private UserPurseService userPurseService;
@Autowired
private SuperLuckyGiftSendService superLuckyGiftSendService;
public void syncSendReward(Lucky25GiftConfig config, long senderUid, Room room, Gift gift, long winGoldNum, long afterMultiple){
sendReward(config, senderUid, room, gift, winGoldNum, afterMultiple);
}
@Async
public void sendReward(Lucky25GiftConfig config, long senderUid, Room room, Gift gift, long winGoldNum, long afterMultiple){
// 道具奖励
double winGoldNumD = (double) winGoldNum;
userPurseService.addDiamond(senderUid, winGoldNumD, BillObjTypeEnum.SUPER_LUCKY_GIFT_DIAMOND);
//飘屏
if (null != room){
superLuckyGiftSendService.sendTip(senderUid, room, BigDecimal.valueOf(winGoldNum), BigDecimal.valueOf(afterMultiple),
afterMultiple >= config.getSpecialTipMulti() ? 2 : 1);
if (winGoldNum >= config.getAllRoomChatToastValue()){
superLuckyGiftSendService.sendAllRoomScreen(senderUid, room, gift, BigDecimal.valueOf(winGoldNum));
}
//飘屏
if (winGoldNum >= config.getSpecialFloatValue()
|| afterMultiple >= config.getSpecialFloatMulti()) {
superLuckyGiftSendService.sendFloating(room, senderUid, gift, BigDecimal.valueOf(afterMultiple), BigDecimal.valueOf(winGoldNum));
}
}
}
public void settlement(Lucky25GiftConfig config, long senderUid, Room room, Gift gift, int everyGiftNum, int totalGiftNum,
Map<Long, Long> winGoldNumMap, SuperLuckyGiftIncomeAllot incomeAllot, Date sendGiftTime) {
superLuckyGiftSendService.settlement(senderUid, gift, everyGiftNum, totalGiftNum, room, incomeAllot, sendGiftTime);
}
}

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 Lucky25StockService implements StockService {
@Autowired
protected RedissonClient redissonClient;
@Override
public RedissonClient getRedissonClient() {
return redissonClient;
}
@Override
public RedisKey getRedisKey() {
return RedisKey.lucky_25_stock;
}
}

View File

@@ -0,0 +1,160 @@
package com.accompany.business.service.lucky;
import com.accompany.business.dto.lucky.Lucky25GiftConfig;
import com.accompany.business.dto.lucky.Lucky25Result;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.core.enumeration.PartitionEnum;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RList;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Slf4j
@Service
public class Lucky25UserMetaService {
private static final String TIMES_KEY = "times";
public static final String INPUT_KEY = "input";
public static final String OUTPUT_KEY = "output";
private static final String TODAY = "today";
private static final int HISTORY_QUEUE_SIZE = 40000;
@Autowired
private RedissonClient redissonClient;
public Long getTimes(Long uid) {
return getUserMeta(uid).getOrDefault(TIMES_KEY, 0L).longValue();
}
public BigDecimal getUserLast2000ProductionRatio(Long uid) {
RList<Lucky25Result> historyQueue = getUserHistoryQueue(uid);
int size = historyQueue.size();
//保留20000条记录
int num = Math.min(size, 2000);
List<Lucky25Result> historyResult = historyQueue.range(num);
long historyInput = historyResult.stream().limit(num).mapToLong(Lucky25Result::getInput).sum();
long historyOutput = historyResult.stream().limit(num).mapToLong(Lucky25Result::getOutput).sum();
BigDecimal productionRatio = num > 0 ? BigDecimal.valueOf(historyOutput).divide(BigDecimal.valueOf(historyInput),2, RoundingMode.HALF_UP): BigDecimal.ZERO;
log.info("[lucky25] buildPool last2000 uid {}, historyInput {}, historyOutput {}, num {}, productionRatio {}",
uid, historyInput, historyOutput, num, productionRatio);
if (size > HISTORY_QUEUE_SIZE){
historyQueue.trim(0, HISTORY_QUEUE_SIZE);
}
return productionRatio;
}
public List<Lucky25Result> getUserLast10wHistory(Long uid) {
RList<Lucky25Result> historyQueue = getUserHistoryQueue(uid);
int size = historyQueue.size();
//保留20000条记录
int num = Math.min(size, HISTORY_QUEUE_SIZE);
List<Lucky25Result> historyResult = historyQueue.range(num);
if (size > HISTORY_QUEUE_SIZE){
historyQueue.trim(0, HISTORY_QUEUE_SIZE);
}
return historyResult;
}
public BigDecimal getUserProductionRatio(Long uid) {
RMap<String, Number> userMetaMap = getUserMeta(uid);
BigDecimal output = BigDecimal.valueOf(userMetaMap.getOrDefault(OUTPUT_KEY, 0L).longValue());
BigDecimal input = BigDecimal.valueOf(userMetaMap.getOrDefault(INPUT_KEY, 0L).longValue());
BigDecimal productionRatio = BigDecimal.ZERO.equals(output)? BigDecimal.ZERO: output.divide(input,2,RoundingMode.HALF_UP);
log.info("[lucky25] buildPool total uid {}, totalOutput {}, totalInput {}, productionRatio {}",
uid, output, input, productionRatio);
return productionRatio;
}
public boolean judgePersonalStock(Lucky25GiftConfig config, long senderUid, long receiverUid, long thisInput, long preWinGoldNum) {
if (preWinGoldNum < config.getPreJudgeValue_H()){
return true;
}
BigDecimal preWinGoldNumB = BigDecimal.valueOf(preWinGoldNum);
RMap<String, Number> userMetaMap = getUserMeta(senderUid);
BigDecimal input = BigDecimal.valueOf(userMetaMap.addAndGet(INPUT_KEY, thisInput).longValue());
BigDecimal output = BigDecimal.valueOf(userMetaMap.addAndGet(OUTPUT_KEY, preWinGoldNum).longValue());
BigDecimal inputOffset = input.compareTo(config.getTotalInput_J()) >= 0 ? config.getTotalInputOffset_K1() : config.getTotalInputOffset_K2();
BigDecimal beforeInput = input.subtract(BigDecimal.valueOf(thisInput));
BigDecimal beforeOutput = output.subtract(preWinGoldNumB);
BigDecimal personalStock = beforeInput.multiply(inputOffset).subtract(beforeOutput);
if (personalStock.compareTo(preWinGoldNumB) < 0){
userMetaMap.addAndGet(INPUT_KEY, -thisInput);
userMetaMap.addAndGet(OUTPUT_KEY, -preWinGoldNum);
log.info("[lucky25] personalStock false uid {} input {} J {} inputOffset {} output {} personalStock {} preWinGoldNum {} receiver {}",
senderUid, beforeInput, config.getTotalInput_J(), inputOffset, beforeOutput, personalStock, preWinGoldNum, receiverUid);
return false;
}
userMetaMap.addAndGet(INPUT_KEY, -thisInput);
userMetaMap.addAndGet(OUTPUT_KEY, -preWinGoldNum);
log.info("[lucky25] personalStock true uid {} input {} J {} inputOffset {} output {} personalStock {} preWinGoldNum {} receiver {}",
senderUid, beforeInput, config.getTotalInput_J(), inputOffset, beforeOutput, personalStock, preWinGoldNum, receiverUid);
return true;
}
public void updateUserMeta(long senderUid, int partitionId, long input, long output) {
RList<Lucky25Result> historyQueue = getUserHistoryQueue(senderUid);
historyQueue.add(0, new Lucky25Result(null, input, output, null));
historyQueue.expire(Duration.of(14, ChronoUnit.DAYS));
if (output > 0L){
getUser10wStat(senderUid).delete();
}
RMap<String, Number> userMetaMap = getUserMeta(senderUid);
long times = userMetaMap.addAndGet(TIMES_KEY, 1L).longValue();
long totalInput = userMetaMap.addAndGet(INPUT_KEY, input).longValue();
long totalOutput = userMetaMap.addAndGet(OUTPUT_KEY, output).longValue();
log.info("[lucky25] updateUserMeta uid {} times {} totalInput {} totalOutput {}",
senderUid, times, totalInput, totalOutput);
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
long userMetaToday = userMetaMap.computeIfAbsent(TODAY, k->todayStartTimeLong).longValue();
if (userMetaToday < todayStartTimeLong
&& userMetaMap.replace(TODAY, userMetaToday, todayStartTimeLong)){
// 清理昨天
String oldToday = String.valueOf(userMetaToday);
String oldTodayInputKey = String.join("_", oldToday, INPUT_KEY);
String oldTodayOutputKey = String.join("_", oldToday, OUTPUT_KEY);
userMetaMap.fastRemoveAsync(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();
log.info("[lucky25] updateUserMeta uid {} times {} today {} todayInput {} todayOutput {}",
senderUid, times, todayStartTimeLong, todayInput, todayOutput);
}
public RMap<String, Number> getUserMeta(Long uid) {
return redissonClient.getMap(RedisKey.lucky_25_user_meta.getKey(uid.toString()));
}
public RList<Lucky25Result> getUserHistoryQueue(Long uid) {
return redissonClient.getList(RedisKey.lucky_25_user_history.getKey(uid.toString()));
}
public RMap<String, Number> getUser10wStat(Long uid) {
return redissonClient.getMapCache(RedisKey.lucky_25_user_10w_stat.getKey(uid.toString()));
}
}

View File

@@ -80,9 +80,26 @@ public class RocketMQService {
.collect(Collectors.toList());
SendResult sendResult = rocketMQTemplate.syncSend(MqConstant.BRAVO_TOPIC, messageList);
if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())){
log.info("sendLucky24Message success result: {} message: {}", JSON.toJSONString(sendResult), messageList);
log.info("sendBravoMessage success result: {} message: {}", JSON.toJSONString(sendResult), messageList);
} else {
log.error("sendLucky24Message fail result: {} message: {}", JSON.toJSONString(sendResult), messageList);
log.error("sendBravoMessage fail result: {} message: {}", JSON.toJSONString(sendResult), messageList);
}
}
/**
* 送礼物消息发送到MQ
*
* @param lucky24Messages
*/
public void sendBatchLucky25Message(Collection<Lucky25Message> lucky24Messages) {
List<Message<String>> messageList = lucky24Messages.stream()
.map(giftMessage -> MessageBuilder.withPayload(JSON.toJSONString(giftMessage)).build())
.collect(Collectors.toList());
SendResult sendResult = rocketMQTemplate.syncSend(MqConstant.LUCKY_25_TOPIC, messageList);
if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())){
log.info("sendLucky25Message success result: {} message: {}", JSON.toJSONString(sendResult), messageList);
} else {
log.error("sendLucky25Message fail result: {} message: {}", JSON.toJSONString(sendResult), messageList);
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.accompany.business.mybatismapper.lucky.Lucky25PoolMapper">
</mapper>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.accompany.business.mybatismapper.lucky.Lucky25StatMapper">
<insert id="savePlatform">
insert into lucky_25_record_platform_stat
VALUES (#{item.date}, #{item.partitionId}, #{item.totalInput}, #{item.totalOutput}, #{item.productionRatio},
#{item.num}, #{item.count}, #{item.winNum}, #{item.winCount}, #{item.winRate})
ON DUPLICATE KEY UPDATE total_input = #{item.totalInput}, total_output = #{item.totalOutput}, production_ratio = #{item.productionRatio},
num = #{item.num}, `count` = #{item.count}, win_num = #{item.winNum}, win_count = #{item.winCount}, win_rate = #{item.winRate};
</insert>
<insert id="savePersonal">
insert into lucky_25_record_personal_stat
VALUES (#{item.date}, #{item.partitionId}, #{item.uid}, #{item.totalInput}, #{item.totalOutput},
#{item.production}, #{item.productionRatio}, #{item.avgInput},
#{item.num}, #{item.winNum}, #{item.winRate})
ON DUPLICATE KEY UPDATE total_input = #{item.totalInput}, total_output = #{item.totalOutput},
production = #{item.production}, production_ratio = #{item.productionRatio}, avg_input = #{item.avgInput},
num = #{item.num}, win_num = #{item.winNum}, win_rate = #{item.winRate};
</insert>
<select id="listPlatformStat" resultType="com.accompany.sharding.vo.Lucky25PlatformStat">
select * from lucky_25_record_platform_stat s
where s.date between #{startDate} and #{endDate}
and s.partition_id = #{partitionId}
</select>
<select id="listPersonalStat" resultType="com.accompany.sharding.vo.Lucky25PersonalStat">
select * from lucky_25_record_personal_stat s
where s.date between #{startDate} and #{endDate}
and s.partition_id = #{partitionId}
<if test="null != uid">
and s.uid = #{uid}
</if>
</select>
</mapper>

View File

@@ -44,6 +44,7 @@ public interface MqConstant {
String GAME_MSG_PUSH_TOPIC = "game_msg_push_topic";
String GAME_MSG_PUSH_CONSUME_GROUP = "game_msg_push_consume_group";
String LUCKY_25_TOPIC = "lucky_25_topic";
String LUCKY_25_CONSUME_GROUP = "lucky_25_consume_group";
}

View File

@@ -1,9 +1,7 @@
package com.accompany.mq.consumer;
import com.accompany.business.message.BravoMessage;
import com.accompany.business.message.Lucky24Message;
import com.accompany.business.service.gift.BravoMessageService;
import com.accompany.business.service.gift.Lucky24MessageService;
import com.accompany.mq.constant.MqConstant;
import com.accompany.mq.listener.AbstractMessageListener;
import lombok.extern.slf4j.Slf4j;

View File

@@ -5,7 +5,6 @@ import com.accompany.business.service.gift.Lucky24MessageService;
import com.accompany.mq.constant.MqConstant;
import com.accompany.mq.listener.AbstractMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

View File

@@ -0,0 +1,28 @@
package com.accompany.mq.consumer;
import com.accompany.business.message.Lucky25Message;
import com.accompany.business.service.gift.Lucky25MessageService;
import com.accompany.mq.constant.MqConstant;
import com.accompany.mq.listener.AbstractMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@ConditionalOnProperty(name = "spring.application.name", havingValue = "web")
@RocketMQMessageListener(topic = MqConstant.LUCKY_25_TOPIC, consumerGroup = MqConstant.LUCKY_25_CONSUME_GROUP)
public class Lucky25MessageConsumer extends AbstractMessageListener<Lucky25Message> {
@Autowired
private Lucky25MessageService messageService;
@Override
public void onMessage(Lucky25Message giftMessage) {
log.info("onMessage lucky25Message: {}", giftMessage.toString());
messageService.handleGiftMessage(giftMessage);
}
}

View File

@@ -0,0 +1,111 @@
package com.accompany.scheduler.task.luckyBag;
import com.accompany.business.message.Lucky25Message;
import com.accompany.business.service.gift.Lucky25MessageService;
import com.accompany.business.service.lucky.Lucky25RecordService;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.core.model.PartitionInfo;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.service.partition.PartitionInfoService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
@Component
@Slf4j
public class Lucky25Task {
@Autowired
private PartitionInfoService partitionInfoService;
@Autowired
private Lucky25RecordService service;
@Resource(name = "bizExecutor")
private ThreadPoolExecutor bizExecutor;
@Autowired
private JedisService jedisService;
@Autowired
private Lucky25MessageService messageService;
/**
* 重新消费队列的消息
*/
@Scheduled(cron = "0 */5 * * * ?")
public void retryLucky25Queue() {
log.info("retryLucky25Queue start ...");
Map<String, String> map = jedisService.hgetAll(RedisKey.lucky_25_status.getKey());
if (map == null || map.isEmpty()) {
return;
}
long curTime = System.currentTimeMillis();
long gapTime = 1000 * 60 * 10; // 十分钟内没被消费
map.entrySet().parallelStream().forEach(entry -> {
try {
String messId = entry.getKey();
String val = entry.getValue();
Lucky25Message giftMessage = JSON.parseObject(val, Lucky25Message.class);
if (curTime - giftMessage.getCreateTime() > gapTime) {
messageService.handleGiftMessage(giftMessage);
}
} catch (Exception e) {
log.error("retryLucky25Queue error", e);
}
});
log.info("retryLucky25Queue end ...");
}
@Scheduled(cron = "0 2 * * * ? ")
public void lucky25RecordStat() {
Date now = new Date();
List<PartitionInfo> partitionInfoList = partitionInfoService.listAll();
for (PartitionInfo partitionInfo : partitionInfoList) {
ZonedDateTime zdt = DateTimeUtil.convertWithZoneId(now, partitionInfo.getZoneId());
ZonedDateTime hourAgo = zdt.minusHours(1L);
log.info("[lucky24RecordStat] zdt {} hourAgo {}, zdtDay {} hourAgoDay {}",
zdt, hourAgo, zdt.getDayOfYear(), hourAgo.getDayOfWeek());
if (zdt.getDayOfYear() == hourAgo.getDayOfYear()){
continue;
}
bizExecutor.execute(() -> {
// 获取当天的第一秒
ZonedDateTime startOfDay = hourAgo.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
Date systemStartTime = DateTimeUtil.converLocalDateTimeToDate(startOfDay.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime());
Date startTime = DateTimeUtil.converLocalDateTimeToDate(startOfDay.toLocalDateTime());
long zoneIdHour = Duration.between(systemStartTime.toInstant(), startTime.toInstant()).toHours();
// 获取当天的最后一秒
ZonedDateTime endOfDay = hourAgo.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(999999999);
Date systemEndTime = DateTimeUtil.converLocalDateTimeToDate(endOfDay.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime());
service.statDate(partitionInfo.getId(), systemStartTime, systemEndTime, zoneIdHour);
});
}
}
public static void main(String[] args) {
Date abc = DateTimeUtil.convertStrToDate("2024-12-18 05:00:00", DateTimeUtil.DEFAULT_DATETIME_PATTERN);
ZonedDateTime now = abc.toInstant().atZone(ZoneId.of("Asia/Riyadh"));
long zoneIdHour = Duration.between(abc.toInstant(), DateTimeUtil.converLocalDateTimeToDate(now.toLocalDateTime()).toInstant()).toHours();
System.out.println(now + "_" + DateTimeUtil.convertDate(abc, DateTimeUtil.DEFAULT_DATETIME_PATTERN) + "_" + zoneIdHour);
}
}