当前位置: 首页 > news >正文

微服务的编程测评系统16-用户答题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 用户答题
    • 1.1 用户刷题
    • 1.2 详细信息
    • 1.3 上一题下一题
    • 1.4 竞赛中答题-获取第一个题目
    • 1.4 竞赛中答题-题目切换
    • 1.4 竞赛中答题-提交代码
  • 2. 判题功能
    • 2.1 逻辑梳理
  • 总结


前言

1. 用户答题

1.1 用户刷题

三个答题–》刷题,竞赛已经开赛报名竞赛已经结束练习竞赛

刷题:先根据questionId获取详细信息

1.2 详细信息

后端:

    @GetMapping("/detail")public  R<QuestionDetailVO> detail(Long questionId){log.info("获取竞赛详细信息,questionId:{}",questionId);return R.ok(questionService.detail(questionId));}
@Data
public class QuestionDetailVO extends QuestionVO{private Long timeLimit;private Long spaceLimit;private String content;private String defaultCode;
}

我们先去es中获取数据,如果没有数据的话,就从数据库中获取,并刷新es

    @Overridepublic QuestionDetailVO detail(Long questionId) {QuestionES questionES = questionRepository.findById(questionId).orElse(null);QuestionDetailVO questionDetailVO = new QuestionDetailVO();if(questionES!=null){BeanUtil.copyProperties(questionES,questionDetailVO);return questionDetailVO;}Question question = questionMapper.selectById(questionId);if(question==null){throw new ServiceException(ResultCode.QUESTION_ID_NOT_EXIST);}BeanUtil.copyProperties(question,questionDetailVO);refreshQuestionEs();return questionDetailVO;}

这样就可以了

前端:要先引入编译器的组件

npm install ace-builds@1.4.13

question.vue中

function goQuestTest(questionId) {router.push(`/c-oj/anwser?questionId=${questionId}`)
}
export function questionDetailService(questionId) {return service({url: "/question/detail",method: "get",params: {questionId},});
}
<template><div class="page praticle-page flex-col"><div class="box_1 flex-row"><div class="group_1 "><img class="label_4" src="@/assets/ide/liebiao.png" /><span>精选题库</span></div><div class="group_2"><el-button type="primary" plain @click="submitQuestion">提交代码</el-button></div><span class="ide-back" @click="goBack()">返回</span></div><div class="box_8 flex-col"><div class="group_12 flex-row justify-between"><div class="image-wrapper_1 flex-row"><img class="thumbnail_2" src="@/assets/ide/xiaobiaoti.png" /><div class="question-nav"><span>题⽬描述</span></div><div class="question-nav" @click="preQuestion"><el-icon><span>上⼀题</span><ArrowLeft /></el-icon></div><div class="question-nav" @click="nextQuestion"><el-icon><ArrowRight /><span>下⼀题</span></el-icon></div></div><div class="image-wrapper_2 flex-row"><img class="image_1" src="@/assets/ide/daima.png" />代码</div></div><div class="group_13 flex-row justify-between"><div class="box_3 flex-col"><span class="question-title">{{ questionDetail.title }}</span><span class="question-limit"><div v-if="questionDetail.difficulty === 1">题⽬难度:简单 时间限制:{{ questionDetail.timeLimit }} ms 空间限制:{{questionDetail.spaceLimit }} 字节</div><div v-if="questionDetail.difficulty === 2">题⽬难度:中等 时间限制:{{ questionDetail.timeLimit }} ms 空间限制:{{questionDetail.spaceLimit }} 字节</div><div v-if="questionDetail.difficulty === 3">题⽬难度:困难 时间限制:{{ questionDetail.timeLimit }} ms 空间限制:{{questionDetail.spaceLimit }} 字节</div></span><span class="question-content" v-html="questionDetail.content"></span></div><div class="group_14 flex-col"><div class="group_8 flex-col"><codeEditor ref="defaultCodeRef" @update:value="handleEditorContent"></codeEditor></div><div class="code-result flex-row"><img class="code-result-image" src="@/assets/ide/codeResult.png" /><span class="code-result-content">执⾏结果</span></div><div class="group_15 flex-row"><div class="section_1 flex-row"><div class="section_3 flex-col"><div class="text-wrapper_2 flex-row justify-between"><span class="text_1 warning">请先提交代码</span></div></div></div></div></div></div></div></div>
</template>
<script setup>
import { reactive, ref } from "vue"
import codeEditor from "@/components/CodeEditor.vue"
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import router from "@/router";
import { getQuestionListService } from "@/apis/question";
function goBack() {router.go(-1);
}</script>
const questionDetail  = reactive({});
let questionId = useRoute().query.questionId;
const defaultCodeRef = ref();//加载到编辑器中
async function getQuestionDetail(){const res = await questionDetailService(questionId);Object.assign(questionDetail,res.data);defaultCodeRef.value.setAceCode(questionDetail.defaultCode)
}
getQuestionDetail()

这样就成功了,我们就不拷贝css代码了

在这里插入图片描述

1.3 上一题下一题

    @GetMapping("/preQuestion")public  R<String> preQuestion(Long questionId){log.info("获取该题目的上一题ID,questionId:{}",questionId);return R.ok(questionService.preQuestion(questionId));}@GetMapping("/nextQuestion")public  R<String> nextQuestion(Long questionId){log.info("获取该题目的下一题ID,questionId:{}",questionId);return R.ok(questionService.nextQuestion(questionId));}

我们将题目的排列顺序存储到redis中,所以先从redis中获取,没有就更新数据

在redisService中增加方法

    public <T>  Long indexOfForList(final  String key ,T value){return redisTemplate.opsForList().indexOf(key,value);}public <T>  T indexForList(final  String key ,long index,Class<T> clazz ){Object o = redisTemplate.opsForList().index(key, index);return  JSON.parseObject(String.valueOf(o), clazz);}

在增加一个QuestionCacheManager来操作redis


@Component
public class QuestionCacheManager {@Autowiredprivate RedisService redisService;@Autowiredprivate QuestionMapper questionMapper;public Long getListSize() {return redisService.getListSize(CacheConstants.QUESTION_ORDER_LIST);}public void refreshQuestionOrderListCache() {List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().orderByDesc(Question::getCreateTime));if(CollectionUtil.isEmpty(questionList)){return;}List<Long> list = questionList.stream().map(Question::getQuestionId).toList();redisService.rightPushAll(CacheConstants.QUESTION_ORDER_LIST,list);}public Long preQuestion(Long questionId) {Long index = redisService.indexOfForList(CacheConstants.QUESTION_ORDER_LIST, questionId);if(index==0){throw new ServiceException(ResultCode.FIRST_QUESTION);}return redisService.indexForList(CacheConstants.QUESTION_ORDER_LIST,index-1, Long.class);}public Long nextQuestion(Long questionId) {Long index = redisService.indexOfForList(CacheConstants.QUESTION_ORDER_LIST, questionId);Long total = redisService.getListSize(CacheConstants.QUESTION_ORDER_LIST);if(index==total-1){throw new ServiceException(ResultCode.FINALLY_QUESTION);}return redisService.indexForList(CacheConstants.QUESTION_ORDER_LIST,index+1, Long.class);}
}
    @Overridepublic String preQuestion(Long questionId) {Long listSize = questionCacheManager.getListSize();if(listSize==null||listSize==0){questionCacheManager.refreshQuestionOrderListCache();}return questionCacheManager.preQuestion(questionId).toString();}@Overridepublic String nextQuestion(Long questionId) {Long listSize = questionCacheManager.getListSize();if(listSize==null||listSize==0){questionCacheManager.refreshQuestionOrderListCache();}return questionCacheManager.nextQuestion(questionId).toString();}

这样就成功了

然后是前端

export function preQuestionService(questionId) {return service({url: "/question/preQuestion",method: "get",params: {questionId},});
}export function nextQuestionService(questionId) {return service({url: "/question/nextQuestion",method: "get",params: {questionId},});
}
async function preQuestion(){const res = await preQuestionService(questionId);questionId = res.data;await getQuestionDetail()
}async function nextQuestion(){const res = await nextQuestionService(questionId);questionId = res.data;await getQuestionDetail()
}

然后就是管理员新增题目和删除题目的时候,要从redis中删除序列

@Component
public class QuestionCacheManager {@Autowiredprivate RedisService redisService;public void addQuestionOrderUpdate(Long questionId){redisService.leftPushForList(CacheConstants.QUESTION_ORDER_LIST,questionId);}public void deleteQuestionOrderUpdate(Long questionId){redisService.removeForList(CacheConstants.QUESTION_ORDER_LIST,questionId);}
}

这样就OK了

1.4 竞赛中答题-获取第一个题目

前面1.1~1.3都是用户自己刷题

第一个是在未完赛中答题
一个是在已经结束的竞赛中答题

前端页面都是一样的,只有一个不一样,就是竞赛里面有倒计时,还有提交竞赛按钮,还有竞赛标题

我们先设计从竞赛中获取第一个题目—》用redis存储竞赛的题目顺序–》和上面设计的redis存储练习题目顺序是一样的

    @GetMapping("/getFirstQuestion")public R<String> getFirstQuestion(Long examId){log.info("获取竞赛的第一个题目ID:examId:{}",examId);return R.ok(examService.getFirstQuestion(examId));}
    public static final String EXAM_QUESTION_ORDER_LIST_EXAMID = "exam:question:order:list:";

在ExamCacheManager增加方法

    /// 竞赛中的题目顺序缓存public Long getExamQuestionOrderListSize(Long examId) {return redisService.getListSize(getExamQuestionOrderListKey(examId));}private String getExamQuestionOrderListKey(Long examId) {return CacheConstants.EXAM_QUESTION_ORDER_LIST_EXAMID + examId;}public void refreshExamQuestionOrderListCache(Long examId) {List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId).orderByAsc(ExamQuestion::getQuestionOrder));List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();redisService.rightPushAll(getExamQuestionOrderListKey(examId),questionIdList);long timesExpiredTime = ChronoUnit.SECONDS.between(LocalDateTime.now(),LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));redisService.expire(getExamQuestionOrderListKey(examId),timesExpiredTime,TimeUnit.SECONDS);}public Long getFirstExamQuestion(Long examId) {return redisService.indexForList(getExamQuestionOrderListKey(examId),0,Long.class);}

refreshExamQuestionOrderListCache中把exam中的questionList的redis设置了有效时间为当天,因为一个比赛一般就是只有当天,而且这样可以节省redis内存

    @Overridepublic String getFirstQuestion(Long examId) {Long listSize = examCacheManager.getExamQuestionOrderListSize(examId);if(listSize==null||listSize==0){examCacheManager.refreshExamQuestionOrderListCache(examId);}return examCacheManager.getFirstExamQuestion(examId).toString();}

这样我们就成功了
然后就是redis的删除那些了
就是在竞赛中增加题目,要修改redis吗
注意这个在竞赛中获取题目列表在redis中,的前提是这个竞赛已经发布了的,然后就是发布的竞赛不能进行增加删除题目,所以redis中的内容不用修改
撤消发布也,没有必要删除缓存
因为撤消发布了说明还没有开始竞赛,那么就不会产生缓存,只有用户访问第一个竞赛才会产生缓存
然后就是前端代码了

function goExam(exam) {router.push(`/c-oj/anwser?examId=${exam.examId}&examTitle=${exam.title}&examEndTime=${exam.endTime}`)
}
                <span>{{ examTitle ? examTitle : 精选题库 }}</span><el-countdown v-if="examEndTime && new Date() < new Date(examEndTime)" class="exam-time-countdown"@finish="handleCountdownFinish" title="距离竞赛结束还有:" :value="new Date(examEndTime)" />

倒计时我们用的是elementplus中的 Statistic统计组件
在这里插入图片描述
value就是目标时间,就是endTime
在这里插入图片描述
finish就是倒计时结束事件

使用new Date(examEndTime)的原因是examEndTime的格式不对,可以用examEndTime来格式化一个时间对象

export function getFirstExamQuestionService(examId) {return service({url: "/exam/getFirstQuestion",method: "get",params: {examId},});
}
let examId = useRoute().query.examId;
let examTitle = useRoute().query.examTitle;
let examEndTime = useRoute().query.examEndTime;
async function getQuestionDetail() {if(examId){const res2 = await getFirstExamQuestionService(examId);questionId = res2.data;}const res = await questionDetailService(questionId);Object.assign(questionDetail, res.data);defaultCodeRef.value.setAceCode(questionDetail.defaultCode)
}
function handleCountdownFinish(){ElMessage.info("竞赛时间结束!!")router.push("/c-oj/home/exam")
}

在这里插入图片描述
这样就成功了

1.4 竞赛中答题-题目切换

    @GetMapping("/preQuestion")public R<String> preQuestion(Long examId,Long questionId){log.info("获取竞赛中的题目的上一题ID:examId:{},questionId:{}",examId,questionId);return R.ok(examService.preQuestion(examId,questionId));}@GetMapping("/nextQuestion")public R<String> nextQuestion(Long examId,Long questionId){log.info("获取竞赛中的题目的下一题ID:examId:{},questionId:{}",examId,questionId);return R.ok(examService.nextQuestion(examId,questionId));}
    @Overridepublic String preQuestion(Long examId, Long questionId) {checkExamQuestionListCache(examId);return examCacheManager.examPreQuestion(examId,questionId).toString();}@Overridepublic String nextQuestion(Long examId, Long questionId) {checkExamQuestionListCache(examId);return examCacheManager.examNextQuestion(examId,questionId).toString();}private void checkExamQuestionListCache(Long examId) {Long listSize = examCacheManager.getExamQuestionOrderListSize(examId);if(listSize==null||listSize==0){examCacheManager.refreshExamQuestionOrderListCache(examId);}}
    public Long examPreQuestion(Long examId, Long questionId) {Long index = redisService.indexOfForList(getExamQuestionOrderListKey(examId), questionId);if(index==0){throw new ServiceException(ResultCode.FIRST_QUESTION);}return redisService.indexForList(getExamQuestionOrderListKey(examId),index-1, Long.class);}public Long examNextQuestion(Long examId, Long questionId) {Long index = redisService.indexOfForList(getExamQuestionOrderListKey(examId), questionId);Long total = redisService.getListSize(getExamQuestionOrderListKey(examId));if(index==total-1){throw new ServiceException(ResultCode.FINALLY_QUESTION);}return redisService.indexForList(getExamQuestionOrderListKey(examId),index+1, Long.class);}

然后是前端代码

export function examPreQuestionService(examId,questionId) {return service({url: "/exam/preQuestion",method: "get",params: {examId,questionId},});
}export function examNextQuestionService(examId,questionId) {return service({url: "/exam/nextQuestion",method: "get",params: {examId,questionId},});
}
async function preQuestion() {if (examId) {const res = await examPreQuestionService(examId,questionId);questionId = res.data;} else {const res = await preQuestionService(questionId);questionId = res.data;}await getQuestionDetail()
}async function nextQuestion() {if (examId) {const res = await examNextQuestionService(examId,questionId);questionId = res.data;} else {const res = await nextQuestionService(questionId);questionId = res.data;}await getQuestionDetail()
}

这样成功了

1.4 竞赛中答题-提交代码

先把提交的代码存起来—》新增

@Data
public class SubmitQuestionDTO {private Long examId;  //可选private Long questionId;private Integer programType;  // (0: java  1:cpp 2: golang)private String userCode;}
@AllArgsConstructor
@Getter
public enum ProgramType {JAVA(0,"java"),CPP(1,"c++"),GO(2,"go");private final Integer value;private final String msg;
}
@AllArgsConstructor
@Getter
public enum SubmitQuestionResult {PASS(1,"提交成功"),ERR(0,"运行失败");private final Integer value;private final String msg;
}
@Data
public class SubmitQuestionOneResultVO {private String input;private String output;//实际输出private String expectOutput;//期望输出
}
@Data
public class SubmitQuestionVO {private Integer result;//0表示失败,1表示成功private String msg;//表示失败的错误信息List<SubmitQuestionOneResultVO> submitQuestionOneResultVOList;
}
    @PostMapping("/submitQuestion")public R<SubmitQuestionVO> submitQuestion(@RequestBody SubmitQuestionDTO submitQuestionDTO){log.info("用户提交题目代码,submitQuestionDTO:{}",submitQuestionDTO);
//        return R.ok(userService.submitQuestion(submitQuestionDTO));return null;}

2. 判题功能

sumitQuestion接口就是判题功能的实现

2.1 逻辑梳理

先对programType进行判断

userCode是不能直接执行的

在这里插入图片描述
所以我们要有main函数,然后还要有入参,questionId去查询入参
在这里插入图片描述
input就是main函数的入参,入参就是从json里面得到的,output就是expectOutput
用questionId来查询
所以我们要把main函数和function拼好
我们用javac来编译,成功就执行java指令,失败的话,终止逻辑,返回原因
然后用java来执行,成功的话,继续执行后续逻辑,失败的话就返回原因
然后是题目答案的比对,与json的output进行比对,比对一致,正确,比对失败,返回错误原因
然后还要在代码中比对时间限制,和空间限制,把实际的时间空间和期望的比对,小于等于期望值就符合要求了,不符合要求就返回错误原因
对于用户的答题结果,无论失败成功都要存储
在这里插入图片描述
分数的话,难题和简单题的分数应该不一样
在这里插入图片描述
在这里插入图片描述
docker就可以完成–》隔离的容器完成代码,资源使用,是相互隔离的,不会互相干扰,可以限制文件的访问权限,通过java来操作docker
判题逻辑–》单独一个微服务–judge
judge就只是判题,不会操作数据库,es啥的
在这里插入图片描述
friend执行完之后,再去调用judge服务,服务间调用–》openfeign

总结

http://www.xdnf.cn/news/1370017.html

相关文章:

  • 什么是索引下推?
  • ADB 安装教程:如何在 Windows、 Linux 上安装 Android Debug Bridge
  • 基于CSO与BP神经网络分类模型的特征选择方法研究(Python实现)
  • 2025第五届人工智能、自动化与高性能计算国际会议 (AIAHPC 2025)
  • Android Glide生命周期管理:实现原理与最佳实践
  • swift 开发抠图工具实现思路,与代码详解
  • 技术分享︱国产化突破:开源MDO工具链在新一代神威超算上的安装与调试
  • 使用QML的Rectangle组件的边框属性
  • HMM简单拓展-HSMM与高阶HMM
  • C/C++ 数据结构 —— 树(2)
  • nginx-负载均衡
  • C++工程实战入门笔记4-函数(一)
  • WeakAuras Lua Script ICC (BarneyICC) Simplified Chinese [Mini]
  • 深入了解linux系统—— 线程互斥
  • ArcGIS学习-11 实战-商场选址
  • 洛谷 P12332 题解
  • 如何利用ArcGIS探究环境与生态因子对水体、土壤、大气污染物等影响实践技术
  • pytorch_grad_cam 库学习笔记—— Ablation-CAM 算法的基类 AblationCAM 和 AblationLayer
  • 如何避免频繁切换npm源
  • pytorch-利用letnet5框架深度学习手写数字识别
  • Vue2(七):配置脚手架、render函数、ref属性、props配置项、mixin(混入)、插件、scoped样式
  • 深入解析交换机端口安全:Sticky MAC的工作原理与应用实践
  • 机器视觉学习-day03-灰度化实验-二值化和自适应二值化
  • 【C++】智能指针底层原理:引用计数与资源管理机制
  • 深度学习篇---LeNet-5网络结构
  • 病理软件Cellprofiler使用教程
  • vue2 和 vue3 生命周期的区别
  • 一篇文章拆解Java主流垃圾回收器及其调优方法。
  • LeetCode-22day:多维动态规划
  • 代码随想录Day62:图论(Floyd 算法精讲、A * 算法精讲、最短路算法总结、图论总结)