当前位置: 首页 > 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.dtcms.com/a/352346.html

相关文章:

  • 【typenum】30 类型级别的取负(Neg)
  • `mmap` 系统调用详解
  • 设备驱动程序 day62
  • 变压器副边电流计算
  • es-toolkit 是一个现代的 JavaScript 实用库
  • 15公里图传模组:为远程飞行赋能,突破极限的无线连接新选择
  • 微服务-28.配置管理-共享配置
  • 微服务-26.网关登录校验-OpenFeign传递用户信息
  • 前端RSA加密库优缺点总结
  • 42_基于深度学习的非机动车头盔佩戴检测系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • Python内存模型与对象系统深度解析
  • 使用Kiro智能开发PYTHON应用程序
  • 25072班8.26日数据结构作业
  • 【CFA三级笔记】资产配置:第一章 资本市场预期(宏观分析)
  • ansible的一些重要配置文件
  • 基于 LQG 控制的轨迹跟踪 —— 从原理到实践
  • 游隼可视化项目
  • python删除执行目录
  • 服装行业/服饰品牌OMS订单管理系统:全渠道零售时代的数字化中枢|商派
  • Chrome您的连接不是私密连接怎么办?试下手敲 thisisunsafe
  • Kafka 生态选型地图、最佳实践与落地清单
  • SELinux相关介绍
  • Android 属性 property 系统
  • MyBatis-Flex多表关联查询指南
  • Dify 父子模式详解:如何实现模块化与高效协作
  • 学习做动画4.回转运动
  • Docker移动安装目录的两种实现方案
  • Qwen3-Coder-30B-A3B-Instruct AWQ 量化
  • 基于51单片机的DS18B20大棚温度监控系统
  • TRUST:a thermohydraulic software package for CFD simulations,开源多物理场数值模拟平台