From 8a1721706ab6bcc41f9a4199cd0f595b95d4bfb2 Mon Sep 17 00:00:00 2001 From: khalil Date: Sun, 4 May 2025 13:53:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B8=E8=BF=9025-giftSendService=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accompany/common/constant/Constant.java | 5 + .../common/constant/GiftTypeEnum.java | 6 +- .../com/accompany/common/redis/RedisKey.java | 11 + .../core/enumeration/BillObjTypeEnum.java | 3 +- .../sharding/model/Lucky25Record.java | 8 + .../sharding/vo/Lucky25PersonalStat.java | 11 + .../sharding/vo/Lucky25PlatformStat.java | 11 + .../sharding/mapper/Lucky25RecordMapper.java | 22 ++ .../sqlmappers/Lucky25RecordMapper.xml | 46 +++ .../constant/Lucky25PoolTypeEnum.java | 43 +++ .../business/dto/lucky/Lucky25GiftConfig.java | 51 +++ .../business/dto/lucky/Lucky25Result.java | 12 + .../business/message/Lucky25Message.java | 24 ++ .../business/model/lucky/Lucky25Pool.java | 10 + .../lucky/Lucky25PoolMapper.java | 7 + .../lucky/Lucky25StatMapper.java | 21 ++ .../service/gift/GiftMessageService.java | 2 + .../service/gift/GiftSendService.java | 42 +-- .../service/gift/Lucky25GiftSendService.java | 260 +++++++++++++++ .../service/gift/Lucky25MessageService.java | 114 +++++++ .../lucky/Lucky25IncomeAllotService.java | 62 ++++ .../service/lucky/Lucky25PoolService.java | 301 ++++++++++++++++++ .../service/lucky/Lucky25RecordService.java | 98 ++++++ .../service/lucky/Lucky25RobotMsgService.java | 133 ++++++++ .../lucky/Lucky25SettlementService.java | 59 ++++ .../service/lucky/Lucky25StockService.java | 25 ++ .../service/lucky/Lucky25UserMetaService.java | 160 ++++++++++ .../business/service/mq/RocketMQService.java | 21 +- .../sqlmappers/Lucky25PoolMapper.xml | 5 + .../sqlmappers/Lucky25StatMapper.xml | 37 +++ .../com/accompany/mq/constant/MqConstant.java | 3 +- .../mq/consumer/BravoMessageConsumer.java | 2 - .../mq/consumer/Lucky24MessageConsumer.java | 1 - .../mq/consumer/Lucky25MessageConsumer.java | 28 ++ .../scheduler/task/luckyBag/Lucky25Task.java | 111 +++++++ 35 files changed, 1719 insertions(+), 36 deletions(-) create mode 100644 accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/model/Lucky25Record.java create mode 100644 accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PersonalStat.java create mode 100644 accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PlatformStat.java create mode 100644 accompany-base/accompany-sharding/accompany-sharding-service/src/main/java/com/accompany/sharding/mapper/Lucky25RecordMapper.java create mode 100644 accompany-base/accompany-sharding/accompany-sharding-service/src/main/resources/sharding/sqlmappers/Lucky25RecordMapper.xml create mode 100644 accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/constant/Lucky25PoolTypeEnum.java create mode 100644 accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25GiftConfig.java create mode 100644 accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25Result.java create mode 100644 accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/message/Lucky25Message.java create mode 100644 accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/lucky/Lucky25Pool.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25PoolMapper.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25StatMapper.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25GiftSendService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25MessageService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25IncomeAllotService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25PoolService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RecordService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RobotMsgService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25SettlementService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25StockService.java create mode 100644 accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25UserMetaService.java create mode 100644 accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25PoolMapper.xml create mode 100644 accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25StatMapper.xml create mode 100644 accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky25MessageConsumer.java create mode 100644 accompany-scheduler/accompany-scheduler-service/src/main/java/com/accompany/scheduler/task/luckyBag/Lucky25Task.java diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java index 82d50efe5..053a22ab0 100644 --- a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java +++ b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java @@ -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 { diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/GiftTypeEnum.java b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/GiftTypeEnum.java index 0633571ec..cd15a8020 100644 --- a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/GiftTypeEnum.java +++ b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/GiftTypeEnum.java @@ -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; diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/common/redis/RedisKey.java b/accompany-base/accompany-core/src/main/java/com/accompany/common/redis/RedisKey.java index 21cd25048..bacb9d980 100644 --- a/accompany-base/accompany-core/src/main/java/com/accompany/common/redis/RedisKey.java +++ b/accompany-base/accompany-core/src/main/java/com/accompany/common/redis/RedisKey.java @@ -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() { diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/core/enumeration/BillObjTypeEnum.java b/accompany-base/accompany-core/src/main/java/com/accompany/core/enumeration/BillObjTypeEnum.java index 2c90aaf84..d9147a944 100644 --- a/accompany-base/accompany-core/src/main/java/com/accompany/core/enumeration/BillObjTypeEnum.java +++ b/accompany-base/accompany-core/src/main/java/com/accompany/core/enumeration/BillObjTypeEnum.java @@ -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) { diff --git a/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/model/Lucky25Record.java b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/model/Lucky25Record.java new file mode 100644 index 000000000..0c03fcef8 --- /dev/null +++ b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/model/Lucky25Record.java @@ -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 { + +} diff --git a/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PersonalStat.java b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PersonalStat.java new file mode 100644 index 000000000..0d9186fe2 --- /dev/null +++ b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PersonalStat.java @@ -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); + } +} diff --git a/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PlatformStat.java b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PlatformStat.java new file mode 100644 index 000000000..d90aa19df --- /dev/null +++ b/accompany-base/accompany-sharding/accompany-sharding-sdk/src/main/java/com/accompany/sharding/vo/Lucky25PlatformStat.java @@ -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); + } +} diff --git a/accompany-base/accompany-sharding/accompany-sharding-service/src/main/java/com/accompany/sharding/mapper/Lucky25RecordMapper.java b/accompany-base/accompany-sharding/accompany-sharding-service/src/main/java/com/accompany/sharding/mapper/Lucky25RecordMapper.java new file mode 100644 index 000000000..ba1ab29cc --- /dev/null +++ b/accompany-base/accompany-sharding/accompany-sharding-service/src/main/java/com/accompany/sharding/mapper/Lucky25RecordMapper.java @@ -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 { + + List listPlatform(@Param("partitionId") Integer partitionId, @Param("startTime") Date startTime, @Param("endTime") Date endTime, + @Param("zoneIdHour") long zoneIdHour); + + List listPersonal(@Param("partitionId") Integer partitionId, + @Param("uid") Long uid, + @Param("startTime") Date startTime, @Param("endTime") Date endTime, + @Param("zoneIdHour") long zoneIdHour); + +} diff --git a/accompany-base/accompany-sharding/accompany-sharding-service/src/main/resources/sharding/sqlmappers/Lucky25RecordMapper.xml b/accompany-base/accompany-sharding/accompany-sharding-service/src/main/resources/sharding/sqlmappers/Lucky25RecordMapper.xml new file mode 100644 index 000000000..04443f6be --- /dev/null +++ b/accompany-base/accompany-sharding/accompany-sharding-service/src/main/resources/sharding/sqlmappers/Lucky25RecordMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/constant/Lucky25PoolTypeEnum.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/constant/Lucky25PoolTypeEnum.java new file mode 100644 index 000000000..849aef3dc --- /dev/null +++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/constant/Lucky25PoolTypeEnum.java @@ -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 result = Arrays.stream(Lucky25PoolTypeEnum.values()).filter(prizePoolTypeEnum -> + prizePoolTypeEnum.type == type).findFirst(); + if (result.isPresent()) { + return result.get(); + } + throw new ServiceException(BusiStatus.PARAMETERILLEGAL); + } +} diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25GiftConfig.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25GiftConfig.java new file mode 100644 index 000000000..580447fce --- /dev/null +++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25GiftConfig.java @@ -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 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 followUidList; + + private Map whiteUidProductionRatioMap; + private List blackUidList; + + private String diamondIcon; + + public Lucky25GiftConfig getRatioByPartitionId(Integer partitionId){ + return ratioPartitionMap.getOrDefault(partitionId, this); + } + +} diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25Result.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25Result.java new file mode 100644 index 000000000..57b9e1337 --- /dev/null +++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/dto/lucky/Lucky25Result.java @@ -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); + } + +} diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/message/Lucky25Message.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/message/Lucky25Message.java new file mode 100644 index 000000000..85c35ab09 --- /dev/null +++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/message/Lucky25Message.java @@ -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; +} diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/lucky/Lucky25Pool.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/lucky/Lucky25Pool.java new file mode 100644 index 000000000..627bbbcda --- /dev/null +++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/lucky/Lucky25Pool.java @@ -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 { + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25PoolMapper.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25PoolMapper.java new file mode 100644 index 000000000..b2d39ec95 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25PoolMapper.java @@ -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 { +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25StatMapper.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25StatMapper.java new file mode 100644 index 000000000..07de51a15 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/lucky/Lucky25StatMapper.java @@ -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 listPlatformStat(@Param("partitionId") Integer partitionId, @Param("startDate") String startDate, @Param("endDate") String endDate); + + List listPersonalStat(@Param("partitionId") Integer partitionId, + @Param("uid") Long uid, + @Param("startDate") String startDate, @Param("endDate") String endDate); + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftMessageService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftMessageService.java index e2fae918a..62cece5b7 100644 --- a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftMessageService.java +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftMessageService.java @@ -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()); diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftSendService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftSendService.java index 85c11df18..762be8d74 100644 --- a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftSendService.java +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/GiftSendService.java @@ -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 filters; - - @PostConstruct - public void init() { - // 注意filter的顺序 - List filters = - Lists.newArrayList( - applicationContext.getBean(LuckyBagGiftDeviatePrizePoolFilter.class), - applicationContext.getBean(LuckyBagGiftNormalPrizePoolFilter.class) - ); - setFilters(filters); - } - - /** - * 方法隔离级别声明为public,只保证子类能访问 - * - * @param filters - */ - public void setFilters(List filters) { - this.filters = filters; - } - - public List 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> luckyBagDrawV2(long sendUid, int giftNum, Gift luckyBag, Date giftSendTime, List receiveUids) { - LuckyBagGiftPrizePoolFilterChain filterChain = new LuckyBagGiftPrizePoolFilterChain(getFilters()); + // 注意filter的顺序 + List 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()); diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25GiftSendService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25GiftSendService.java new file mode 100644 index 000000000..122732457 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25GiftSendService.java @@ -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 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 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 draw(Lucky25GiftConfig config, Lucky25GiftConfig partitionConfig, Long senderUid, int partitionId, + Gift gift, int everyGiftNum, long everyoneGoldNum, + List 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 userStatCacheMap = userMetaService.getUser10wStat(senderUid); + Map userStatMap = userStatCacheMap.readAllMap(); + if (userStatMap.isEmpty()){ + List 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 recordMap) { + Map caches = new HashMap<>(recordMap.size()); + List 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); + } + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25MessageService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25MessageService.java new file mode 100644 index 000000000..611deb1c7 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/gift/Lucky25MessageService.java @@ -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; + } + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25IncomeAllotService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25IncomeAllotService.java new file mode 100644 index 000000000..34310bab8 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25IncomeAllotService.java @@ -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 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 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)); + } +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25PoolService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25PoolService.java new file mode 100644 index 000000000..e5e5ff2ba --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25PoolService.java @@ -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 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 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 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 userPool, Lucky25Pool pool) { + List poolItemList = pool.getItemList().stream().filter(item->item.getNum()>0).collect(Collectors.toList()); + List winList = buildWinList(poolItemList); + int[] poolArray = new int[config.getPoolSize()]; + for (int i = 0; i < config.getPoolSize(); i++) { + poolArray[i] = 0; + } + + if (!winList.isEmpty()){ + List 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 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 buildWinList(List poolItemList) { + List 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 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 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 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 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 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 getUserPool(Long uid) { + return redissonClient.getDeque(RedisKey.lucky_25_user_pool.getKey(uid.toString())); + } + + public void updateUserMulti(Long uid) { + RDeque 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 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(); + } + } + } + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RecordService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RecordService.java new file mode 100644 index 000000000..80c740105 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RecordService.java @@ -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 { + + @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 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 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 = Lists.partition(personalList, 20); + for (List personalStatList : list) { + bizExecutor.execute(() -> { + for (Lucky25PersonalStat personalStat : personalStatList) { + statMapper.savePersonal(personalStat); + } + }); + } + } + } +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RobotMsgService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RobotMsgService.java new file mode 100644 index 000000000..bc1c92016 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25RobotMsgService.java @@ -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 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 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 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); + } +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25SettlementService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25SettlementService.java new file mode 100644 index 000000000..ed2cddbf6 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25SettlementService.java @@ -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 winGoldNumMap, SuperLuckyGiftIncomeAllot incomeAllot, Date sendGiftTime) { + superLuckyGiftSendService.settlement(senderUid, gift, everyGiftNum, totalGiftNum, room, incomeAllot, sendGiftTime); + } +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25StockService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25StockService.java new file mode 100644 index 000000000..ec92ce713 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25StockService.java @@ -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; + } +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25UserMetaService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25UserMetaService.java new file mode 100644 index 000000000..02a92aa1c --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/lucky/Lucky25UserMetaService.java @@ -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 historyQueue = getUserHistoryQueue(uid); + int size = historyQueue.size(); + //保留20000条记录 + int num = Math.min(size, 2000); + List 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 getUserLast10wHistory(Long uid) { + RList historyQueue = getUserHistoryQueue(uid); + int size = historyQueue.size(); + //保留20000条记录 + int num = Math.min(size, HISTORY_QUEUE_SIZE); + List historyResult = historyQueue.range(num); + if (size > HISTORY_QUEUE_SIZE){ + historyQueue.trim(0, HISTORY_QUEUE_SIZE); + } + return historyResult; + } + + public BigDecimal getUserProductionRatio(Long uid) { + RMap 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 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 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 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 getUserMeta(Long uid) { + return redissonClient.getMap(RedisKey.lucky_25_user_meta.getKey(uid.toString())); + } + + public RList getUserHistoryQueue(Long uid) { + return redissonClient.getList(RedisKey.lucky_25_user_history.getKey(uid.toString())); + } + + public RMap getUser10wStat(Long uid) { + return redissonClient.getMapCache(RedisKey.lucky_25_user_10w_stat.getKey(uid.toString())); + } + +} diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/mq/RocketMQService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/mq/RocketMQService.java index c07e45652..7003ebf47 100644 --- a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/mq/RocketMQService.java +++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/mq/RocketMQService.java @@ -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 lucky24Messages) { + List> 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); } } diff --git a/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25PoolMapper.xml b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25PoolMapper.xml new file mode 100644 index 000000000..3c5c27bff --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25PoolMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25StatMapper.xml b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25StatMapper.xml new file mode 100644 index 000000000..5b3cf56f8 --- /dev/null +++ b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/Lucky25StatMapper.xml @@ -0,0 +1,37 @@ + + + + + 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 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}; + + + + + + + diff --git a/accompany-mq/accompany-mq-sdk/src/main/java/com/accompany/mq/constant/MqConstant.java b/accompany-mq/accompany-mq-sdk/src/main/java/com/accompany/mq/constant/MqConstant.java index 4fa9801b3..aca06855c 100644 --- a/accompany-mq/accompany-mq-sdk/src/main/java/com/accompany/mq/constant/MqConstant.java +++ b/accompany-mq/accompany-mq-sdk/src/main/java/com/accompany/mq/constant/MqConstant.java @@ -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"; } diff --git a/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/BravoMessageConsumer.java b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/BravoMessageConsumer.java index cfb35d516..3fd825e77 100644 --- a/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/BravoMessageConsumer.java +++ b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/BravoMessageConsumer.java @@ -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; diff --git a/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky24MessageConsumer.java b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky24MessageConsumer.java index f14f8494d..051bc6f5a 100644 --- a/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky24MessageConsumer.java +++ b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky24MessageConsumer.java @@ -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; diff --git a/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky25MessageConsumer.java b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky25MessageConsumer.java new file mode 100644 index 000000000..f046d268d --- /dev/null +++ b/accompany-mq/accompany-mq-web/src/main/java/com/accompany/mq/consumer/Lucky25MessageConsumer.java @@ -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 { + + @Autowired + private Lucky25MessageService messageService; + + @Override + public void onMessage(Lucky25Message giftMessage) { + log.info("onMessage lucky25Message: {}", giftMessage.toString()); + messageService.handleGiftMessage(giftMessage); + } + +} diff --git a/accompany-scheduler/accompany-scheduler-service/src/main/java/com/accompany/scheduler/task/luckyBag/Lucky25Task.java b/accompany-scheduler/accompany-scheduler-service/src/main/java/com/accompany/scheduler/task/luckyBag/Lucky25Task.java new file mode 100644 index 000000000..e10f63fdd --- /dev/null +++ b/accompany-scheduler/accompany-scheduler-service/src/main/java/com/accompany/scheduler/task/luckyBag/Lucky25Task.java @@ -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 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 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); + } +}