提交 f440f6b3 编写于 作者: 鲸落和鲨掉's avatar 鲸落和鲨掉

修改抽奖策略,将抽奖分为抽奖策略抽奖类:概率装配接口、抽奖调度接口 以及 抽奖策略算法实现类依赖抽奖算法抽象类

上级 133be328
...@@ -46,5 +46,9 @@ ...@@ -46,5 +46,9 @@
from strategy_award from strategy_award
where strategy_id = #{strategyId} and award_id = #{awardId} where strategy_id = #{strategyId} and award_id = #{awardId}
</select> </select>
<select id="queryOpenActivityStrategyAwardList" resultMap="dataMap">
select strategy_id, award_id from strategy_award
where strategy_id in (select strategy_id from raffle_activity where state = 'open')
</select>
</mapper> </mapper>
...@@ -21,12 +21,14 @@ public interface IStrategyRepository { ...@@ -21,12 +21,14 @@ public interface IStrategyRepository {
List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId); List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId);
void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable); <K, V> void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<K, V> strategyAwardSearchRateTable);
<K, V> Map<K, V> getMap(String key);
Integer getStrategyAwardAssemble(String key, Integer rateKey); Integer getStrategyAwardAssemble(String key, Integer rateKey);
int getRateRange(Long strategyId); int getRateRange(Long strategyId);
int getRateRange(String key); int getRateRange(String key);
StrategyEntity queryStrategyEntityByStrategyId(Long strategyId); StrategyEntity queryStrategyEntityByStrategyId(Long strategyId);
...@@ -142,5 +144,27 @@ public interface IStrategyRepository { ...@@ -142,5 +144,27 @@ public interface IStrategyRepository {
* @return 权重规则 * @return 权重规则
*/ */
List<RuleWeightVO> queryAwardRuleWeight(Long strategyId); List<RuleWeightVO> queryAwardRuleWeight(Long strategyId);
/**
* 查询有效活动的奖品配置
*
* @return 奖品配置列表
*/
List<StrategyAwardStockKeyVO> queryOpenActivityStrategyAwardList();
/**
* 存储抽奖策略对应的Bean算法
*
* @param key 策略ID
* @param beanName 策略对象名称
*/
void cacheStrategyArmoryAlgorithm(String key, String beanName);
/**
* 获取存储抽奖策略对应的Bean算法
*
* @param key 策略ID
* @return 策略对象名称
*/
String queryStrategyArmoryAlgorithmFromCache(String key);
} }
package cn.bugstack.domain.strategy.service.armory;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.types.common.Constants;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.*;
/**
* @ClassName: AbstractStrageyAlgorithm
* @Description: 抽奖策略算法抽象类
* @Author: zhaoyongfeng
* @Date: 2025/1/23 22:22
*/
public abstract class AbstractStrategyAlgorithm implements IStrategyArmory, IStrategyDispatch {
@Resource
protected IStrategyRepository repository;
protected final SecureRandom secureRandom = new SecureRandom();
@Override
public boolean assembleLotteryStrategyByActivityId(Long activityId){
Long strategyId = repository.queryStrategyIdByActivityId(activityId);
return assembleLotteryStrategy(strategyId);
}
@Override
public boolean assembleLotteryStrategy(Long strategyId){
// 1.查询策略配置
List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId);
// 2. 缓存奖品库存【用于decr扣减库存使用】
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId();
Integer awardCountSurplus = strategyAward.getAwardCountSurplus();
cacheStrategyAwardCount(strategyId,awardId,awardCountSurplus);
}
// 3.1 默认装配配置【全量抽奖概率】
armoryAlgorithm(String.valueOf(strategyId),strategyAwardEntities);
// 3.2 权重策略配置 - 适用于 rule_weight 权重规则配置【4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109】
StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategyId);
String ruleWeight = strategyEntity.getRuleWeight();
// 为空抛出异常,设定异常
if(null == ruleWeight) return true;
StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategyId, ruleWeight);
// 业务异常,策略规则中 rule_weight 权重规则已适用但未配置
if(null == strategyRuleEntity){
throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
}
Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
for (String key : ruleWeightValueMap.keySet()) {
List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
armoryAlgorithm(String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(key), strategyAwardEntitiesClone);
}
return true;
}
/*
* @description: 装配算法
* @param: key 为策略ID、权重ID、strategyAwardEntities 对应的奖品概率
* @return: void
* @author zhaoyongfeng
* @date: 2025/1/23 23:02
*/
protected abstract void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities);
/*
* @description: 缓存奖品库存到Redis
* @param: strategyId 策略ID awardId 奖品ID awardCount 奖品库存
* @author zhaoyongfeng
* @date: 2025/1/23 23:04
*/
private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount){
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
repository.cacheStrategyAwardCount(cacheKey, awardCount);
}
protected abstract Integer dispatchAlgorithm(String key);
@Override
public Integer getRandomAwardId(Long strategyId) {
return dispatchAlgorithm(String.valueOf(strategyId));
}
@Override
public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
String key = String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(ruleWeightValue);
return getRandomAwardId(key);
}
@Override
public Integer getRandomAwardId(String key) {
return dispatchAlgorithm(key);
}
@Override
public Boolean subtractionAwardStock(Long strategyId, Integer awardId, Date endDateTime) {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
return repository.subtractionAwardStock(cacheKey, endDateTime);
}
// 概率最小值
protected BigDecimal minAwardRate(List<StrategyAwardEntity> strategyAwardEntities){
return strategyAwardEntities.stream()
.map(StrategyAwardEntity::getAwardRate)
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
}
// 概率范围值,百分位、千分位、万分位
protected double convert(double min) {
if (0 == min) return 1D;
String minStr = String.valueOf(min);
// 小数点前
String beginVale = minStr.substring(0, minStr.indexOf("."));
int beginLength = 0;
if (Double.parseDouble(beginVale) > 0) {
beginLength = minStr.substring(0, minStr.indexOf(".")).length();
}
// 小数点后
String endValue = minStr.substring(minStr.indexOf(".") + 1);
int endLength = 0;
if (Double.parseDouble(endValue) > 0) {
endLength = minStr.substring(minStr.indexOf(".") + 1).length();
}
return Math.pow(10, beginLength + endLength);
}
}
package cn.bugstack.domain.strategy.service.armory; package cn.bugstack.domain.strategy.service.armory;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity; import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity; import cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm;
import cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity; import cn.bugstack.domain.strategy.service.armory.algorithm.IAlgorithm;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.types.common.Constants;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.security.SecureRandom; import java.util.List;
import java.util.*; import java.util.Map;
/** /**
* @author Fuzhengwei bugstack.cn @小傅哥 * @ClassName: StrategyArmoryDispatch
* @description 策略装配库(兵工厂),负责初始化策略计算 * @Description:
* @create 2023-12-23 10:02 * @Author: zhaoyongfeng
* @Date: 2025/1/23 23:26
*/ */
@Slf4j @Slf4j
@Service @Service("strategyArmoryDispatch")
public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatch { public class StrategyArmoryDispatch extends AbstractStrategyAlgorithm{
// 抽奖策略算法
private final Map<String, IAlgorithm> algorithmMap;
// 抽奖算法阈值,在多少范围内开始选择不同选择
private final Integer ALGORITHM_THRESHOLD_VALUE = 10000;
@Resource public StrategyArmoryDispatch(Map<String, IAlgorithm> algorithmMap) {
private IStrategyRepository repository; this.algorithmMap = algorithmMap;
private final SecureRandom secureRandom = new SecureRandom();
@Override
public boolean assembleLotteryStrategyByActivityId(Long activityId) {
Long strategyId = repository.queryStrategyIdByActivityId(activityId);
return assembleLotteryStrategy(strategyId);
} }
@Override
public boolean assembleLotteryStrategy(Long strategyId) {
// 1. 查询策略配置
List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId);
// 2 缓存奖品库存【用于decr扣减库存使用】
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId();
Integer awardCount = strategyAward.getAwardCountSurplus();
cacheStrategyAwardCount(strategyId, awardId, awardCount);
}
// 3.1 默认装配配置【全量抽奖概率】
assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntities);
// 3.2 权重策略配置 - 适用于 rule_weight 权重规则配置【4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109】
StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategyId);
String ruleWeight = strategyEntity.getRuleWeight();
if (null == ruleWeight) return true;
StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategyId, ruleWeight);
// 业务异常,策略规则中 rule_weight 权重规则已适用但未配置
if (null == strategyRuleEntity) {
throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
}
Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
for (String key : ruleWeightValueMap.keySet()) {
List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
assembleLotteryStrategy(String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(key), strategyAwardEntitiesClone);
}
return true;
}
/**
* 计算公式;
* 1. 找到范围内最小的概率值,比如 0.1、0.02、0.003,需要找到的值是 0.003
* 2. 基于1找到的最小值,0.003 就可以计算出百分比、千分比的整数值。这里就是1000
* 3. 那么「概率 * 1000」分别占比100个、20个、3个,总计是123个
* 4. 后续的抽奖就用123作为随机数的范围值,生成的值100个都是0.1概率的奖品、20个是概率0.02的奖品、最后是3个是0.003的奖品。
*/
private void assembleLotteryStrategy(String key, List<StrategyAwardEntity> strategyAwardEntities) {
// 1. 获取最小概率值
BigDecimal minAwardRate = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getAwardRate)
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
// 2. 循环计算找到概率范围值
BigDecimal rateRange = BigDecimal.valueOf(convert(minAwardRate.doubleValue()));
// 3. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId();
BigDecimal awardRate = strategyAward.getAwardRate();
// 计算出每个概率值需要存放到查找表的数量,循环填充
for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {
strategyAwardSearchRateTables.add(awardId);
}
}
// 4. 对存储的奖品进行乱序操作
Collections.shuffle(strategyAwardSearchRateTables);
// 5. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i));
}
// 6. 存放到 Redis
repository.storeStrategyAwardSearchRateTable(key, shuffleStrategyAwardSearchRateTable.size(), shuffleStrategyAwardSearchRateTable);
}
/**
* 转换计算,只根据小数位来计算。如【0.01返回100】、【0.009返回1000】、【0.0018返回10000】
*/
private double convertOld1(double min) {
if (0 == min) return 1D;
double current = min;
double max = 1;
while (current % 1 != 0) {
current = current * 10;
max = max * 10;
}
return max;
}
private double convertOld2(double min) {
if (min == 0) return 1D;
String minStr = Double.toString(min);
int decimalPlaces = 0;
int decimalPointIndex = minStr.indexOf('.');
if (decimalPointIndex != -1) {
decimalPlaces = minStr.length() - decimalPointIndex - 1;
}
return Math.pow(10, decimalPlaces);
}
private double convert(double min) {
if (0 == min) return 1D;
String minStr = String.valueOf(min);
// 小数点前
String beginVale = minStr.substring(0, minStr.indexOf("."));
int beginLength = 0;
if (Double.parseDouble(beginVale) > 0) {
beginLength = minStr.substring(0, minStr.indexOf(".")).length();
}
// 小数点后
String endValue = minStr.substring(minStr.indexOf(".") + 1);
int endLength = 0;
if (Double.parseDouble(endValue) > 0) {
endLength = minStr.substring(minStr.indexOf(".") + 1).length();
}
return Math.pow(10, beginLength + endLength);
}
/**
* 缓存奖品库存到Redis
*
* @param strategyId 策略ID
* @param awardId 奖品ID
* @param awardCount 奖品库存
*/
private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
repository.cacheStrategyAwardCount(cacheKey, awardCount);
}
@Override @Override
public Integer getRandomAwardId(Long strategyId) { protected void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities) {
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。 // 1. 概率最小值
int rateRange = repository.getRateRange(strategyId); BigDecimal minAwardRate = minAwardRate(strategyAwardEntities);
// 通过生成的随机值,获取概率值奖品查找表的结果 // 2. 概率范围值
return repository.getStrategyAwardAssemble(String.valueOf(strategyId), secureRandom.nextInt(rateRange)); double rateRange = convert(minAwardRate.doubleValue());
} // 3. 根据概率值范围选择算法
if (rateRange <= ALGORITHM_THRESHOLD_VALUE) {
@Override IAlgorithm o1Algorithm = algorithmMap.get(AbstractAlgorithm.Algorithm.O1.getKey());
public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) { o1Algorithm.armoryAlgorithm(key, strategyAwardEntities, new BigDecimal(rateRange));
String key = String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(ruleWeightValue); repository.cacheStrategyArmoryAlgorithm(key, AbstractAlgorithm.Algorithm.O1.getKey());
return getRandomAwardId(key); } else {
IAlgorithm oLogNAlgorithm = algorithmMap.get(AbstractAlgorithm.Algorithm.OLogN.getKey());
oLogNAlgorithm.armoryAlgorithm(key, strategyAwardEntities, new BigDecimal(rateRange));
repository.cacheStrategyArmoryAlgorithm(key, AbstractAlgorithm.Algorithm.OLogN.getKey());
}
} }
@Override @Override
public Integer getRandomAwardId(String key) { protected Integer dispatchAlgorithm(String key) {
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。 String beanName = repository.queryStrategyArmoryAlgorithmFromCache(key);
int rateRange = repository.getRateRange(key); if (null == beanName) throw new RuntimeException("key " + key + " beanName is " + beanName);
// 通过生成的随机值,获取概率值奖品查找表的结果 IAlgorithm algorithm = algorithmMap.get(beanName);
return repository.getStrategyAwardAssemble(key, secureRandom.nextInt(rateRange)); return algorithm.dispatchAlgorithm(key);
} }
@Override
public Boolean subtractionAwardStock(Long strategyId, Integer awardId, Date endDateTime) {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
return repository.subtractionAwardStock(cacheKey, endDateTime);
}
} }
package cn.bugstack.domain.strategy.service.armory.algorithm;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.annotation.Resource;
import java.security.SecureRandom;
/**
* @ClassName: AbstractAlgorithm
* @Description: 抽奖算法抽象类
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:32
*/
public abstract class AbstractAlgorithm implements IAlgorithm{
@Resource
protected IStrategyRepository repository;
protected final SecureRandom secureRandom = new SecureRandom();
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum Algorithm {
O1("o1Algorithm"), OLogN("oLogNAlgorithm");
private String key;
}
}
package cn.bugstack.domain.strategy.service.armory.algorithm;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import java.math.BigDecimal;
import java.util.List;
/**
* @author
* @description 抽奖算法
* @create 2024-08-24 13:02
*/
public interface IAlgorithm {
void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities, BigDecimal rateRange);
Integer dispatchAlgorithm(String key);
}
package cn.bugstack.domain.strategy.service.armory.algorithm.impl;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.*;
/**
* @ClassName: O1Algorithm
* @Description: o(1) 时间复杂度算法,将抽奖概率分配到 Redis Map 结构中,1个概率对应的奖品就是一个Key,抽奖时直接通过 get 获取。
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:34
*/
@Slf4j
@Component("o1Algorithm")
public class O1Algorithm extends AbstractAlgorithm {
@Override
public void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities, BigDecimal rateRange) {
log.info("抽奖算法 O(1) 装配 key:{}", key);
// 1. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId();
BigDecimal awardRate = strategyAward.getAwardRate();
// 计算出每个概率值需要存放到查找表的数量,循环填充
for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {
strategyAwardSearchRateTables.add(awardId);
}
}
// 2. 对存储的奖品进行乱序操作
Collections.shuffle(strategyAwardSearchRateTables);
// 3. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i));
}
// 4. 存放到 Redis
repository.storeStrategyAwardSearchRateTable(key, shuffleStrategyAwardSearchRateTable.size(), shuffleStrategyAwardSearchRateTable);
}
@Override
public Integer dispatchAlgorithm(String key) {
log.info("抽奖算法 O(1) 抽奖计算 key:{}", key);
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
int rateRange = repository.getRateRange(key);
// 通过生成的随机值,获取概率值奖品查找表的结果
return repository.getStrategyAwardAssemble(key, secureRandom.nextInt(rateRange));
}
}
package cn.bugstack.domain.strategy.service.armory.algorithm.impl;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
/**
* @ClassName: OLogNAlgorithm
* @Description: 循环、二分、多线程,查找抽奖算法
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:37
*/
@Slf4j
@Component("oLogNAlgorithm")
public class OLogNAlgorithm extends AbstractAlgorithm {
@Resource
private ThreadPoolExecutor threadPoolExecutor;
/**
* 预热活动概率值
* 如概率值为;3、4、2、9,存储为 [1~3]、[4~7]、[8~9]、[10~18],抽奖时,for循环匹配。
*
* @param key 为策略ID、权重ID
* @param strategyAwardEntities 对应的奖品概率
*/
@Override
public void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities, BigDecimal rateRange) {
log.info("抽奖算法 OLog(n) 装配 key:{}", key);
int from = 1;
int to = 0;
Map<Map<Integer, Integer>, Integer> table = new HashMap<>();
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId();
BigDecimal awardRate = strategyAward.getAwardRate();
to += rateRange.multiply(awardRate).intValue();
Map<Integer, Integer> ft = new HashMap<>();
ft.put(from, to);
table.put(ft, awardId);
from = to + 1;
}
repository.storeStrategyAwardSearchRateTable(key, to, table);
}
@Override
public Integer dispatchAlgorithm(String key) {
int rateRange = repository.getRateRange(key);
Map<Map<String, Integer>, Integer> table = repository.getMap(key);
// 小于等于8 for循环、小于等于16 二分查找、更多检索走多线程
if (table.size() <= 8) {
log.info("抽奖算法 OLog(n) 抽奖计算(循环) key:{}", key);
return forSearch(secureRandom.nextInt(rateRange), table);
} else if (table.size() <= 16) {
log.info("抽奖算法 OLog(n) 抽奖计算(二分) key:{}", key);
return binarySearch(secureRandom.nextInt(rateRange), table);
} else {
log.info("抽奖算法 OLog(n) 抽奖计算(多线程) key:{}", key);
return threadSearch(secureRandom.nextInt(rateRange), table);
}
}
private Integer forSearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
Integer awardId = null;
for (Map.Entry<Map<String, Integer>, Integer> entry : table.entrySet()) {
Map<String, Integer> rangeMap = entry.getKey();
for (Map.Entry<String, Integer> range : rangeMap.entrySet()) {
int start = Integer.parseInt(range.getKey());
int end = range.getValue();
if (rateKey >= start && rateKey <= end) {
awardId = entry.getValue();
break;
}
}
if (awardId != null) {
break;
}
}
return awardId;
}
private Integer binarySearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
List<Map.Entry<Map<String, Integer>, Integer>> entries = new ArrayList<>(table.entrySet());
entries.sort(Comparator.comparingInt(e -> Integer.parseInt(e.getKey().keySet().iterator().next())));
int left = 0;
int right = entries.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
Map.Entry<Map<String, Integer>, Integer> entry = entries.get(mid);
Map<String, Integer> rangeMap = entry.getKey();
Map.Entry<String, Integer> range = rangeMap.entrySet().iterator().next();
int start = Integer.parseInt(range.getKey());
int end = range.getValue();
if (rateKey < start) {
right = mid - 1;
} else if (rateKey > end) {
left = mid + 1;
} else {
return entry.getValue();
}
}
return null;
}
private Integer threadSearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
List<CompletableFuture<Map.Entry<Map<String, Integer>, Integer>>> futures = table.entrySet().stream()
.map(entry -> CompletableFuture.supplyAsync(() -> {
Map<String, Integer> rangeMap = entry.getKey();
for (Map.Entry<String, Integer> rangeEntry : rangeMap.entrySet()) {
int start = Integer.parseInt(rangeEntry.getKey());
int end = rangeEntry.getValue();
if (rateKey >= start && rateKey <= end) {
return entry;
}
}
return null;
}, threadPoolExecutor))
.collect(Collectors.toList());
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
try {
// 等待所有异步任务完成,同时返回第一个匹配的结果
allFutures.join();
for (CompletableFuture<Map.Entry<Map<String, Integer>, Integer>> future : futures) {
Map.Entry<Map<String, Integer>, Integer> result = future.getNow(null);
if (result != null) {
return result.getValue();
}
}
} catch (CompletionException e) {
e.printStackTrace();
}
return null;
}
}
\ No newline at end of file
...@@ -22,5 +22,5 @@ public interface IStrategyAwardDao { ...@@ -22,5 +22,5 @@ public interface IStrategyAwardDao {
void updateStrategyAwardStock(StrategyAward strategyAward); void updateStrategyAwardStock(StrategyAward strategyAward);
StrategyAward queryStrategyAward(StrategyAward strategyAwardReq); StrategyAward queryStrategyAward(StrategyAward strategyAwardReq);
List<StrategyAward> queryOpenActivityStrategyAwardList();
} }
...@@ -87,14 +87,22 @@ public class StrategyRepository implements IStrategyRepository { ...@@ -87,14 +87,22 @@ public class StrategyRepository implements IStrategyRepository {
* 简单来说,getMap 方法返回的 RMap 对象是懒加载的,只有在你实际进行操作时,Redis 数据库中的数据结构才会被创建或修改。 * 简单来说,getMap 方法返回的 RMap 对象是懒加载的,只有在你实际进行操作时,Redis 数据库中的数据结构才会被创建或修改。
*/ */
@Override @Override
public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) { public <K, V> void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<K, V> strategyAwardSearchRateTable) {
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数 // 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange); redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
// 2. 存储概率查找表 // 2. 存储概率查找表 - 存在则删除重新装配
Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key); String tableCacheKey = Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key;
if (redisService.isExists(tableCacheKey)) {
redisService.remove(tableCacheKey);
}
Map<K, V> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
cacheRateTable.putAll(strategyAwardSearchRateTable); cacheRateTable.putAll(strategyAwardSearchRateTable);
} }
@Override
public <K, V> Map<K, V> getMap(String key) {
return redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
}
@Override @Override
public Integer getStrategyAwardAssemble(String key, Integer rateKey) { public Integer getStrategyAwardAssemble(String key, Integer rateKey) {
return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey); return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey);
...@@ -404,5 +412,31 @@ public class StrategyRepository implements IStrategyRepository { ...@@ -404,5 +412,31 @@ public class StrategyRepository implements IStrategyRepository {
return ruleWeightVOS; return ruleWeightVOS;
} }
@Override
public List<StrategyAwardStockKeyVO> queryOpenActivityStrategyAwardList() {
List<StrategyAward> strategyAwards = strategyAwardDao.queryOpenActivityStrategyAwardList();
if (null == strategyAwards || strategyAwards.isEmpty()) return null;
List<StrategyAwardStockKeyVO> strategyAwardStockKeyVOS = new ArrayList<>();
for (StrategyAward strategyAward : strategyAwards) {
StrategyAwardStockKeyVO strategyAwardStockKeyVO = StrategyAwardStockKeyVO.builder()
.strategyId(strategyAward.getStrategyId())
.awardId(strategyAward.getAwardId())
.build();
strategyAwardStockKeyVOS.add(strategyAwardStockKeyVO);
}
return strategyAwardStockKeyVOS;
}
@Override
public void cacheStrategyArmoryAlgorithm(String key, String beanName) {
String cacheKey = Constants.RedisKey.STRATEGY_ARMORY_ALGORITHM_KEY + key;
redisService.setValue(cacheKey, beanName);
}
@Override
public String queryStrategyArmoryAlgorithmFromCache(String key) {
String cacheKey = Constants.RedisKey.STRATEGY_ARMORY_ALGORITHM_KEY + key;
if (!redisService.isExists(cacheKey)) return null;
return redisService.getValue(cacheKey);
}
} }
...@@ -29,6 +29,7 @@ public class Constants { ...@@ -29,6 +29,7 @@ public class Constants {
public static String ACTIVITY_ACCOUNT_LOCK = "activity_account_lock_"; public static String ACTIVITY_ACCOUNT_LOCK = "activity_account_lock_";
public static String ACTIVITY_ACCOUNT_UPDATE_LOCK = "activity_account_update_lock_"; public static String ACTIVITY_ACCOUNT_UPDATE_LOCK = "activity_account_update_lock_";
public static String USER_CREDIT_ACCOUNT_LOCK = "user_credit_account_lock_"; public static String USER_CREDIT_ACCOUNT_LOCK = "user_credit_account_lock_";
public static String STRATEGY_ARMORY_ALGORITHM_KEY = "strategy_armory_algorithm_key_";
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册