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

微服务项目->在线oj系统(Java版 - 5)

相信自己,终会成功

微服务代码:  lyyy-oj: 微服务

目录

C端代码

用户题目接口

 修改后用户提交代码(应用版)

 用户提交题目判题结果

代码沙箱

1. 代码沙箱的核心功能

2. 常见的代码沙箱实现方式

3. 代码沙箱的关键问题与解决方案

4. 你的代码如何与沙箱交互?

6. 总结

Elasticsearch

RabbitMQ

RabbitMQ 主要功能

RabbitMQ 工作流程示例

典型应用场景


C端代码

用户题目接口

从前端接收到数据,判断是什么语言,如果是Java语言(目前只能进行Java语言的判定)

利用代码沙箱(下方有介绍),对语言进行判断

assembleJudgeSubmitDTO:拿到questionId , 从es中查询题目信息

如果questionES不等于空,

BeanUtil.copyProperties(questionES,judgeSubmitDTO),将questionES复制给 judgeSubmitDTO

否则,从数据库中查找数据,将查出的数据复制给judgeSubmitDTO

然后将数据存入es中

将从线程池中拿到的数据赋值给judgeSubmitDTO

拼接代码后进行解析测试用例

步骤说明
questionCaseList.stream()List<QuestionCase>转为Stream<QuestionCase>,支持链式操作
.map(QuestionCase::getInput)提取每个QuestionCase对象的input字段(方法引用,等价于x -> x.getInput()
.toList()Stream<String>收集为不可变List<String>(Java 16+特性)
setInputList/setOutputList最终将输入/输出列表设置到判题DTO对象

数据流转示例

假设原始数据:

List<QuestionCase> questionCaseList = [{"input": "1 2", "output": "3"},{"input": "3 4", "output": "7"}
]

 转换后结果:

inputList = ["1 2", "3 4"]  // 所有input的集合
outputList = ["3", "7"]      // 所有output的集合
 @Override//后端接收到请求,获取参数,根据getProgramType判断用户提交代码语言类型//    UserSubmitDTO(用户提交的数据,包括题目ID、代码、考试ID等)
//    JudgeSubmitDTO(判题服务需要的数据,包括题目信息、测试用例、用户代码等)public R<UserQuestionResultVO> submit(UserSubmitDTO submitDTO) {Integer programType = submitDTO.getProgramType();if(ProgramType.JAVA.getValue().equals(programType)){//按照Java逻辑处理JudgeSubmitDTO judgeSubmitDTO=assembleJudgeSubmitDTO(submitDTO);
//            remoteJudgeService.doJudgeJavaCode(judgeSubmitDTO)return remoteJudgeService.doJudgeJavaCode(judgeSubmitDTO);}throw new ServiceException(ResultCode.FAILED_NOT_SUPPORT_PROGRAM);}private JudgeSubmitDTO assembleJudgeSubmitDTO(UserSubmitDTO submitDTO) {Long questionId = submitDTO.getQuestionId();//orElse,findbyId返回的是Optional对象,并不是数据本身,如果能查出数据,返回数据本身//查不出来数据,返回null// 1. 查询题目信息(优先ES,不存在则查MySQL并缓存)QuestionES questionES = questionRepository.findById(questionId).orElse(null);JudgeSubmitDTO judgeSubmitDTO=new JudgeSubmitDTO();if(questionES!=null){BeanUtil.copyProperties(questionES,judgeSubmitDTO);}else{Question question = questionMapper.selectById(questionId);BeanUtil.copyProperties(question,judgeSubmitDTO);questionES=new QuestionES();// 2. 组装 JudgeSubmitDTOBeanUtil.copyProperties(question, questionES);questionRepository.save(questionES);}// 3. 设置用户信息(从 ThreadLocal 获取用户ID)judgeSubmitDTO.setUserId(ThreadLocalUtil.get(Constants.USER_ID,Long.class));judgeSubmitDTO.setExamId(submitDTO.getExamId());judgeSubmitDTO.setProgramType(submitDTO.getProgramType());// 4.拼接用户代码和题目主函数judgeSubmitDTO.setUserCode(codeConnect(submitDTO.getUserCode(),questionES.getMainFuc()));// 5. 解析测试用例(从 ES 的 JSON 字符串转换成 List)List<QuestionCase> questionCaseList = JSONUtil.toList(questionES.getQuestionCase(), QuestionCase.class);List<String> inputList = questionCaseList.stream().map(QuestionCase::getInput).toList();judgeSubmitDTO.setInputList(inputList);List<String> outputList = questionCaseList.stream().map(QuestionCase::getOutput).toList();judgeSubmitDTO.setOutputList(outputList);return judgeSubmitDTO;}

 修改后用户提交代码(应用版)

JudgeProducer(使用了RabbitMQ(下方有介绍))

 /*** 将用户提交的代码通过RabbitMQ异步发送给判题服务(目前仅支持Java)* @param submitDTO 用户提交的代码信息,包含代码内容、题目ID、编程语言类型等* @return true 提交成功 | 抛出异常 提交失败(不支持的编程语言)* @throws ServiceException 如果编程语言不支持,抛出业务异常(ResultCode.FAILED_NOT_SUPPORT_PROGRAM)*/@Overridepublic boolean rabbitSubmit(UserSubmitDTO submitDTO) {// 1. 获取用户提交的编程语言类型Integer programType = submitDTO.getProgramType();if(ProgramType.JAVA.getValue().equals(programType)){//按照Java逻辑处理, 组装判题服务需要的DTO(包括代码、测试用例等信息)JudgeSubmitDTO judgeSubmitDTO=assembleJudgeSubmitDTO(submitDTO);// 通过RabbitMQ生产者将判题任务发送到消息队列(异步处理)
//             把参数给rabbitmq,但是没执行判题结果,目前实现是同步调用远程服务judgeProducer.produceMsg(judgeSubmitDTO);// 返回true表示消息已成功提交到队列(注意:不表示判题已完成)return true;}throw new ServiceException(ResultCode.FAILED_NOT_SUPPORT_PROGRAM);}
@Component
//@Component:将该类标记为 Spring 组件,由 Spring 容器管理
@Slf4j
public class JudgeProducer {@Autowiredprivate RabbitTemplate rabbitTemplate;public void produceMsg(JudgeSubmitDTO judgeSubmitDTO) {try {
//            使用 RabbitTemplate 向 RabbitMQ
//            发送消息消息内容是 JudgeSubmitDTO(判题提交数据传输对象)
//            发送到名为 OJ_WORK_QUEUE 的队列rabbitTemplate.convertAndSend(RabbitMQConstants.OJ_WORK_QUEUE, judgeSubmitDTO);} catch (Exception e) {log.error("生产者发送消息异常", e);throw new ServiceException(ResultCode.FAILED_RABBIT_PRODUCE);}}
}

 用户提交题目判题结果

  /*** 根据考试ID、题目ID和时间戳查询用户的判题结果* @param examId 考试ID* @param questionId 题目ID* @param currentTime 提交时间标识(用于区分同一题目的多次提交)* @return UserQuestionResultVO 包含判题状态、执行结果、用例详情等*/@Overridepublic UserQuestionResultVO exeResult(Long examId, Long questionId, String currentTime) {//把结果获取出来// 1. 从ThreadLocal中获取当前用户ID(基于登录上下文)Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);// 2. 查询数据库获取用户提交记录UserSubmit userSubmit = userSubmitMapper.selectCurrentUserSubmit(userId, questionId, examId, currentTime);// 3. 构建返回VO对象UserQuestionResultVO resultVO = new UserQuestionResultVO();// 4. 判题结果不存在的情况(可能还在判题中)if (userSubmit == null) {resultVO.setPass(QuestionResType.IN_JUDGE.getValue()); // 设置状态为"判题中"}// 5. 存在判题结果else {resultVO.setPass(userSubmit.getPass());resultVO.setExeMessage(userSubmit.getExeMessage());if (StrUtil.isNotEmpty(userSubmit.getCaseJudgeRes())) {resultVO.setUserExeResultList(JSON.parseArray(userSubmit.getCaseJudgeRes(), UserExeResult.class));}}return resultVO;}

代码沙箱

代码沙箱(Code Sandbox)是一种安全隔离的执行环境,用于运行不受信任的代码(如用户提交的编程题答案),防止恶意代码影响主系统。在在线判题系统(Online Judge)中,代码沙箱是核心组件之一。

1. 代码沙箱的核心功能

功能

说明

安全隔离

防止用户代码破坏主机(如删除文件、无限循环、占用资源)。

资源限制

限制 CPU、内存、执行时间,避免恶意代码耗尽系统资源。

输入/输出控制

提供标准输入(测试用例),捕获标准输出/错误,与判题系统交互。

多语言支持

支持 Java、Python、C++ 等语言的编译和运行。

错误处理

捕获运行时异常、编译错误,并返回友好提示。

2. 常见的代码沙箱实现方式

1.基于 Docker 的沙箱

原理:每个用户提交的代码在一个临时 Docker 容器中运行,运行后销毁。

优点

强隔离性(进程、文件系统、网络均隔离)。

可限制 CPU、内存等资源(通过 cgroups)。

示例流程

用户提交代码 → 判题系统接收。

生成临时 Docker 容器,挂载代码文件。

在容器内编译/运行代码,传入测试用例。

捕获输出,对比预期结果。

销毁容器。

2. 基于 JVM 沙箱(Java 专用)

原理:利用 Java 的 SecurityManager 或字节码修改(如 ASM)限制敏感操作。

优点

轻量级,启动快。

适合纯 Java 判题场景。

缺点

无法完全隔离系统调用(如 System.exit())。

需要自定义安全策略。

3. 第三方沙箱服务

示例

Judge0:开源的在线判题沙箱(支持 60+ 语言)。

Piston:轻量级多语言执行引擎。

优点:无需自行维护沙箱环境。

3. 代码沙箱的关键问题与解决方案

问题

解决方案

恶意代码

使用 Docker 隔离,限制系统调用(如 forkexec)。

无限循环

设置超时机制(如 Linux 的 timeout 命令)。

内存溢出

通过 -Xmx 限制 JVM 内存,或 Docker --memory 限制容器内存。

文件系统安全

Docker 使用只读文件系统,或临时挂载空目录。

网络隔离

禁用容器网络(--network none)。

4. 你的代码如何与沙箱交互?

执行流程

用户提交代码 → 你的服务组装 JudgeSubmitDTO(题目ID、代码、测试用例等)。

通过 Feign 调用判题服务(remoteJudgeService.doJudgeJavaCode)。

判题服务将代码发送到 代码沙箱 执行。

沙箱返回结果(通过/失败、错误信息、用时等)。

你的服务接收结果并返回给用户。

6. 总结

代码沙箱 是判题系统的核心,确保安全性和稳定性。

推荐方案

小型系统:用 Docker 快速实现。

大型系统:结合 Kubernetes 管理沙箱集群。

扩展方向

支持更多语言(Python、C++)。

分布式判题(提高并发能力)。

为什么需要沙箱?

安全隔离:防止用户代码破坏宿主系统。

资源控制:限制CPU/内存使用,避免恶意代码耗尽资源。

环境一致性:确保每次执行都在干净的环境中运行。


Elasticsearch

官方网站:

Elastic Docs | Elastic

Elasticsearch(简称 ES)是一个开源的分布式 搜索和分析引擎,基于 Apache Lucene 构建,专为处理海量数据设计,支持近实时(NRT, Near Real-Time)搜索。

优点

高性能搜索,支持复杂查询(全文检索、模糊匹配、聚合分析)。

水平扩展能力强,适合大数据场景。

生态完善(ELK Stack、APM、SIEM 等)。

缺点

不支持事务(不适合金融级一致性要求场景)。

资源消耗较高(尤其是内存)。

学习曲线较陡(需理解分词、映射、集群管理等)


RabbitMQ

RabbitMQ 是一个开源的 消息代理(Message Broker),实现了 AMQP(Advanced Message Queuing Protocol) 协议,用于在分布式系统中存储、转发消息。

核心角色:生产者(Producer)→ RabbitMQ → 消费者(Consumer)

典型场景:异步任务处理、应用解耦、流量削峰、分布式系统通信。

概念说明
Producer消息生产者,发送消息到 Exchange
Consumer消息消费者,从 Queue 接收消息
Exchange消息路由组件,决定消息投递到哪些 Queue(类型:Direct、Fanout、Topic、Headers)
Queue存储消息的缓冲区,消费者从中订阅消息
BindingExchange 和 Queue 的绑定规则(如路由键 Routing Key)
Channel轻量级连接(复用 TCP 连接,减少开销)
Virtual Host虚拟隔离环境(类似命名空间,不同 vhost 资源互不干扰)

RabbitMQ 主要功能

消息路由(Exchange Types)

Direct Exchange
→ 精确匹配 Routing Key,消息投递到完全匹配的 Queue。

// 示例:日志级别路由(error、warning、info)
channel.queueBind("error_queue", "logs_exchange", "error");

Fanout Exchange
→ 广播模式,消息发送到所有绑定的 Queue(忽略 Routing Key)。

// 示例:新闻通知广播
channel.exchangeDeclare("news", BuiltinExchangeType.FANOUT);

 Topic Exchange
→ 通配符匹配 Routing Key* 匹配一个词,# 匹配多个词)。

// 示例:订单路由(order.create、order.payment.success)
channel.queueBind("queue_payment", "orders", "order.payment.*");

 Headers Exchange
→ 基于消息头(Headers)匹配,不依赖 Routing Key(性能较低,较少使用)。

 消息可靠性

消息确认(ACK/NACK)
→ 消费者处理成功后发送 ACK,失败时 NACK(可配置重试或进入死信队列)。

channel.basicConsume(queue, false, consumer); // 手动ACK
channel.basicAck(deliveryTag, false); // 确认处理成功

 持久化(Persistence)
→ Exchange、Queue、消息均可持久化到磁盘,防止服务重启丢失。

// 声明持久化队列
channel.queueDeclare("task_queue", true, false, false, null);

 死信队列(DLX)
→ 处理失败或超时的消息可转发到死信队列,用于异常监控和重试。

Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx_exchange");
channel.queueDeclare("normal_queue", false, false, false, args);

高级特性

TTL(Time-To-Live)
→ 设置消息或队列的过期时间(超时未消费则自动删除)。

// 消息级别TTL
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().expiration("60000") // 60秒过期.build();
channel.basicPublish(exchange, routingKey, props, message.getBytes());

优先级队列(Priority Queue)
→ 消息按优先级消费(需队列声明时支持)。

Map<String, Object> args = new HashMap<>();
args.put("x-max-priority", 10); // 最大优先级为10
channel.queueDeclare("priority_queue", false, false, false, args);

 集群与镜像队列
→ 支持多节点集群,镜像队列(Mirrored Queue)实现高可用。

RabbitMQ 工作流程示例

典型应用场景

  1. 异步任务处理
    → 用户注册后异步发送邮件/短信。

  2. 应用解耦
    → 订单系统与库存系统通过消息队列通信。

  3. 流量削峰
    → 秒杀请求先写入队列,后端按能力消费。

  4. 日志收集
    → 多个服务发送日志到统一队列,由消费者存储到ES/数据库。

 对比其他消息队列

RabbitMQKafkaRocketMQ
协议AMQP自定义协议自定义协议
吞吐量中等(万级TPS)高(百万级TPS)高(十万级TPS)
延迟低(毫秒级)中(依赖批量)
适用场景业务消息、实时处理日志流、大数据金融级事务消息

RabbitMQ 是轻量级、高可用的消息中间件,适合需要可靠消息传递的分布式系统。通过灵活的路由规则和丰富的特性,平衡了性能与功能需求 

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

相关文章:

  • 《Building effective agents》学习总结
  • C++中聚合类(Aggregate Class)知识详解和注意事项
  • 深入理解动态规划:从斐波那契数列到最优子结构
  • YoloV9改进策略:卷积篇|风车卷积|即插即用
  • 【Python-Day 15】深入探索 Python 字典 (下):常用方法、遍历、推导式与嵌套实战
  • C++容器适配器
  • DAPO:用于指令微调的直接偏好优化解读
  • 【idea 报错:java: 非法字符: ‘\ufeff‘】
  • 第二十一次博客打卡
  • 【C语言内存函数】--memcpy和memmove的使用和模拟实现,memset函数的使用,memcmp函数的使用
  • 1 asyncio模块
  • Ubuntu——配置静态IP
  • 基于Transformers与深度学习的微博评论情感分析及AI自动回复系统
  • 【C++】模版(1)
  • 基于不完美维修的定期检测与备件策略联合优化算法matlab仿真
  • megatron——EP并行
  • 商标名称起好后,尽快申请注册确权!
  • 【cursor疑惑】cursor续杯后使用agent对话时,提示“需要pro或商业订阅的用户才能使用“
  • 电路研究9.3.6——合宙Air780EP中的AT开发指南:FTP 应用指南
  • np.r_的用法
  • 代码随想录 算法训练 Day6:哈希表part1
  • Mybatis的标签:if标签、where标签、choose,when标签、set标签
  • 【vs2022的C#窗体项目】打开运行+sql Server改为mysql数据库+发布
  • React学习———Immer 和 use-immer
  • 编译zstd
  • 《垒球百科全书》垒球是什么·棒球1号位
  • `asyncio.gather()` 是什么
  • 深度强化学习框架DI-engine
  • Java大师成长计划之第27天:RESTful API设计与实现
  • 算法竞赛 Java 高精度 大数 小数 模版