简介这是一套面向编程教育者、自学开发者及高校计算机课程实践者的AIGC驱动型在线编程评测系统源码旨在解决传统练习平台题目静态、反馈滞后、路径单一等痛点支持从入门语法训练到算法能力进阶的全流程学习闭环。资源包共61个文件以46个Java核心业务类涵盖题目生成、评测引擎、用户认证、消息队列处理等模块为主体辅以6个XML配置文件、1个application.yml、1个SQL建表脚本及README.md、说明文件.txt等工程文档整体仅81KB轻量易部署。已有52人下载学习可直接运行调试完整功能链路包括基于用户画像的动态题目生成、多用例自动编译与结果校验、实时代码质量反馈含风格与性能提示、个性化学习路径推荐引擎以及基于Spring Security的认证授权、Redis缓存优化和RabbitMQ异步消息机制等工业级架构实践。 接到这个项目标题时我第一反应是“这包里东西不少”。一个集成AIGC的在线编程评测系统从功能列表来看作者想解决的问题并不只是“做个OJ”而是把题目生成、自动判题、实时反馈、学习路径推荐、认证授权、缓存、异步消息全部串在一起形成一个能自运转的闭环教学平台。这类系统放到两三年前人工出题加一个判题内核就够用了。但现在题目供给成了瓶颈尤其是面向大量水平参差不齐的学习者时固定题库根本没有梯度可言。引入AIGC之后题目生成、测试用例生成、知识点标注这些最耗人工的环节全都可以自动化。这篇文章我会结合这个项目来做一次完整拆解从架构设计、核心模块实现到实际踩坑把每一环的选型逻辑和实现思路讲清楚适合准备做同类平台、或者正在做在线教育类产品的团队参考。1. 项目整体设计思路与架构选择1.1 这个系统到底解决了什么问题传统在线评测系统的痛点是“题不够用”和“评测不可靠”。先说题不够用人工出题不仅慢而且质量波动大。一个老师一天能出三道高质量题目就算不错还要配套写测试用例、标准答案、知识点标签工作量非常重。再说评测不可靠很多轻量级OJ用子进程直接跑用户代码没做资源隔离一段恶意代码就能把整个宿主机打挂这在实际教学场景里是不能接受的。这个项目把AIGC放在题目生成环节等于把“出题”这个上游生产环节自动化。系统用大模型生成题目描述、输入输出样例、标准参考代码再通过规则校验和数据增强保证题目质量。下游判题模块用沙箱隔离用户代码执行配合异步消息队列把提交和评测解耦这样即使上百人同时交代码服务也不会被瞬时流量压垮。再加上用户认证和学习路径推荐整个平台是完整的而不是各个功能堆在一起。1.2 为什么把AIGC放在题目生成这个环节很多人觉得AIGC在编程教育里最好的应用场景是“AI答题助手”让学生直接问ChatGPT拿答案。但在这个项目里AIGC被用来生成题目而不是解题。这个思路更符合教学平台的利益。答题助手会让评测体系失效而题目生成是供给端的自动化它不会破坏评判标准反而扩大了题目池的丰富度。实现上系统通过精心设计的提示词模板让模型按照固定JSON结构输出题目数据包括题目名称、难度等级、知识点标签、描述文本、输入输出格式、样例、测试用例、参考代码。然后用一个质量校验模块去检查模型输出是否完整、测试用例是否可验证、题目是否存在逻辑矛盾。最后把通过的题目写入题库并建立索引。这个流程最大的好处是人工只需要做抽检和审核而不是从零开始写题。1.3 整体技术架构与模块划分从架构上看这个项目分成了六个核心模块AIGC题目生成服务、评测沙箱服务、认证授权服务、学习路径推荐服务、缓存层和异步消息层。题目生成服务对接大模型API负责提示词构造、质量校验、题目入库。评测沙箱服务隔离执行用户提交的代码控制资源占用判定运行结果。认证授权服务负责用户注册登录、JWT签发与刷新、角色权限控制。学习路径推荐服务根据用户提交记录和做题正确率推荐下一阶段题目。缓存层Redis为主缓存热点题目、用户会话、排行榜等数据。异步消息层使用RabbitMQ或Redis Stream解耦提交请求与评测任务。整个系统采用前后端分离架构后端拆成多个业务模块服务之间通过HTTP接口和消息队列通信。这样的好处是每个模块都能独立扩展评测沙箱的硬件资源可以单独扩容题目生成服务可以在低峰期批量跑避免占用在线评测的资源。2. AIGC题目生成模块的实现细节2.1 题目生成模型与提示词设计题目生成最核心的是提示词构造。直接丢给模型一句“给我生成一道动态规划题”是不行的输出的结构化程度根本无法直接入库。这个项目用的做法是定义一套严格的输出模板让模型按JSON格式输出同时在提示词里明确知识点的范围、难度上限和输入数据约束。一个典型的提示词模板长这样PROMPT_TEMPLATE 你是一名算法竞赛出题专家请根据以下要求生成一道编程题目。 要求 - 知识点{knowledge_point} - 难度等级{difficulty} - 题目类型{problem_type} - 输入数据范围{data_range} 请严格按照以下JSON格式输出不要包含任何多余内容 { title: 题目名称, description: 题目描述, input_format: 输入格式说明, output_format: 输出格式说明, samples: [{input: 样例输入, output: 样例输出}], test_cases: [{input: 测试数据, output: 期望输出}], reference_code: 参考解答代码, knowledge_points: [知识点1, 知识点2], difficulty: 3 } 这里有个细节test_cases必须由模型一次性生成但实际项目中测试用例通常要经过二次校验才能上线因为大模型生成的测试数据偶尔会有错。尤其是输入范围比较大的情况模型可能会生成不合法的数据比如要求n 10^5但样例里给了n 200000这种数据用户一跑就会出问题。2.2 题目质量校验与去重策略AIGC生成的内容天然存在不确定性所以质量校验模块是必须的不能直接信任模型输出。这个项目在入库前做了四道校验结构校验检查JSON字段是否完整类型是否正确。可运行性校验把参考代码在沙箱里跑一遍确认参考答案能通过所有测试用例。输入合法性校验检查测试数据是否符合题目约束范围。去重校验计算题目描述的相似度防止模型生成重复题目。去重这一块比较好用的方案是用MinHash或者SimHash计算文本相似度设定一个阈值相似度超过85%就认为是重复题直接丢弃。实测下来大模型在连续生成同一个知识点的题目时重复概率不低尤其是简单题容易生成变着说法但其实一样的题目所以去重必须做在入库前。2.3 测试用例与标准答案的生成测试用例的生成是整个题目生成环节里最容易被低估的部分。模型生成的测试用例通常能覆盖基本流程但边界条件覆盖率很差。比如字符串处理题模型往往生成一些正常输入不会主动去生成空字符串、包含空格、超长字符串这类极端测试。这个项目做了一些数据增强处理对模型生成的测试用例做规则扩展自动生成边界用例比如数组长度取1、取最大值数值取INT_MIN/INT_MAX。随机生成大规模数据用脚本按题目约束范围随机生成大体积输入验证算法复杂度。反向校验用暴力解法跑小规模测试数据和参考代码输出做对比确保标准答案不唯一。标准答案方面系统会用两套不同的参考实现交叉验证。比如一道动态规划题一套标准DP解法一套记忆化搜索解法如果两套代码输出一致才认为参考答案可信。这个做法虽然增加了一点算力开销但能有效防止模型给出错误的“标准答案”。3. 代码自动评测与实时反馈实践3.1 沙箱隔离与安全评测方案在线判题系统最危险的是运行用户提交的代码尤其是C/C这种可以操作内存和文件的语言。如果不做隔离恶意代码可以读取服务器上的敏感文件、占用全部CPU甚至直接删库跑路。这个项目采用双层隔离底层用容器隔离CPU内存和文件系统上层在评测进程内用setrlimit限制资源使用。容器方案推荐Docker配合特权模式禁用给每个评测任务起一个一次性容器运行完立即销毁。这里有几个关键参数resources: limits: cpus: 1 memory: 256M security_opt: - no-new-privileges:true read_only: true tmpfs: - /tmp:size64m内存限制这块C程序建议限制在256MB以内Python可以放宽到512MB。注意read_only: true把根文件系统设为只读用户代码无法在容器里写入任何文件。tmpfs挂载到/tmp是为了给程序运行提供临时目录同时限制大小防止恶意写盘。3.2 判题核心流程与状态判定判题流程大体是这样的用户提交代码后请求先到评测接口评测接口把提交写入消息队列评测消费者从队列拉到任务标记状态为“排队中”然后创建沙箱、编译代码、运行测试用例、比对输出结果最后更新提交记录。状态判定这块常见的OJ一般只有AC、WA、TLE、MLE、RE几种。这个项目把状态细分成了更完整的集合状态含义触发条件AC通过所有测试用例输出一致WA答案错误输出与期望不一致TLE超时运行时间超过阈值MLE超内存内存占用超过阈值RE运行时错误程序崩溃、非零退出码CE编译错误编译阶段失败OLE输出超限输出文件大小超过限制判定输出一致时有不少细节坑。最典型的是行末空格和文件末尾换行不能直接用字符串逐字符比较要先把两边的输出按行读入再去掉每行首尾空格最后做行级别的比较。这个规则需要提前定义好否则会出现本地AC但平台判WA的尴尬情况。3.3 实时反馈从提交到出结果的链路设计用户提交代码之后如果使用同步HTTP调用在沙箱运行期间连接会一直挂着。简单题也许一两秒就有结果但复杂题运行十几秒甚至几十秒同步接口很容易超时体验很差。这个项目采用“提交即返回 异步通知”的模式。实现方式有两种常见方案WebSocket推送用户提交后服务端通过WebSocket把状态更新推到前端。SSE轮询补充WebSocket断线时前端用轮询兜底。实际项目中我建议两者都做。WebSocket处理理想情况短轮询处理异常情况。每次状态变化都推一次从“排队中”到“评测中”再到“通过/失败”用户可以实时看到运行过程这个体验和LeetCode的提交体验是一致的。4. 异步消息处理与数据缓存架构4.1 为什么需要异步消息队列如果评测请求直接同步处理一个用户提交代码服务端就一直阻塞到评测完成。假设一个测试用例集运行需要5秒每秒有20个提交那就意味着同时有100个评测任务在跑服务端线程池一满后续请求全部排队整个系统响应变得越来越慢。引入消息队列之后提交接口只负责做两件事把提交记录写入数据库把评测任务发送到队列。真正耗时的评测过程由消费者异步处理。这样提交接口的响应时间在几十毫秒以内系统可以轻松扛住突发提交流量。队列选型上项目里对比了RabbitMQ、Kafka和Redis Stream。对于这种判题场景任务量远没有大到需要Kafka的程度RabbitMQ的ack机制正好可以保证评测任务不丢失。Redis Stream适合小规模部署不需要额外维护消息中间件但持久化能力和可靠性弱一点。我的建议是如果没有独立运维MQ的预算直接用Redis Stream做轻量消息队列就够了。# Redis Stream 生产端实现 import redis r redis.Redis(hostlocalhost, port6379, decode_responsesTrue) def submit_judge_task(submission_id, code, problem_id): r.xadd(judge_queue, { submission_id: submission_id, code: code, problem_id: problem_id }, maxlen100000)# Redis Stream 消费端实现 while True: entries r.xread({judge_queue: }, count1, block5000) for stream, messages in entries: for msg in messages: task_id, data msg process_judge_task(data) r.xack(judge_queue, judge_group, task_id)4.2 缓存分层与数据一致性策略这个系统的缓存设计分了三层本地缓存、Redis分布式缓存、数据库。本地缓存用Caffeine存题目详情这类读多写少的数据Redis存用户会话、排行榜、热题列表等共享数据数据库做最终持久化。缓存最怕的是穿透和击穿。穿透是指查询一个不存在的题目ID每次都打到数据库击穿是指某个热点题目缓存过期大量请求瞬间打到数据库。这个项目里做了两层防护布隆过滤器拦截不存在的ID请求。热点数据缓存永不过期只做主动更新。考虑到题目数据本身更新频率极低把题库缓存设计成“永不过期定时刷新”是合理的。判断题目的数据变更用版本号控制每次有题目更新版本号加一缓存客户端定时去检查版本号变了就重新加载。这个方案实现简单也不容易出现缓存一致性问题。4.3 用户认证授权模块的实现要点认证模块直接用了JWT设计。用户登录成功后服务端签发AccessToken和RefreshTokenAccessToken有效期设置15分钟RefreshToken有效期设置7天。AccessToken无状态服务端不必存储会话刷新逻辑由独立的接口处理。角色权限上系统分了三类角色学生、教师、管理员。学生只能看题、提交、做题教师可以创建题目、查看学生统计数据、审核AI生成的题目管理员拥有全部权限。权限控制用Spring Security的注解驱动在Controller方法上加PreAuthorize即可。JWT需要注意的一个坑是注销问题。因为Token无状态服务端没法主动让一个Token失效。这个项目做法是维护一个Redis黑名单用户注销时把当前AccessToken的jti加入黑名单设置过期时间等于Token剩余有效期。后续请求过滤器先查黑名单命中就直接拒绝。这样既保留了JWT的无状态优势又解决了注销失效问题。5. 学习路径推荐与前端交互5.1 基于答题记录的学习路径推荐学习路径推荐是这个系统比较有特色的一块。推荐算法没有用太复杂的模型而是基于知识图谱和用户答题记录的规则引擎。系统把每个题目映射到知识点比如“动态规划-背包问题”、“图论-最短路”。用户每完成一题系统更新该知识点的熟练度熟练度计算公式用的是类似遗忘曲线的模型proficiency proficiency * decay (correct ? 1 : 0)做题正确则熟练度上升错误则下降同时会衰减代表一段时间不练就会遗忘。根据熟练度系统把知识点划分为“未掌握”、“学习中”、“已掌握”三个状态。推荐策略是“未掌握”的知识点优先推荐基础题目。“学习中”的知识点推荐同类题巩固。“已掌握”的知识点推荐进阶题目或跳过。这个推荐逻辑虽然朴素但胜在可解释性强学生可以清楚知道系统为什么推荐这些题。比起纯冷启动的协同过滤这种基于知识点的推荐在编程学习场景下更实用。5.2 前端实时反馈交互设计前端的核心页面有三个题库列表页、做题页面、个人中心。做题页面是最复杂的需要同时承载题目描述、代码编辑器、提交状态反馈和信息面板。代码编辑器选型上项目使用的是Monaco Editor就是VS Code的编辑器内核。集成过程比较繁琐尤其是和React/Vue的联动、主题定制、语言补全配置。这里分享一个经验Monaco Editor的Worker加载方式在打包构建时必须单独配置否则生产环境会报找不到Worker的错误。题目的描述和样例展示用了Markdown渲染。AI生成的题目描述虽然初始是纯文本但还是统一用Markdown格式存储方便后续排版优化。注意大模型生成的Markdown可能包含不规范标签入库前要用解析器清洗一遍防止XSS注入。5.3 系统性能优化与压测结果项目在开发完成后做了一轮压测压测目标是模拟100用户同时提交代码。压测时发现的一个瓶颈是评测沙箱创建速度。Docker容器每次创建需要600毫秒到1秒100个提交就意味着至少100秒远远满足不了并发需求。后来做了池化优化预先创建一批闲置容器提交进来直接从池里取用完归还。这样每次评测的容器获取时间降到10毫秒以内吞吐量大幅提升。容器池的大小根据最大并发数配置默认20个队列堆积超过阈值时动态扩容。还有一个性能优化点测试用例的读取做了预加载。题目常用测试用例在Redis里缓存一份评测时直接内存读取避免I/O等待。对于大型测试数据文件走对象存储评测沙箱挂载时按需拉取。6. 开发中踩过的坑与排障实录6.1 典型问题排查表这个项目开发和测试过程中遇到不少问题整理一下比较有代表性的几个问题现象原因解决方案评测结果偶发超时但代码本地秒跑完容器冷启动耗时容器池化预创建闲置容器AI生成题目描述有乱码模型输出编码问题入库前统一UTF-8编码检查用户提交高并发时接口响应变慢消息队列消费者数量不足消费者数量线性扩展到核心数两倍题目列表出现重复去重逻辑没覆盖描述改写相似度阈值从80%调整到90%Redis缓存雪崩热点数据设置了相同过期时间随机化过期时间热点数据永不过期沙箱内Python程序内存超限波动大Python解释器自身占用内存限制额外增加64MB缓冲最麻烦的一个问题出现在沙箱和消息队列之间的对账上。消费者从队列拿到任务后沙箱执行过程中如果进程崩溃任务就丢了用户这边显示一直在排队。后来引入了任务状态机评测任务在Redis里存状态消费者启动时先做一次恢复扫描把超时的任务重新入队。这个机制虽然增加了代码复杂度但可靠性提升非常大。6.2 我个人的一些实操心得这个项目整个做下来我最深的体会是一个在线评测系统能不能站稳评判标准不是题目多不多而是评测准不准、反馈快不快。从用户视角来看他提交一段代码希望看到的是明确清晰的反馈是WA还是TLE哪个测试用例没通过哪里超时了这些信息比换十道新题都重要。AIGC生成题目天然在边界测例上偏弱初期上线时学生反馈“题目总有一个隐藏测试用例过不去”排查下来发现大部分其实是题目约束和测试数据范围不一致。这个系统每次AI生成完题目我都会跑一遍标准答案和暴力答案的交叉验证确认通过之后才进入正式题库。另一个经验是异步化一定要从第一天就做。很多人在前期图省事用同步接口先跑通流程等用户量上来了再改异步改造成本远高于一开始就设计好。提交接口和评测任务解耦之后后面无论做容器池扩展还是消费者横向扩容都只是改配置的事情。最后再说一点安全上的建议。评测沙箱的隔离级别决定了整个平台的安全性即使只是内部教学系统也不要裸奔跑用户代码。我之前见过有人在测试环境用root直接跑用户提交的C程序一次死循环就把整台测试服务器CPU拉满这种事故不应该发生。至少要做到时间限制、内存限制、PUID/PGID隔离再考虑Docker级别的隔离。本文还有配套的精品资源点击获取