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

添加 抽奖中规则过滤(锁定奖品)、 改写抽奖模式为责任链模式

上级 b647fac1
......@@ -28,5 +28,10 @@
where strategy_id = #{strategy_id}
</select>
<select id="queryStrategyAwardRuleModels" parameterType="java.lang.Long" resultMap="dataMap">
select rule_models from strategy_award
where strategy_id = #{strategyId} and award_id = #{awardId}
</select>
</mapper>
package cn.bugstack.test.domain;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.domain.strategy.service.rule.chain.ILogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.chain.impl.RuleWeightLogicChain;
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;
/**
* @ClassName: LogicChainTest
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/18 22:49
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class LogicChainTest {
@Resource
private IStrategyArmory strategyArmory;
@Resource
private RuleWeightLogicChain ruleWeightLogicChain;
@Resource
private DefaultChainFactory defaultChainFactory;
@Before
public void setUp() {
// 策略装配 100001、100002、100003
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
// log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100002L));
// log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100003L));
}
@Test
public void test_LogicChain_rule_blacklist() {
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("user001", 100001L);
log.info("测试结果:{}", awardId);
}
@Test
public void test_LogicChain_rule_weight() {
// 通过反射 mock 规则中的值
ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 4900L);
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("xiaofuge", 100001L);
log.info("测试结果:{}", awardId);
}
@Test
public void test_LogicChain_rule_default() {
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("xiaofuge", 100001L);
log.info("测试结果:{}", awardId);
}
}
......@@ -2,8 +2,10 @@ 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 cn.bugstack.domain.strategy.IRaffleStrategy;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.domain.strategy.service.rule.chain.impl.DefaultLogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.impl.RuleWeightLogicChain;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
......@@ -14,7 +16,6 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import javax.annotation.Resource;
import java.security.PrivateKey;
/**
* @ClassName: RaffleStrategyTest
......@@ -26,14 +27,27 @@ import java.security.PrivateKey;
@Slf4j
@RunWith(SpringRunner.class)
public class RaffleStrategyTest {
@Resource
private IStrategyArmory strategyArmory;
@Resource
private IRaffleStrategy raffleStrategy;
@Resource
private RuleWeightLogicFilter ruleWeightLogicFilter;
private RuleWeightLogicChain ruleWeightLogicChain;
@Resource
private DefaultLogicChain defaultLogicChain;
@Before
public void setUp() {
ReflectionTestUtils.setField(ruleWeightLogicFilter, "userScore", 4500L);
// 策略装配 100001、100002、100003
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
/* log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100002L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100003L));*/
// 通过反射 mock 规则中的值
ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 40500L);
ReflectionTestUtils.setField(defaultLogicChain, "userRaffleCount", 10L);
}
@Test
public void test_performRaffle() {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
......@@ -60,6 +74,24 @@ public class RaffleStrategyTest {
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}
/**
* 次数错校验,抽奖n次后解锁。100003 策略,你可以通过调整 @Before 的 setUp 方法中个人抽奖次数来验证。比如最开始设置0,之后设置10
* ReflectionTestUtils.setField(ruleLockLogicFilter, "userRaffleCount", 10L);
*/
@Test
public void test_raffle_center_rule_lock(){
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge")
.strategyId(100003L)
.build();
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}
}
package cn.bugstack.domain.strategy.service.raffle;
package cn.bugstack.domain.strategy;
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.model.valobj.StrategyAwardRuleModelVO;
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.domain.strategy.service.rule.chain.ILogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
......@@ -27,10 +28,13 @@ public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
protected IStrategyRepository repository;
// 策略调度服务 -> 只负责抽奖处理,通过新增接口的方式,隔离职责,不需要使用方关心或者调用抽奖的初始化
protected IStrategyDispatch strategyDispatch;
// 抽奖的责任链 -> 从抽奖的规则中,解耦出前置规则为责任链处理
private final DefaultChainFactory defaultChainFactory;
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) {
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory) {
this.repository = repository;
this.strategyDispatch = strategyDispatch;
this.defaultChainFactory = defaultChainFactory;
}
@Override
......@@ -42,37 +46,35 @@ public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
// 2. 策略查询
StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);
// 2. 获取抽奖责任链 - 前置规则的责任链处理
ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
// 3. 抽奖前 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = this.doCheckRaffleBeforeLogic(RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(), strategy.ruleModels());
// 3. 通过责任链获得,奖品ID
Integer awardId = logicChain.logic(userId, strategyId);
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. 查询奖品规则「抽奖中(拿到奖品ID时,过滤规则)、抽奖后(扣减完奖品库存后过滤,抽奖中拦截和无库存则走兜底)」
StrategyAwardRuleModelVO strategyAwardRuleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
// 4. 默认抽奖流程
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
// 5. 抽奖中 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionCenterEntity = this.doCheckRaffleCenterLogic(RaffleFactorEntity.builder()
.userId(userId)
.strategyId(strategyId)
.awardId(awardId)
.build(), strategyAwardRuleModelVO.raffleCenterRuleModelList());
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionCenterEntity.getCode())){
log.info("【临时日志】中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。");
return RaffleAwardEntity.builder()
.awardDesc("中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。")
.build();
}
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
protected abstract RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
protected abstract RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
}
package cn.bugstack.domain.strategy.service;
package cn.bugstack.domain.strategy;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
......
......@@ -21,5 +21,8 @@ public class RaffleFactorEntity {
private String userId;
/** 策略ID */
private Long strategyId;
/** 奖品ID */
private Integer awardId;
}
......@@ -48,12 +48,50 @@ public class RuleActionEntity<T extends RuleActionEntity.RaffleEntity> {
}
// 抽奖之中
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleCenterEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
// 抽奖之后
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleAfterEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
......
package cn.bugstack.domain.strategy.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* @ClassName: RuleLogicCheckTypeVO
......
package cn.bugstack.domain.strategy.model.valobj;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import cn.bugstack.types.common.Constants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: StrategyAwardRuleModelVO
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/18 20:39
*/
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StrategyAwardRuleModelVO {
private String ruleModels;
/**
* 获取抽奖中规则;或者使用 lambda 表达式
* <p>
* List<String> ruleModelList = Arrays.stream(ruleModels.split(Constants.SPLIT))
* .filter(DefaultLogicFactory.LogicModel::isCenter)
* .collect(Collectors.toList());
* return ruleModelList;
* <p>
* List<String> collect = Arrays.stream(ruleModelValues).filter(DefaultLogicFactory.LogicModel::isCenter).collect(Collectors.toList());
*/
public String[] raffleCenterRuleModelList() {
List<String> ruleModelList = new ArrayList<>();
String[] ruleModelValues = ruleModels.split(Constants.SPLIT);
for (String ruleModelValue : ruleModelValues) {
if (DefaultLogicFactory.LogicModel.isCenter(ruleModelValue)) {
ruleModelList.add(ruleModelValue);
}
}
return ruleModelList.toArray(new String[0]);
}
public String[] raffleAfterRuleModelList() {
List<String> ruleModelList = new ArrayList<>();
String[] ruleModelValues = ruleModels.split(Constants.SPLIT);
for (String ruleModelValue : ruleModelValues) {
if (DefaultLogicFactory.LogicModel.isAfter(ruleModelValue)) {
ruleModelList.add(ruleModelValue);
}
}
return ruleModelList.toArray(new String[0]);
}
}
......@@ -3,6 +3,7 @@ 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 cn.bugstack.domain.strategy.model.valobj.StrategyAwardRuleModelVO;
import java.util.List;
import java.util.Map;
......@@ -11,7 +12,7 @@ public interface IStrategyRepository {
List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId);
void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable);
void storeStrategyAwardSearchRateTable(String key, int rateRange, Map<Integer, Integer> strategyAwardSearchRateTable);
Integer getStrategyAwardAssemble(String key, Integer rateKey);
......@@ -24,6 +25,8 @@ public interface IStrategyRepository {
StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel);
String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel);
String queryStrategyRuleValue(Long strategyId, String ruleModel);
StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId);
}
package cn.bugstack.domain.strategy.service.annotation;
import cn.bugstack.domain.strategy.service.rule.factory.DefaultLogicFactory;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
......@@ -21,3 +22,4 @@ public @interface LogicStrategy {
}
package cn.bugstack.domain.strategy.service.raffle;
import cn.bugstack.domain.strategy.AbstractRaffleStrategy;
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 cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
......@@ -31,54 +33,38 @@ 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();
public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory) {
super(repository, strategyDispatch, defaultChainFactory);
}
// 黑名单规则优先过滤
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;
}
}
@Override
protected RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics) {
if (logics == null || 0 == logics.length) return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
// 顺序过滤剩余规则
List<String> ruleList = Arrays.stream(logics)
.filter(s -> !s.equals(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.collect(Collectors.toList());
Map<String, ILogicFilter<RuleActionEntity.RaffleCenterEntity>> logicFilterGroup = logicFactory.openLogicFilter();
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = null;
for (String ruleModel : ruleList) {
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionEntity = null;
for (String ruleModel : logics) {
ILogicFilter<RuleActionEntity.RaffleCenterEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId());
ruleMatterEntity.setAwardId(raffleFactorEntity.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());
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.chain;
import lombok.extern.slf4j.Slf4j;
/**
* @ClassName: AbstractLogicChain
* @Description: 抽奖策略责任链,判断走那种抽奖策略。如;默认抽象、权重抽奖、黑名单抽奖
* @Author: zhaoyongfeng
* @Date: 2024/11/18 22:03
*/
@Slf4j
public abstract class AbstractLogicChain implements ILogicChain {
private ILogicChain next;
@Override
public ILogicChain next() {
return next;
}
@Override
public ILogicChain appendNext(ILogicChain next) {
this.next = next;
return next;
}
protected abstract String ruleModel();
}
package cn.bugstack.domain.strategy.service.rule.chain;
/*
* @return
* @author zhaoyongfeng
* @description 抽奖策略规则责任链接口
*
* @param null
*/
public interface ILogicChain extends ILogicChainArmory{
/**
* 责任链接口
*
* @param userId 用户ID
* @param strategyId 策略ID
* @return 奖品ID
*/
Integer logic(String userId, Long strategyId);
}
package cn.bugstack.domain.strategy.service.rule.chain;
/*
* @return
* @author zhaoyongfeng
* @description 责任链装配
*
* @param null
*/
public interface ILogicChainArmory {
ILogicChain next();
ILogicChain appendNext(ILogicChain next);
}
package cn.bugstack.domain.strategy.service.rule.chain.factory;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.rule.chain.ILogicChain;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @ClassName: DefaultLogicFactory
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:05
*/
@Service
public class DefaultChainFactory {
private final Map<String, ILogicChain> logicChainGroup;
protected IStrategyRepository repository;
public DefaultChainFactory(Map<String, ILogicChain> logicChainGroup, IStrategyRepository repository) {
this.logicChainGroup = logicChainGroup;
this.repository = repository;
}
/**
* 通过策略ID,构建责任链
*
* @param strategyId 策略ID
* @return LogicChain
*/
public ILogicChain openLogicChain(Long strategyId) {
StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);
String[] ruleModels = strategy.ruleModels();
// 如果未配置策略规则,则只装填一个默认责任链
if (null == ruleModels || 0 == ruleModels.length) return logicChainGroup.get("default");
// 按照配置顺序装填用户配置的责任链;rule_blacklist、rule_weight 「注意此数据从Redis缓存中获取,如果更新库表,记得在测试阶段手动处理缓存」
ILogicChain logicChain = logicChainGroup.get(ruleModels[0]);
ILogicChain current = logicChain;
for (int i = 1; i < ruleModels.length; i++) {
ILogicChain nextChain = logicChainGroup.get(ruleModels[i]);
current = current.appendNext(nextChain);
}
// 责任链的最后装填默认责任链
current.appendNext(logicChainGroup.get("default"));
return logicChain;
}
}
package cn.bugstack.domain.strategy.service.rule.chain.impl;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
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("rule_blacklist")
public class BackListLogicChain extends AbstractLogicChain {
@Resource
private IStrategyRepository repository;
@Override
public Integer logic(String userId, Long strategyId) {
log.info("抽奖责任链-黑名单开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
// 查询规则值配置
String ruleValue = repository.queryStrategyRuleValue(strategyId, ruleModel());
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)) {
log.info("抽奖责任链-黑名单接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
}
// 过滤其他责任链
log.info("抽奖责任链-黑名单放行 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
return next().logic(userId, strategyId);
}
@Override
protected String ruleModel() {
return "rule_blacklist";
}
}
package cn.bugstack.domain.strategy.service.rule.chain.impl;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: RuleLockLogicFilter
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/18 20:25
*/
@Slf4j
@Component("default")
public class DefaultLogicChain extends AbstractLogicChain {
@Resource
protected IStrategyDispatch strategyDispatch;
@Override
public Integer logic(String userId, Long strategyId) {
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
log.info("抽奖责任链-默认处理 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
@Override
protected String ruleModel() {
return "default";
}
}
package cn.bugstack.domain.strategy.service.rule.impl;
package cn.bugstack.domain.strategy.service.rule.chain.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.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -20,66 +16,74 @@ import java.util.*;
* @Date: 2024/11/17 16:08
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT)
public class RuleWeightLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@Component("rule_weight")
public class RuleWeightLogicChain extends AbstractLogicChain {
@Resource
private IStrategyRepository repository;
public Long userScore = 4500L;
@Resource
protected IStrategyDispatch strategyDispatch;
// 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
/**
* 权重规则过滤;
* 权重责任链过滤;
* 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());
public Integer logic(String userId, Long strategyId) {
log.info("抽奖责任链-权重开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
String userId = ruleMatterEntity.getUserId();
Long strategyId = ruleMatterEntity.getStrategyId();
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
String ruleValue = repository.queryStrategyRuleValue(strategyId, ruleModel());
// 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();
}
if (null == analyticalValueGroup || analyticalValueGroup.isEmpty()) return null;
// 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】
/* 找到最后一个符合的值[如用户传了一个 5900 应该返回正确结果为 5000],如果使用 Lambda findFirst 需要注意使用 sorted 反转结果
* Long nextValue = null;
* for (Long analyticalSortedKeyValue : analyticalSortedKeys) {
* if (userScore >= analyticalSortedKeyValue){
* nextValue = analyticalSortedKeyValue;
* }
* }
*
* Long nextValue = analyticalSortedKeys.stream()
* .filter(key -> userScore >= key)
* .max(Comparator.naturalOrder())
* .orElse(null);
*/
Long nextValue = analyticalSortedKeys.stream()
.filter(key -> userScore >= key)
.sorted(Comparator.reverseOrder())
.filter(analyticalSortedKeyValue -> userScore >= analyticalSortedKeyValue)
.findFirst()
.orElse(null);
// 4. 权重抽奖
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();
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, analyticalValueGroup.get(nextValue));
log.info("抽奖责任链-权重接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
// 5. 过滤其他责任链
log.info("抽奖责任链-权重放行 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
return next().logic(userId, strategyId);
}
@Override
protected String ruleModel() {
return "rule_weight";
}
private Map<Long, String> getAnalyticalValue(String ruleValue) {
......
package cn.bugstack.domain.strategy.service.rule;
package cn.bugstack.domain.strategy.service.rule.filter;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
......
package cn.bugstack.domain.strategy.service.rule.factory;
package cn.bugstack.domain.strategy.service.rule.filter.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 cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.core.annotation.AnnotationUtils;
......@@ -14,12 +14,13 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: DefaultLogicFactory
* @Description:
* @Description: 规则工厂
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:05
* @Date: 2024/11/18 22:27
*/
@Service
public class DefaultLogicFactory {
public Map<String, ILogicFilter<?>> logicFilterMap = new ConcurrentHashMap<>();
public DefaultLogicFactory(List<ILogicFilter<?>> logicFilters) {
......@@ -39,13 +40,21 @@ public class DefaultLogicFactory {
@AllArgsConstructor
public enum LogicModel {
RULE_WIGHT("rule_weight","【抽奖前规则】根据抽奖权重返回可抽奖范围KEY"),
RULE_BLACKLIST("rule_blacklist","【抽奖前规则】黑名单规则过滤,命中黑名单则直接返回"),
RULE_LOCK("rule_lock", "【抽奖中规则】抽奖n次后,对应奖品可解锁抽奖", "center"),
RULE_LUCK_AWARD("rule_luck_award", "【抽奖后规则】抽奖n次后,对应奖品可解锁抽奖", "after"),
;
private final String code;
private final String info;
private final String type;
public static boolean isCenter(String code){
return "center".equals(LogicModel.valueOf(code.toUpperCase()).type);
}
public static boolean isAfter(String code){
return "after".equals(LogicModel.valueOf(code.toUpperCase()).type);
}
}
......
package cn.bugstack.domain.strategy.service.rule.impl;
package cn.bugstack.domain.strategy.service.rule.filter.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 cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: RuleBackListLogicFilter
* @Description: 【抽奖前规则】黑名单用户过滤规则
* @ClassName: RuleLockLogicFilter
* @Description: 用户抽奖n次后,对应奖品可解锁抽奖
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:07
* @Date: 2024/11/18 22:27
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_BLACKLIST)
public class RuleBackListLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_LOCK)
public class RuleLockLogicFilter implements ILogicFilter<RuleActionEntity.RaffleCenterEntity> {
@Resource
private IStrategyRepository repository;
@Override
public RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-黑名单 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
// 用户抽奖次数,后续完成这部分流程开发的时候,从数据库/Redis中读取
private Long userRaffleCount = 0L;
String userId = ruleMatterEntity.getUserId();
@Override
public RuleActionEntity<RuleActionEntity.RaffleCenterEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-次数锁 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
// 查询规则值配置
// 查询规则值配置;当前奖品ID,抽奖中规则对应的校验值。如;1、2、6
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();
}
long raffleCount = Long.parseLong(ruleValue);
// 用户抽奖次数大于规则限定值,规则放行
if (userRaffleCount>= raffleCount) {
return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
// 用户抽奖次数小于规则限定值,规则拦截
return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
}
}
......@@ -12,4 +12,5 @@ public interface IStrategyAwardDao {
List<StrategyAward> queryStrategyAwardListByStrategyId(Long strategyId);
String queryStrategyAwardRuleModels(StrategyAward strategyAward);
}
package cn.bugstack.infrastructure.persistent.po;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
......@@ -12,6 +14,8 @@ import java.util.Date;
* @Date: 2024/11/13 20:26
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Strategy {
......
......@@ -4,6 +4,7 @@ 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.model.valobj.StrategyAwardRuleModelVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.infrastructure.persistent.dao.IStrategyAwardDao;
import cn.bugstack.infrastructure.persistent.dao.IStrategyDao;
......@@ -63,7 +64,7 @@ public class StrategyRepository implements IStrategyRepository {
}
@Override
public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
public void storeStrategyAwardSearchRateTable(String key, int rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
// 2. 存储概率查找表
......@@ -127,6 +128,22 @@ public class StrategyRepository implements IStrategyRepository {
return strategyRuleDao.queryStrategyRuleValue(strategyRule);
}
@Override
public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
return queryStrategyRuleValue(strategyId, null, ruleModel);
}
@Override
public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId) {
StrategyAward strategyAward = new StrategyAward();
strategyAward.setStrategyId(strategyId);
strategyAward.setAwardId(awardId);
String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册