一套从出题到出分的考试系统。八种题型、规则判分 + AI 判主观题、断电续答; 另配一个 Windows 考场客户端——座位即身份,考生不输账号密码,监考老师一点开考, 机器自动进入答题页并锁屏。
本仓库只放编译好的产物,暂不开源代码。 系统还在完善,难免有没暴露出来的问题, 等稳定之后再把源码放出来。下载解压就能部署。
考生浏览器 ─┐ ┌─ PostgreSQL 16 题库 / 试卷 / 考试 / 成绩(落盘)
考场客户端 ─┼─ nginx ─┬─ API 节点 ×N(无状态)─┤
监考端 ─┘ 静态页 └─ … └─ Redis 作答缓冲 / 待刷盘队列 / AI 判卷队列
▲ │
└── 局域网 UDP 探询 / 信标 ┘(考场客户端找监考端,装机零输入) DeepSeek / 千问(OpenAI 兼容接口)
| 部分 | 技术 | 说明 |
|---|---|---|
| 服务端 | Rust | 服务端打成单个 exe,不装运行时 |
| axum 0.8 | 全部 HTTP 接口:后台、考生答题、机位、监考端 | |
| tokio | 后台任务(刷盘、到点交卷、AI 判卷、资源采样)和 API 跑在同一个进程 | |
| sqlx 0.8 | 访问 PostgreSQL;自带迁移,首次启动自动建表 | |
| 数据 | PostgreSQL 16 | 唯一的真相:考试进度、截止时间、作答都以库里为准 |
| Redis 6.2+ | 只做作答缓冲和队列,里面只有还没刷盘的最近几秒 | |
| 网页端 | Vue 3 | 后台、考生答题、机位页同一个包,中英双语 |
| Element Plus | 界面组件 | |
| Vite | 构建;漏翻一条文案构建就失败 | |
| 考场客户端 | Rust | 单个 exe,不依赖浏览器 |
| egui 0.36 | 答题界面 | |
| Win32 | 两级锁定:键盘钩子 / 前台守护 / 任务管理器与 U 盘开关 | |
| 监考端 | Rust | 单个 exe |
| egui 0.36 | 看板、取号、布机 | |
| AI 判卷 | OpenAI 兼容接口 | 默认接 DeepSeek / 千问;失败自动重试,判不出来的转教师 |
| 认证 | JWT | 考生、教师走账号;考场电脑走「座位」—— 服务端把这个座位对应考生的令牌发给它 |
几条决定整个系统样子的设计:
- 作答不丢。 考生每改一题,1 秒防抖后写进 Redis,同时在本机留一份;后台每 3 秒把 Redis 里的批量刷进 PostgreSQL。断网时本机那份是兜底,网回来自动补发;到点那一刻断网,页面原地重试到服务端宽限期结束, 送达之前不清本机、不跳走。
- 时间只信服务端。 截止时间在开考那一刻算定并存库;考场电脑的钟快了慢了都不影响, 倒计时按服务端时间走,断电重进接着算,不会重置也不会多给。
- 交卷幂等。 考生点交卷、到点自动交、老师强制交、切屏超限交 —— 四条路撞在一起只算一次, 重复提交返回同一份成绩。
- API 节点无状态。 会话在 JWT 里、作答在 Redis 里,nginx 后面挂几个节点都行;扩容就是加机器。
- 判分规则写死在服务端。 八种题型的判法、多选漏选给分、填空的等价答案都在服务端算, 客户端只负责收集作答,改不了分。
- 座位即身份。 考场电脑绑的是「考场码 + 座位号」,不是账号;考生坐下什么都不用输, 老师开考时服务端才把这个座位对应的考生身份发下去。
- 颜色不说谎。 界面上的绿色只留给「判过而且对了」;题型、已交卷、已批改一律中性色, 免得考生和老师把「做完了」读成「做对了」。
实测:四核开发机、数据库同机,200 人同时交卷并完成判分 368 毫秒,零丢失。 可靠性靠跑出来而不是写出来:加固回归 61 项、并发 11 项、断电恢复 18 项、接口契约 81 项, 外加一组真浏览器冒烟(断网、刷新、到点交卷都在真 Chrome 里发生一遍),中英文各跑一轮。
同一套程序覆盖三种考法,区别只在考试设置里勾什么:
| 场景 | 怎么用 |
|---|---|
| 随堂小测、课后作业 | 考生用自己的电脑或手机开浏览器,登录做题 |
| 期中期末机考 | 机房统一开考,装考场客户端,考生不接触账号密码 |
| 高利害统考 | 客户端 + 严格锁定 + 随机卷,每人一套不同的题 |
三个角色:管理员管账号和服务器,教师出题、组卷、监考、批改,考生只看到自己的考试。 教师看不到用户管理和系统监控,考生看不到任何别人的卷子。
浏览器——考生自己登录,进入「我的考试」,点开就答。断电、关标签、换设备,回来接着答。 界面支持中英文,顶栏有个「中 / EN」开关,选了之后这台浏览器记住。
考场客户端——考场电脑开机自启,绑到座位号上;考生坐下什么都不用做,老师一点开考, 这台机器自动用这个座位对应考生的身份进入答题页。详见 考场客户端。
![]() |
![]() |
八种题型,每种的判分规则都写死在服务端,客户端只负责收集作答:
| 题型 | 判分 |
|---|---|
| 单选 | 选对得满分 |
| 多选 | 全对得满分;可选「漏选给一半分」,错选一律 0 分 |
| 判断 | 对错 |
| 填空 | 每个空可以配多个同义答案,答中任意一个算对;总分按空数平均分 |
| 数值 | 支持误差范围,比如 3.14 允许 ±0.01 |
| 排序 | 展示时打乱,顺序全对才得分 |
| 匹配(连线) | 按连对的组数给分;右栏可以比左栏多,多出来的是干扰项 |
| 简答 | 交给教师 / AI / AI 初判 + 教师复核,三选一 |
填空题的空由按钮插入。 点「在光标处插入空位」,题干里就多一个空, 下面自动多一行答案,预览里用 ①②③ 标出考生会看到的样子。 不用自己敲下划线,也就不会出现「敲了三条线只填了两个答案」这种存盘才发现的错。
组卷两种方式:
- 固定卷——从题库挑题、逐题设分值,所有考生同一份。
- 随机卷——写几条规则(分类、题型、难度区间、抽几道、每题几分), 每个考生抽到的题都不一样。发布前点「保存并试抽」,题库不够会当场告诉你缺多少道, 而不是等到考试当天考生进不去。
题目多的时候不用一道道敲。三种来源,最后都落到同一张预览表上, 确认无误再入库:
| 来源 | 怎么用 |
|---|---|
| 表格 | 选一个 Excel(.xlsx)或 CSV。一行一题,列名中英文都认,界面上可以下载模板。八种题型都支持 |
| 文字(AI) | 把 Word 里复制出来的题目直接粘进去,AI 整理成题目草稿 |
| JSON | 老格式保留,接口对接时用得上 |
表格那条路在浏览器里解析,文件不上传服务器。GBK 编码的 CSV(WPS 导出的默认就是)、 表头前面的标题行、空着的列,都认得出来。哪一行为什么不能导,会直接写在那一行后面 —— 「答案里的 C 没有对应的选项」比「导入失败」有用得多。
列名和模板对不上也不用回去改表头,点「交给 AI 整理」,整张表丢给模型。
AI 认出来的题一律先进预览表,人点「导入」才写数据库。 模型会编答案,这一步不能省。 服务端没配 AI 时这一项会直接说「没配」,不会转圈。
建考试时定三件事:考试窗口(几点到几点谁都进不来)、答题时长(从考生自己点开始算)、 以及一串防作弊开关。考生的截止时间取「他的开始时刻 + 时长」和「考试结束时间」里较早的那个。
考试分草稿、已发布、已关闭三种状态:草稿不会出现在考生那边,发布之后考生才会在 「我的考试」里看到它,关闭会把还没交的卷子一并强制交上来。界面上再按时间细分成 阶段——未开始、进行中、已结束(时间到了还没关)——列表按阶段分页签,正在考的排最上面。
老师登录先落到工作台:现在谁在考、到了几个人、接下来 7 天考什么、还有多少卷子没批,一页看完。
每场考试有一个 6 位考场码,考场电脑靠它加座位号定位自己。考场码可以随时重置, 重置时能一并把全场机位解绑——考场码万一外泄,只换码是不够的。
监考页实时显示每个座位:没安排考生、没绑电脑、待开考、答题中、掉线、已交卷, 外加每人的切屏次数。可以按座位图看,也可以按名单看。老师能对单个考生强制交卷或补时, 也能一键关闭整场考试。
一个机房连着几天考试,同一批学生反复进来。每一场开始前都重新抽一遍机器吗? 系统不替你决定 —— 把几场考试串成一个排期,每一场单独选:
| 选项 | 什么时候用 |
|---|---|
| 重新抽号 | 座位固定等于给了作弊余地,要求场场重抽的学校用这个 |
| 沿用上一场 | 学生知道自己那台机器在哪,进场快、少出错 |
第一场永远是重新抽号 —— 它没有上一场可沿用。这一条在服务端也拍死, 不是只在界面上灰掉。
沿用不保证成功。 那台机器可能没开机,或者已经被别人取走。 取号时会退回随机抽一台,并且在监考端上明确显示换了机器 —— 不会让老师照着上一场的名单把学生指错位置。
座位一律现场随机抽。 考生到场,监考老师点「取号」,系统从空闲机器里随机抽一台给他。 没有「提前把谁坐哪排死」这个选项 —— 考生事先知道自己坐哪台,随机就白随机了。
建考试时从下拉里选一间机房。机房是一份台账:在哪个楼、哪个房号、有几台机器。
选了机房,三件事跟着来:
| 考生知道去哪 | 「我的考试」上显示「信息楼 302 · 一号机房」,不用再在班级群里另发通知 |
| 坐不坐得下算给你看 | 「这间 40 台,留 2 台备用 → 能发给考生 38 台」。名单比这个多,考试页上一条黄色提醒 —— 只提醒不拦你,老师知道有人不来、知道能临时搬机器 |
| 时间撞了拦住你 | 同一间机房、时间窗重叠、都不是草稿 = 冲突。发布时 409 挡下,并告诉你撞的是哪一场、几点到几点 |
冲突在发布那一刻硬挡,草稿阶段只提醒 —— 草稿是草稿,老师可能先建一堆再挨个调时间; 而「发布」是他说"就这么定了",那一刻必须是干净的。 判定用半开区间,所以挨着的两场不算撞:上午 9:00–11:00、下午 11:00–13:00 是正常排法。
机房停用优先于删除:排过考试的机房删不掉,系统会让你去停用 ——
停用的不再出现在建考试的下拉里,但历史考试还查得到。
考试上存的地点是快照:机房以后改名也不影响「那场考试当时在哪儿考」,
这和 attempts.paper_snapshot 冻住卷子是同一个道理。
机房和考场码是两回事,别搞混:考场码是 6 位绑定码,给考场电脑输的,只发给监考老师; 机房是给人看的地点加一份台账。系统不做预约审批流、不做设备台账、不做平面图 —— 这是考试系统,不是场地预订系统。
建考试时还要定留几台备用机。机房里总得有一两台不发出去的机器:考试中有人的 机器死机、断网、键盘坏了,得有一台已知能用的顶上去。留的是座位号最大的那几台 (通常在最后一排,搬过去最不打扰别人),取号时不参与随机抽。
但它不是死规矩:真的没机器可发时,宁可动用备用机也不能让考生站着。 那种情况下监考端会明说「空机器用完了,这是一台备用机」—— 悄悄发出去不吭声,机器坏了那一刻就没有后手了。
考前核对身份这一步,光有名单不够 —— 老师手里只有一张纸,对面站着一个人。 所以座位卡上带考生照片:点开座位就是照片、姓名、账号三样摆在一起, 对得上才放行。
照片在「用户管理」里整批导入:选一个 zip(或者直接选一堆图), 按文件名对人 —— 先对账号、再对姓名。同名的不猜,标出来让你改文件名, 因为贴错照片比没有照片更糟。图片在浏览器里压到 256×256 再上传, 手机里导出来的 4MB 原图不会砸进数据库。
存的是证件照,不是身份证扫描件。后者是敏感个人信息,存它要另一套东西 (加密、留痕、保留期限),这个系统不碰。照片只有教师和管理员取得到,能删,删了不留副本。
答案不会丢。 考生每答一题就写回服务端,先落 Redis 再由后台任务批量刷进 PostgreSQL。 断电、蓝屏、断网重连,回来直接续答,一道不少。倒计时按服务端时间算, 考场机器时钟不准、或者被人改过,都不影响。
两种都行,而且这个决定可以放到考完之后再做。
建考试时的「交卷后看分数」定的是交卷当场给不给看:随堂小测开着,考生自己对答案; 正式考试关掉,交完卷只看得到「已交卷」。
关掉之后不是就没下文了 —— 卷子批完、分数核过,到考试详情页点一下公布成绩, 全体考生立刻看得到自己的分数和每题得分。
考试关闭之后照样能公布。 这正是它单独做成一个入口、而不是放在建考试页上的原因: 批改本来就发生在考完之后,那时候整场考试已经锁上了(改卷子、改时间、改及格分都不允许, 否则成绩就不可信)。而「分数给不给看」不动任何分数,只是一次发布决定, 所以锁照旧,另开一个只能碰这一件事的口子。
勾「同时公布标准答案」要想清楚:答案一旦放出去就等于公开了,这套题以后不适合再用。 公布错了可以撤回,但撤回挡不住已经看过的人 —— 他记得自己多少分。
前七种题型交卷即判完。简答题、以及按规则没拿满分的填空题,走主观题流程,三选一:
- 教师批改——老师逐题给分,可以写评语。
- AI 判分——直接采用 AI 给的分。
- AI 初判 + 教师复核——AI 先给一个分和一段按评分点展开的说明,老师看一眼, 同意就一键采纳,不同意直接改。AI 原来给的分会一直留档,改完还能对照。
AI 走任意 OpenAI 兼容接口,配置模板里预置了 DeepSeek 和通义千问的地址。 判分请求失败会重试,重试用尽就把这道题转人工,不会悄悄记 0 分。 简答题的「解析」栏请写评分要点(比如「说清三个步骤各 2 分,提到历史重复连接 2 分」), AI 会读它,写得越具体给分越准;人工批改时也一眼能对着给。
考完有成绩分析:平均分、最高最低、及格率、分数段分布,以及每题正确率 ——正确率低的排在前面,讲评时先讲哪道题一目了然。成绩可以导出。
考生这边能不能看分、能不能看答案,由每场考试单独设置,客户端不自作主张。
管理员能看到这台 API 节点的实时状态:CPU / 内存 / 磁盘 / 交换、PostgreSQL 与 Redis 的 响应时间和连接池、四个后台任务的心跳与累计处理量、当前负荷、以及 AI 判卷的队列和成败计数。
判断标准写在服务端,页面只负责显示结论——「磁盘已用 97%,写满后数据库会直接停止写入」 这种话由服务端给,换个客户端来读也是同一套结论。
后台任务这一块值得单说:心跳停了和「没活干」是两回事。 刷盘任务每 3 秒一轮,即使没有作答要写也会记一次心跳;心跳超过间隔的四倍还没动静, 它就会被标成卡住。这样才分得清「闲着」和「死了」。
这一页的数字只描述当前这台 API 节点。数据库和 Redis 是全集群共用的, 其余数字(运行时长、后台任务、计数器、CPU / 内存 / 磁盘)只属于本次请求落到的那台机器。
Windows x64,单个 exe,不装任何运行时,不依赖浏览器。界面支持中英文,
lang = "en" 时整个客户端是英文的(英文版说明里是英文界面的截图)。
考生不输账号密码。 机器开机自己绑到座位号上并开始轮询 —— 绑的是机器和座位号,不是机器和人;人是考生到场时由监考老师取号随机分过来的。 取到号、老师一点开考,服务端把这个座位对应考生的凭证发下来,客户端自动进答题页。
![]() |
![]() |
答题期间屏幕锁定:独占全屏、窗口置顶、没有标题栏,低级键盘钩子吞掉
Win、Alt+Tab、Alt+Esc、Ctrl+Esc、Ctrl+Shift+Esc、Alt+F4、Alt+空格 和 PrintScreen。
窗口一丢焦点,看门狗立刻上报并把它抢回前台。更严的一档还会禁用任务管理器、禁用 U 盘存储、
并结束黑名单里的程序。
八种题型在客户端都是原生绘制的,不是网页套壳:连线题真的画线,排序题按住就能拖、 松手前下面的行就跟着让位,判断题的对勾和叉是画出来的矢量图形 (中文字体里根本没有 ✓ ✗ 这两个字形,用字符只会得到两个豆腐块)。
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
答题卡上填色表示已答、空心表示未答,不用绿色——考试还没判分, 绿色会被考生读成「我这题做对了」。
一个装在老师自己那台电脑上的小程序(proctor/rexam-proctor.exe)。
考试当天用它取号、看考场;考前维护用它布机。详见
监考端说明。
登录、选考试、进考场之后默认落在看板:一台电脑一块砖,按窗口宽度平摊、铺满整屏。 每块砖上最显眼的是大号座位号和考生照片、姓名——老师站在机房里是拿号去对屋里那台机器、 拿照片去对人的。顶上一行是应到 / 已到场 / 答题中 / 已交卷 / 掉线;掉线的砖是红框, 答题中的左边一道蓝条,已交卷的变灰。点任意一块能核对照片、补时、强制交卷。
取号是另一页,左边名单、右边核对区:考生报名字 → 搜一下 → 右边出现他的大照片 → 对着照片确认是本人 → 取号, 系统从空闲机器里随机抽一台,把号码大字报出来。没输字的时候这一页直接列 还没取号的人,老师照着队首那个点一下就行。抽号在服务端做且整场加锁 —— 两个老师同时取号不会把两个学生指到同一台机器上。
号码旁边会写清楚"为什么是这一台":沿用上一场、动用了备用机、或者他本来就取过号。 悄悄换一台不吭声,老师会照着上一场的名单把学生指到错误的位置上。
布机是维护人员那一套:开起来之后它在局域网里每两秒广播一次「我在这」, 考场机开机听见就连过来领座位。顶上一条读数就是布机进度 ——「已连上 N / M 台」、 信标广播次数、最近一次发出多少份、收到多少次探询;下面是连上来的机器, 一行一台,带主机名、IP 和连上的时刻。排查网络问题要看这些。
它不判分、不算时间、不自己决定谁坐哪 —— 排座和抽号都是调服务端的接口。 判分和时间规则只有一份,在服务端,监考端重写一份就等于埋下「两边算得不一样」的雷。
- 不需要它也能开考。 装机时给了
--server、--room-code、--seat-no的机器 照旧直接绑定,监考端只是把这三样从装机环节挪到了开考环节。 - 考场机仍然直连服务器答题。 监考端只管布机,不转发答题流量, 所以它中途关掉不影响正在考试的人。
- 它不能在没有外网的考场里收卷。 那需要考场包离线下载、监考端本地签发考生凭据、 题面进包、答案落盘缓冲 —— 每一件都得单独设计,现在还没做。
| 端口 | 谁监听 | |
|---|---|---|
| 发现 | UDP 45301 | 双方(广播 + 组播 239.255.73.77) |
| 布机 | TCP 45300 | 监考端 |
考场机找得到监考端却连不上,十有八九是监考端那台电脑的防火墙挡了入站连接 —— 出站的广播照发,所以症状很像「发现不了」。考场机屏幕上会把这句话直接写出来。
局域网发现用的是明文 UDP,谁都能广播一条假信标。所以:
- 信标只是一条线索,报文里不带 IP,地址一律取 UDP 源地址 —— 否则伪造一条信标就能把整场考试指向任意主机;
- 装机时带了
--server的机器只认那个地址,假监考端骗不走它; - 老师屏幕上「连上 N 台 / 应到 M 台」对不上,就是有机器被别处接走了。
这一步没有做 TLS 和公钥固定,威胁模型只到「防手滑、防配置错」, 不到「防同网段的主动攻击者」。写清楚,免得有人高估它。
监考端说了算,不在考生那台机器上。
考生交完卷,屏幕继续锁着 —— 先交卷的人不该在别人还在考的时候回到桌面。老师在监考页的 座位表上点「放行」,那台机器下一轮轮询(默认 3 秒)就解锁;散场时直接关闭整场考试, 服务端会把全场机位一并放行,不用挨台点。
所以装机时不需要给每台机器写监考口令 —— 口令写在考生够得着的机器上本来也谈不上多严。
exit_password 仍然保留,但只是断网时的兜底:服务端连不上、又必须把机器从锁定里
弄出来时才用得上。不设的话结束页干脆不显示口令框。
要在考试中途放一台机器走(考生身体不适之类),先强制交卷再放行 —— 收卷和放人是两件事,不该一个动作做完。
这组键本身拦不住。 它是 Windows 的安全注意序列,由 Winlogon 直接处理,低级键盘钩子 按设计就摸不到它 —— 这是系统用来保证「这一屏一定是系统弹的」的机制,不是可以绕过的疏漏。
所以硬锁换了个打法:拦不住那一屏,就让那一屏没有东西可点。 任务管理器、锁定计算机、
更改密码、注销、切换用户会被逐项关掉,考生按下去只看到一屏全是灰的选项,按 Esc 退回来
——而这次失焦已经被看门狗记下并上报了。这几项里有四项写在 HKCU 下,
不需要管理员权限,考生自己的普通账号就能生效。
真要连那一屏都不出来,只有 Windows 的键盘筛选器(Keyboard Filter,企业版 / IoT 企业版
的可选功能)做得到。装了这个功能的机器可以打开 block_sas。默认不开,因为它是机器级
开关、靠客户端退出时还原:客户端被强杀(断电、从别的账号结束进程)就还不回去,
那台机器会一直按不出 Ctrl+Alt+Del,而下一个用机器的人完全不知道发生了什么。
开之前请先在一台机器上确认还原是好使的。
硬件手段管不了——手机、纸条、第二台设备。客户端只负责让软件层面的作弊留下记录 并且变麻烦,剩下的靠监考老师走动。写清楚比藏着强,免得有人高估它。
所有违规都只上报。切屏几次算超限、要不要自动交卷,一律由服务端判定,客户端不自己算 ——跑在考生机器上的判断本来就不可信。
改过的注册表项在退出时按改之前读到的原值还原,包括「本来就没有这个值」的情况; 进程被强杀也有兜底,不会把机器带着「任务管理器已禁用」还给机房。
一条命令,不带任何参数:
rexam-client.exe --install它自己写 config.toml、自己登记开机自启(HKCU 下的 Run 项,不需要管理员),
然后退出。装机脚本按机器循环一遍即可,--uninstall 撤销。
服务器地址、考场码、座位号都不用填。 老师在自己那台电脑上打开监考端、 登录、选一场考试,考场机开机就会在局域网上找到它,把这三样全部领回来。 详见下面的 监考端。
口令也不用给。 散场是监考老师点「放行」,解锁从服务端下发到机器, 这台机器上不用输入任何东西。
高利害考试建议钉死服务器:
rexam-client.exe --install --server=https://exam.example.com加了这一项,机器只认这个地址,监考端下发别的一律忽略。 这个值全校每台机器都一样,不是「每台不同」的那种麻烦,装机脚本里写死一次即可。 为什么建议加:局域网发现用的是明文 UDP,谁都能广播一条假信标 —— 钉死之后,伪造的监考端最多让机器连不上,骗不走它。
座位号可以不给:不写 --seat-no 的话,机器开机会停在绑定页,由监考老师现场敲一次
考场码和座位号;有座位表的机房照旧按机器下发 --seat-no=7。
想手写配置也行,config.toml 放在 exe 同目录:
server = "https://exam.example.com"
lock = "soft" # soft 不需要管理员权限;hard 另外还会禁任务管理器和 U 盘
lang = "zh" # 界面语言:zh 中文(默认)/ en English
room_code = "E86HDZ" # 本场考试的考场码,在考试详情页看
seat_no = 7 # 每台机器不同
exit_password = "" # 断网兜底用的本地口令,一般不用设批量装机时座位号可以用命令行给,一台一条:
rexam-client.exe --server=https://exam.example.com --room-code=E86HDZ --seat-no=7lang 只换客户端自己的文案。题干、选项、考生姓名都是服务端下发的出题人内容,
一律按原样显示 —— 中文卷子配英文界面,题目还是中文的。
config.toml.example 里每一项都写了改它会影响什么。
把 lock 设成 off 时界面上会直接出现退出按钮——那是调试用的,正式考试绝不能用
(上面的客户端截图都是调试模式拍的,所以右上角有「退出(调试)」)。
老机器没有独立显卡、或者通过远程桌面使用时,把 low_graphics 打开。
它关掉抗锯齿羽化,而纯软件绘制时羽化正是每帧开销的大头。
没有客户端也能开考:浏览器打开
/seat,输考场码和座位号,同样能当机位用。 拦不住切屏,适合要求没那么严的场合。
需要 PostgreSQL 16+ 和 Redis 6.2+(6.2 是下限,刷盘用到了 LPOP key count)。
copy server\.env.example server\.env
:: 改 .env:数据库地址、Redis 地址,以及下面强调的那两项
server\rexam.exe首次启动自动建表;用户表为空时会创建初始管理员。监听地址由 BIND_ADDR 决定,
默认 0.0.0.0:8080。
前端是 web/ 下的一堆静态文件。nginx 指过去,再把 /api 反代到服务端即可。
deploy/nginx.conf 就是这么配的,也带了单页应用需要的深层路由回落。
关于平台:这里的服务端是 Windows x64 版,因为构建机就是 Windows。
deploy/ 里的 systemd 单元是给 Linux 版用的,一并放进来供参考。
本次发布没有 Linux 版二进制,需要的话得在 Linux 上(或用交叉工具链)从源码编译。
这两项必须改掉:
JWT_SECRET——模板里那串是占位符,不是密钥。ADMIN_PASSWORD——不设的话程序会退回编译进去的默认值, 等于任何知道这个项目的人都能用管理员身份登进来。
另外两条建议:数据库和 Redis 不要对公网开放;服务端放在 nginx 后面走 HTTPS, 考场客户端和浏览器都用同一个域名。
密码用 argon2 存,接口鉴权用 JWT,角色权限在服务端逐个接口校验—— 前端藏起来的菜单挡不住直接调接口,所以没靠它。
同一套程序,几十人的随堂测验和两万人的统考都能跑,区别只是加机器。 API 节点无状态,答案先写 Redis 再由后台任务批量刷进 PostgreSQL,交卷是幂等的, 所以 nginx 后面挂多少个节点都行。
在一台四核开发机上实测(数据库同机):200 人同时交卷并完成判分耗时 368 毫秒,零丢失。
考生考到一半断电了怎么办? 重新开机进来接着答。已答的题都在,倒计时按服务端时间续,不会重置也不会多给。
考场没有外网可以吗? 可以。服务端、数据库、前端都放在内网机器上,考场客户端指向内网地址即可。 AI 判分需要外网;不用 AI 的话把它关掉就行,主观题转教师批改。
能不能一场考试每个人题目都不一样? 用随机卷。抽题规则按分类、题型、难度设定,同一个人不会抽到重复题。
切屏计数会不会误伤? 会。有些电脑弹个系统通知也会触发失焦。建议上限设 3 到 5,别设 1。 设成 0 表示不限制,只记录不自动交卷。
AI 判错了怎么办? 用「AI 初判 + 教师复核」这一档,老师改分即可,AI 原来给的分仍然留档可对照。
教师能看到别人的题库吗? 能看到题库和试卷(共享的),但用户管理和系统监控只有管理员进得去。
| 路径 | 是什么 |
|---|---|
server/rexam.exe |
后端服务,Windows x64。整套 REST 接口都在这里。 |
server/.env.example |
配置模板。复制成 .env 放在程序同目录。 |
client/rexam-client.exe |
考场客户端,Windows x64。单个文件,不装任何运行时。 |
client/config.toml.example |
客户端配置模板,每一项都注明改它会影响什么。 |
proctor/rexam-proctor.exe |
监考端,Windows x64。装在老师自己的电脑上,考场机靠它零配置开考。 |
web/ |
编译好的前端,纯静态文件,交给 nginx 或任意 web 服务器托管。 |
deploy/ |
nginx 站点配置和 systemd 单元。 |
docs/监考操作手册.md |
写给老师看的全流程操作手册,不需要懂技术。 |
docs/screenshots/ |
上面这些截图。 |
技术栈:服务端 Rust(axum + tokio + sqlx + redis),前端 Vue 3 + Vite + Element Plus, 考场客户端 Rust + egui 原生绘制。
源码暂时不放出来。 系统还在完善阶段,肯定还有没暴露出来的问题, 这时候把代码摊开,别人拿去跑真考试反而容易出事。等跑稳了再开源。
现在这个仓库放的是可以直接部署的编译产物:服务端、前端和考场客户端, 功能是完整的,不是试用版。用下来有问题欢迎提 issue。
未指定。转发前请先联系仓库所有者。

































