提交 d9161499 编写于 作者: H hudingrong

完成策略权重分配

上级 a4d23f5c
......@@ -6,6 +6,7 @@
<id column="id" property="id"/>
<result column="strategy_id" property="strategyId"/>
<result column="strategy_desc" property="strategyDesc"/>
<result column="rule_models" property="ruleModels" />
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
......@@ -15,5 +16,10 @@
from strategy
limit 10
</select>
<select id="queryStrategyByStrategyId" resultMap="dataMap">
select strategy_id, strategy_desc, rule_models
from strategy
where strategy_id = #{strategyId}
</select>
</mapper>
......@@ -19,5 +19,10 @@
from strategy_rule
limit 10
</select>
<select id="queryStrategyRule" resultMap="dataMap">
select strategy_id, award_id, rule_type, rule_model, rule_value, rule_desc
from strategy_rule
where strategy_id = #{strategyId} and rule_model = #{ruleModel}
</select>
</mapper>
package cn.bugstack.test.domain;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.infrastructure.persistent.redis.IRedisService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RMap;
......@@ -25,21 +27,34 @@ public class StrategyTest {
@Resource
private IStrategyArmory strategyArmory;
@Resource
private IStrategyDispatch strategyDispatch;
/**
* 策略ID;100001L、100002L 装配的时候创建策略表写入到 Redis Map 中
*/
@Test
public void test_strategyArmory() {
strategyArmory.assembleLotteryStrategy(100002L);
log.info("测试结果:{}");
boolean success = strategyArmory.assembleLotteryStrategy(100001L);
log.info("测试结果:{}", success);
}
/**
* 从装配的策略中随机获取奖品ID值
*/
@Test
public void test_getAssembleRandomVal() {
log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L));
public void test_getRandomAwardId() {
log.info("测试结果:{} - 奖品ID值", strategyDispatch.getRandomAwardId(100001L));
}
/**
* 根据策略ID+权重值,从装配的策略中随机获取奖品ID值
*/
@Test
public void test_getRandomAwardId_ruleWeightValue() {
log.info("测试结果:{} - 4000 策略配置", strategyDispatch.getRandomAwardId(100001L, "4000:102,103,104,105"));
log.info("测试结果:{} - 5000 策略配置", strategyDispatch.getRandomAwardId(100001L, "5000:102,103,104,105,106,107"));
log.info("测试结果:{} - 6000 策略配置", strategyDispatch.getRandomAwardId(100001L, "6000:102,103,104,105,106,107,108,109"));
}
@Resource
......@@ -90,4 +105,5 @@ public class StrategyTest {
}
}
}
......@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
/**
......@@ -17,7 +18,7 @@ import java.math.BigDecimal;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StrategyAwardEntity {
public class StrategyAwardEntity implements Serializable {
/** 抽奖策略ID */
private Long strategyId;
/** 抽奖奖品ID - 内部流转使用 */
......
package cn.bugstack.domain.strategy.model.entity;
import cn.bugstack.types.common.Constants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* @description: 策略实体
* @author: hdr
* @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity
* @DATE: 2024/4/9
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StrategyEntity {
/** 抽奖策略ID */
private Long strategyId;
/** 抽奖策略描述 */
private String strategyDesc;
/** 抽奖规则模型 rule_weight,rule_blacklist */
private String ruleModels;
public String[] ruleModels() {
if (StringUtils.isBlank(ruleModels)) return null;
return ruleModels.split(Constants.SPLIT);
}
public String getRuleWeight() {
String[] ruleModels = this.ruleModels();
for (String ruleModel : ruleModels) {
if ("rule_weight".equals(ruleModel)) return ruleModel;
}
return null;
}
}
package cn.bugstack.domain.strategy.model.entity;
import cn.bugstack.types.common.Constants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @description:
* @author: hdr
* @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity
* @DATE: 2024/4/9
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StrategyRuleEntity {
/** 抽奖策略ID */
private Long strategyId;
/** 抽奖奖品ID【规则类型为策略,则不需要奖品ID】 */
private Integer awardId;
/** 抽象规则类型;1-策略规则、2-奖品规则 */
private Integer ruleType;
/** 抽奖规则类型【rule_random - 随机值计算、rule_lock - 抽奖几次后解锁、rule_luck_award - 幸运奖(兜底奖品)】 */
private String ruleModel;
/** 抽奖规则比值 */
private String ruleValue;
/** 抽奖规则描述 */
private String ruleDesc;
/**
* 获取权重值
* 数据案例;4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109
*/
public Map<String, List<Integer>> getRuleWeightValues() {
if (!"rule_weight".equals(ruleModel)) return null;
String[] ruleValueGroups = ruleValue.split(Constants.SPACE);
Map<String, List<Integer>> resultMap = new HashMap<>();
for (String ruleValueGroup : ruleValueGroups) {
// 检查输入是否为空
if (ruleValueGroup == null || ruleValueGroup.isEmpty()) {
return resultMap;
}
// 分割字符串以获取键和值
String[] parts = ruleValueGroup.split(Constants.COLON);
if (parts.length != 2) {
throw new IllegalArgumentException("rule_weight rule_rule invalid input format" + ruleValueGroup);
}
// 解析值
String[] valueStrings = parts[1].split(Constants.SPLIT);
List<Integer> values = new ArrayList<>();
for (String valueString : valueStrings) {
values.add(Integer.parseInt(valueString));
}
// 将键和值放入Map中
resultMap.put(ruleValueGroup, values);
}
return resultMap;
}
}
package cn.bugstack.domain.strategy.repository;
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 java.math.BigDecimal;
import java.util.HashMap;
......@@ -23,11 +25,11 @@ public interface IStrategyRepository {
List<StrategyAwardEntity> queryStrategyAwardList(Long strategy);
/**
* @param strategyId 策略id
* @param key 策略id
* @param rateRange 概率
* @param shuffleStrategyAwardSearchRateTables 所有保存的概率
*/
void storeStrategyAwardSearchRateTables(Long strategyId, Integer rateRange, HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables);
void storeStrategyAwardSearchRateTables(String key, Integer rateRange, HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables);
/**
*
......@@ -37,10 +39,32 @@ public interface IStrategyRepository {
int getRandomAwardId(Long strategyId);
/**
* 获取奖品id
* @param strategyId
* 获取奖品id
* @param key
* @param nextInt
* @return
*/
Integer getStrategyAwardAssemble(Long strategyId, int nextInt);
Integer getStrategyAwardAssemble(String key, int nextInt);
/**
* 获取抽奖策略
* @param strategy 策略id
* @return
*/
StrategyEntity queryStrategyEntityByStrategyId(Long strategy);
/**
* 查询抽奖策略规则
* @param strategy 策略id
* @param ruleWeight 策略id
* @return
*/
StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleWeight);
/**
* 获取概率值
* @param key
* @return
*/
int getRateRange(String key);
}
......@@ -12,12 +12,7 @@ public interface IStrategyArmory {
* 抽奖策略
* @param strategy
*/
void assembleLotteryStrategy(Long strategy);
boolean assembleLotteryStrategy(Long strategy);
/**
* 获取随机奖品id
* @param strategyId
* @return
*/
Integer getRandomAwardId(Long strategyId);
}
package cn.bugstack.domain.strategy.service.armory;
/**
* @description: 策略抽奖调度
* @author: hdr
* @PACKAGE_NAME: cn.bugstack.domain.strategy.service.armory
* @DATE: 2024/4/8
*/
public interface IStrategyDispatch {
/**
* 获取随机奖品id
* @param strategyId
* @return
*/
Integer getRandomAwardId(Long strategyId);
/**
* 随机获取奖品
* @param strategyId
* @param ruleWeightValue
* @return
*/
Integer getRandomAwardId(Long strategyId, String ruleWeightValue);
}
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.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -10,7 +15,6 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;
/**
* @description:
......@@ -20,28 +24,60 @@ import java.util.stream.Collectors;
*/
@Slf4j
@Service
public class StrategyArmory implements IStrategyArmory {
public class StrategyArmory implements IStrategyArmory, IStrategyDispatch {
@Autowired
private IStrategyRepository repository;
public static void main(String[] args) {
StrategyArmory strategyArmory = new StrategyArmory();
strategyArmory.assembleLotteryStrategy(100001L);
}
@Override
public void assembleLotteryStrategy(Long strategy) {
public boolean assembleLotteryStrategy(Long strategy) {
// 1. 查询策略配置
List<StrategyAwardEntity> awardEntityList = repository.queryStrategyAwardList(strategy);
assembleLotteryStrategy(String.valueOf(strategy),awardEntityList);
// 2. 权重策略陪住 - 使用于 rule_weight 权重规则配置
StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategy);
System.out.println(JSONObject.toJSONString(strategyEntity));
if (null == strategyEntity.getRuleWeight()||null == strategyEntity) return true;
String ruleWeight = strategyEntity.getRuleWeight();
// 2.2 查询策略规则
StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategy, ruleWeight);
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();
Set<String> keys = ruleWeightValueMap.keySet();
// 2.3 将获取到的所有 规则写入缓存
for (String key : keys) {
List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(awardEntityList);
strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
assembleLotteryStrategy(String.valueOf(strategy).concat("_").concat(key), strategyAwardEntitiesClone);
}
return true;
// 2. 获取最小概率值
BigDecimal minAwardRate = awardEntityList.stream().map(StrategyAwardEntity::getAwardRate)
}
private void assembleLotteryStrategy(String key,List<StrategyAwardEntity> awardEntityList) {
// 1. 获取最小概率值
BigDecimal minAwardRate = awardEntityList.stream()
.map(StrategyAwardEntity::getAwardRate)
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
// 3. 获取概率值总和
// 2. 获取概率值总和
BigDecimal totalAwardRate = awardEntityList.stream()
.map(StrategyAwardEntity::getAwardRate)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 4. 用 1% 0.001获取概率范围,百分位、千分位、万分位
// 3. 用 1% 0.001获取概率范围,百分位、千分位、万分位
BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);
......@@ -50,34 +86,37 @@ public class StrategyArmory implements IStrategyArmory {
Integer awardId = strategyAwardEntity.getAwardId();
BigDecimal awardRate = strategyAwardEntity.getAwardRate();
// 5. 计算出每个概率值需要存储到查找表的数量,循环填充
log.info("rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue()"+rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue());
// 4. 计算出每个概率值需要存储到查找表的数量,循环填充
for (int i = 0; i < rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue(); i++) {
strategyAwardSearchRateTables.add(awardId);
}
// 6. 乱序
// 5. 乱序
Collections.shuffle(strategyAwardSearchRateTables);
// 7.
// 6. 组装抽奖表
HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables = new HashMap<>();
for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
shuffleStrategyAwardSearchRateTables.put(i, (Integer) strategyAwardSearchRateTables.get(i));
}
log.info(shuffleStrategyAwardSearchRateTables.size()+"");
// 8. 存储到 redis中
repository.storeStrategyAwardSearchRateTables(strategy,shuffleStrategyAwardSearchRateTables.size(), shuffleStrategyAwardSearchRateTables);
// 7. 存储到 redis中
repository.storeStrategyAwardSearchRateTables(key,shuffleStrategyAwardSearchRateTables.size(), shuffleStrategyAwardSearchRateTables);
}
}
@Override
public Integer getRandomAwardId(Long strategyId) {
int rateRange = repository.getRandomAwardId(strategyId);
return repository.getStrategyAwardAssemble(strategyId,new SecureRandom().nextInt(rateRange));
return repository.getStrategyAwardAssemble(String.valueOf(strategyId),new SecureRandom().nextInt(rateRange));
}
@Override
public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
String key = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
int rateRange = repository.getRateRange(key);
// 通过生成的随机值,获取概率值奖品查找表的结果
return repository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
}
}
......@@ -2,6 +2,7 @@ package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.Strategy;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
......@@ -15,4 +16,10 @@ public interface IStrategyDao {
List<Strategy> queryStrategyList();
/**
* 通过策略id查询策略
* @param strategyId 策略id
* @return
*/
Strategy queryStrategyByStrategyId(@Param("strategyId") Long strategyId);
}
......@@ -15,4 +15,10 @@ public interface IStrategyRuleDao {
List<StrategyRule> queryStrategyRuleList();
/**
* 策略规则和策略id查询
* @param strategyRuleReq
* @return
*/
StrategyRule queryStrategyRule(StrategyRule strategyRuleReq);
}
......@@ -19,6 +19,8 @@ public class Strategy {
private Long strategyId;
/** 抽奖策略描述 */
private String strategyDesc;
/** 抽奖规则模型 rule_weight,rule_blacklist */
private String ruleModels;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
......
package cn.bugstack.infrastructure.persistent.repository;
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.infrastructure.persistent.dao.IStrategyAwardDao;
import cn.bugstack.infrastructure.persistent.dao.IStrategyDao;
import cn.bugstack.infrastructure.persistent.dao.IStrategyRuleDao;
import cn.bugstack.infrastructure.persistent.po.Strategy;
import cn.bugstack.infrastructure.persistent.po.StrategyAward;
import cn.bugstack.infrastructure.persistent.po.StrategyRule;
import cn.bugstack.infrastructure.persistent.redis.IRedisService;
import cn.bugstack.infrastructure.persistent.redis.RedisService;
import cn.bugstack.types.common.Constants;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.redisson.misc.Hash;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -35,11 +43,20 @@ public class StrategyRepository implements IStrategyRepository {
@Autowired
private IRedisService redisService;
@Autowired
private IStrategyDao strategyDao;
@Autowired
private IStrategyRuleDao strategyRuleDao;
@Override
public List<StrategyAwardEntity> queryStrategyAwardList(Long strategy) {
// 1. 查询redis中是否有保存策略数据有的话直接返回
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategy;
List<StrategyAwardEntity> strategyAwardEntities = redisService.getValue(cacheKey);
Object json = redisService.getValue(cacheKey);
List<StrategyAwardEntity> strategyAwardEntities = JSON.parseArray(json.toString(), StrategyAwardEntity.class);
if (strategyAwardEntities != null && !strategyAwardEntities.isEmpty()) {
return strategyAwardEntities;
}
......@@ -57,28 +74,73 @@ public class StrategyRepository implements IStrategyRepository {
}).collect(Collectors.toList());
// 2.1 保存到redis中
redisService.setValue(cacheKey,strategyAwardEntityList);
// redisService.setValue(cacheKey, strategyAwardEntityList);
return strategyAwardEntityList;
}
@Override
public void storeStrategyAwardSearchRateTables(Long strategyId, Integer rateRange, HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables) {
public void storeStrategyAwardSearchRateTables(String key, Integer rateRange, HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables) {
// 1. 存储概率值
redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + strategyId, rateRange.intValue());
redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange.intValue());
// 2. 存储概率查找表
Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + strategyId);
Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
cacheRateTable.putAll(shuffleStrategyAwardSearchRateTables);
}
@Override
public int getRandomAwardId(Long strategyId) {
return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + strategyId);
return getRateRange(String.valueOf(strategyId));
}
@Override
public Integer getStrategyAwardAssemble(String key, int nextInt) {
return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key,nextInt);
}
@Override
public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
// 1. 优先从缓存获取
String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
Map<String,Object> maps = redisService.getValue(cacheKey);
StrategyEntity strategyEntity = StrategyEntity.builder()
.strategyId(Long.parseLong(maps.get("strategyId").toString()))
.strategyDesc((String) maps.get("strategyDesc"))
.ruleModels((String) maps.get("ruleModels"))
.build();
if (null != strategyEntity) return strategyEntity;
// 2. 没有数据从数据库获取
Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
strategyEntity = StrategyEntity.builder()
.strategyId(strategy.getStrategyId())
.strategyDesc(strategy.getStrategyDesc())
.ruleModels(strategy.getRuleModels())
.build();
// 3. 写入缓存
redisService.setValue(cacheKey,strategyEntity);
return strategyEntity;
}
@Override
public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
StrategyRule strategyRuleReq = new StrategyRule();
strategyRuleReq.setStrategyId(strategyId);
strategyRuleReq.setRuleModel(ruleModel);
StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRuleReq);
return StrategyRuleEntity.builder()
.strategyId(strategyRuleRes.getStrategyId())
.awardId(strategyRuleRes.getAwardId())
.ruleType(strategyRuleRes.getRuleType())
.ruleModel(strategyRuleRes.getRuleModel())
.ruleValue(strategyRuleRes.getRuleValue())
.ruleDesc(strategyRuleRes.getRuleDesc())
.build();
}
@Override
public Integer getStrategyAwardAssemble(Long strategyId, int nextInt) {
return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + strategyId,nextInt);
public int getRateRange(String key) {
return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key);
}
}
......@@ -3,8 +3,11 @@ package cn.bugstack.types.common;
public class Constants {
public final static String SPLIT = ",";
public final static String COLON = ":";
public final static String SPACE = " ";
public static class RedisKey {
public static final String STRATEGY_KEY = "big_market_strategy_key_";
public static String STRATEGY_AWARD_KEY = "big_market_strategy_award_key_";
public static String STRATEGY_RATE_TABLE_KEY = "big_market_strategy_rate_table_key_";
public static String STRATEGY_RATE_RANGE_KEY = "big_market_strategy_rate_range_key_";
......
......@@ -12,6 +12,7 @@ public enum ResponseCode {
SUCCESS("0000", "成功"),
UN_ERROR("0001", "未知失败"),
ILLEGAL_PARAMETER("0002", "非法参数"),
STRATEGY_RULE_WEIGHT_IS_NULL("ERR_BIZ_001", "业务异常,策略规则中 rule_weight 权重规则已适用但未配置")
;
private String code;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册