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

策略权重概率装配、抽奖前置规则过滤

上级 ebac0706
...@@ -13,5 +13,19 @@ ...@@ -13,5 +13,19 @@
</jdbc-additional-properties> </jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
<data-source source="LOCAL" name="big_market@127.0.0.1" uuid="297e8e74-a084-4e7f-beeb-45bb9a7d972e">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/xfg-frame-archetype-lite-app/src/main/resources/application-dev.yml</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:13306/big_market?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;zeroDateTimeBehavior=convertToNull&amp;serverTimezone=UTC&amp;useSSL=true</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component> </component>
</project> </project>
\ No newline at end of file
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<id column="id" property="id"/> <id column="id" property="id"/>
<result column="strategy_id" property="strategyId"/> <result column="strategy_id" property="strategyId"/>
<result column="strategy_desc" property="strategyDesc"/> <result column="strategy_desc" property="strategyDesc"/>
<result column="rule_models" property="ruleModels"/>
<result column="create_time" property="createTime"/> <result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/> <result column="update_time" property="updateTime"/>
</resultMap> </resultMap>
...@@ -16,5 +17,10 @@ ...@@ -16,5 +17,10 @@
limit 10 limit 10
</select> </select>
</mapper> <select id="queryStrategyByStrategyId" parameterType="java.lang.Long" resultMap="dataMap">
select strategy_id, strategy_desc, rule_models
from strategy
where strategy_id = #{strategyId}
</select>
</mapper>
...@@ -20,5 +20,18 @@ ...@@ -20,5 +20,18 @@
limit 10 limit 10
</select> </select>
</mapper> <select id="queryStrategyRule" parameterType="cn.bugstack.infrastructure.persistent.po.StrategyRule" 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>
<select id="queryStrategyRuleValue" parameterType="cn.bugstack.infrastructure.persistent.po.StrategyRule" resultType="java.lang.String">
select rule_value from strategy_rule
where strategy_id = #{strategyId} and rule_model = #{ruleModel}
<if test="awardId != null">
and award_id = #{awardId}
</if>
</select>
</mapper>
package cn.bugstack.test.domain;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.service.IRaffleStrategy;
import cn.bugstack.domain.strategy.service.rule.impl.RuleWeightLogicFilter;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import javax.annotation.Resource;
import java.security.PrivateKey;
/**
* @ClassName: RaffleStrategyTest
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/17 17:11
*/
@SpringBootTest
@Slf4j
@RunWith(SpringRunner.class)
public class RaffleStrategyTest {
@Resource
private IRaffleStrategy raffleStrategy;
@Resource
private RuleWeightLogicFilter ruleWeightLogicFilter;
@Before
public void setUp() {
ReflectionTestUtils.setField(ruleWeightLogicFilter, "userScore", 4500L);
}
@Test
public void test_performRaffle() {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge")
.strategyId(100001L)
.build();
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}
@Test
public void test_performRaffle_blacklist() {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("user003") // 黑名单用户 user001,user002,user003
.strategyId(100001L)
.build();
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}
}
...@@ -18,18 +18,18 @@ import javax.annotation.Resource; ...@@ -18,18 +18,18 @@ import javax.annotation.Resource;
@SpringBootTest @SpringBootTest
@Slf4j @Slf4j
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
public class StrategyArmoryTest { public class StrategyArmoryDispatchTest {
@Resource @Resource
private IStrategyArmory strategyArmory; private IStrategyArmory strategyArmory;
@Test /*@Test
public void test_strategyArmory(){ public void test_strategyArmory(){
strategyArmory.assembleLotteryStrategy(100002L); strategyArmory.assembleLotteryStrategy(100002L);
} }
/** *//**
* 从装配的策略中随机获取奖品ID值 * 从装配的策略中随机获取奖品ID值
*/ *//*
@Test @Test
public void test_getAssembleRandomVal() { public void test_getAssembleRandomVal() {
log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L)); log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L));
...@@ -38,5 +38,5 @@ public class StrategyArmoryTest { ...@@ -38,5 +38,5 @@ public class StrategyArmoryTest {
log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L)); log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L));
log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L)); log.info("测试结果:{} - 奖品ID值", strategyArmory.getRandomAwardId(100002L));
} }*/
} }
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;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.*;
/**
* @ClassName: StrategyTest
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/17 17:18
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class StrategyTest {
@Resource
private IStrategyArmory strategyArmory;
@Resource
private IStrategyDispatch strategyDispatch;
/**
* 策略ID;100001L、100002L 装配的时候创建策略表写入到 Redis Map 中
*/
@Before
public void test_strategyArmory() {
boolean success = strategyArmory.assembleLotteryStrategy(100001L);
log.info("测试结果:{}", success);
}
/**
* 从装配的策略中随机获取奖品ID值
*/
@Test
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
private IRedisService redisService;
@Test
public void test_map() {
RMap<Integer, Integer> map = redisService.getMap("strategy_id_100001");
map.put(1, 101);
map.put(2, 101);
map.put(3, 101);
map.put(4, 102);
map.put(5, 102);
map.put(6, 102);
map.put(7, 103);
map.put(8, 103);
map.put(9, 104);
map.put(10, 105);
log.info("测试结果:{}", redisService.getMap("strategy_id_100001").get(1));
}
@Test
public void test_shuffle(){
Map<Integer, Integer> strategyAwardSearchRateTable = new HashMap<>();
// 添加内容到Map中
strategyAwardSearchRateTable.put(1, 10);
strategyAwardSearchRateTable.put(2, 20);
strategyAwardSearchRateTable.put(3, 30);
strategyAwardSearchRateTable.put(4, 40);
// 将Map中的值转换为List
List<Integer> valueList = new ArrayList<>(strategyAwardSearchRateTable.values());
// 使用Collections.shuffle()方法对值的List进行乱序
Collections.shuffle(valueList);
// 将乱序后的值重新放回Map中
Map<Integer, Integer> randomizedMap = new LinkedHashMap<>();
Iterator<Integer> valueIterator = valueList.iterator();
for (Integer key : strategyAwardSearchRateTable.keySet()) {
randomizedMap.put(key, valueIterator.next());
}
// 打印乱序后的Map内容
for (Map.Entry<Integer, Integer> entry : randomizedMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
package cn.bugstack.domain.strategy.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName: RaffleAwardEntity
* @Description: 抽奖奖品实体
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:25
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RaffleAwardEntity {
/** 策略ID */
private Long strategyId;
/** 奖品ID */
private Integer awardId;
/** 奖品对接标识 - 每一个都是一个对应的发奖策略 */
private String awardKey;
/** 奖品配置信息 */
private String awardConfig;
/** 奖品内容描述 */
private String awardDesc;
}
package cn.bugstack.domain.strategy.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.units.qual.A;
/**
* @ClassName: RaffleFactorEntity
* @Description: 抽奖因子实体
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:26
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RaffleFactorEntity {
/** 用户ID */
private String userId;
/** 策略ID */
private Long strategyId;
}
package cn.bugstack.domain.strategy.model.entity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import lombok.*;
/**
* @ClassName: RuleActionEntity
* @Description: 规则动作
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:26
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleActionEntity<T extends RuleActionEntity.RaffleEntity> {
private String code = RuleLogicCheckTypeVO.ALLOW.getCode();
private String info = RuleLogicCheckTypeVO.ALLOW.getInfo();
private String ruleModel;
private T data;
static public class RaffleEntity {
}
// 抽奖之前
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleBeforeEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
// 抽奖之中
static public class RaffleCenterEntity extends RaffleEntity {
}
// 抽奖之后
static public class RaffleAfterEntity extends RaffleEntity {
}
}
package cn.bugstack.domain.strategy.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.units.qual.A;
/**
* @ClassName: RuleMatterEntity
* @Description: 规则物料实体对象,用于过滤规则的必要参数信息
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:26
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleMatterEntity {
/** 用户ID */
private String userId;
/** 策略ID */
private Long strategyId;
/** 抽奖奖品ID【规则类型为策略,则不需要奖品ID】 */
private Integer awardId;
/** 抽奖规则类型【rule_random - 随机值计算、rule_lock - 抽奖几次后解锁、rule_luck_award - 幸运奖(兜底奖品)】 */
private String ruleModel;
}
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;
/**
* @ClassName: StrategyEntity
* @Description: 策略实体
* @Author: zhaoyongfeng
* @Date: 2024/11/15 23:35
*/
@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;
/**
* @ClassName: StrategyRuleEntity
* @Description: 策略规则实体
* @Author: zhaoyongfeng
* @Date: 2024/11/15 23:36
*/
@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.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @ClassName: RuleLogicCheckTypeVO
* @Description: 规则过滤校验类型值对象
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:24
*/
@Getter
@AllArgsConstructor
public enum RuleLogicCheckTypeVO {
ALLOW("0000", "放行;执行后续的流程,不受规则引擎影响"),
TAKE_OVER("0001","接管;后续的流程,受规则引擎执行结果影响"),
;
private final String code;
private final String info;
}
package cn.bugstack.domain.strategy.repository; package cn.bugstack.domain.strategy.repository;
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.model.entity.StrategyRuleEntity;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public interface IStrategyRepository { public interface IStrategyRepository {
List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId); List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId);
void storeStrategyAwardSearchRateTable(Long strategyId, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable); void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable);
Integer getStrategyAwardAssemble(Long strategyId, Integer rateKey); Integer getStrategyAwardAssemble(String key, Integer rateKey);
int getRateRange(Long strategyId); int getRateRange(Long strategyId);
int getRateRange(String key);
StrategyEntity queryStrategyEntityByStrategyId(Long strategyId);
StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel);
String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel);
} }
package cn.bugstack.domain.strategy.service;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
public interface IRaffleStrategy {
/**
* 执行抽奖;用抽奖因子入参,执行抽奖计算,返回奖品信息
*
* @param raffleFactorEntity 抽奖因子实体对象,根据入参信息计算抽奖结果
* @return 抽奖的奖品
*/
RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity);
}
package cn.bugstack.domain.strategy.service.annotation;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: LogicStrategy
* @Description: 策略自定义枚举
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:02
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {
DefaultLogicFactory.LogicModel logicMode();
}
...@@ -10,12 +10,5 @@ public interface IStrategyArmory { ...@@ -10,12 +10,5 @@ public interface IStrategyArmory {
*/ */
boolean assembleLotteryStrategy(Long strategyId); boolean assembleLotteryStrategy(Long strategyId);
/**
* 获取抽奖策略装配的随机结果
*
* @param strategyId 策略ID
* @return 抽奖结果
*/
Integer getRandomAwardId(Long strategyId);
} }
package cn.bugstack.domain.strategy.service.armory;
public interface IStrategyDispatch {
/**
* 获取抽奖策略装配的随机结果
*
* @param strategyId 策略ID
* @return 抽奖结果
*/
Integer getRandomAwardId(Long strategyId);
Integer getRandomAwardId(Long strategyId, String ruleWeightValue);
}
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.model.entity.StrategyRuleEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository; import cn.bugstack.domain.strategy.repository.IStrategyRepository;
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;
...@@ -19,51 +23,74 @@ import java.util.*; ...@@ -19,51 +23,74 @@ import java.util.*;
*/ */
@Slf4j @Slf4j
@Service @Service
public class StrategyArmory implements IStrategyArmory{ public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch{
@Resource @Resource
private IStrategyRepository repository; private IStrategyRepository repository;
@Override @Override
public boolean assembleLotteryStrategy(Long strategyId) { public boolean assembleLotteryStrategy(Long strategyId) {
//1. 查询策略配置 // 1. 查询策略配置
List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId); List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId);
// 获取最小概率值 assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntities);
// 2. 权重策略配置 - 适用于 rule_weight 权重规则配置
StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategyId);
String ruleWeight = strategyEntity.getRuleWeight();
if (null == ruleWeight) return true;
StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategyId, 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();
for (String key : keys) {
List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
assembleLotteryStrategy(String.valueOf(strategyId).concat("_").concat(key), strategyAwardEntitiesClone);
}
return true;
}
private void assembleLotteryStrategy(String key, List<StrategyAwardEntity> strategyAwardEntities) {
// 1. 获取最小概率值
BigDecimal minAwardRate = strategyAwardEntities.stream() BigDecimal minAwardRate = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getAwardRate) .map(StrategyAwardEntity::getAwardRate)
.min(BigDecimal::compareTo) .min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO); .orElse(BigDecimal.ZERO);
// 3. 获取概率值总和 // 2. 获取概率值总和
BigDecimal totalAwardRate = strategyAwardEntities.stream() BigDecimal totalAwardRate = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getAwardRate) .map(StrategyAwardEntity::getAwardRate)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
//TODO 需要修改问题
// 4. 用 1 % 0.0001 获得概率范围,百分位、千分位、万分位 // 3. 用 1 % 0.0001 获得概率范围,百分位、千分位、万分位
BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING); BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);
// 5. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
ArrayList<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue()); // 4. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
for(StrategyAwardEntity strategyAward: strategyAwardEntities){ List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardId = strategyAward.getAwardId(); Integer awardId = strategyAward.getAwardId();
BigDecimal awardRate = strategyAward.getAwardRate(); BigDecimal awardRate = strategyAward.getAwardRate();
// 计算出每个概率值需要存放到查找表的数量,循环填充 // 计算出每个概率值需要存放到查找表的数量,循环填充
for (int i = 0; i < rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue(); i++) { for (int i = 0; i < rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue(); i++) {
strategyAwardSearchRateTables.add(awardId); strategyAwardSearchRateTables.add(awardId);
} }
} }
// 6. 对存储的奖品进行乱序操作
// 5. 对存储的奖品进行乱序操作
Collections.shuffle(strategyAwardSearchRateTables); Collections.shuffle(strategyAwardSearchRateTables);
// 7. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
// 6. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>(); Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) { for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i)); shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i));
} }
// 8. 存放到 Redis
repository.storeStrategyAwardSearchRateTable(strategyId, shuffleStrategyAwardSearchRateTable.size(), shuffleStrategyAwardSearchRateTable);
return true;
// 7. 存放到 Redis
repository.storeStrategyAwardSearchRateTable(key, shuffleStrategyAwardSearchRateTable.size(), shuffleStrategyAwardSearchRateTable);
} }
@Override @Override
...@@ -71,7 +98,17 @@ public class StrategyArmory implements IStrategyArmory{ ...@@ -71,7 +98,17 @@ public class StrategyArmory implements IStrategyArmory{
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。 // 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
int rateRange = repository.getRateRange(strategyId); int rateRange = repository.getRateRange(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));
} }
} }
package cn.bugstack.domain.strategy.service.raffle;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.IRaffleStrategy;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* @ClassName: AbstractRaffleStrategy
* @Description: 抽奖策略抽象类,定义抽奖的标准流程
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:14
*/
@Slf4j
public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
// 策略仓储服务 -> domain层像一个大厨,仓储层提供米面粮油
protected IStrategyRepository repository;
// 策略调度服务 -> 只负责抽奖处理,通过新增接口的方式,隔离职责,不需要使用方关心或者调用抽奖的初始化
protected IStrategyDispatch strategyDispatch;
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) {
this.repository = repository;
this.strategyDispatch = strategyDispatch;
}
@Override
public RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity) {
// 1. 参数校验
String userId = raffleFactorEntity.getUserId();
Long strategyId = raffleFactorEntity.getStrategyId();
if (null == strategyId || StringUtils.isBlank(userId)) {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
// 2. 策略查询
StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);
// 3. 抽奖前 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = this.doCheckRaffleBeforeLogic(RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(), strategy.ruleModels());
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionEntity.getCode())) {
if (DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode().equals(ruleActionEntity.getRuleModel())) {
// 黑名单返回固定的奖品ID
return RaffleAwardEntity.builder()
.awardId(ruleActionEntity.getData().getAwardId())
.build();
} else if (DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode().equals(ruleActionEntity.getRuleModel())) {
// 权重根据返回的信息进行抽奖
RuleActionEntity.RaffleBeforeEntity raffleBeforeEntity = ruleActionEntity.getData();
String ruleWeightValueKey = raffleBeforeEntity.getRuleWeightValueKey();
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, ruleWeightValueKey);
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
}
// 4. 默认抽奖流程
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
protected abstract RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
}
package cn.bugstack.domain.strategy.service.raffle;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @ClassName: DefaultRaffleStrategy
* @Description: 默认的抽奖策略实现
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:16
*/
@Slf4j
@Service
public class DefaultRaffleStrategy extends AbstractRaffleStrategy {
@Resource
private DefaultLogicFactory logicFactory;
public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) {
super(repository, strategyDispatch);
}
@Override
protected RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics) {
Map<String, ILogicFilter<RuleActionEntity.RaffleBeforeEntity>> logicFilterGroup = logicFactory.openLogicFilter();
// 黑名单规则优先过滤
String ruleBackList = Arrays.stream(logics)
.filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.findFirst()
.orElse(null);
if (StringUtils.isNotBlank(ruleBackList)) {
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode());
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId());
ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId());
ruleMatterEntity.setRuleModel(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode());
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = logicFilter.filter(ruleMatterEntity);
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) {
return ruleActionEntity;
}
}
// 顺序过滤剩余规则
List<String> ruleList = Arrays.stream(logics)
.filter(s -> !s.equals(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.collect(Collectors.toList());
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = null;
for (String ruleModel : ruleList) {
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId());
ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId());
ruleMatterEntity.setRuleModel(ruleModel);
ruleActionEntity = logicFilter.filter(ruleMatterEntity);
// 非放行结果则顺序过滤
log.info("抽奖前规则过滤 userId: {} ruleModel: {} code: {} info: {}", raffleFactorEntity.getUserId(), ruleModel, ruleActionEntity.getCode(), ruleActionEntity.getInfo());
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) return ruleActionEntity;
}
return ruleActionEntity;
}
}
package cn.bugstack.domain.strategy.service.rule;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
/*
* @return
* @author zhaoyongfeng
* @description 抽奖规则过滤接口
*
* @param null
*/
public interface ILogicFilter<T extends RuleActionEntity.RaffleEntity> {
RuleActionEntity<T> filter(RuleMatterEntity ruleMatterEntity);
}
package cn.bugstack.domain.strategy.service.rule.factory;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.service.annotation.LogicStrategy;
import cn.bugstack.domain.strategy.service.rule.ILogicFilter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: DefaultLogicFactory
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:05
*/
@Service
public class DefaultLogicFactory {
public Map<String, ILogicFilter<?>> logicFilterMap = new ConcurrentHashMap<>();
public DefaultLogicFactory(List<ILogicFilter<?>> logicFilters) {
logicFilters.forEach(logic -> {
LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class);
if (null != strategy) {
logicFilterMap.put(strategy.logicMode().getCode(), logic);
}
});
}
public <T extends RuleActionEntity.RaffleEntity> Map<String, ILogicFilter<T>> openLogicFilter() {
return (Map<String, ILogicFilter<T>>) (Map<?, ?>) logicFilterMap;
}
@Getter
@AllArgsConstructor
public enum LogicModel {
RULE_WIGHT("rule_weight","【抽奖前规则】根据抽奖权重返回可抽奖范围KEY"),
RULE_BLACKLIST("rule_blacklist","【抽奖前规则】黑名单规则过滤,命中黑名单则直接返回"),
;
private final String code;
private final String info;
}
}
package cn.bugstack.domain.strategy.service.rule.impl;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.annotation.LogicStrategy;
import cn.bugstack.domain.strategy.service.rule.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: RuleBackListLogicFilter
* @Description: 【抽奖前规则】黑名单用户过滤规则
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:07
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_BLACKLIST)
public class RuleBackListLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@Resource
private IStrategyRepository repository;
@Override
public RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-黑名单 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
String userId = ruleMatterEntity.getUserId();
// 查询规则值配置
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
String[] splitRuleValue = ruleValue.split(Constants.COLON);
Integer awardId = Integer.parseInt(splitRuleValue[0]);
// 过滤其他规则
String[] userBlackIds = splitRuleValue[1].split(Constants.SPLIT);
for (String userBlackId : userBlackIds) {
if (userId.equals(userBlackId)) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.ruleModel(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())
.data(RuleActionEntity.RaffleBeforeEntity.builder()
.strategyId(ruleMatterEntity.getStrategyId())
.awardId(awardId)
.build())
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
}
package cn.bugstack.domain.strategy.service.rule.impl;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.annotation.LogicStrategy;
import cn.bugstack.domain.strategy.service.rule.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
* @ClassName: RuleWeightLogicFilter
* @Description: 【抽奖前规则】根据抽奖权重返回可抽奖范围KEY
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:08
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT)
public class RuleWeightLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@Resource
private IStrategyRepository repository;
public Long userScore = 4500L;
/**
* 权重规则过滤;
* 1. 权重规则格式;4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109
* 2. 解析数据格式;判断哪个范围符合用户的特定抽奖范围
*
* @param ruleMatterEntity 规则物料实体对象
* @return 规则过滤结果
*/
@Override
public RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-权重范围 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
String userId = ruleMatterEntity.getUserId();
Long strategyId = ruleMatterEntity.getStrategyId();
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
// 1. 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
Map<Long, String> analyticalValueGroup = getAnalyticalValue(ruleValue);
if (null == analyticalValueGroup || analyticalValueGroup.isEmpty()) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
// 2. 转换Keys值,并默认排序
List<Long> analyticalSortedKeys = new ArrayList<>(analyticalValueGroup.keySet());
Collections.sort(analyticalSortedKeys);
// 3. 找出最小符合的值,也就是【4500 积分,能找到 4000:102,103,104,105】、【5000 积分,能找到 5000:102,103,104,105,106,107】
Long nextValue = analyticalSortedKeys.stream()
.filter(key -> userScore >= key)
.findFirst()
.orElse(null);
if (null != nextValue) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.data(RuleActionEntity.RaffleBeforeEntity.builder()
.strategyId(strategyId)
.ruleWeightValueKey(analyticalValueGroup.get(nextValue))
.build())
.ruleModel(DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode())
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
private Map<Long, String> getAnalyticalValue(String ruleValue) {
String[] ruleValueGroups = ruleValue.split(Constants.SPACE);
Map<Long, String> ruleValueMap = new HashMap<>();
for (String ruleValueKey : ruleValueGroups) {
// 检查输入是否为空
if (ruleValueKey == null || ruleValueKey.isEmpty()) {
return ruleValueMap;
}
// 分割字符串以获取键和值
String[] parts = ruleValueKey.split(Constants.COLON);
if (parts.length != 2) {
throw new IllegalArgumentException("rule_weight rule_rule invalid input format" + ruleValueKey);
}
ruleValueMap.put(Long.parseLong(parts[0]), ruleValueKey);
}
return ruleValueMap;
}
}
package cn.bugstack.infrastructure.persistent.dao; package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.Award; import cn.bugstack.infrastructure.persistent.po.Award;
import cn.bugstack.infrastructure.persistent.po.Strategy;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface IStrategyDao { public interface IStrategyDao {
List<Award> queryStrategyList(); List<Strategy> queryStrategyList();
Strategy queryStrategyByStrategyId(Long strategyId);
} }
package cn.bugstack.infrastructure.persistent.dao; package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.Award; import cn.bugstack.infrastructure.persistent.po.Award;
import cn.bugstack.infrastructure.persistent.po.StrategyRule;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface IStrategyRuleDao { public interface IStrategyRuleDao {
List<Award> queryStrategyRuleList(); List<StrategyRule> queryStrategyRuleList();
StrategyRule queryStrategyRule(StrategyRule strategyRuleReq);
String queryStrategyRuleValue(StrategyRule strategyRule);
} }
package cn.bugstack.infrastructure.persistent.po; package cn.bugstack.infrastructure.persistent.po;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
...@@ -28,4 +29,5 @@ public class Award { ...@@ -28,4 +29,5 @@ public class Award {
/** 更新时间 */ /** 更新时间 */
private Date updateTime; private Date updateTime;
} }
package cn.bugstack.infrastructure.persistent.po; package cn.bugstack.infrastructure.persistent.po;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
...@@ -11,21 +12,21 @@ import java.util.Date; ...@@ -11,21 +12,21 @@ import java.util.Date;
* @Date: 2024/11/13 20:26 * @Date: 2024/11/13 20:26
*/ */
@Data @Data
@Builder
public class Strategy { public class Strategy {
/* ID*/ /** 自增ID */
private Long id; private Long id;
/** 抽奖策略ID */
/* 抽奖策略描述*/
private Long strategyId; private Long strategyId;
/** 抽奖策略描述 */
/* 抽奖策略ID*/
private String strategyDesc; private String strategyDesc;
/** 抽奖规则模型 */
/* 创建时间*/ private String ruleModels;
/** 创建时间 */
private Date createTime; private Date createTime;
/** 更新时间 */
/* 更新时间*/
private Date updateTime; private Date updateTime;
} }
package cn.bugstack.infrastructure.persistent.po; package cn.bugstack.infrastructure.persistent.po;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
...@@ -38,4 +39,5 @@ public class StrategyAward { ...@@ -38,4 +39,5 @@ public class StrategyAward {
/** 修改时间 */ /** 修改时间 */
private Date updateTime; private Date updateTime;
} }
package cn.bugstack.infrastructure.persistent.po; package cn.bugstack.infrastructure.persistent.po;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
......
...@@ -2,9 +2,15 @@ package cn.bugstack.infrastructure.persistent.repository; ...@@ -2,9 +2,15 @@ package cn.bugstack.infrastructure.persistent.repository;
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.model.entity.StrategyRuleEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository; import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.infrastructure.persistent.dao.IStrategyAwardDao; 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.StrategyAward;
import cn.bugstack.infrastructure.persistent.po.StrategyRule;
import cn.bugstack.infrastructure.persistent.redis.IRedisService; import cn.bugstack.infrastructure.persistent.redis.IRedisService;
import cn.bugstack.types.common.Constants; import cn.bugstack.types.common.Constants;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
...@@ -24,6 +30,10 @@ import java.util.Map; ...@@ -24,6 +30,10 @@ import java.util.Map;
@Repository @Repository
public class StrategyRepository implements IStrategyRepository { public class StrategyRepository implements IStrategyRepository {
@Resource
private IStrategyDao strategyDao;
@Resource
private IStrategyRuleDao strategyRuleDao;
@Resource @Resource
private IStrategyAwardDao strategyAwardDao; private IStrategyAwardDao strategyAwardDao;
@Resource @Resource
...@@ -53,24 +63,70 @@ public class StrategyRepository implements IStrategyRepository { ...@@ -53,24 +63,70 @@ public class StrategyRepository implements IStrategyRepository {
} }
@Override @Override
public void storeStrategyAwardSearchRateTable(Long strategyId, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) { public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数 // 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + strategyId, 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 + strategyId); Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
cacheRateTable.putAll(strategyAwardSearchRateTable); cacheRateTable.putAll(strategyAwardSearchRateTable);
} }
@Override @Override
public Integer getStrategyAwardAssemble(Long strategyId, Integer rateKey) { public Integer getStrategyAwardAssemble(String key, Integer rateKey) {
return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + strategyId, rateKey); return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey);
} }
@Override @Override
public int getRateRange(Long strategyId) { public int getRateRange(Long strategyId) {
return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + strategyId); return getRateRange(String.valueOf(strategyId));
}
@Override
public int getRateRange(String key) {
return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key);
}
@Override
public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
// 优先从缓存获取
String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
StrategyEntity strategyEntity = redisService.getValue(cacheKey);
if (null != strategyEntity) return strategyEntity;
Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
strategyEntity = StrategyEntity.builder()
.strategyId(strategy.getStrategyId())
.strategyDesc(strategy.getStrategyDesc())
.ruleModels(strategy.getRuleModels())
.build();
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 String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
StrategyRule strategyRule = new StrategyRule();
strategyRule.setStrategyId(strategyId);
strategyRule.setAwardId(awardId);
strategyRule.setRuleModel(ruleModel);
return strategyRuleDao.queryStrategyRuleValue(strategyRule);
} }
} }
...@@ -3,11 +3,16 @@ package cn.bugstack.types.common; ...@@ -3,11 +3,16 @@ package cn.bugstack.types.common;
public class Constants { public class Constants {
public final static String SPLIT = ","; public final static String SPLIT = ",";
public final static String SPACE = " ";
public final static String COLON = ":";
public static class RedisKey { public static class RedisKey {
public static String STRATEGY_KEY = "big_market_strategy_key_";
public static String STRATEGY_AWARD_KEY = "big_market_strategy_award_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_TABLE_KEY = "big_market_strategy_rate_table_key_";
public static String STRATEGY_RATE_RANGE_KEY = "big_market_strategy_rate_range_key_"; public static String STRATEGY_RATE_RANGE_KEY = "big_market_strategy_rate_range_key_";
} }
......
...@@ -12,9 +12,11 @@ public enum ResponseCode { ...@@ -12,9 +12,11 @@ public enum ResponseCode {
SUCCESS("0000", "成功"), SUCCESS("0000", "成功"),
UN_ERROR("0001", "未知失败"), UN_ERROR("0001", "未知失败"),
ILLEGAL_PARAMETER("0002", "非法参数"), ILLEGAL_PARAMETER("0002", "非法参数"),
STRATEGY_RULE_WEIGHT_IS_NULL("ERR_BIZ_001", "业务异常,策略规则中 rule_weight 权重规则已适用但未配置"),
; ;
private String code; private String code;
private String info; private String info;
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册