diff --git a/xfg-frame-archetype-lite-app/src/main/resources/mybatis/mapper/strategy_rule_mapper.xml b/xfg-frame-archetype-lite-app/src/main/resources/mybatis/mapper/strategy_rule_mapper.xml index f160d79406c368130a8559f118816bbb0e182ba4..6c8f8bb4f77018284f68edba08865e65cdf05ec7 100644 --- a/xfg-frame-archetype-lite-app/src/main/resources/mybatis/mapper/strategy_rule_mapper.xml +++ b/xfg-frame-archetype-lite-app/src/main/resources/mybatis/mapper/strategy_rule_mapper.xml @@ -24,5 +24,12 @@ from strategy_rule where strategy_id = #{strategyId} and rule_model = #{ruleModel} + diff --git a/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/ApiTest.java b/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/ApiTest.java index 9bc1a201d91950bc85a3ad92354618d6b3c5f2a0..181edd5a6e9667d8a2839014f5617e8a1926fb09 100644 --- a/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/ApiTest.java +++ b/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/ApiTest.java @@ -16,4 +16,11 @@ public class ApiTest { log.info("测试完成"); } + @Test + public void test1() { + String testSplit = "abc 123"; + String[] s = testSplit.split("d"); + System.out.println(s[0]); + } + } diff --git a/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/domain/RaffleStrategyTest.java b/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/domain/RaffleStrategyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1595037998b2dd9d88127d762c17ed47d0bc537f --- /dev/null +++ b/xfg-frame-archetype-lite-app/src/test/java/cn/bugstack/test/domain/RaffleStrategyTest.java @@ -0,0 +1,65 @@ +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; + +/** + * @author Fuzhengwei bugstack.cn @小傅哥 + * @description 抽奖策略测试 + * @create 2024-01-06 13:28 + */ +@Slf4j +@RunWith(SpringRunner.class) +@SpringBootTest +public class RaffleStrategyTest { + + @Resource + private IRaffleStrategy raffleStrategy; + + @Resource + private RuleWeightLogicFilter ruleWeightLogicFilter; + + @Before + public void setUp() { + ReflectionTestUtils.setField(ruleWeightLogicFilter, "userScore", 40500L); + } + + @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)); + } + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/AwardEntity.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/AwardEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..d78307cdd8059916fc0ee34155a3f31096799bb3 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/AwardEntity.java @@ -0,0 +1,25 @@ +package cn.bugstack.domain.strategy.model.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: 策略结果实体 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity + * @DATE: 2024/4/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AwardEntity { + + /** 用户ID */ + private String userId; + /** 奖品ID */ + private Integer awardId; + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleAwardEntity.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleAwardEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..ca96da47c1fa83c528a3d98cbd411304aa0c3070 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleAwardEntity.java @@ -0,0 +1,44 @@ +package cn.bugstack.domain.strategy.model.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: 抽奖奖品实体 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity + * @DATE: 2024/4/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RaffleAwardEntity { + + /** + * 策略Id + */ + private Long strategyId; + + /** + * 奖品ID + */ + private Integer awardId; + + /** + * 奖品对接标识 - 每一个都是一个对应的发奖策略 + */ + private String awardKey; + + /** + * 奖品配置信息 + */ + private String awardConfig; + + /** + * 奖品内容描述 + */ + private String awardDesc; +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleFactorEntity.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleFactorEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..04c2a2078bbe53bd07c33865a3e19f7aab551074 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RaffleFactorEntity.java @@ -0,0 +1,25 @@ +package cn.bugstack.domain.strategy.model.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: 抽奖因子实体 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity + * @DATE: 2024/4/12 + */ + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RaffleFactorEntity { + + /** 用户ID */ + private String userId; + /** 策略ID */ + private Long strategyId; +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleActionEntity.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleActionEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..d15af889d74abb750a35a559cee5b452f640af45 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleActionEntity.java @@ -0,0 +1,59 @@ +package cn.bugstack.domain.strategy.model.entity; + +import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO; +import lombok.*; + +/** + * @description: 规则动作实体 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity + * @DATE: 2024/4/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RuleActionEntity { + + 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 { + + } +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleMatterEntity.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleMatterEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..770126b766b01fe7676d2b3a37e9473d70901ac0 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/entity/RuleMatterEntity.java @@ -0,0 +1,23 @@ +package cn.bugstack.domain.strategy.model.entity; + +import lombok.Data; + +/** + * @description: 规则物料实体对象,用于过滤规则的必要参数信息。 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity + * @DATE: 2024/4/12 + */ +@Data +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; + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/valobj/RuleLogicCheckTypeVO.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/valobj/RuleLogicCheckTypeVO.java new file mode 100644 index 0000000000000000000000000000000000000000..0c3735ffb204a60ba86ff36201a7065db9d7a319 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/model/valobj/RuleLogicCheckTypeVO.java @@ -0,0 +1,23 @@ +package cn.bugstack.domain.strategy.model.valobj; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @description: 规则过滤校验类型值对象 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.model.entity.valobj + * @DATE: 2024/4/12 + */ +@Getter +@AllArgsConstructor +public enum RuleLogicCheckTypeVO { + + ALLOW("0000", "放行;执行后续的流程,不受规则引擎影响"), + TAKE_OVER("0001","接管;后续的流程,受规则引擎执行结果影响"), + ; + + private final String code; + private final String info; + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java index d0908f65aa14649979a548576955427d069c5d0e..4e5d63397ec4a3cc93c72af8d0b462ceb215b93e 100644 --- a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java @@ -67,4 +67,13 @@ public interface IStrategyRepository { * @return */ int getRateRange(String key); + + /** + * 查询策略规则值 + * @param strategyId 策略id + * @param awardId 奖品id + * @param ruleModel 规则模型 + * @return + */ + String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel); } diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/IRaffleStrategy.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/IRaffleStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..19eb4fb968ed8f34c0b47a93249171b600211edb --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/IRaffleStrategy.java @@ -0,0 +1,20 @@ +package cn.bugstack.domain.strategy.service; + +import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity; +import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity; + +/** + * @description: 抽奖策略接口 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service + * @DATE: 2024/4/11 + */ +public interface IRaffleStrategy { + + /** + * 执行抽奖:用抽奖影子入参,执行抽奖计算,返回奖品信息 + * @param raffleFactorEntity 抽奖因子实体 + * @return 抽奖的奖品 + */ + RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity); +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/annotation/LogicStrategy.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/annotation/LogicStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..7a5d9ea7ff771da5f99b815e44d013ae8500d207 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/annotation/LogicStrategy.java @@ -0,0 +1,21 @@ +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; + +/** + * @author Fuzhengwei bugstack.cn @小傅哥 + * @description 策略自定义枚举 + * @create 2023-12-31 11:29 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface LogicStrategy { + + DefaultLogicFactory.LogicModel logicMode(); + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/AbstractRaffleStrategy.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/AbstractRaffleStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..bdba2ba7badd4ea312ad9cc64d4fbb80d14ba01f --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/AbstractRaffleStrategy.java @@ -0,0 +1,86 @@ +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.domain.strategy.service.rule.impl.RuleBackListLogicFilter; +import cn.bugstack.types.enums.ResponseCode; +import cn.bugstack.types.exception.AppException; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * @description: + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.raffle + * @DATE: 2024/4/12 + */ +@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 = this.doCheckRaffleBeforeLogic(RaffleFactorEntity.builder() + .userId(userId) + .strategyId(strategyId) + .build(), strategy.ruleModels()); + log.info("ruleActionEntity:{}", JSON.toJSONString(ruleActionEntity)); + + 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())) { + // 权重根据返回的信息进行抽奖 + System.out.println("权重抽奖"); + RuleActionEntity.RaffleBeforeEntity raffleBeforeEntity = ruleActionEntity.getData(); + String ruleWeightValueKey = raffleBeforeEntity.getRuleWeightValueKey(); + Integer awardId = strategyDispatch.getRandomAwardId(strategyId,ruleWeightValueKey); + return RaffleAwardEntity.builder() + .awardId(awardId) + .build(); + } + } + + // 4. 默认抽奖流程 + System.out.println("默认抽奖"); + Integer awardId = strategyDispatch.getRandomAwardId(strategyId); + + return RaffleAwardEntity.builder() + .awardId(awardId) + .build(); + } + + protected abstract RuleActionEntity doCheckRaffleBeforeLogic(RaffleFactorEntity factorEntity, String... logics); +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/DefaultRaffleStrategy.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/DefaultRaffleStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..9ae0dcb86f29f64f87aafd5928efb0fa3b0d2ac4 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/raffle/DefaultRaffleStrategy.java @@ -0,0 +1,88 @@ +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 com.alibaba.fastjson.JSON; +import com.sun.deploy.security.ruleset.RuleAction; +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; + +/** + * @description: + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.raffle + * @DATE: 2024/4/12 + */ +@Slf4j +@Service +public class DefaultRaffleStrategy extends AbstractRaffleStrategy{ + + @Resource + private DefaultLogicFactory logicFactory; + + public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) { + super(repository, strategyDispatch); + } + @Override + protected RuleActionEntity doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics) { + + log.info("raffleFactorEntity:{},logics:{}", JSON.toJSONString(raffleFactorEntity),logics); + Map> 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 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 = logicFilter.filter(ruleMatterEntity); + if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) { + return ruleActionEntity; + } + } + + // 顺序过滤剩余规则 + List ruleList = Arrays.stream(logics) + .filter(str-> !str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())) + .collect(Collectors.toList()); + + RuleActionEntity ruleActionEntity = null; + for (String ruleModel : ruleList) { + ILogicFilter logicFilter = logicFilterGroup.get(ruleModel); + RuleMatterEntity ruleMatterEntity = new RuleMatterEntity(); + ruleMatterEntity.setUserId(raffleFactorEntity.getUserId()); + // todo 数据不对 + ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId()); + ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId()); + ruleMatterEntity.setRuleModel(ruleModel); + ruleActionEntity = logicFilter.filter(ruleMatterEntity); + log.info("ruleActionEntity:{}",JSON.toJSONString(ruleActionEntity)); + log.info("抽奖前规则过滤 userId: {} ruleModel: {} code: {} info: {}", raffleFactorEntity.getUserId(), ruleModel, ruleActionEntity.getCode(), ruleActionEntity.getInfo()); + if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) { + return ruleActionEntity; + } + } + + return ruleActionEntity; + } +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/ILogicFilter.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/ILogicFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..f7dc29c8b249f1be412bf0b4ebb9820a3485928c --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/ILogicFilter.java @@ -0,0 +1,16 @@ +package cn.bugstack.domain.strategy.service.rule; + +import cn.bugstack.domain.strategy.model.entity.RuleActionEntity; +import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity; + +/** + * @description: 抽奖规则过滤接口 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.rule + * @DATE: 2024/4/12 + */ +public interface ILogicFilter { + + RuleActionEntity filter(RuleMatterEntity ruleMatterEntity); + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/factory/DefaultLogicFactory.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/factory/DefaultLogicFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..4cabdff5a2d6b7eced136afed89700db3e695ef6 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/factory/DefaultLogicFactory.java @@ -0,0 +1,53 @@ +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; + +/** + * @description: 规则工厂 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.rule.factory + * @DATE: 2024/4/12 + */ +@Service +public class DefaultLogicFactory { + + public Map> logicFilterMap = new ConcurrentHashMap<>(); + + public DefaultLogicFactory(List> logicFilters) { + logicFilters.forEach(logic -> { + LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class); + if (null != strategy) { + logicFilterMap.put(strategy.logicMode().getCode(), logic); + } + }); + } + + public Map> openLogicFilter() { + return (Map>) (Map) logicFilterMap; + } + + @Getter + @AllArgsConstructor + public enum LogicModel { + + RULE_WIGHT("rule_weight","【抽奖前规则】根据抽奖权重返回可抽奖范围KEY"), + RULE_BLACKLIST("rule_blacklist","【抽奖前规则】黑名单规则过滤,命中黑名单则直接返回"), + + ; + + private final String code; + private final String info; + + } + +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleBackListLogicFilter.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleBackListLogicFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..934bd7c3b62ba2c7ac6df8c6112d286af1728780 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleBackListLogicFilter.java @@ -0,0 +1,65 @@ +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; + +/** + * @description: [抽奖前规则]黑名单用户过滤规则 + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.rule.impl + * @DATE: 2024/4/12 + */ +@Slf4j +@Component +@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_BLACKLIST) +public class RuleBackListLogicFilter implements ILogicFilter { + + @Resource + private IStrategyRepository repository; + + @Override + public RuleActionEntity filter(RuleMatterEntity ruleMatterEntity) { + + log.info("规则过滤-黑名单 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel()); + + String userId = ruleMatterEntity.getUserId(); + + // 100:user001,user002,user003 + // 查询规则值配置 + 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.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.builder() + .code(RuleLogicCheckTypeVO.ALLOW.getCode()) + .info(RuleLogicCheckTypeVO.ALLOW.getInfo()) + .build(); + } +} diff --git a/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleWeightLogicFilter.java b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleWeightLogicFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..a8a51a7d214a9af9bf20ebbbf984cb823eaf4522 --- /dev/null +++ b/xfg-frame-archetype-lite-domain/src/main/java/cn/bugstack/domain/strategy/service/rule/impl/RuleWeightLogicFilter.java @@ -0,0 +1,102 @@ +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.*; + +/** + * @description: [抽奖前规则] 根据抽奖鉴权重返可抽奖范围key + * @author: hdr + * @PACKAGE_NAME: cn.bugstack.domain.strategy.service.rule.impl + * @DATE: 2024/4/12 + */ +@Slf4j +@Component +@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT) +public class RuleWeightLogicFilter implements ILogicFilter { + + @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 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 analyticalValueGroup = getAnalyticalValue(ruleValue); + if (null == analyticalValueGroup || analyticalValueGroup.isEmpty()) { + return RuleActionEntity.builder() + .code(RuleLogicCheckTypeVO.ALLOW.getCode()) + .info(RuleLogicCheckTypeVO.ALLOW.getInfo()) + .build(); + } + + // 2. 转换Keys值,并默认排序 + List 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.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.builder() + .code(RuleLogicCheckTypeVO.ALLOW.getCode()) + .info(RuleLogicCheckTypeVO.ALLOW.getInfo()) + .build(); + } + + private Map getAnalyticalValue(String ruleValue) { + String[] ruleValueGroups = ruleValue.split(Constants.SPACE); + Map ruleValueMap = new HashMap<>(); + for (String ruleValueGroup : ruleValueGroups) { + // 检查驶入是否为空 + if (ruleValueGroup == null || ruleValueGroup.isEmpty()) { + return ruleValueMap; + } + + // 分割字符串以获取键和值 + String[] parts = ruleValueGroup.split(Constants.COLON); + if (parts.length < 2) { + throw new IllegalArgumentException("rule_weight rule_rule invalid input format" + ruleValueGroup); + } + ruleValueMap.put(Long.parseLong(parts[0]),ruleValueGroup); + } + return ruleValueMap; + } +} diff --git a/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyRuleDao.java b/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyRuleDao.java index d37bbe74127db6dd5c6fe1ad15721b6c98749274..1ac180f6788beb5870f49e5f0c97846843a09328 100644 --- a/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyRuleDao.java +++ b/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyRuleDao.java @@ -21,4 +21,11 @@ public interface IStrategyRuleDao { * @return */ StrategyRule queryStrategyRule(StrategyRule strategyRuleReq); + + /** + * 查询规则值 + * @param strategyRule + * @return + */ + String queryStrategyRuleValue(StrategyRule strategyRule); } diff --git a/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java b/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java index 40069d7d98df4b0201a404dc39e7da9f5c4eb34a..6df15a91cbe5df65ed06472d673eb966f5705f87 100644 --- a/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java +++ b/xfg-frame-archetype-lite-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java @@ -143,4 +143,13 @@ public class StrategyRepository implements IStrategyRepository { return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key); } + @Override + public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) { + StrategyRule strategyRule = new StrategyRule(); + strategyRule.setStrategyId(strategyId); + strategyRule.setRuleModel(ruleModel); + strategyRule.setAwardId(awardId); + return strategyRuleDao.queryStrategyRuleValue(strategyRule); + } + }