基于.NET的在线考试系统开发实战:从技术选型到核心模块设计

发布时间:2026/10/8 3:34:33
基于.NET的在线考试系统开发实战:从技术选型到核心模块设计 1. 为什么是 .NET在线考试系统的技术选型复盘1.1 项目背景与需求画像先说下这个项目是怎么来的。做在线考试系统这类题目十个里八个是课程设计或者毕业设计剩下两个是企业内部的培训考核平台。我这个项目属于后者但需求比课设复杂不少要支持管理员维护题库、教师组卷、考生在线答题、系统自动判分、成绩统计导出还要能扛住几百人同时在线考试不卡顿。刚开始我搜了一圈现有方案发现这个题目方向的参考资料几乎被 PHP、Java 系和 .NET 三家瓜分。搜在线考试系统出来的论文和源码无非就是基于 PHP 的、基于 Springboot 的、基于 SSM 的、基于 asp.net 的前端又从 jQuery 时代一路卷到 vue3。标题里把 PHP、asp.net、java、Springboot、SSM、vue3 全列上看起来像全家桶大乱斗实际上每个关键词背后都代表一条完整的技术路线。做这类系统关键不是你会多少框架而是想清楚哪条路能让你把核心的考务逻辑做扎实。这篇文章就围绕基于.NET的在线考试系统这条主线把需求拆解、技术选型、数据库设计、核心模块实现、Vue3 前端交互、常见坑点全过一遍。如果你也在做类似题目不管是课设还是实际项目这套思路可以直接照搬。1.2 主流方案横向对比PHP、Java系与.NET的三国杀考试系统的本质是题库管理 组卷 答题 判分 统计分析这是典型的业务密集型应用并不需要极致的并发性能或复杂的分布式架构。所以在选型时我重点考虑的是开发效率、生态成熟度和部署成本。先看 PHP 方案。PHP 做这类系统最大的优势是上手快、部署简单虚拟主机都能跑Laravel 或 ThinkPHP 写业务逻辑也非常顺手。但它的问题在于强类型约束弱考试系统里涉及分数计算、时间校验、答案比对这类精度敏感的逻辑PHP 的弱类型特性容易埋雷。我见过不少 PHP 写的判分模块因为浮点运算和类型转换问题出现 59.999999 分这种诡异结果。不是说 PHP 不行而是为了少踩坑得选更稳的。再看 Java 系。Springboot 是当前 Java 后端的事实标准SSMSpring SpringMVC MyBatis是经典组合资料铺天盖地遇到问题随便一搜就有答案。做毕业设计的话Springboot Vue3 几乎成了标配组合。但 Java 系有个现实问题学习曲线陡环境配置繁琐Maven 依赖版本冲突能把人折腾到怀疑人生。Springboot 版本太高时某些旧版 MyBatis 插件会出现兼容问题Springboot 结构不熟练时光理解自动配置、starter 机制就要花不少时间。如果时间紧、任务重Java 系的前期成本要先算清楚。最后是 .NET 系。asp.net 是经典 Web 框架.NET Core / .NET 5 之后跨平台能力已经非常成熟性能在 TechEmpower 榜单上长期稳居第一梯队。C# 语言的强类型、LINQ、异步编程模型写业务逻辑非常舒服。VS 自带模板和调试工具NuGet 包管理也比 Maven 省心。更重要的是如果你有 Windows 环境IIS 部署一步到位不用折腾 Nginx 反向代理。我的选择是 .NET。不选 Springboot 不是因为 Java 不好而是对一个以考务逻辑为核心、对类型安全和开发效率要求高的项目来说C# .NET 的体验更顺。你要是学校要求必须用 Java那 Springboot SSM 也完全成立架构思路是通用的。1.3 技术栈定版清单最终定版的技术栈如下表所示供参考层次技术选型说明后端.NET 8 ASP.NET Core Web API跨平台、高性能、强类型数据访问EF Core SQL Server也可以用 MySQLEF Core 都支持前端Vue3 Element Plus Vite组合式 API开发效率高认证JWT 刷新令牌无状态认证适合 Web API缓存Redis考试会话缓存、并发锁部署Windows Server IIS / Docker按环境二选一这里有个细节EF Core 和 MyBatis 这类 ORM 相比最大的优势是 LINQ 可以在 C# 里写强类型查询编译期就能发现字段拼写错误。我用 SQL Server 是因为公司环境现成你用 MySQL 完全可以EF Core 换数据库只需要改连接串和 Provider。2. 数据库设计考试系统的地基怎么打2.1 核心表结构与关系设计考试系统的数据模型绕不开四类实体用户、题库、试卷、考试记录。我第一次做这类系统时把题目和试卷混在一张表里结果组卷逻辑写得一团糟。后来重构时老老实实按题目独立、试卷引用、答卷快照的思路拆表整个架构才顺起来。核心表大致是这些User用户表区分考生、教师、管理员三种角色Question题目表存题干、选项、答案、题型、难度、知识点QuestionCategory知识点/分类表用于按知识点比例组卷ExamPaper试卷表存试卷名称、总分、时长、组卷规则PaperQuestion试卷题目关联表记录某张试卷包含哪些题、每题分值ExamRecord考试记录表一次考试一条记录存考生、试卷、开始时间、交卷时间、总分AnswerRecord答题记录表记录考生每道题的作答内容与判分结果关系上一张试卷对应多道题目多对多通过PaperQuestion关联一份考试记录对应多条答题记录一对多。ExamRecord和AnswerRecord是考试过程中写入最频繁的两张表设计时索引要提前规划ExamRecord上建(UserId, ExamId)联合索引AnswerRecord上建(RecordId, QuestionId)联合索引否则数据量上来后查询会明显变慢。2.2 试卷生成与题目存储策略题目表的字段设计直接决定组卷逻辑好不好写。我建议Question表至少包含这些字段QuestionType单选、多选、判断、填空、简答、Difficulty1到5的整数、CategoryId知识点外键、Content题干文本、Options选项 JSON、Answer标准答案、Score默认分值。Options存 JSON 而不是单独建选项表是个很实用的决策。对于单选和多选题选项数量固定、结构简单JSON 存[选项A, 选项B, 选项C, 选项D]就够了。单独建选项表要加排序字段、选项分组逻辑查询还得 JOIN纯属给自己找麻烦。但Answer字段要小心单选存A多选存A,C,D这种逗号分隔字符串判断存T/F简答存参考答案文本。这种混合存储方式判分模块写起来最简单按题型分支处理不用做复杂的表关联。一个小教训多选的答案字符串存的时候一定要排序后拼接比如存A,C,D而不是C,A,D不然后端比对时每次都要先 split 再排序容易出错。2.3 答题快照与试卷快照设计这是考试系统数据库设计里最容易被忽略但最重要的一个点。如果一个考生考到一半管理员改了试卷里的某道题那么已经答完的考生答案怎么算试卷总分改了已经提交的成绩怎么算标准的做法是快照机制。ExamRecord表里存一个PaperSnapshot字段类型是 NVARCHAR(MAX)把考生开始考试那一刻的试卷全部信息题目、选项、分值、顺序序列化成 JSON 存下来。判分时读快照里的题目和答案而不是去查实时状态的Question表和PaperQuestion表。这样做的好处非常实在考试中途修改题库不影响已经开始的考试判分逻辑只依赖快照天然保证一致性审计时有据可查考生申诉时可以还原当时的考卷我在实际项目里就遇到过一个管理员在考试进行中误删了一道题如果考生作答和判分都实时查题库这道题所有考生的答案瞬间变成题目不存在的异常状态。有了快照机制完全不受影响。这也是这类系统设计里最值得抄作业的一个点。3. 后端核心模块考务逻辑才是重头戏3.1 自动组卷算法按知识点和难度比例抽取题目自动组卷说白了就是一道带约束的抽样题给定试卷总分、题型分布、知识点占比、难度分布从题库里抽出满足条件的题目组合。我用的算法不复杂就是按条件分桶逐桶随机抽取。假设一份试卷要求总分100分单选20题每题2分40分多选10题每题3分30分判断10题每题2分20分简答2题每题5分10分。同时要求数据库知识点占比40%难度2-3级的题目占比70%。组卷步骤按题型分成四个桶每个桶内先按知识点比例算出每个知识点需要的题数每个知识点桶内再按难度比例二次分配题数每层分配时用 SQL 查询符合条件的题目集合用ORDER BY NEWID()SQL Server或RAND()MySQL随机取指定条数把选出的题目按规则生成PaperQuestion记录同时生成试卷快照 JSON用 EF Core 写核心逻辑大概是这样的public async TaskListPaperQuestion GeneratePaperAsync(ExamPaper paper) { var questions new ListPaperQuestion(); var rule JsonSerializer.DeserializePaperRule(paper.RuleJson); foreach (var section in rule.Sections) { // 按知识点分组取题 var grouped await _context.Questions .Where(q q.QuestionType section.QuestionType) .GroupBy(q q.CategoryId) .ToListAsync(); foreach (var group in grouped) { var count CalculateCountInCategory(section, group.Key); var picked group .Where(q q.Difficulty section.MinDifficulty q.Difficulty section.MaxDifficulty) .OrderBy(q Guid.NewGuid()) .Take(count) .ToList(); foreach (var q in picked) { questions.Add(new PaperQuestion { ExamPaperId paper.Id, QuestionId q.Id, Score CalculateScore(section, q) }); } } } return questions; }这里有个关键点GroupBy之后筛选再取随机是在内存里做的。如果题库量极大百万级这种方式性能不够得改成纯 SQL 分步抽题。但对绝大多数考试系统来说题库几万道已经是极限了内存处理完全够用。组卷算法有个容易被忽略的业务细节——边界处理。比如某个知识点下的题目数量不够分配或者符合条件的题目总数不足试卷要求。我的处理方式是在组卷前先做一次库存校验每个分组统计题量不足就抛业务异常提示知识点【数据库】符合条件的题目不足需要12题实际只有8题。这种提示远比组卷到一半失败、界面弹个500错误友好得多。3.2 考试计时与交卷处理在线考试的计时逻辑是故障率最高的模块。前端倒计时 后端时间戳双端校验是必须的。前端倒计时的作用是用户体验后端才是唯一可信的时间源。我的设计是考生点击开始考试时后端在ExamRecord表写入StartTime同时把EndTime StartTime.AddMinutes(paper.Duration)算好。每次考生请求答题接口时后端都要校验DateTime.Now EndTime就拒绝答题强制交卷。这里要小心一个陷阱前端倒计时的基准时间不能取前端本地时间必须取后端返回的ServerTime。考生的电脑时间可能比服务器慢5分钟如果前端用自己的本地时间做倒计时就会出现考试时间到了但界面还在答题的情况。正确做法是// 后端返回统一时间基准 var response new { StartTime record.StartTime, EndTime record.EndTime, ServerTime DateTime.Now };前端拿到这三个时间后用serverTime (endTime - serverTime)这种差值方式计算剩余时间并且每分钟向后端同步一次时间校准。即便考生修改本地时间也骗不过后端。交卷处理要考虑自动交卷和手动交卷两条路径手动交卷前端把所有答案一次性发给后端后端校验时间并落库自动交卷倒计时归零时前端把所有缓冲区答案提交。但万一考生直接关浏览器前端就没了这种场景要靠后端兜底——考试时间到了之后考生下一次请求接口时强制触发交卷把已有的答题记录保存并判分关键思路是交卷的本质是封存快照 逐题判分 汇总成绩所以后端要提供一个幂等的提交接口。考生重复点击交卷、或者前端断线重连后再次提交都不会导致重复扣分或成绩覆盖异常。3.3 自动判分客观题精确、主观题辅助判分模块是最能体现代码功底的地方。客观题单选、多选、判断可以全自动精确判分主观题简答、论述我用的是关键词匹配 人工复核的混合模式。客观题判分的核心逻辑private decimal GradeObjective(AnswerRecord answer, Question question) { switch (question.QuestionType) { case QuestionType.SingleChoice: return answer.UserAnswer question.Answer ? question.Score : 0; case QuestionType.MultipleChoice: // 双方都排序后拼接避免顺序差异 var user SortAnswer(answer.UserAnswer); var std SortAnswer(question.Answer); if (user std) return question.Score; // 部分给分策略答对但漏选给一半分 var userSet user.Split(,); var stdSet std.Split(,); if (userSet.All(stdSet.Contains)) return question.Score / 2; return 0; case QuestionType.Judge: return answer.UserAnswer.Trim().ToUpper() question.Answer.Trim().ToUpper() ? question.Score : 0; default: return 0; } }多选判分这里值得展开说。很多考试系统的多选是错选、漏选都零分但实际业务中通常有部分给分的需求。我采用的政策是完全一致给满分只选了一部分正确的给一半分包含错误选项的零分。这个规则要和业务方确认清楚不是每种考试都允许部分给分。主观题我用的是关键词权重方案预先在Question表里给简答题维护一个KeywordsJSON 字段格式是[{word: 索引, weight: 0.5}, ...]后端匹配关键词的命中率按权重算出初评分数。但这个初评分数只能作为参考最终分数需要教师登录后台人工复核后确认发布。这里我踩过一个坑一开始想让系统全自动给简答题判分结果关键词命中率逻辑做出来之后考生答得非常口语化但意思对判分偏低考生投诉一大片。后来改成机器初评 人工复核双轨制才算是兼顾了效率和公平。3.4 考试状态机与防作弊设计考试记录的状态不能只用一两个字段硬扛我用一个状态机管理未开始 - 考试中 - 已交卷(待批阅 - 已批阅 - 已发布) - 异常(超时/中断)状态机的好处是强制了流程合法性。比如一个已交卷的记录不可能直接回到考试中判分操作只允许在已交卷状态执行任何人想通过改数据库绕过流程都会因为状态不合法而在业务层被拦住。防作弊方面我的实践经验是不要试图在纯 Web 环境下做严密的防作弊那是不现实的。能做的是痕迹记录 异常标记。我在ExamRecord表里加了几个字段IpAddress登录IPUserAgent浏览器UAScreenLockCount全屏退出次数SwitchTabCount切换标签页次数前端通过visibilitychange事件监听页面可见性变化一旦检测到考生切出页面或浏览器全屏退出就向后端记录一次。后端在成绩单上对异常次数超过阈值的考生打一个AbnormalFlag标记由监考教师人工判断是否可疑。另外答题的每个动作都写在AnswerRecord表的UpdateTime上。如果系统发现某次考试中两道题目的答题时间间隔异常短比如判断题一秒做十道也是异常标记的来源之一。这些数据不一定能直接证明作弊但能大大降低人工核查的成本。4. Vue3 前端考生端与管理员的体验分水岭4.1 项目搭建与目录结构这年头做管理系统类前端Vue3 基本是默认选项。我用的组合是 Vite Vue3 TypeScript Element Plus Pinia Vue Router全部组件式开发。Vite 的冷启动速度和 HMR 体验比老一代 webpack 方案舒服太多开发阶段改个组件秒级刷新效率提升是实打实的。组件目录我做了模块化拆分src/ ├── api/ # 所有接口请求 │ ├── auth.ts │ ├── exam.ts │ └── paper.ts ├── stores/ # Pinia 状态 │ ├── user.ts │ └── exam.ts ├── views/ │ ├── student/ │ │ ├── ExamHall.vue # 考试大厅 │ │ ├── ExamRoom.vue # 答题页 │ │ └── ResultDetail.vue # 成绩详情 │ ├── admin/ │ │ ├── QuestionManage.vue # 题库管理 │ │ ├── PaperManage.vue # 试卷管理 │ │ └── ExamMonitor.vue # 考试监控 │ └── ... └── utils/ ├── auth.ts └── timer.ts # 倒计时工具这里我特别说一下 api/ 目录的封装。所有请求统一走一个 request.ts 封装层内部用 axios 实例拦截器在请求头自动带 JWT Token响应拦截器统一处理 401 跳转登录、业务错误码提示。这样各个页面组件里的代码就只关注业务不用重复处理 token 过期和错误弹窗。 ### 4.2 倒计时与答题交互的实现细节 答题页是考生体验的核心交互上有一个原则**任何操作都不能让考生产生我的答案丢了的恐慌**。 我的做法是本地 state 为主 定时批量保存 离开页面前保存三层保障。考生点击选项时答案先写入 Pinia 的 examStore同时标记该题为已答。然后每30秒前端把所有有改动的答案批量 POST 到后端保存。这样即使考生不小心关了浏览器再打开已经保存的答案也不会丢。 答题进度提示也是个细节。我用 computed 实时计算已答数量、未答数量、剩余时间页面顶部固定一个进度条和题号导航栏。题号导航栏里已答题显示绿色、未答题显示灰色、当前题高亮考生一眼就能看出还有哪几题没做。 倒计时组件我单独封装成一个 useCountdown 组合式函数 typescript export function useCountdown(endTime: string, syncInterval 60000) { const remaining ref(0); const serverTimeOffset ref(0); // 从后端获取标准时间 async function syncTime() { const { serverTime } await getServerTime(); serverTimeOffset.value Date.now() - new Date(serverTime).getTime(); } function update() { const localNow Date.now(); const realNow localNow - serverTimeOffset.value; remaining.value Math.max(0, new Date(endTime).getTime() - realNow); } onMounted(() { syncTime(); update(); const timer setInterval(() { update(); if (remaining.value 0) { clearInterval(timer); handleTimeoutSubmit(); } }, 1000); // 每分钟校准一次时间 setInterval(syncTime, syncInterval); }); return { remaining }; }这里serverTimeOffset是关键。考生的本机时间和服务器时间有偏差时用第一次同步时计算出的偏移量修正倒计时并且在考试过程中定期校准防止考生通过改本地时间作弊。倒计时归零时立即触发handleTimeoutSubmit把当前所有答案提交出去。有一个交互细节很容易被忽略交卷前必须做二次确认。我用 Element Plus 的ElMessageBox.confirm弹窗明确提示剩余未答题X道确认交卷吗。这个弹窗不是多余的它能在真实考试里拦住那些手滑点了交卷的考生减少后续申诉。4.3 接口设计与状态管理前后端接口采用 RESTful 风格核心接口清单如下方法路径功能POST/api/auth/login登录返回 JWTGET/api/exam/available获取当前可参加的考试列表POST/api/exam/{examId}/start开始考试创建考试记录GET/api/exam/record/{recordId}/paper获取试卷快照POST/api/exam/record/{recordId}/answer保存单题答案POST/api/exam/record/{recordId}/submit手动交卷GET/api/exam/record/{recordId}/result查询成绩接口设计上我坚持一个原则快照接口和实时接口分离。考生开始考试后所有题目数据都从PaperSnapshot里读取不经过题目实时表。这个在前面数据库设计部分已经强调过了这里再次提是因为接口层也必须遵守这个约束否则前端一个高频请求照样能把实时数据查穿。Pinia 的考务状态管理我只把当前考试记录ID、试题列表、答案缓冲、剩余时间放到examStore里其他全局用户信息放在userStore。考试相关的组件通过 store 访问答案缓冲保证刷新页面后还能从本地状态恢复一部分体验。当然刷新后主要还是靠重新拉取后端保存的答案来恢复。5. 安全与性能考试系统不能忽视的两个底线5.1 身份认证与接口防刷在线考试系统的安全防线第一层是身份认证。我用 JWT 做无状态认证登录接口发放 Access Token有效期2小时和 Refresh Token有效期7天。每次请求都在 axios 拦截器里带上Authorization: Bearer token。这里有个前期设计上的坑一开始我图省事把 token 存在 localStorage后来发现 XSS 攻击下 token 会泄露。改成存内存 Refresh Token 刷新的模式后虽然刷新页面需要短暂重新静默登录一次但安全性提升明显。管理端接口还要加角色权限校验我的做法是用 ASP.NET Core 的[Authorize(Roles Admin)]特性标记只有管理员能访问的接口考生 token 访问管理接口直接返回 403。接口防刷是另一个重点。在线考试有个特殊场景交卷时如果接口被恶意刷可能会导致成绩被反复计算。我做了三个层面的防护IP UserId 维度限流同一考生存答案接口每秒最多请求5次交卷接口加幂等校验同一recordId只允许提交一次后续请求直接返回已完成结果更敏感的操作如修改密码、导出成绩加短信验证码或操作日志审计用 ASP.NET Core 实现限流最简单的方案是自定义一个RateLimitAttribute配合内存缓存或者直接用AspNetCoreRateLimit这个中间件。我现在项目里用的是自定义方案代码量并不大核心就是ConcurrentDictionary存访问计数超过阈值就返回 429。5.2 并发考试的性能优化几百人同时在线考试每秒的请求量其实并不夸张真正的压力在于交卷那一瞬间所有考生同时提交判分模块要一次性处理大量答题记录。我在性能上做了这几个优化判分离线化交卷接口只做数据落库和状态流转不立即判分。判分放到后台任务IHostedService里异步执行考试成绩生成后通过 WebSocket 或轮询推送给前端Redis 缓存热点数据试卷快照、考生已保存的答案用 Redis 缓存减少数据库高频读取数据库连接池调优SQL Server 默认Max Pool Size100这种规模够了但 EF Core 查询要避免 N1批量查答题记录时用Include或显式加载用 Redis 缓存答案的典型流程是考生前端每30秒批量保存答案接口把答案写入 Redis 的哈希结构record:{recordId}:answers同时由后台任务定期刷盘到 SQL Server。这样考试过程中数据库的写入压力被大大平滑化交卷时再一次性把所有答案从 Redis 取出来落库。如果 Redis 不可用还有降级方案——直接写 SQL Server只是压力大一些。还有一个容易忽略的性能瓶颈题库查询。管理端在维护题库时经常要按条件搜索如果Question表没有合适的索引LIKE %关键词%查询会全表扫描。我的经验是给题目内容字段建全文索引给CategoryId、QuestionType、Difficulty建普通索引日常查询速度和全表扫描完全不是一个量级。6. 常见问题排查实录我踩过的坑都在这了6.1 高频报错速查表做这类系统最容易翻车的几个问题我整理成一张表基本能解决 80% 的开发期问题。现象根本原因解决办法前端请求后端 401但登录接口能通JWT 过期时间配置不一致检查 token 过期时间确认 Refresh Token 流程多选判分总是错答案字符串顺序不一致入库存时统一排序后拼接判分时也排序考试时间到了还能答题前端用了本地时间做倒计时改用工单后端返回 ServerTime 做时间校准EF Core 查询报Invalid column name实体类和数据库表结构不同步先跑迁移或对比数据库结构再改代码Redis 连不上导致交卷失败缓存层没做降级加 try-catch 降级到直连数据库并发提交成绩被覆盖交卷接口没有幂等控制加recordId唯一校验重复提交直接返回Vue3 打包后白屏baseURL路径不对或路由懒加载失败检查 Vitebase配置用相对路径或绝对路径保持一致组卷时提示题目不足题库过滤条件下数量不够加库存校验提示明确缺少哪个知识点/难度的题6.2 最值得分享的几条实战经验一个是关于框架版本的选择。Springboot 的坑在网上讨论很多版本太高时旧版 MyBatis 插件兼容不了版本太低又有一堆漏洞.NET 这边相对省心些.NET 6 和 .NET 8 都是 LTS 版本直接用最新稳定版就行。我给的建议是如果你做毕业设计或课程设计不要追求最新框架版本稳定是第一位。什么预览版RC版一律不碰只选已经正式发布半年以上的稳定版本这样遇到问题搜到的资料才够多。另一个是关于考中改卷的问题。有一次我已经把考试发布出去了发现试卷里有一道题的选项文字写错了。如果直接去改Question表的题目内容正在考试的考生拿到的快照还能不能用答案是快照不受影响但已经生成在PaperQuestion里的题目内容如果实时联查题库就会读到改后的内容。我的处理方式是题目内容本身允许在考前统一修改但一旦有考生开始考试这道题就锁定不允许再改。等考试全部结束后管理员才能重新编辑题目。这个锁的逻辑不复杂但在需求文档里必须提前约定。最后分享一个小技巧给每个考生生成成绩时判分日志一定要留。我在ScoreLog表里记录每道题的判分依据标准答案、考生答案、判定结果、扣分原因考生对成绩有疑问时管理员可以逐个查看每道题的判分记录。这个功能看似增加工作量但省掉了无数为什么我的分这么低的扯皮环节。实际运维下来这是整个系统里最值得的一笔投入。7. 写在最后从最开始搜各种框架的资料到最后上线稳定运行这个基于 .NET 的在线考试系统前后花了大约两个多月时间。中间踩过的坑不少但最有价值的是把考试这个看似简单的场景真正落地时的各种边界情况时间同步、快照隔离、判分准确性、并发交卷、防作弊取证。这些经验单纯看框架文档是学不到的。如果让我给正在做类似系统的人一句建议那就是先画清楚状态机再写第一行代码。考试系统的核心混乱点不在某个科技树上而在状态流转不清晰。不管你是用 .NET、Springboot、SSM 还是 PHP只要把试卷、考试记录、答题记录这三条核心链路的状态设计好后面的开发都会顺利很多。这套架构的后续扩展方向也很多加一个试卷难度分析模块用统计方法分析试卷的区分度和信度或者把判分模块接上大模型做更智能的主观题评分。底子打好了往上加功能只是时间问题。