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

微服务的编程测评系统18-判题功能-Rabbitmq-用户拉黑

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

文章目录

  • 前言
  • 1. 判题功能
    • 1.1 Rabbitmq
    • 1.2 Rabbitmq判题结果的获取
    • 1.3 热榜排行
  • 2. 用户拉黑功能-用户行为限制
  • 总结


前言

1. 判题功能

1.1 Rabbitmq

docker pull rabbitmq:3.8.30-management

先安装
启动容器

docker run -d --name oj-rabbit-dev -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:3.8.30-management

指定用户名密码为admin
启⽤管理插件:

rabbitmq-plugins enable rabbitmq_management

在容器里面执行

然后点击端口号15672就进入管理页面了

<!--rabbitmq-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

引入–》建立一个Rabbitmq包
维护rabbitMQ常量,在core中

public class RabbitMQConstants {public static final String OJ_WORK_QUEUE = "oj-work-queue";
}

然后是Rabbitmq的基本配置
messageConverter是一个json转化器,就是把对象和json之间转换

@Configuration
public class RabbitConfig {@Beanpublic Queue workQueue() {return new Queue(RabbitMQConstants.OJ_WORK_QUEUE, true);}@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}
}

然后是生产者和消费者
friend就是生产者–》发送判题
judge是消费者–》判题
在friend中

@Component
@Slf4j
public class JudgeProducer {@Autowiredprivate RabbitTemplate rabbitTemplate;public void produceMsg(JudgeSubmitDTO judgeSubmitDTO) {try {rabbitTemplate.convertAndSend(RabbitMQConstants.OJ_WORK_QUEUE,judgeSubmitDTO);} catch (Exception e) {log.error("⽣产者发送消息异常", e);throw new ServiceException(ResultCode.FAILED_RABBIT_PRODUCE);}}
}

这个就是生产者,把消息发送到队列

然后是judge消费者

@Slf4j
@Component
public class JudgeConsumer {@Autowiredprivate IJudgeService judgeService;@RabbitListener(queues = RabbitMQConstants.OJ_WORK_QUEUE)public void consume(JudgeSubmitDTO judgeSubmitDTO) {log.info("收到消息为: {}", judgeSubmitDTO);judgeService.doJudgeJavaCode(judgeSubmitDTO);}
}

现在开始使用
现在我们是生产者和消费者
那么在friend中就不用手动调用judge服务了
直接给mq就可以了
消费者自己知道消费

我们给UserQuestionController写一个新的接口

    @PostMapping("/rabbit/submitQuestion")public R<Void> rabbitSubmitQuestion(@RequestBody SubmitQuestionDTO submitQuestionDTO){log.info("用户提交题目代码,rabbitSubmitQuestion:{}",submitQuestionDTO);return toR(userQuestionService.rabbitSubmitQuestion(submitQuestionDTO));}
    @Overridepublic boolean rabbitSubmitQuestion(SubmitQuestionDTO submitQuestionDTO) {Integer programType = submitQuestionDTO.getProgramType();if(ProgramType.JAVA.getValue().equals(programType)){JudgeSubmitDTO judgeSubmitDTO = makeJudgeSubmitDTO(submitQuestionDTO);judgeProducer.produceMsg(judgeSubmitDTO);return true;}throw new ServiceException(ResultCode.PROGRAM_TYPE_ERR);}

这样就可以了但是没有返回值呢–》先不管

然后测试一下

@Configuration
public class DockerSandBoxPoolConfig {@Value("${sandbox.docker.host:tcp://localhost:2375}")private String dockerHost;@Value("${sandbox.docker.image:openjdk:8-jdk-alpine}")private String sandboxImage;//镜像名称@Value("${sandbox.docker.volume:/usr/share/java}")private String volumeDir;//挂载目录@Value("${sandbox.limit.memory:100000000}")private Long memoryLimit;@Value("${sandbox.limit.memory-swap:100000000}")private Long memorySwapLimit;@Value("${sandbox.limit.cpu:1}")private Long cpuLimit;@Value("${sandbox.docker.pool.size:4}")private int poolSize;@Value("${sandbox.docker.name-prefix:oj-sandbox-jdk}")private String containerNamePrefix;///容器名称前缀@Beanpublic DockerClient createDockerClient(){DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(dockerHost).build();DockerClient dockerClient = DockerClientBuilder.getInstance(clientConfig).withDockerCmdExecFactory(new NettyDockerCmdExecFactory()).build();return dockerClient;}@Beanpublic DockerSandBoxPool createDockerSandBoxPool(DockerClient dockerClient){DockerSandBoxPool dockerSandBoxPool = new DockerSandBoxPool(dockerClient,sandboxImage,volumeDir,memoryLimit,memorySwapLimit,cpuLimit,poolSize,containerNamePrefix);dockerSandBoxPool.initDockerPool();return dockerSandBoxPool;}
}

这个容器池改造一下,就是dockerClient

记得还要加入mq的配置,在两个服务中

spring:rabbitmq:host: localhostport: 5672username: adminpassword: admin

先不启动judge服务–》可以看到在队列的消息
然后就可以测试了

在这里插入图片描述
在这里插入图片描述

这样就成功了

在这里插入图片描述

1.2 Rabbitmq判题结果的获取

我们在提供一个接口,专门用来查询判题结果—》查询数据库可以–》但是这个判题结果不是马上就能获取出来的,因为判题是需要时间的
—》搞一个定时器每隔一段时间获取结果

然后就是前端传递的参数还要有一个判题时间的参数,因为万一这次是二次提交,那么第二次判题还没结束(第一次的结果也没有删除),那么就可能获取到第一次的判题结果
有一个时间的话,就只需要这个判题时间之后的判题结果就可以了

    @GetMapping("/exe/result")public R<UserQuestionResultVO> exeResult( Long questionId ,Long examId,String currentTime){log.info("定时查询判题结果,questionID:{},examId:{},currentTime:{}",questionId,examId,currentTime);return R.ok(userQuestionService.exeResult(questionId,examId,currentTime));}
    @Overridepublic UserQuestionResultVO exeResult(Long questionId, Long examId, String currentTime) {Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);UserSubmit userSubmit = userSubmitMapper.selectCurrentUserSubmit(userId,questionId,examId,currentTime);return null;}

然后是xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.friend.mapper.user.UserSubmitMapper"><select id="selectCurrentUserSubmit" resultType="com.ck.friend.domain.user.UserSubmit">SELECTsubmit_id,pass,exe_message,case_judge_resFROMtb_user_submit<where><if test="questionId != null">AND question_id = #{questionId}</if><if test="examId !=null ">AND exam_id = #{examId}</if><if test="examId == null ">AND exam_id is null</if><if test="userId !=null ">AND user_id = #{userId}</if><if test="currentTime !=null and currentTime !='' ">AND (create_time &gt;= #{currentTime} or update_time &gt;= #{currentTime})</if></where></select></mapper>

case_judge_res是返回的结果信息,就是判题的输出结果—》input,output,exeOutput

但是数据库没有设计这个字段----》增加一下

	private String caseJudgeRes;

在这里插入图片描述
把输出结果转化成json来存储

我们在judge服务的insertUserSubmit方法中增加

        userSubmit.setCaseJudgeRes(JSON.toJSONString(userQuestionResultVO.getUserExeResultList()));

其中用的是fastJson的JSON
这样就存储成功了

@Getter
public enum QuestionResType {ERROR(0), //未通过PASS(1), //通过UN_SUBMIT(2),  //未提交IN_JUDGE(3); //  系统判题中private Integer value;QuestionResType(Integer value) {this.value = value;}
}
    @Overridepublic UserQuestionResultVO exeResult(Long questionId, Long examId, String currentTime) {Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);UserSubmit userSubmit = userSubmitMapper.selectCurrentUserSubmit(userId,questionId,examId,currentTime);UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();if(userSubmit==null){userQuestionResultVO.setPass(QuestionResType.IN_JUDGE.getValue());}else{userQuestionResultVO.setExeMessage(userSubmit.getExeMessage());userQuestionResultVO.setScore(userSubmit.getScore());userQuestionResultVO.setPass(userSubmit.getPass());if(StrUtil.isNotEmpty(userSubmit.getCaseJudgeRes())){userQuestionResultVO.setUserExeResultList(JSON.parseArray(userSubmit.getCaseJudgeRes(), UserExeResult.class));}}return userQuestionResultVO;}

这样就可以了
然后
定时任务是在前端实现的

export function getQuestionResultService(examId, questionId, currentTime) {return service({url: "/user/question/exe/result",method: "get",params: { examId, questionId, currentTime }});
}
export function userSubmitService(params = {}) {return service({url: "/user/question/rabbit/submitQuestion",method: "post",data: params,});
}
<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>{{ examTitle ? examTitle : 精选题库 }}</span><el-countdown v-if="examEndTime && new Date() < new Date(examEndTime)" class="exam-time-countdown"@finish="handleCountdownFinish" title="距离竞赛结束还有:" :value="new Date(examEndTime)" /></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 red" v-if="userQuestionResultVO.pass === 0">未通过</span><span class="text_1 success" v-if="userQuestionResultVO.pass === 1">通过</span><span class="text_1 warning" v-if="userQuestionResultVO.pass === 2">请先执行代码</span><span class="text_1 info" v-if="userQuestionResultVO.pass === 3">系统正在处理您的代码,请稍后</span></div><span class="error-text" v-if="userQuestionResultVO.pass === 0">异常信息:{{userQuestionResultVO.exeMessage }}</span><el-table v-if="userQuestionResultVO.userExeResultList && userQuestionResultVO.userExeResultList.length > 0":data="userQuestionResultVO.userExeResultList"><el-table-column prop="input" label="输入" /><el-table-column prop="output" label="预期结果" /><el-table-column prop="exeOutput" label="实际输出" /></el-table></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 { useRoute } from "vue-router"
import { questionDetailService, preQuestionService, nextQuestionService, getQuestionResultService } from "@/apis/question"
import router from "@/router"
import { examNextQuestionService, examPreQuestionService, getFirstExamQuestionService } from "@/apis/exam"
import { ElMessage } from "element-plus"
import { userSubmitService } from "@/apis/user"function goBack() {router.go(-1);
}
const questionDetail = reactive({})
const defaultCodeRef = ref()let questionId = useRoute().query.questionId
let examId = useRoute().query.examId
let examTitle = useRoute().query.examTitle
let examEndTime = useRoute().query.examEndTimeconsole.log('examTitle: ', examTitle)async function getQuestionDetail() {if (examId && (questionId == null || questionId == '')) {const eqrs = await getFirstExamQuestionService(examId)questionId = eqrs.dataconsole.log('qId: ', questionId)}const res = await questionDetailService(questionId)Object.assign(questionDetail, res.data)defaultCodeRef.value.setAceCode(questionDetail.defaultCode)
}
getQuestionDetail()async function preQuestion() {if (examId) {//竞赛中上一题的逻辑  需要提供一个竞赛中获取上一题的接口const res = await examPreQuestionService(examId, questionId)questionId = res.data} else {const res = await preQuestionService(questionId)questionId = res.data}getQuestionDetail()
}async function nextQuestion() {if (examId) {//竞赛中下一题的逻辑 需要提供一个竞赛中获取下一题的接口const res = await examNextQuestionService(examId, questionId)questionId = res.data} else {const res = await nextQuestionService(questionId)questionId = res.data}getQuestionDetail()
}function handleCountdownFinish() {ElMessage.info('竞赛已经结束了哦')router.push('/c-oj/home/exam')
}const submitDTO = reactive({examId:'',questionId:'',programType: 0,userCode: ''
})function handleEditorContent(content) {submitDTO.userCode = content
}const userQuestionResultVO = ref({pass: 2,  //默认值为2,未提交代码exeMessage: '',userExeResultList: [],
})const pollingInterval = ref(null);
let currentTimefunction startPolling() {stopPolling(); // 停止之前的轮询pollingInterval.value = setInterval(() => {getQuestionResult();}, 2000); // 每隔2秒请求一次
}function stopPolling() {if (pollingInterval.value) {clearInterval(pollingInterval.value);pollingInterval.value = null;}
}async function submitQuestion() {submitDTO.examId = examIdsubmitDTO.questionId = questionIdawait userSubmitService(submitDTO)currentTime = new Date().toLocaleString();userQuestionResultVO.value.pass = 3startPolling()
}async function getQuestionResult() {const res = await getQuestionResultService(submitDTO.examId, submitDTO.questionId, currentTime)userQuestionResultVO.value = res.dataif (userQuestionResultVO.value.pass !== 3) {stopPolling();}
}</script>

然后就成功了,可以测试了

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果我们没有使用容器池的话,就会很慢
在这里插入图片描述
前端就会调用定时器一直访问这个接口

还有一个我的错误就是,friend中没有把examId传递给judge,代码要改一下

1.3 热榜排行

热榜排行就是被提交次数最多的题目–》用户提交表–》然后展示排名前五前十–》redis查询—》分页查询–》差不到的话,就去数据库,然后同步数据到redis—》排名一直会变–》后端定时任务–》凌晨统计或者频率快点都是可以的–》可以在redis中存储questionId的list,然后去es查询title

2. 用户拉黑功能-用户行为限制

在这里插入图片描述
接口我们已经写了,然后是权限的限制
—》比如:拉黑的用户不能报名竞赛,不能开始答题等等,只能浏览页面,不能操作,很多的接口都要限制住权限—》AOP–》多个接口会重复调用判断权限–》注解来

先导入依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

我们来创建一个注解

在这里插入图片描述

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckUserStatus {} 

这个就是我们的自定义注解

然后创建一个切面类

@Aspect
@Component
public class UserStatusCheckAspect {@Autowiredprivate UserCacheManager userCacheManager;@Before(value = "@annotation(com.ck.friend.aspect.CheckUserStatus)")public void before(JoinPoint point){Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);UserVO user = userCacheManager.getUserById(userId);if (user == null) {throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);}if (Objects.equals(user.getStatus(), Constants.FALSE)) {throw new ServiceException(ResultCode.FAILED_USER_BANNED);}}
}

注解Aspect的作用就是标识这是一个切面类
注解Before加在方法before上面的意思就是标识这个方法是一个前置统治类型的方法
—》代表这个方法会在目标方法之前执行,操作之前验证权限—》所以用注解Before

所以现在就变成了加自定义注解CheckUserStatus—》自动执行before方法
所以注解加在哪个方法上面就会执行before

比如加在报名竞赛的方法上面

    @CheckUserStatus@PostMapping("/enter")@Operation(description = "用户报名竞赛")public R<Void> enter(@RequestBody ExamDto examDto, @RequestHeader(HttpConstants.AUTHENTICATION) String token){log.info("用户报名竞赛:examDto:{},token:{}",examDto,token);return toR(userExamService.enter(examDto.getExamId(),token));}

因为用户数据以前就存在数据库中了
所以我们可以直接从缓存中获取
然后是把用户数据存入缓存的时机是访问个人中心,我们记得要把status字段存入缓存
然后是修改status的时候,还要修改缓存,status不能删除,不能增加,所以只能修改缓存了,查询已经写好了
缓存的增删查改
因为这个用户数据的缓存是设计了过期时间的,所以在getUserById的时候,缓存里面没有,从数据库中获取,并刷新
所以修改缓存的时候,如果缓存都过期了,那么就不用修改了
没有过期的话,就要更新了

    public void updateStatus(Long userId,Integer status ) {//刷新用户缓存String userKey = getUserKey(userId);User user = redisService.getCacheObject(userKey, User.class);if(user==null){return;}user.setStatus(status);redisService.setCacheObject(userKey, user);//设置用户缓存有效期为10分钟redisService.expire(userKey, CacheConstants.USER_DETAIL_EXP, TimeUnit.MINUTES);}

在UserCacheManager增加如上方法
在这里插入图片描述
这样就OK了

在这里插入图片描述

这样就成功了

总结

如果有显示的问题的话,就编译一下,compile

http://www.dtcms.com/a/359944.html

相关文章:

  • Elasticsearch面试精讲 Day 3:分片与副本策略详解
  • 【图论】 Graph.jl 概览
  • Linex进程管理
  • OC-属性关键字
  • GEE 实战:计算 Landsat8 月均 NDVI 并导出(2013-2024)_后附完整代码
  • 【pve】
  • 秋招 AI 方向 —— 华为机考
  • 【学习笔记】LLM Interview(Agent相关)
  • 计算机视觉与深度学习 | 低照度图像处理算法综述:发展、技术与趋势
  • 大数据毕业设计选题推荐-基于大数据的大气和海洋动力学数据分析与可视化系统-Spark-Hadoop-Bigdata
  • (数组的定义与使用) 本篇目标 1. 理解数组基本概念 2. 掌握数组的基本用法 3. 数组与方法互操作 4. 熟练掌握数组相关的常见问题和代码
  • 同类软件对比(三):Python vs Anaconda vs Miniconda:深入解析与选择策略
  • 2025.8.18-2025.8.24第35周:备稿演讲有进步
  • Paimon——官网阅读:Spark 引擎
  • 【图论】Graph.jl 核心函数
  • 如何通过 AI IDE 集成开发工具快速生成简易留言板系统
  • Java面试-微服务(spring cloud篇)
  • 飞牛Docker部署免费frp内网穿透
  • RK3568平台开发系列讲解:瑞芯微平台4G模块篇移植
  • TFS-2005《A Possibilistic Fuzzy c-Means Clustering Algorithm》
  • 商业航天:中、美、欧“软件定义卫星” 路线全解析
  • Iterative loop of ML development|机器学习的迭代发展
  • JavaEE初阶网络原理-初识
  • PythonDay42
  • 提取动漫图像轮廓并拟合为样条曲线(MATLAB)
  • Mysql学习 Day3 Explain详解与索引优化
  • APB验证VIP Agent的各个组件之间的通信
  • SpringAI应用开发面试实录:核心技术、架构设计与业务场景全解析
  • React前端开发_Day12_极客园移动端项目
  • 解决 uni-app 中大数据列表的静默UI渲染失败问题