Redis實戰(zhàn)(6)-數(shù)據(jù)結(jié)構(gòu)Set實戰(zhàn)之獲取隨機亂序唯一的試卷題目
作者:
修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
摘要:緩存中間件Redis擁有許多豐富、重要且有趣的數(shù)據(jù)結(jié)構(gòu),集合Set便是其中的一個佼佼者,其核心特性跟JavaSE集合體系中的Set幾乎一毛一樣,即“無序”且“唯一”,當我們向集合Set伸手要一個元素時,其底層會隨機地給我們發(fā)一個元素!本文我們將繼續(xù)給各位小伙伴介紹并實戰(zhàn)另外一種典型的業(yè)務場景~從“考試系統(tǒng)”中獲取隨機、亂序且唯一的試卷題目列表。
內(nèi)容:“考試”對于很多小伙伴來說應該并不陌生,像小學升初中考試、高中升大學考試、大學期間的各種各樣的考試、如今許多人報名學車“科目一”的考試 以及 各種培訓考證涉及的考試等等,或多或少相信大家都有經(jīng)歷過!
一份試卷,其核心就在于“題目”,對于監(jiān)考方以及出題方而言,如何降低學員考試期間的“作弊”率則是最令人頭疼的問題,不知什么時候,有位“人才”想出了一種頗有成效的方法,那就是“盡量讓每位考生拿到的試卷題目是一樣的(當然啦,題目總數(shù)是一樣)”
現(xiàn)在大部分的“在線考試系統(tǒng)”也幾乎是采取了這種方式,即考生成功登錄“考試系統(tǒng)”后,展現(xiàn)在每個考生面前的試卷題目幾乎是不一樣的 又或者 題目是一樣的,但是不同考生的題目順序卻完全是不一樣的,即有差異性!有時候細想這種方式,不得不說真是一種不錯的 用于預防考生現(xiàn)場交頭接耳作弊 的法子(真TN是個天才?。?/span>
接下來,我們將基于緩存中間件Redis的集合Set實戰(zhàn)實現(xiàn)“在線考試系統(tǒng)”中這一典型的業(yè)務場景,即獲取隨機、唯一且亂序的試卷題目列表,其核心業(yè)務流程如下圖所示:
從該業(yè)務流程圖中,我們將主要做兩件事情:
A、項目啟動后從數(shù)據(jù)庫DB中拉出所有的試卷題目列表,并將其塞入緩存Set集合中
B、開發(fā)一請求方法,用于從緩存中獲取隨機、無序且唯一的 N 道試題,并將其返回給當前成功登錄考試系統(tǒng)的考生!
(1)工欲善其事,同樣也是必先利其器,首先我們需要建立一數(shù)據(jù)庫表“試卷題目表”,用于存儲管理員新增的題目信息,其DDL如下所示:
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 DEFAULT CHARSET=utf8 COMMENT='試卷題目表';
采用Mybatis逆向工程或者代碼生成器可生成該數(shù)據(jù)庫表的Entity實體類、Mapper操作接口以及用于操作動態(tài)SQL的Mapper.xml。除此之外,我們還在該數(shù)據(jù)庫表中新增了一系列的題目(算是測試用例),如下圖所示:
(2)緊接著,我們開發(fā)一個 ProblemService服務類,實現(xiàn)“項目啟動后前往數(shù)據(jù)庫DB拉取試卷題目列表,并將其塞入緩存集合Set中去”!同時,也在該服務類ProblemService中實現(xiàn)“從緩存集合Set中獲取隨機、亂序且唯一的N道題目列表”,其完整源代碼如下所示:
/**試卷題目服務
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
**/
@Service
public class ProblemService {
private static final Logger log= LoggerFactory.getLogger(ProblemService.class);
@Autowired
private ProblemMapper problemMapper;
@Autowired
private RedisTemplate redisTemplate;
//TODO:項目啟動拉取出數(shù)據(jù)庫中的題庫,并塞入緩存Set集合中
@PostConstruct
public void init(){
initDBToCache();
}
//TODO:拉取出數(shù)據(jù)庫中的所有題目列表,并塞入緩存Set集合中
private void initDBToCache(){
try {
//redisTemplate.delete(Arrays.asList(Constant.RedisSetProblemKey,Constant.RedisSetProblemsKey));
SetOperations<String,Problem> setOperations=redisTemplate.opsForSet();
Set<Problem> set=problemMapper.getAll();
if (set!=null && !set.isEmpty()){
set.forEach(problem -> setOperations.add(Constant.RedisSetProblemKey,problem));
set.forEach(problem -> setOperations.add(Constant.RedisSetProblemsKey,problem));
}
}catch (Exception e){
log.error("項目啟動拉取出數(shù)據(jù)庫中的題庫,并塞入緩存Set集合中-發(fā)生異常:",e.fillInStackTrace());
}
}
//TODO:從緩存中獲取隨機的、亂序的試題列表
public Set<Problem> getRandomEntitys(Integer total){
Set<Problem> problems=Sets.newHashSet();
try {
SetOperations<String,Problem> setOperations=redisTemplate.opsForSet();
problems=setOperations.distinctRandomMembers(Constant.RedisSetProblemsKey,total);
}catch (Exception e){
log.error("從緩存中獲取隨機的、亂序的試題列表-發(fā)生異常:",e.fillInStackTrace());
}
return problems;
}
}
(3)之后,我們需要在SetController中開發(fā)一個請求方法,實現(xiàn)“考生成功登錄后,從緩存集合Set中獲取隨機、無序且唯一的N道題目”,其完整源代碼如下所示:
//TODO:取出(不移除)隨機問題庫-固定數(shù)量的隨機試卷題目
@RequestMapping(value = "problems/random",method = RequestMethod.GET)
public BaseResponse getRandomProblems(@RequestParam Integer total){
if (total<=0){
return new BaseResponse(StatusCode.InvalidParams);
}
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
response.setData(setService.getRandomProblems(total));
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
其中,setService.getRandomProblems(total) 即為“從緩存集合Set中獲取隨機的total道題目列表”,其源代碼如下所示:
//TODO:從問題庫中取出固定數(shù)量的隨機的、亂序試題列表
public Set<Problem> getRandomProblems(Integer total) throws Exception{
return problemService.getRandomEntitys(total);
}
最終你會發(fā)現(xiàn)“前面巴拉巴拉說了一大堆,核心重點就只是調(diào)用了SetOperations”中的這一API/方法:
setOperations.distinctRandomMembers(Constant.RedisSetProblemsKey,total);
即對應著Redis中Set的SRANDMEMBER命令!該命令的作用以及用法我們在上一篇文章中已經(jīng)介紹過了,如下圖所示:
最后,我們打開Postman對其進行一波測試,如下幾張圖所示:
好了,本篇文章我們就介紹到這里了,建議各位小伙伴一定要照著文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!
對Redis相關技術棧以及實際應用場景實戰(zhàn)感興趣的小伙伴可以前往Debug搭建的技術社區(qū)的課程中心進行學習觀看:https://www.fightjava.com/web/index/course/detail/12 !
其他相關的技術,感興趣的小伙伴可以關注底部Debug的技術公眾號,或者加Debug的微信,拉你進“微信版”的真正技術交流群!一起學習、共同成長!
補充:
1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:
https://gitee.com/steadyjack/SpringBootRedis
2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:https://www.fightjava.com/web/index/course/detail/12
3、關注一下Debug的技術微信公眾號,最新的技術文章、課程以及技術專欄將會第一時間在公眾號發(fā)布哦!