从零搭建在线编程笔试平台:NodeJS+Vue+MySQL全栈实战

发布时间:2026/10/14 14:53:08
从零搭建在线编程笔试平台:NodeJS+Vue+MySQL全栈实战 1. 在线编程笔试平台为什么值得从零搭一个上周有个朋友跟我聊天说他们公司招聘工程师时还在用在线文档加人工阅卷的方式组织笔试题候选人写完代码贴回来面试官肉眼核对输出结果一份卷子批改下来至少半小时遇到边界条件还得自己跑一遍验证。我当时就说这种情况非常适合用一套轻量的在线编程笔试平台来改善。后来我抽空把之前做过的一个基于NodeJSVueMySQL的在线编程笔试系统整理了一遍发现这个方向的选题确实很典型适合用来分享一整套从架构到落地的实践经验。这套系统能做的事情其实非常聚焦管理员发布题目候选人通过浏览器进入考试环境在线编写代码并提交运行系统自动判定结果并打分面试官在后台实时查看进度和成绩。相比传统笔试流程它把“发卷、答题、收卷、判卷、统计”全链路压缩到一个Web应用里组织一场技术笔试从按天计算变成了按小时计算。对正在学习全栈开发的同学来说这个项目也很有代表性——它同时覆盖了权限体系、在线编辑、代码执行沙箱、消息推送、判题引擎、数据统计这些复杂场景几乎把前端、后端、数据库、运维的知识点全部串起来了。我当时选型时没有用Python后端也没用React技术栈最终定下NodeJSVueMySQL这个组合。这一套选型不是随便拍的每一个环节都有明确的考量。下文我会把这套方案的整体架构设计、判题引擎的核心原理、多个实战环节的落地细节、以及我踩过的坑和排查经验完整拆开讲一遍希望对准备做类似系统或者正在学习全栈开发的朋友有参考价值。顺便说一句做这类系统最让人头疼的并不是写代码本身而是“让一份代码在服务器上安全地运行并得到正确结果”这一件事。这个环节坑很深我在第3章会重点展开讲。如果你只是想了解前端界面怎么做那市面上教程一大把但如果你想让平台真正可用、敢发出去给候选人用那判题引擎和安全隔离这两关是绕不过去的。2. 整体方案设计三端架构与核心模块拆解2.1 为什么选NodeJS做后端而不是Python或Java很多人的第一反应是判题类系统用Python写起来更顺手因为判断结果、解析输出这些操作用脚本语言非常高效。但我实际对比过之后还是选了NodeJS原因主要有三条。NodeJS的异步I/O模型特别适合这类“连接密集型”场景。在线编程笔试过程中浏览器和服务器之间会维持大量长连接候选人打开题目页面要记录进入时间编辑代码要自动保存草稿运行结果要通过WebSocket实时推送面试官后台要看到所有候选人的在线状态和答题进展。这些操作大多是短小的消息交互NodeJS的单线程事件循环配合非阻塞I/O在这种场景下资源消耗很低一个Node进程撑几百个并发连接毫无压力。如果用Java写同样的效果要引入更多线程配置和框架选型开发成本会高不少。前后端语言统一心智负担小。Vue前端和Node后端都用JavaScript/TypeScript意味着数据结构、命名风格、校验规则可以前后端复用。我在这个项目里把题目的判题配置、测试用例的期望输出、用户的权限标识这几个核心模型定义成了共享的JSON结构后端和前端引用的是同一份类型定义联调时少了很多“字段名对不上”的扯皮。生态成熟做原型和做生产级系统都有现成方案。Express/Koa处理路由Sequelize/Prisma操作MySQLjsonwebtoken做登录态Socket.IO做实时通信这些库我都实际用过稳定可靠。另外NodeJS的包管理机制对部署非常友好一条npm install就能把整个后端服务拉起来。当然NodeJS也不是没有缺点。判题任务如果涉及大量CPU密集型运算Node的事件循环会被阻塞好在我们这个场景里判题逻辑是由单独的Worker进程承担的主服务只负责接收请求和返回结果这个会在后文展开。2.2 VueMySQL在系统里的定位与选型理由前端这一层我选择Vue 3而不是Vue 2因为Composition API在处理考试界面这种“状态多变、逻辑复用要求高”的场景非常顺手。候选人答题页面同时要管理当前题目索引、编辑器内容、剩余时间、运行结果、草稿自动保存状态如果还用Options API这些逻辑会散落在data、methods、watch里代码很快就会失控。用Composition API可以按功能切分逻辑块比如useTimer管理倒计时useEditor管理IDE状态useSubmission管理提交动作每个文件内聚一个职责维护起来非常舒服。UI组件库用的是Element Plus。这套组件库对Vue 3支持好表格、表单、对话框、标签页这些管理后台常用的组件开箱即用。候选人考试页面的代码编辑器我选的是Monaco Editor就是VS Code同款的编辑器内核通过npm包引入支持语法高亮、自动补全、错误提示。用Monaco而不是简单的textarea是为了给候选人接近真实IDE的体验——我实际调研过候选人反馈他们普遍认为编辑器的自动缩进和括号配对功能对答题效率有明显帮助。MySQL在这套系统里的角色就是最基础、最可靠的数据持久层。选它不选MongoDB是因为笔试平台的数据关系非常明确用户有角色题目有关联的测试用例提交记录属于某个用户和某道题目这些关系用MySQL的外键和联合查询处理非常顺。而且MySQL对事务支持完善比如发布一场考试时需要同时插入考试记录、关联题目列表、初始化所有候考人的答题状态这一步必须用事务保证原子性否则会出现“考试建好了但部分考生没有绑定上”这种脏数据。MySQL的版本我建议直接用8.x。8.0以上的窗口函数、公用表表达式、JSON字段类型都是实打实的生产力提升。比如统计每道题目的提交次数和通过率一个窗口函数就能搞定换成5.7你得写一堆子查询。数据库连接池我用的是连接池组件而不是默认的mysql库连接方式。生产环境下一旦有几十个候选人同时交卷数据库连接数会瞬间飙升没有连接池很容易把MySQL的连接数打满。配置上我一般把最大连接数设为20等待超时设为10秒这个参数在中等并发下表现很稳定。2.3 系统模块全景拆解从前端页面到后端服务这个平台从功能上可以分成三个端我用一张表先把核心模块盘清楚。端核心模块关键功能候选人端登录/注册、考试列表、答题页身份验证、倒计时、代码编辑、运行与提交、实时结果反馈管理员端题库管理、考试管理、用户管理、成绩统计题目CRUD、测试用例维护、考试成绩导出、通过率分析服务端认证服务、题目服务、判题服务、实时通信服务、文件服务JWT签发、用例管理、沙箱执行、Socket推送、附件存储开发顺序我建议是先做管理员端的题库管理和考试管理因为这是平台的地基再做候选人端的答题流程这是核心体验最后做实时监控和成绩统计这类锦上添花的能力。这样每个阶段都能得到一个可演示的版本不会到了最后一天才发现题目和考试连不起来。做这个项目时我有一个体会很深的点不要一开始就想着做“完美的判题系统”。第一版能支持最常见的几种语言、能跑通“运行用例、比对输出、给出通过或失败”这条主链路就足够了。等主流程稳定后再慢慢扩展语言支持、增加内存和CPU限制、做相似度检测这些高级功能。我见过很多开发者把大量时间花在优化判题精度上结果基础流程还没跑通这个顺序需要注意。3. 判题引擎的设计与实现安全、性能与正确性三重考验3.1 判题流程与沙箱隔离方案怎么做到既安全又高效判题引擎是整个平台技术含量最高的一块因为它直接面临三个互相牵扯的问题让候选人的代码能跑起来、让运行结果可信、同时不能让候选人的代码把服务器搞挂或泄露数据。先讲这个引擎的架构分层。候选人提交代码后主服务不会直接执行这段代码而是把它写成一个任务放进一个先进先出的消息队列。独立的判题Worker进程从队列里取任务在隔离环境里执行代码把执行结果包括运行输出、错误信息、耗时、内存峰值写回数据库再通过WebSocket通知前端刷新结果。这样主服务和判题服务彻底解耦即使某一段代码导致判题进程重启主服务也不会受影响候选人的其他请求依然可以正常处理。沙箱方案我在这里先说结论不要自己实现也不要裸用child_process直接采用成熟的隔离容器方案。选型时我在纯Docker方案和受限的本地沙箱方案之间犹豫过。纯Docker隔离性最强但镜像制备和容器启动开销比较大每判一道题都要创建和销毁容器性能损耗明显。本地沙箱方案速度快但隔离性不够彻底一个系统调用就可能逃逸。最终我用的是折中方案外层用容器做进程隔离内层用资源限制机制限制CPU和内存同时配合系统级的只读文件系统挂载既保证了隔离强度又把单次判题的启动耗时控制在了几十毫秒级别。这里有一个细节值得提为了提速我没有每次判题都重新创建容器而是采用“预创建复用”的策略。系统启动时预先创建一批处于待命状态的容器池收到判题任务后从池里取出一个可用容器执行用完销毁。这就像餐厅提前摆好餐具客人来了直接上菜不用临时洗碗。实测下来判题吞吐量提升了将近一倍。3.2 判题判定规则与测试用例设计技巧判定逻辑表面上很简单就是跑一遍候选人的代码把输出和期望输出逐字对比。但实际做起来坑非常多。第一个坑是输出格式的宽松匹配。真实场景里候选人代码输出的空格、换行、大小写经常和标准答案不完全一致。比如题目要求输出“Hello World”候选人写了“Hello World”多了一个空格按逐字节比较肯定是错但从语义上又是对的。我采用了两级匹配策略第一级做严格匹配不行再降级为忽略首尾空白和连续空格的模糊匹配再不行就看数字类型的浮点误差是否在容差范围内。这个策略能有效减少争议对候选人更公平。第二个坑是测试用例的覆盖设计。一道题光有一两个示例用例远远不够判题系统要验证的是代码的逻辑正确性而不仅是示例能跑通。我在设计测试用例时遵循三条原则先覆盖题目描述里的示例再覆盖边界条件比如数组的极值、空输入、单元素输入最后是隐藏的随机化大数据量用例用来验证算法的时间复杂度是否符合要求。最后一类用例我会在生成时记录随机种子保证判题结果可复现避免“这次过了下次没过”的情况。第三个坑是超时和资源超限的处理。每个判题任务我设置了三重限制CPU时间限制普通题2秒重计算题5秒、内存限制各家语言256MB到512MB之间、输出体积限制通常限制在10MB以内。超限的任务直接判为失败错误信息会明确告诉候选人是“运行超时”“内存超限”还是“输出过多”。这样候选人可以有针对性地优化自己的代码。3.3 多语言编译执行方案从Java到Python的差异化处理支持多语言是这类平台的基本盘但每种语言在判题环境里的处理方式差别很大下面我逐个说。C/C是最干净的场景编译和运行分离。编译阶段用g把源码编译成可执行文件编译参数统一为-O2 -stdc14关掉所有警告输出以免干扰信息再把可执行文件放入隔离环境运行。运行时内存限制要略微放宽因为程序本身加载就要占用一定空间但CPU时间限制必须卡紧防止死循环把判题Worker拖垮。Java比较特殊因为Java程序启动时JVM本身就要吃一部分CPU和内存。我设置了独立的规则CPU时间限制比C放宽50%内存上限直接给到512MB。编译时用javac运行阶段用java -cp加上类路径参数。这里还有一个小坑Java的默认安全机制会限制程序访问某些系统属性比如读取环境变量和系统信息时可能抛出异常。我在判题环境里预设了安全的系统属性白名单避免候选人的代码因访问系统信息而意外失败。Python是动态语言没有编译环节直接解释执行。但它有两个问题一是解释器启动本身耗时二是裸解释器很容易被恶意代码利用。我的处理方式是用受限模式启动Python解释器并在代码执行前注入一个资源监控线程每秒检查当前内存占用超过阈值就主动终止进程。这样即使候选人写了个无限增长列表的程序也不会把服务器内存吃掉。Go语言这个场景用得相对少但我也支持了。它的编译速度和C接近运行时没有虚拟机资源消耗低判题体验很好。唯一要注意的是Go编译出的二进制体积较大需要限制编译产物的输出目录避免一次性写满磁盘。3.4 判题结果判定不只是通过和不通过判题结果不能只返回一个布尔值候选人需要知道自己的代码到底错在哪。我的结果结构包含五个字段通过状态通过、失败、超时、内存超限、编译错误、运行错误、系统错误、执行的测试用例数量、通过的数量、失败时前几个用例的期望输出与实际输出、以及耗时和内存峰值。候选人端拿到这份结果后界面会展示运行时间、内存消耗和具体的错误输出对比帮助候选人定位问题。这个设计看似简单但实际上非常影响用户体验。我第一版只返回了“通过/不通过”候选人反馈根本不知道怎么改。加上了错误输出对比后答题体验明显提升很多候选人能根据提示快速修正代码不再需要反复盲目试错。从面试官视角判题结果还有一个隐藏用途观察候选人的调试过程。一次提交就通过、多次提交后才通过、提交中途转型换思路这些行为模式从提交历史里都能看出来对技术判断很有参考价值。所以我会在提交记录表里完整保存每次提交的代码快照、判题结果和时间戳这个数据积累下来对招聘决策很有帮助。4. 系统核心实现权限、实时通信与数据库设计的实战细节4.1 基于JWT的角色权限体系前后端如何协同守卫在线笔试平台天然有角色区分管理员、面试官、候选人。三种角色看到的页面和能执行的操做完全不一样所以权限体系必须从第一行代码就开始做。我用的是JWT方案。用户登录成功后后端用密钥签发一个JWT里面携带用户ID和角色标识默认有效期设置为2小时。前端拿到JWT后存储在当前会话存储里每次发送请求都在Authorization头带上这个Token。后端中间件会统一解析Token、校验签名、检查过期时间把用户信息挂到请求上下文里。这样每个接口只需要声明自己允许的角色由中间层统一拦截。但JWT方案有一个坑它天生无状态服务端不记录“当前登录用户”是谁所以无法在服务端主动踢人下线。如果某个候选人在考试中被管理员判定了作弊他的Token在过期之前依然有效。我的解决方案是维护一个用户状态缓存登录和操作时更新用户在缓存中的状态标记认证中间件在鉴权时会先检查这个标记如果发现被禁用就直接拒绝。简单说就是用一层内存态缓存给无状态的JWT补上了“状态”能力。前端也不能只靠隐藏按钮来实现权限控制。正确的做法是用路由守卫在Vue Router的beforeEach钩子中读取当前用户角色逐个比对目标路由的meta.roles配置不匹配就跳转到一个无权限提示页。注意这只是为了体验流畅真正的安全边界一定在后端接口前端路由守卫只能算锦上添花。4.2 WebSocket实时通信在考试场景里的妙用在线笔试平台里有几个场景天然需要实时通信候选人提交代码后判题结果要尽快推送回页面面试官后台要实时看到每个候选人的当前状态在线、答题中、已交卷管理员发布考试变更公告时所有候选人页面要即时收到弹窗提示。这些场景里HTTP轮询不是不能做但会造成大量无效请求。我采用Socket.IO来实现双向通信主要原因是它对断线重连、心跳检测、房间广播这些能力都有现成支持不需要自己从零实现一套WebSocket协议细节。关于通信的设计有一个关键点是“按房间隔离消息”。候选人进入某场考试时前端会调用socket.join(exam-考试ID)加入对应的房间。判题完成后后端向exam-考试ID房间广播结果只有这场考试里的人能收到。面试官后台则join一个管理员的全局房间这样任何候选人交卷后台都能第一时间收到通知。这种房间机制在Socket.IO里十分成熟实现起来也优雅比自己在每条消息里维护接收人列表要省心得多。4.3 核心数据表设计与事务实践数据库方面核心数据表至少要有五张用户表、题目表、测试用例表、考试表、提交记录表。加上辅助的关系表总共十几张。我把关键表的结构大致说一下。用户表包含账号、密码哈希、角色、姓名、所属组织、创建时间。密码用加盐哈希存储不要用明文这是底线。题目表包含题目标题、描述、难度等级、语言类型、时间限制、内存限制、模板代码、是否启用。测试用例表关联题目ID存输入数据、期望输出、是否公开可见。考试表包含考试名称、开始时间、结束时间、时长、创建人、状态。提交记录表是数据量最大的表关联用户、题目、考试存提交代码、判题结果、耗时、内存、状态、时间戳这个表后面还要建索引。面条字段用TEXT类型测试用例的输入输出数据也可能很长用MEDIUMTEXT存储。需要核心查询条件的字段都建索引比如提交记录表的考试ID、用户ID、状态复合索引这直接决定了后台统计页面的响应速度。事务我举个例子。管理员创建一场新考试时后端要做一连串操作插入考试记录、关联题目列表、初始化本场考试所有候选人的答题状态记录、创建统计摘要记录。任何一步失败整个流程都必须回滚否则会出现考试创建了一半的脏数据。我把这四个步骤包在同一个数据库事务里任何异常都统一回滚这是最基本的可靠性保障。5. 实操过程实录从零搭建到跑通一次完整答题链路5.1 前后端项目初始化与关键依赖选配我实际搭建的流程是先建一个项目根目录里面分三个子目录clientVue前端、serverNode后端、judger判题服务。前端用Vite工具链初始化Vue 3项目然后装插件依赖路由插件、状态管理插件、UI组件库、请求库、编辑器组件。初始化时要注意Vite默认端口是5173后端API服务我用的是3000端口跨域问题通过后端的跨域中间件统一处理。后端用Express框架初始化目录结构我习惯按业务模块划分路由层、控制器层、服务层、数据访问层外加中间件目录和工具目录。数据库访问我用的是ORM框架配置好MySQL连接信息后通过模型定义同步建表会更方便。ORM的迁移功能在开发阶段很实用改模型后执行一次迁移就能更新表结构省去了手工敲SQL的麻烦。安装依赖时有一个实用经验锁定版本。不要装“最新版”因为新版可能引入破坏性变更。项目的依赖我全部写在package.json里并提交到版本管理同事拉下来后npm install能复现完全一致的环境。这种可复现性在部署时太重要了。5.2 候选人答题页的完整流程实现答题页是候选人端体验的核心。我的实现流程是这样的页面加载时先请求考试信息和第一道题目同时启动一个前端定时器显示剩余时间。候选人选择题目后在左侧展示题面右侧加载Monaco编辑器并把题目自带的模板代码填入编辑器。编辑器内容变化时做防抖自动保存每隔30秒自动把草稿同步到后端避免候选人因为误关页面而丢失代码。候选人点击“运行”按钮时前端把当前编辑器内容连同语言信息发送到后端进入判题流程。这里有一个状态处理的小细节运行期间按钮要置灰并显示进度状态防止候选人重复提交同时用加载动画减少等待焦虑。判题结束时WebSocket推送结果前端更新界面把通过/失败状态和测试用例对比清晰地展示出来。最后是“交卷”环节。交卷前前端会做两次确认弹窗第一次提示“确定要交卷吗交卷后不能继续修改”第二次要求输入“确认”关键字后才能真正提交。这个双确认机制非常有效我统计过因为误触导致提前交卷的投诉几乎为零。交卷动作的后端处理是一个事务更新考试状态为已交卷、标记交卷时间、把当前所有未提交的草稿做一次最终提交、计算基础得分。这套逻辑确保交卷后数据是完整且一致的。5.3 管理员后台的核心页面逻辑管理员后台我做得比较克制没有堆砌花哨的图表而是把精力放在数据准确和操作高效上。题库管理页面用表格展示题目列表支持按难度、语言、状态筛选。新增或编辑题目时用抽屉式表单题面支持Markdown格式测试用例用表格录入可以动态增减行。这个页面最核心的是“保存前校验”所有测试用例的输入和期望输出必须有值时间限制和内存限制必须在允许范围内否则不让保存。考试管理页面用于创建和编辑考试。创建考试时选择题目并配置场次信息系统会自动计算本场考试的总分和时长。创建完成后可以生成一个考试邀请码候选人凭邀请码加入考试。成绩统计页面展示每场考试的名单、每人的总分、每题得分、提交次数、最快通过时间。表格支持按分数排序支持导出CSV。导出功能是用后端生成的前端只负责下载因为候选人和分数数据量大了以后前端做导出容易卡死浏览器。5.4 从本地到云服务器完整部署过程记录部署是我在这个项目里花时间比较多的一块。本地开发环境和生产环境的差异之大超出很多新手的想象。我用的部署方案是一台Linux云服务器安装Docker和Docker Compose。用一个编排文件把四个服务管理起来MySQL数据库容器、Node后端容器、判题服务容器、Nginx静态文件服务器容器。前端构建后的产物通过构建镜像直接跑在Nginx里后端API和判题服务分别映射到内部端口。部署前需要准备的几个文件后端的环境变量配置文件里面配置数据库地址、JWT密钥、判题服务地址Nginx的站点配置把根路径指向前端静态目录把/api路径反向代理到后端端口再配置好WebSocket升级的转发头。WebSocket代理如果不加升级相关配置头前端会一直连不上Socket这是我第一次部署时排查最久的问题。数据库方面我用了一份初始化SQL脚本包括建库、建表、插入默认管理员账号和示例题目。部署时执行一次初始化脚本然后启动容器编排。这里有一个经验不要在生产环境用ORM自动同步表结构很容易出现意外。固定的初始化脚本可控得多改表结构时走迁移更安全。6. 稳定性与安全加固这个平台不能只“能跑”6.1 接口安全与防作弊的落地手段在线笔试场景的防作弊跟线下监考完全不同技术手段再强也只能降低作弊概率不能彻底消除。我能做的是把常见的作弊路径尽量堵住。接口层面我做了一套风控设计。每一项答题操作都记录了时间戳和动作类型后端通过简单规则识别异常模式。比如同一IP在几秒内提交了来自不同账号的几乎相同代码或者某账号在极短时间内提交大量不同语言的代码这些异常会被标记并在后台高亮展示给面试官。这套规则不追求精确判定只负责把人可疑的行为暴露出来最终判断交给人工。代码相似度检测我也做了。因为判题服务里存了所有提交的代码副本每次交卷时都会执行一次代码相似度哈希比较超过阈值就把相关提交全部标记为疑似雷同面试官可以在后台看到雷同代码片段的具体对比。这个功能用的是局部敏感哈希算法性能和准确率之间的平衡还可以。IP和登录设备指纹的采集是另一个维度。候选人登录、答题、交卷三个阶段都会上报IP、UA、屏幕分辨率等环境信息后台可以横向对比发现设备跳变等异常情况。这些数据平时完全不打扰候选人答题过程只在需要回溯时作为参考。6.2 高并发下的API限流与性能优化笔试平台的流量通常集中在考试开始和交卷这两个瞬间所以限流策略要针对这两个入口重点设计。我给后端API做了一层全局限流中间件接口维度按IP和用户ID分别限流。普通查询接口限制每用户每分钟60次提交代码接口每用户每分钟5次交卷接口每用户每分钟2次。超限后直接返回429状态前端会友好提示“操作过于频繁请稍后重试”。这层保护非常关键某些候选人会用脚本循环点击“运行”按钮压测接口没有限流的话判题队列会被瞬间打满。数据库层面还有一个优化我特意做了提交记录表按考试ID做了分区因为每次考试的提交数据量会急剧增长。分区后按考试ID查询成绩时只扫描对应分区速度提升非常明显。同时定时任务会在每天凌晨清理三个月前的过期草稿缓存避免数据库无限膨胀。6.3 日志、监控与数据备份判题系统出问题时的定位速度直接取决于日志的质量。我的日志体系分三层访问日志记录所有HTTP请求的方法、路径、状态码、耗时业务日志记录关键业务动作比如谁创建了考试、谁提交了判题、谁修改了题目判题日志记录判题全过程的输出、错误、耗时和资源消耗。每一条日志都带上请求ID和时间戳排查问题时可以按一个请求ID把所有链路日志串起来。监控方面我没有上重型监控系统就用了轻量方案进程管理工具守护Node进程进程崩溃后自动拉起后端每30秒上报一次健康指标到管理接口包括事件循环延迟、内存占用、数据库连接数对判题失败率、接口错误率设置简单阈值超过后触发webhook通知到钉钉群。这套轻量监控对于中小规模平台够用了成本也低。备份是很多人忽视但一旦出事就后悔到极点的环节。我设置了每天凌晨两点自动对MySQL执行全量备份同时开启binlog保留最近七天的增量记录。恢复方案也测试过在新环境执行全量恢复再应用增量日志数据能恢复到最近一次备份前五分钟的状态。这个能力在真正遇到误删数据时值千金。7. 常见问题排查面向真实踩坑现场7.1 判题结果不正确、超时、内存超限的排查路线先说说判题结果不对的情况。如果候选人的输出和期望输出不匹配我会先看是全部用例都不对还是部分用例不对。全部不对大概率是编译或运行环境出问题比如依赖库没安装、类路径配错部分不对大概率是判定规则太严格比如浮点精度问题没处理。排查时把判题Worker记录的原始输出全部打印出来直接对比就能定位。超时问题我一般查三个地方代码本身是否有死循环这是最常见的判题容器是否被其他任务拖慢因为单机容器资源竞争会放大耗时波动CPU时间限制是否设置得过紧。排查方法是给慢查询加日志看一次判题的完整生命周期时间分布编译耗多少、运行耗多少、输出采集耗多少一清二楚。内存超限的方向不一样正常代码跑完就释放了如果被判定超限往往意味着测试用例数据量太大或代码有内存泄漏。有一次我排查一个C判题超限的案例最后发现是代码里的全局向量没有清理所有测试用例在同一个进程里跑导致内存累积。解决方案是每个用例用独立子进程执行虽然慢一些但是结果更靠谱。7.2 数据库连接数被打满与数据一致性异常这类系统上线后第一个容易爆的问题就是数据库连接数。早期我配置的连接池太小考试开始瞬间几十个人同时查询题目连接池被打满后面的请求全部排队卡死。排查时查后端日志会发现大量“获取连接等待超时”的错误。解决方案有两层调大连接池到合适的值同时对高频读接口加了一层本地缓存题目内容这种很少变化的数据不需要每次都查库。数据一致性异常常见于并发交卷场景。两个候选人同时交卷后端都在处理“更新考试状态、写入提交记录”如果没有事务隔离可能出现状态错乱。我遇到过的问题是同一场考试的两个候选人同时交卷结果一个人的交卷时间写到了另一个人的记录上原因是更新语句的条件字段写错了。排查这类问题时我习惯把关键操作的SQL打出来仔细核对条件避免只盯着代码逻辑而忽略了具体查询条件。7.3 前端页面白屏、Socket连不上、本地与线上表现不一致前端页面白屏第一反应查浏览器控制台报错。常见的原因有接口跨域配置不对、打包后的资源路径不对、静态资源没有正确映射到Nginx。印象最深的一次是前端构建后图片路径变成了绝对路径导致所有图片404排查半天才确认是路径配置问题。Socket连不上大概率是Nginx代理没有正确转发WebSocket升级请求。必须显式配置升级相关的请求头否则浏览器和服务器之间只能建立普通HTTP连接。这个坑我踩过一次需要在Nginx的location块里加上升级相关的头配置才能解决加完立刻连上印象非常深刻。本地正常线上异常最常见的原因是环境变量差异。本地开发时数据库地址是localhost线上是容器内网络别名本地没开启严格跨域线上开启后部分浏览器拦截了跨域请求。我的建议是环境相关配置全部走环境变量注入不要写死在代码里而且要有一个文档把每个环境变量都写清楚是干什么用的。8. 个人体会与后续扩展建议真把这个平台从零做完一遍后我对全栈开发的体感完全不同了。以前做CRUD管理系统只需要关心“数据能不能存进去、能不能查出来”做这种在线编程平台才知道真正难的是那些夹在页面和数据库之间的隐性工程问题怎么在代码里执行代码、怎么隔离恶意输入、怎么保证并发场景的一致性、怎么让运维故障处理得像开卷考试一样有据可查。这些能力不是看教程能学来的必须亲手踩过坑才能长在身上。如果这个系统后续要继续扩展我会优先做三件事第一是引入更多的语言运行时支持比如补上Rust和JavaScript让平台覆盖更广的岗位类型第二是给判题引擎加上K8s集群调度能力让判题任务可以在多台机器上横向扩展应对大规模校招这种并发非常高的场景第三是完善成绩的统计分析报表从平均分、分位数、每题的区分度这些维度做更深入的分析让面试官能更快地识别出值得进入下一轮面试的候选人。最后分享一个做这类系统最值得牢记的体会平台的可靠性远比功能丰富度重要。一场考试进行到一半服务器挂了哪怕你功能再全对使用者的信任打击也是毁灭性的。所以耗费精力做好监控、限流、备份不是浪费时间而是这类产品能够被你放心推荐给别人的基础。