内容:用户成功登录在线考试系统后,获取一套拥有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();
}