假设你是一位视障学生,登录考试系统后,系统没有弹窗问你"需要无障碍模式吗",而是直接读出题目、延长你的答题时间、把界面切换成语音交互——这一切发生在第一道题加载之前。这不是未来的设想,是AceExam已经实现的架构。

为什么现有方案总在"打补丁"

打开网易新闻 查看精彩图片

大多数数字考试平台的逻辑很简单:先做一套标准系统,再把无障碍功能塞进去。结果是学生得自己装浏览器插件、外接读屏软件,或者依赖监考老师代为输入。

AceExam团队发现,这种"附加式"思路有个致命问题——它把无障碍当成故障处理,而非设计前提。视障学生用读屏软件"硬撑",阅读障碍学生靠字体插件凑合,运动障碍学生等人代打字。每种情况都是事后补救,而非系统原生支持。

团队的原话很直接:「无障碍不是功能,是起点。」他们把accommodation_type(无障碍类型)字段写进了数据库核心层,和学生ID、姓名处于同一权重。

三层权限与那个关键字段

系统架构不复杂:组织(超级管理员)→教师/管理员→学生。三个角色,三套仪表盘。

教师创建考试时,除了设置题目和难度,还要勾选学生的disability_type。这个字段的值决定了后续一切——考试时间是否延长、界面如何渲染、AI模型调用哪一套。

学生登录瞬间,系统从MongoDB读取该字段,自动激活对应的无障碍配置。没有设置菜单,没有"是否启用"的确认弹窗。团队的理由很实际:让有障碍的学生自己去"开启无障碍功能",本身就是障碍。

技术栈选得偏保守但务实。后端用Python(Flask)+ MongoDB,前端是HTML/CSS/原生JavaScript,服务端渲染走Jinja2模板。AI层用了PyTorch自研模型,做语音转文字和眼动追踪,NLP库处理文本简化,TTS(文本转语音)调用浏览器原生的Web Speech API。

部署形态是网页版,学生端零安装。无障碍功能尽量用浏览器API在本地跑,减少服务端压力和隐私风险。

MongoDB为什么是非选不可

考试数据的结构本身就很"不规则"。一次答题记录要打包:学生档案、无障碍类型、题目集合、每题答案、时间戳、无障碍状态日志。而这些字段在不同无障碍类型下差异极大——视障学生的记录包含语音指令日志,阅读障碍学生的记录存着字体和遮罩设置。

团队尝试过关系型数据库,发现两条路都走不通:要么所有表都留大量空列(稀疏矩阵灾难),要么拆成十几张关联表(查询性能爆炸)。MongoDB的文档模型让同一张"考试记录"集合里,不同文档可以有完全不同的字段结构,查询时按需提取。

更关键的是,无障碍类型可能持续增加。今天支持三种,明年可能要加第四种。文档数据库加字段不需要改表结构,新类型的数据直接以新字段存入,历史数据不受影响。

AI不是炫技,是替代交互路径

AceExam的AI层没有用大模型做题目生成或自动批改——那些是"锦上添花"。团队把算力砸在更底层的事:让无法打字的人能答题,让无法看屏幕的人能读题。

语音转文字模型针对考试场景做了定制训练。通用模型识别"下一题"可能没问题,但考试里有大量专业术语、公式读法、选项字母("选A还是选诶")。眼动追踪则替代鼠标点击,让运动障碍学生用视线完成选择。

文本简化功能面向阅读障碍学生,把长句拆短、替换低频词,但保留原意和考核点。这个度很难把握,团队用NLP库做基础处理,再结合学科老师的规则校验。

所有AI功能都跑在客户端或边缘,不依赖云端大模型。考试场景对稳定性要求极高,"正在加载"或"服务繁忙"是不可接受的。

一个被忽略的产品逻辑

AceExam的设计暴露了一个行业惯性:无障碍功能常被当作合规 checklist 的最后一项,而非用户旅程的第一公里。

团队的做法是反向的——先定义"视障学生如何完成一次考试"的完整流程,再倒推系统需要哪些模块。结果是同一套代码库服务所有学生,但每个学生的体验路径完全不同。这不是"适配",是"原生"。

技术选型也体现了这个思路。不用React/Vue做SPA(单页应用),是因为服务端渲染对读屏软件更友好;坚持用浅色主题,是因为部分视觉障碍者对高对比度暗色模式反而不适;五个按钮的工具栏常驻屏幕,是因为复杂菜单对运动障碍用户是灾难。

这些细节不会出现在技术博客的标题里,但决定了产品是否可用。

冷幽默

AceExam的GitHub仓库大概永远不会出现"feat: add accessibility mode"这样的提交记录——因为他们的第一个commit就是"feat: build the entire thing"。当你的无障碍类型字段和主键平起平坐,你甚至找不到一个合适的时机来"庆祝"自己支持了无障碍。这大概是技术团队能想到的最无聊的浪漫。