Lottery项目流程

需求设计

抽奖整体流程图

使用脚手架工具创建项目

抽奖策略领域库表设计

  • 四张表:策略表、策略奖牌表、奖品表、奖品规则表

基础层持久化数据

  • mybatis配置
  • infrastructure层dao、po代码开发

策略概率装配

导入redisson
  • 在app层加入redisson配置类
  • 脚手架已在infrastructure层对redisson的一些方法简单封装
策略装配
  • 创建策略领域,策略装配厂,数据查询由repository(infrastructure层)实现
  • 创建StrategyRepository
  • 通过策略id从redis获取策略奖品list,null则查询数据库并存入redis,具体数据库查询由dao实现
  • 回到策略装配厂,通过策略奖品list生产抽奖hashmap并存入redis,供抽奖使用

抽奖的实现思想为以空间换时间。例:一个有100个格子的map,在其中3个格子中都填入奖品一,则对应奖品一的概率为3%,10个格子中都填入奖品二,则对应奖品二的概率为10%,

疑问:为什么要用linkedhashmap来存入奖品,直接用arraylist不好吗,而且这个具体实现方法也有问题,总是向上取整,不得溢出?

具体实现

1、获取最小概率值,概率值总和

2、最小概率值 / 概率值总和(向上取整)得到占位格总数量(尽量节省空间)

3、通过 当前奖品概率 * 占位格总数量(向上取整)得到当前奖品的占位格数,将所有占位格都放入list中

4、Collections.shuffle()对存储的奖品进行乱序操作

5、将list转为Mapper,其中map的key为1,2,3,4…..作为抽奖时get的参数,并存入redis

策略权重概率装配

整节的理解:调用抽奖接口时,依据传入的第二个参数(重载)制定不同的key,抽取redis中对应的表

抽奖流程

抽奖流程

抽奖前置规则过滤

草稿:看看测试的调用顺序
  1. @Resource注入ruleWeightLogicFilter、raffleStrategy
  2. @Before通过反射ruleWeightLogicFilter暂时修改角色的累计积分,测试累计积分解锁新奖品的功能
  3. 构建一个抽奖因子实体raffleFactorEntity(用户id、抽奖策略)
  4. raffleStrategy.performRaffle(raffleFactorEntity)执行抽奖

抽奖嘛,需要抽奖参数,我们就使用一个抽奖因子实体类来表示,最后作为抽奖的参数

具体抽奖方法的结构
  1. IRaffleStrategy抽奖策略接口
  2. AbstractRaffleStrategy implements IRaffleStrategy 抽奖策略抽象类,定义抽奖的标准流程
  3. DefaultRaffleStrategy extends AbstractRaffleStrategy 默认的抽奖策略实现
  4. 其中的关系:接口定义了抽奖方法performRaffle,抽象类实现了抽奖的过程,但是没有实现doCheckRaffleBeforeLogic(逻辑检查?)
  5. DefaultRaffleStrategy 实现doCheckRaffleBeforeLogic
performRaffle(RaffleFactorEntity raffleFactorEntity) 方法详细
  1. 参数校验

  2. 策略查询

    查到策略的规则模型

  3. 抽奖前 - 规则过滤

    1.调用doCheckRaffleBeforeLogic返回RuleActionEntity(规则动作实体)

    ​ 拿到的参数有抽奖因子实体(策略id,用户id),规则数组logics

    创建一个规则过滤器组(map)

    ​ 规则过滤器由规则工厂logicFactory(这里使用的是DefaultLogicFactory)产出,工厂设计详细在下

    ​ 处理具体过程:

    ​ 先判断是否有黑名单规则

    ​ 从规则过滤器组(map)获取对应的规则过滤器logicFilter

    ​ 将抽奖的信息打包成规则物料实体ruleMatterEntity

    ​ 执行方法logicFilter.filter(ruleMatterEntity),得到规则动作实体RuleActionEntity

    ​ 如果规则动作实体表示被接管,则直接return

    ​ 再将黑名单规则排除,过滤其他规则

    ​ 生成新的规则物料实体,调用logicFilter.filter(ruleMatterEntity)

    2.定义enum RuleLogicCheckTypeVO规则过滤校验类型值对象

    3.ruleActionEntity.getCode与RuleLogicCheckTypeVO比较

    4.判断是黑名单规则还是权重规则,进行抽奖

    ​ 判断的具体过程:

    ​ 先判断是否受规则影响,受影响则进一步判断

    ​ 再优先判断是否为黑名单,黑名单则直接返回T中的奖品

    ​ 是否有权重规则,从T中获取具体权重规则,调用之前写好的抽奖方法

  4. 默认抽奖流程

RuleActionEntity(规则动作实体)
1
2
3
4
private String code = RuleLogicCheckTypeVO.ALLOW.getCode();
private String info = RuleLogicCheckTypeVO.ALLOW.getInfo();
private String ruleModel;
private T data;

成员变量code为是否受规则影响

其中包含三个静态内部类,包括抽奖的前中后,也就对应成员变量T

规则工厂DefaultLogicFactory

收集规则生产一个规则过滤器logicFilterMap

通过构造方法将规则装入logicFilterMap

ILogicFilter(规则过滤器)

ILogicFilter其中T表示抽奖前、中、后,并且包含对应的规则

目前有两个实现类RuleBackListLogicFilter和RuleWeightLogicFilter

主要方法filter(RuleMatterEntity ruleMatterEntity)

具体的两个filter实现很简单,围绕RuleActionEntity处理

抽奖中置规则过滤

责任链模式处理抽奖规则

责任链

抽奖规则树模型结构设计

流程设计

这里有一个矛盾点需要解决。对于抽奖策略的前置规则过滤是顺序一条链的,有一个成功就可以返回。比如;黑名单抽奖、权重人群抽奖、默认抽奖,总之它只能有一种情况,所以这样的流程是适合责任链的。

FsCkqW_UZV1JbWfQeC4ShFjVU3lW

那么对于抽奖中到抽奖后的规则,它是一个非多分支情况的规则过滤。单独的责任链是不能满足的,如果是拆分开抽奖中规则和抽奖后规则分阶段处理,中间单独写逻辑处理库存操作。那么是可以实现的。但这样的方式始终不够优雅,配置化的内容较低,后续的规则开发仍需要在代码上改造。所以这里实现一版组合模式的决策树模型设计。

Fu73hjF7mbfiWWR6V6xR-9x5saaq

@Resource与@Autowired的区别

@Resource 注解和 @Autowired 注解都是在 Spring Framework 中进行依赖注入的注解,但它们之间有一些区别:

  1. 来源不同:

@Resource 注解是由 Java EE 提供的注解,它定义在 javax.annotation.Resource 包下。
@Autowired 注解是由 Spring 提供的注解,它定义在 org.springframework.beans.factory.annotation.Autowired 包下。

  1. 依赖注入策略不同:

@Resource 注解默认按照名称(你写的变量名)进行依赖注入,名称匹配失败则按类型来匹配,如果有多个具有相同类型的依赖,可以使用 name 属性指定依赖的名称。
@Autowired 注解默认按照类型进行依赖注入,如果有多个具有相同类型的依赖,可以使用 @Qualifier 注解或者 @Primary 注解指定依赖的名称或主要依赖。

  1. 兼容性不同:

@Resource 注解属于 Java EE 标准的注解,在 Java EE 环境中可以正常使用。
@Autowired 注解是 Spring Framework 提供的特定于 Spring 的注解,它可以在 Spring 环境下使用。

  1. 注解属性不同:

@Resource 注解可以添加 name 属性来指定依赖的名称,还可以添加 mappedName 属性来指定依赖的 JNDI 名称。
@Autowired 注解可以添加 required 属性来指定依赖是否必须,默认为 true。
综上所述,@Resource 注解和 @Autowired 注解在功能上有一些相似之处,但也有一些区别。如果你在 Java EE 环境中使用依赖注入,可以选择使用 @Resource 注解;如果在 Spring Framework 环境中使用依赖注入,可以选择使用 @Autowired 注解。需要根据具体的情况选择适合的注解进行依赖注入。

泛型的相关

  • 在Java中,泛型需要在定义类、方法或接口时声明。声明泛型的语法是在类名或方法名后面使用尖括号<>,并在尖括号中指定泛型参数的类型
  • <?>通配符泛型

它表示未知类型,可以用来表示任意类型。通配符泛型通常用于方法参数的声明或变量的定义,可以接受任意类型的参数例如:

1
2
3
4
5
6
7
8
9
10
11
javapublic void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}

List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");

printList(stringList);

需要注意的是,使用<?>通配符泛型时,只能对元素进行读取操作,不能对元素进行写入操作。这是因为Java无法确定通配符泛型的具体类型,所以无法保证类型安全

Java不允许直接进行泛型类型的转换,所以我们需要先转换为通配符类型,再转换为目标类型,在进行这种类型转换时,需要确保类型转换是安全的。

  • 泛型的擦除

在Java中,泛型信息只存在于代码编译阶段,在编译后会被擦除,转换为原始类型。因此,在运行时无法获取泛型类型的具体信息