集合Set实战之在线考试系统获取随机 乱序的试题

内容:用户成功登录在线考试系统后,获取一套拥有N道试题的试卷!

前提:我们的数据库中已经存储好了即将用于考试的试题.....

要求:保证每个考生获取到的试题数量相同、试题相同,但是顺序不相同,即所谓的A、B卷

传统实现:每个用户登录成功后,请求达到我们的后端接口,之后便是前往DB查询出大量的试题,然后再在代码的层次(内存)做一个随机的排序,最终返回给用户

缺陷:在高并发请求的环境下(比如同时有1000个考生登录考试),DB和内存负载压力将很大

实现:基于缓存中间件Redis具有"随机","乱序"功能特性的数据结构Set来实现!

 试卷题库:初始时存于DB中,项目启动成功后或者"往DB新增题目"时,则添加进缓存(试卷题库)

1. 创建problem表

CREATE TABLE `problem` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(150) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '问题标题',
  `choice_a` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '选项A',
  `choice_b` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '选项B',
  `is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
  `order_by` tinyint(4) DEFAULT '0' COMMENT '排序',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_title` (`title`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='问题库列表';

2. 逆向工程:entity mapper 和mapper.xml

3. 在ProblemMapper.java和ProblemMapper.xml中添加一个方法

 Set<Problem> getAll();
  <select id="getAll" resultType="com.redis.model.entity.Problem">
    select 
	    <include refid="Base_Column_List"></include>
    from 
	    problem
    where 
	    is_active = 1
    order by 
	    order_by asc;
      
  </select>

4. 创建一个ProblemService,在容器启动后初始化问题库,从数据库中获取所有试题缓存至redis的set集合中。

Constant中

public static final String RedisSetProblemsKey = "SpringBoot:Redis:Set:Problems:V1";
//问题库初始化服务-项目启动成功之后,而不是之中。
@Service
public class ProblemService implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(ProblemService.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProblemMapper problemMapper;

    @Override
    public void run(String... args) throws Exception {
        log.info("--问题数据库初始化服务 ~ 项目启动成功之后--开始做试题库的初始化");
        this.initProblemsToCache();
    }

   //将db中的试题查出出来,然后塞到缓存中的set中去
    public void initProblemsToCache(){
        try{
            //为了和数据库保持一直,在初始化之前先情况缓存
            redisTemplate.delete(Constant.RedisSetProblemsKey);
            Set<Problem> set = problemMapper.getAll();
            if(set != null && !set.isEmpty()){
                SetOperations<String, Problem> setOperations = redisTemplate.opsForSet();
                set.forEach(problem -> {
                    setOperations.add(Constant.RedisSetProblemsKey, problem);
                });
            }
        }catch (Exception e){
            log.error("--问题库初始化服务 ~ 项目启动成功之后--开始做试题库的初始化--发生异常:", e);
        }
    }
    //从缓存中获取N道试题
    public Set<Problem> getRandomProblems(final Integer total){
        Set<Problem> set = Sets.newHashSet();
        try{
            SetOperations<String, Problem> setOperations = redisTemplate.opsForSet();
            return  setOperations.distinctRandomMembers(Constant.RedisSetProblemsKey, total);
        }catch (Exception e){
            log.error("--问题库初始化服务 ~从缓存中获取N道试题--发生异常:", e);
        }
        return set;
    }
}

SetController.java

@RestController
@RequestMapping("set/problem")
public class SetController extends  AbstractController {


    @Autowired
    private SetService setService;

    //取出(不移除)随机问题库-固定数量的随机试卷题目
    @RequestMapping(value = "random", method = RequestMethod.GET)
    public BaseResponse random() {

        BaseResponse response = new BaseResponse(StatusCode.Success);
        try {
            response.setData(setService.getRandomProblems());
        } catch (Exception e) {
            log.error("--数据结构Set获取试题-发生异常:{}", e.fillInStackTrace());
            response = new BaseResponse(StatusCode.Fail.getCode(), e.getMessage());
        }
        return response;
    }

}

SetService.java

@Service
public class SetService {

    private static final Logger log = LoggerFactory.getLogger(SetService.class);

    private static final Integer problemTotal = 10;

    @Autowired
    private ProblemMapper problemMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UsrMapper usrMapper;

    @Autowired
    private ProblemService problemService;

    //从缓存中取出数量相同的、随机且乱序的试题列表
    public Set<Problem> getRandomProblems() throws Exception{
        return problemService.getRandomProblems(problemTotal);
    }
}

结果显示:每次获取到的试卷都是不同的。

管理员添加试题

把握试卷塞进缓存的时机:项目启动成功,管理员新增试题

利用的特性:随机、无序

SetController.java

//添加试题
    @RequestMapping(value = "put", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResponse putProblem(@RequestBody @Validated Problem problem, BindingResult result){
        String checkRes = ValidatorUtil.checkResult(result);
        if(StringUtils.isNotBlank(checkRes)){
            return  new BaseResponse(StatusCode.Fail.getCode(), checkRes);
        }
        BaseResponse response = new BaseResponse(StatusCode.Success);
        try{
            response.setData(setService.addProblem(problem));
            log.info("--数据结构Set添加试题:{}", problem);
        }catch (Exception e){
            log.error("--数据结构Set添加试题-异常:{}", e.getMessage());
            response = new BaseResponse(StatusCode.Fail.getCode(), e.getMessage());
        }
        return  response;
    }

SetService.java

//管理员手动添加试题至数据库
    public Integer addProblem(Problem problem) throws Exception{
        //先往db存一份数据
        problem.setId(null);
        int res = problemMapper.insertSelective(problem);
        //成功之后往cache里存入一份
        if(res > 0){
            //存在两种方式,一种是调用init方法,另外一种是存一个缓存一个
            //problemService.initProblemsToCache();
            redisTemplate.opsForSet().add(Constant.RedisSetProblemsKey,  problem);
        }
        return problem.getId();
    }

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值