Skip to content

Commit

Permalink
抽奖策略领域模块设计-模板设计模式抽象抽奖流程基本完成
Browse files Browse the repository at this point in the history
  • Loading branch information
Casflawed committed Jul 22, 2023
1 parent 83982f0 commit 30896db
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,21 @@

public abstract class BaseLotteryStrategy implements ILotteryStrategy {
protected final static int TOTAL_RATE = 100;
private final static int ARRAY_SIZE = 128;
protected final static int ARRAY_SIZE = 128;
private final static int HASH_INCREMENT = 0x61c88647;
//奖品池,strategyId -> 奖品和概率
protected Map<Long, Long[]> awardPool = new HashMap<>();
protected Map<Long, List<AwardRateInfo>> awardInfos = new HashMap<>();
/**
* 检查抽奖策略是否初始化
*
* @param strategyId 策略id
*/
public void checkAndInitStrategy(Long strategyId, List<StrategyDetail> strategyDetails){
//已初始化
if (awardPool.containsKey(strategyId)) {
//奖品信息
protected Map<Long, List<AwardRateInfo>> commonAwardRateInfos = new HashMap<>();

@Override
public void initAwardRateInfo(Long strategyId, List<StrategyDetail> strategyDetails){
Assert.notEmpty(strategyDetails, "奖品概率信息为空");
//奖品概率信息已初始化
if (commonAwardRateInfos.containsKey(strategyId)){
return;
}
Assert.notEmpty(strategyDetails, "策略详情信息为空");
List<AwardRateInfo> awardRateInfos = awardInfos.computeIfAbsent(strategyId, key -> new ArrayList<>());

List<AwardRateInfo> awardRateInfos = commonAwardRateInfos.computeIfAbsent(strategyId, key -> new ArrayList<>());
//防止浅拷贝,dto 转换
strategyDetails.forEach(strategyDetail -> awardRateInfos.add(new AwardRateInfo(strategyDetail.getAwardId(), strategyDetail.getAwardRate())));


}

public void init(Long strategyId){
//奖品信息
List<AwardRateInfo> rateInfos = awardInfos.get(strategyId);
//奖品池
Long[] rateArr = awardPool.computeIfAbsent(strategyId, key -> new Long[ARRAY_SIZE]);

//初始化奖品池:使用散列函数将抽奖概率平均发散
int lRange = 0;
for (AwardRateInfo awardInfo : rateInfos) {
int rateValue = (int) (awardInfo.getAwardRate() * TOTAL_RATE);
for (int i = lRange + 1; i <= lRange + rateValue; i++) {
Integer idx = hashIdx(i);
rateArr[idx] = awardInfo.getAwardId();
}
lRange += rateValue;
}

//本地持久化抽奖策略
awardPool.put(strategyId, rateArr);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,13 @@ public interface ILotteryStrategy {
* @param strategyId 策略id
* @param excludedAwardIds 排除掉的奖品id
*/
String draw(Long strategyId, List<Long> excludedAwardIds);
Long draw(Long strategyId, List<Long> excludedAwardIds);

/**
* 检查抽奖策略是否初始化
* 检查并初始化
*
* @param strategyId 策略id
* @param strategyDetails 策略详情信息
* @param strategyDetails 奖品概率信息
*/
void checkAndInitStrategy(Long strategyId, List<StrategyDetail> strategyDetails);

/**
* 初始化单项概率抽奖策略
*
* @param strategyId 策略id
*/
void init(Long strategyId);
void initAwardRateInfo(Long strategyId, List<StrategyDetail> strategyDetails);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.flameking.lottery.domain.strategy.algorithm.impl;

import com.flameking.lottery.domain.strategy.algorithm.BaseLotteryStrategy;
import com.flameking.lottery.domain.strategy.algorithm.ILotteryStrategy;
import com.flameking.lottery.domain.strategy.model.AwardRateInfo;

import java.util.ArrayList;
Expand All @@ -11,10 +10,10 @@
/**
* 必中奖抽奖策略
*/
public class EntiretyRateRandomDrawAlgorithm extends BaseLotteryStrategy implements ILotteryStrategy {
public class EntiretyRateRandomDrawAlgorithm extends BaseLotteryStrategy {
@Override
public String draw(Long strategyId, List<Long> excludedAwardIds) {
List<AwardRateInfo> rateInfos = awardInfos.get(strategyId);
public Long draw(Long strategyId, List<Long> excludedAwardIds) {
List<AwardRateInfo> rateInfos = commonAwardRateInfos.get(strategyId);
List<AwardRateInfo> tmpRateInfos = new ArrayList<>();

//动态计算全概率
Expand All @@ -35,7 +34,7 @@ public String draw(Long strategyId, List<Long> excludedAwardIds) {
}
//有效奖品数量 == 1
if (tmpRateInfos.size() == 1){
return String.valueOf(tmpRateInfos.get(0).getAwardId());
return tmpRateInfos.get(0).getAwardId();
}

//获取随机概率值
Expand All @@ -54,6 +53,6 @@ public String draw(Long strategyId, List<Long> excludedAwardIds) {
}

// 返回中奖结果
return String.valueOf(awardId);
return awardId;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.flameking.lottery.domain.strategy.algorithm.impl;

import com.flameking.lottery.domain.strategy.algorithm.ILotteryStrategy;
import com.flameking.lottery.domain.strategy.algorithm.BaseLotteryStrategy;
import com.flameking.lottery.domain.strategy.model.AwardRateInfo;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
Expand All @@ -14,10 +16,13 @@
* @dateTime 2023/7/19 21:58
*/
@Slf4j
public class SingleProbabilityLotteryStrategy extends BaseLotteryStrategy implements ILotteryStrategy {
public class SingleProbabilityLotteryStrategy extends BaseLotteryStrategy {
//奖品池
private final Map<Long, Long[]> singleAwardRatePool = new HashMap<>();

@Override
public String draw(Long strategyId, List<Long> excludedAwardIds) {
Long[] rateArr = awardPool.get(strategyId);
public Long draw(Long strategyId, List<Long> excludedAwardIds) {
Long[] rateArr = singleAwardRatePool.get(strategyId);

//生成随机码
int randomCode = new Random().nextInt(TOTAL_RATE) + 1;
Expand All @@ -33,7 +38,42 @@ public String draw(Long strategyId, List<Long> excludedAwardIds) {
}

//返回奖品id
return String.valueOf(awardId);
return awardId;
}

/**
* 初始化单项概率的配置信息
*
* @param strategyId 策略id
*/
public void initAwardRatePool(Long strategyId){
//奖品池已初始化
if (singleAwardRatePool.containsKey(strategyId)) {
return;
}

//获取奖品概率信息
List<AwardRateInfo> rateInfos = commonAwardRateInfos.get(strategyId);

//创建该策略下的奖品池
Long[] rateArr = singleAwardRatePool.computeIfAbsent(strategyId, key -> new Long[ARRAY_SIZE]);

//初始化奖品池:使用散列函数将奖品信息平均发散
int lRange = 0;
for (AwardRateInfo awardInfo : rateInfos) {
int rateValue = (int) (awardInfo.getAwardRate() * TOTAL_RATE);
//右范围
int rRange = lRange + rateValue;
for (int i = lRange + 1; i <= rRange; i++) {
Integer idx = hashIdx(i);
rateArr[idx] = awardInfo.getAwardId();
}
//更新左范围
lRange = rRange;
}

//内存持久化单项概率抽奖策略
singleAwardRatePool.put(strategyId, rateArr);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.flameking.lottery.domain.strategy.draw;

import com.flameking.lottery.domain.strategy.algorithm.ILotteryStrategy;
import com.flameking.lottery.domain.strategy.factory.LotteryStrategyFactory;
import com.flameking.lottery.domain.strategy.model.StrategyRich;
import com.flameking.lottery.infrastructure.entity.Award;
import com.flameking.lottery.infrastructure.entity.Strategy;

import java.util.List;

public abstract class BaseDrawTemplate extends DrawStrategySupport implements IDrawTemplate{


@Override
public Award doDraw(Long uId, Long strategyId) {
//查询奖品概率和策略配置信息
StrategyRich strategyRich = this.queryStrategyRich(strategyId);
Strategy strategy = strategyRich.getStrategy();

//初始化抽奖策略
this.initDrawStrategy(strategy.getStrategyMode(), strategyRich);

//查询不在抽奖范围内的奖品
List<Long> excludeAwardIds = this.queryExcludeAwardIds(strategyId);

//执行抽奖
Long awardId = this.excDrawStrategy(strategyId, strategy.getStrategyMode(), excludeAwardIds);

//包装奖品信息
return awardId == null ? null : this.queryAward(awardId);
}

/**
* 初始化抽奖策略
*
* @param strategyMode
* @param strategyRich
*/
private void initDrawStrategy(Integer strategyMode, StrategyRich strategyRich) {
ILotteryStrategy lotteryStrategy = LotteryStrategyFactory.getLotteryStrategy(strategyMode);
lotteryStrategy.initAwardRateInfo(strategyRich.getStrategyId(), strategyRich.getStrategyDetailList());
}

/**
* 执行抽奖策略
*
* @param strategyId
* @param strategyMode
* @param excludeAwardIds
*/
protected abstract Long excDrawStrategy(Long strategyId, Integer strategyMode, List<Long> excludeAwardIds);

/**
* 查询查询不在抽奖范围内的奖品
*
* @param strategyId
*/
protected abstract List<Long> queryExcludeAwardIds(Long strategyId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.flameking.lottery.domain.strategy.draw;

import com.flameking.lottery.domain.strategy.factory.LotteryStrategyFactory;
import com.flameking.lottery.domain.strategy.model.StrategyRich;
import com.flameking.lottery.domain.strategy.repository.IStrategyRepository;
import com.flameking.lottery.infrastructure.entity.Award;

import javax.annotation.Resource;
import java.util.List;

public class DrawStrategySupport {
@Resource
protected IStrategyRepository strategyRepository;

protected StrategyRich queryStrategyRich(Long strategyId){
return strategyRepository.queryStrategyRich(strategyId);
}
protected Award queryAward(Long awardId){
return strategyRepository.queryAward(awardId);
}
protected List<Long> queryAwardIdsWithoutAmount(Long strategyId){
return strategyRepository.queryAwardIdsWithoutAmount(strategyId);
}
protected boolean decreaseLeftAwardCount(Long strategyId, Long awardId){
return strategyRepository.decreaseLeftAwardCount(strategyId, awardId);
}

}
Original file line number Diff line number Diff line change
@@ -1,72 +1,39 @@
package com.flameking.lottery.domain.strategy.draw.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.flameking.lottery.domain.strategy.algorithm.ILotteryStrategy;
import com.flameking.lottery.domain.strategy.algorithm.impl.SingleProbabilityLotteryStrategy;
import com.flameking.lottery.domain.strategy.draw.BaseDrawTemplate;
import com.flameking.lottery.domain.strategy.factory.LotteryStrategyFactory;
import com.flameking.lottery.infrastructure.entity.Award;
import com.flameking.lottery.infrastructure.entity.Strategy;
import com.flameking.lottery.infrastructure.entity.StrategyDetail;
import com.flameking.lottery.infrastructure.service.IAwardService;
import com.flameking.lottery.domain.strategy.draw.IDrawTemplate;
import com.flameking.lottery.infrastructure.service.IStrategyDetailService;
import com.flameking.lottery.infrastructure.service.IStrategyService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;


@Component
public class DrawTemplateImpl implements IDrawTemplate {
@Resource
private IStrategyDetailService strategyDetailService;
@Resource
private IStrategyService strategyService;
@Resource
private IAwardService awardService;

/**
* 抽奖策略是否初始化
*
* @param strategy
* @param awardInfos
*/
private ILotteryStrategy checkAndInitStrategy(Strategy strategy, List<StrategyDetail> awardInfos){
Integer strategyMode = strategy.getStrategyMode();
ILotteryStrategy lotteryStrategy = LotteryStrategyFactory.getLotteryStrategy(strategy.getStrategyMode());
lotteryStrategy.checkAndInitStrategy(strategy.getStrategyId(), awardInfos);
public class DrawTemplateImpl extends BaseDrawTemplate {
@Override
protected Long excDrawStrategy(Long strategyId, Integer strategyMode, List<Long> excludeAwardIds) {
ILotteryStrategy lotteryStrategy = LotteryStrategyFactory.getLotteryStrategy(strategyMode);
Long awardId;
//单项概率抽奖策略需要初始化奖品概率池
if (strategyMode == 1){
lotteryStrategy.init(strategy.getStrategyId());
SingleProbabilityLotteryStrategy singleProbabilityLotteryStrategy = (SingleProbabilityLotteryStrategy) lotteryStrategy;
singleProbabilityLotteryStrategy.initAwardRatePool(strategyId);
}
awardId = lotteryStrategy.draw(strategyId, excludeAwardIds);

if (awardId != null){
boolean isSuccess = this.decreaseLeftAwardCount(strategyId, awardId);
//扣减失败,则等于未中奖
if (!isSuccess){
return null;
}
}
return lotteryStrategy;
return awardId;
}

@Override
public Award doDraw(Long uId, Long strategyId) {
//查询奖品信息和被排除的奖品信息
List<StrategyDetail> awardInfos = strategyDetailService.getStrategyDetailsByStrategyId(strategyId);

//根据 strategyId 查询策略信息
Strategy strategy = strategyService.getByStrategyId(strategyId);

//初始化抽奖策略
ILotteryStrategy lotteryStrategy = checkAndInitStrategy(strategy, awardInfos);

//排除掉的奖品id
List<Long> excludeAwardIds = strategyDetailService.getExcludedAwardIds(strategyId);

//执行抽奖
String awardId = lotteryStrategy.draw(strategyId, excludeAwardIds);

//未中奖
if (awardId == null){
return null;
}

//抽奖成功,扣减库存
boolean isSuccess = strategyDetailService.decreaseLeftAwardCount(strategyId, awardId);

//包装奖品信息
return isSuccess ? awardService.getAwardByAwardId(Long.valueOf(awardId)) : null;
protected List<Long> queryExcludeAwardIds(Long strategyId) {
return this.queryAwardIdsWithoutAmount(strategyId);
}
}
Loading

0 comments on commit 30896db

Please sign in to comment.