基于SpringBoot+AI的在线考试管理系统
程序代码下载
本系统是一个基于 B/S 架构、前后端分离的在线考试与刷题练习平台,面向学校教学场景,覆盖“出题→考试→判分→错题巩固→学情分析”的完整教学闭环。系统将 AI 大模型能力(Spring AI)深度融入刷题、考试、错题复习等核心环节,为不同角色(学生、教师、管理员)提供差异化的功能与数据视图。
层次 | 技术 | 版本 | 用途 |
后端框架 | Spring Boot | 3.2.5 | Web 服务、依赖管理 |
运行环境 | Java | 17 | 后端运行时 |
ORM | MyBatis-Plus | 3.5.5 | 数据访问、分页、逻辑删除 |
数据库 | MySQL | 8.x(utf8mb4) | 业务数据存储 |
AI 框架 | Spring AI(OpenAI 兼容 starter) | 1.0.0-M5 | 对接大模型服务 |
前端框架 | Vue | 3.5 | 用户界面(Composition API) |
构建工具 | Vite | 8 | 前端开发与构建 |
UI 组件库 | Element Plus | 2.14 | 界面组件 |
状态管理 | Pinia | 3 | 全局状态(用户/考试/题目/配置) |
路由 | Vue Router | 4 | SPA 路由与守卫 |
HTTP 客户端 | Axios | 1.x | 前后端通信 |
辅助工具 | Lombok、Validation | — | 简化代码、参数校验 |
学生端(/student)
模块 | 功能 |
大厅首页 | 个人学习数据总览、公告浏览、AI 学习建议 |
浏览题库 | 按科目/题型浏览题目与解析 |
刷题练习 | 随机抽题练习,即时判分,练习记录留存 |
模拟考试 | 查看已发布考试→进入考试答题(限时计时)→交卷自动判分出分 |
我的成绩 | 历史考试成绩、得分明细、用时统计 |
错题本 | 考试/练习错题自动归集、AI 错题分析、标记“已掌握/待复习” |
AI 智能助手 | 多轮对话问答,结合个人学习数据给出解答与建议 |
个人中心 | 资料修改、头像上传(前端压缩)、密码修改 |
教师端(/teacher)
模块 | 功能 |
教师主页 | 所授科目题量、考试场次、成绩概况 |
题库管理 | 题目增删改查、批量录入、AI 自动生成答案解析、图片上传 |
考试管理 | 创建考试(选题组卷、设置时长/总分/及格分/起止时间)、发布与下架 |
成绩统计 | 考试成绩单、正确率/通过率统计、AI 教学分析(薄弱点与教学建议) |
公告管理 | 发布与维护系统公告 |
管理员端(/admin)
模块 | 功能 |
控制台 | 用户数、科目数、题库规模、考试场次等全局概览 |
用户管理 | 三类用户分页查询、新增/禁用/删除、密码重置 |
科目管理 | 科目维护与任课教师分配 |
数据统计 | 全平台学习/考试数据统计 |
系统设置 | 系统参数与 AI 模型在线配置(密钥加密存储于 sys_config) |
公告管理 | 全局公告维护 |
参考代码
packagecom.studyroom.ai;importcom.studyroom.ai.model.*;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Service;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;@Slf4j@ServicepublicclassAIChatService{privatefinalSparkAIClient sparkClient;privatefinalAIToolRegistry toolRegistry;privatefinalMap<Long, List<SparkMessage>>sessionHistory=newConcurrentHashMap<>();privatefinaldoubletemperature;privatefinalintmaxTokens;privatestaticfinalStringSYSTEM_PROMPT="""你是校园自习室预约系统的AI助手,可以帮助用户查询系统各类数据、预约座位、管理预约等。你可以查询以下数据:【自习室】自习室列表、详情、座位布局、可用座位【预约】我的预约记录、预约座位、取消预约、签到、签退【个人】个人信息(姓名/学号/院系/信用分)、信用积分变动记录【消息】我的消息通知、未读消息【公告】系统公告列表【反馈】我提交的意见反馈及处理状态【评价】座位评价列表及平均评分【统计】系统整体数据(用户数、预约数、热门时段、热门座位等)注意事项:- 当用户想预约座位时,如果没指定具体时间,主动询问日期和时间段- 当用户想查可用座位时,如果没指定自习室,先列出所有自习室让用户选择- 用户问"我的XXX"时,直接调用对应工具查询,无需额外确认- 回复简洁友好,用中文,适当使用emoji- 如果操作成功,用积极的语气告知结果- 如果操作失败,解释原因并给出建议- 如果用户的问题涉及多个数据来源,可以一次调用多个工具""";publicAIChatService(SparkAIClient sparkClient,AIToolRegistry toolRegistry,@Value("${spark.temperature}")doubletemperature,@Value("${spark.max-tokens}")intmaxTokens) {this.sparkClient = sparkClient;this.toolRegistry = toolRegistry;this.temperature = temperature;this.maxTokens = maxTokens;}publicChatResponsechat(String message, Long userId) {List<SparkMessage>history= sessionHistory.computeIfAbsent(userId, k ->newArrayList<>());if(history.isEmpty()) {history.add(SparkMessage.builder().role("system").content(SYSTEM_PROMPT).build());}history.add(SparkMessage.builder().role("user").content(message).build());SparkRequestrequest= SparkRequest.builder().messages(newArrayList<>(history)).temperature(temperature).max_tokens(maxTokens).tools(toolRegistry.getTools()).tool_choice("auto").build();SparkResponseresponse= sparkClient.chat(request);if(response ==null|| response.getChoices() ==null|| response.getChoices().isEmpty()) {returnChatResponse.builder().content("抱歉,AI 服务暂时无响应,请稍后重试。").build();}SparkMessageassistantMessage= response.getChoices().get(0).getMessage();history.add(assistantMessage);if(assistantMessage.getTool_calls() !=null&& !assistantMessage.getTool_calls().isEmpty()) {for(ToolCall toolCall : assistantMessage.getTool_calls()) {StringfunctionName= toolCall.getFunction().getName();Stringarguments= toolCall.getFunction().getArguments();log.info("AI calling tool: {} with args: {}", functionName, arguments);StringtoolResult= toolRegistry.execute(functionName, arguments, userId);history.add(SparkMessage.builder().role("tool").tool_call_id(toolCall.getId()).content(toolResult).build());}SparkRequestfollowUp= SparkRequest.builder().messages(newArrayList<>(history)).temperature(temperature).max_tokens(maxTokens).build();SparkResponsefollowUpResponse= sparkClient.chat(followUp);if(followUpResponse !=null&& followUpResponse.getChoices() !=null&& !followUpResponse.getChoices().isEmpty()) {SparkMessagefinalMessage= followUpResponse.getChoices().get(0).getMessage();history.add(finalMessage);returnChatResponse.builder().content(finalMessage.getContent()).toolCalled(true).build();}}if(history.size() >40) {SparkMessagesystemMsg= history.get(0);history.subList(1, history.size() -20).clear();history.add(0, systemMsg);}returnChatResponse.builder().content(assistantMessage.getContent() !=null? assistantMessage.getContent():"已处理您的请求").build();}publicvoidclearHistory(Long userId) {sessionHistory.remove(userId);}}