- AI 智能生成试卷:根据知识库内容或用户自由描述,自动生成包含单选、多选、文本题的试卷,并自动分配分值。
- AI 批量阅卷:用户提交答案后,AI 一次性批改所有题目,返回包含详细评分、正确答案和评语的完整成绩单。
本文将提供可直接运行的代码、数据库脚本以及提示词模板,并对关键逻辑添加详细注释,帮助开发者快速落地自己的 AI 应用。完整代码使用CRUD生成即可
一、技术栈与架构说明
| 技术组件 | 版本 / 说明 |
|---|---|
| 基础框架 | RuoYi-Vue-Plus 5.x(Spring Boot 3.5 + JDK 17 + Sa-Token) |
| ORM | MyBatis-Plus |
| JSON 处理 | Fastjson2 |
| HTTP 客户端 | Hutool HttpUtil |
| AI 模型 | DeepSeek(兼容 OpenAI API 格式) |
| 数据库 | MySQL 8.0(支持 JSON 字段) |
整体流程:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 前端 Vue3 │ ───→ │ Controller 层 │ ───→ │ AI 生成/阅卷服务 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ ↓ ↓ ┌─────────────────┐ ┌─────────────────┐ │ MyBatis-Plus │ │ DeepSeek API │ └─────────────────┘ └─────────────────┘二、数据库设计(含完整建表脚本)
以下为实际项目使用的表结构,包含 RuoYi 框架的标准字段(租户、部门、逻辑删除等)。JSON 字段用于存储灵活的配置和答案快照。
CREATE TABLE `a_exam_paper` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(100) NOT NULL COMMENT '试卷名称', `gen_type` char(1) NOT NULL COMMENT '生成方式:1=知识库 2=自由描述', `gen_config` varchar(500) DEFAULT NULL COMMENT '生成配置快照(如目录ID、描述原文、模型参数)', `total_score` int NOT NULL DEFAULT '0' COMMENT '试卷总分', `question_count` int NOT NULL DEFAULT '0' COMMENT '题目总数', `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态:0生成中 1已生成 2已作废', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `del_flag` int DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='试卷主表'; CREATE TABLE `a_exam_question` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `paper_id` bigint NOT NULL COMMENT '所属试卷ID', `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '题型:1=单选 2=多选 3=文本', `content` text NOT NULL COMMENT '题干', `options` text COMMENT '选项列表(仅单选/多选有值)', `answer` text NOT NULL COMMENT '标准答案(单选存A,多生存JSON数组,文本存要点)', `analysis` text COMMENT '答案解析', `score` int NOT NULL DEFAULT '0' COMMENT '分值', `sort` int NOT NULL DEFAULT '0' COMMENT '排序', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `del_flag` int DEFAULT '0' COMMENT '删除标志', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_paper_id` (`paper_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='题目明细表'; CREATE TABLE `a_exam_user_record` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `paper_id` bigint NOT NULL COMMENT '试卷ID', `user_id` bigint NOT NULL COMMENT '用户ID(关联sys_user)', `start_time` datetime DEFAULT NULL COMMENT '开始考试时间', `end_time` datetime DEFAULT NULL COMMENT '交卷时间', `total_score` int NOT NULL DEFAULT '0' COMMENT '试卷总分(冗余,便于统计)', `user_score` int NOT NULL DEFAULT '0' COMMENT '用户实际得分', `status` char(1) NOT NULL DEFAULT '0' COMMENT '考试状态:0进行中 1已完成 2已阅卷', `answers_snapshot` json DEFAULT NULL COMMENT '用户全部答案快照(JSON格式,便于回溯)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `del_flag` int DEFAULT '0' COMMENT '删除标志', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', PRIMARY KEY (`id`), UNIQUE KEY `uk_paper_user` (`paper_id`,`user_id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户考试记录表(含总分与答案快照)'; DROP TABLE IF EXISTS `a_kb_category`; CREATE TABLE `a_kb_category` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父级ID,0为根目录', `name` varchar(100) NOT NULL COMMENT '目录名称', `sort` int NOT NULL DEFAULT '0' COMMENT '排序', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `del_flag` int DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`), KEY `idx_parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识库目录表'; CREATE TABLE `a_kb_document` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `category_id` bigint NOT NULL COMMENT '所属目录ID', `title` varchar(200) NOT NULL COMMENT '文档标题', `content` longtext NOT NULL COMMENT '文档正文(支持Markdown)', `keywords` varchar(500) DEFAULT NULL COMMENT '关键词,多个用逗号分隔', `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态:0正常 1停用', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `del_flag` int DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`), FULLTEXT KEY `ft_content` (`title`,`content`,`keywords`) /*!50100 WITH PARSER `ngram` */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识库文档表';三、AI 服务封装(AIService)
我们使用 Hutool 的HttpUtil直接调用 DeepSeek API,因其完全兼容 OpenAI 接口规范,无需额外 SDK。
package org.dromara.exam.utils; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; /** * AI 服务类,封装 DeepSeek API 调用 * 使用 HTTP 直连方式,兼容 OpenAI 格式 */ @Slf4j @Service public class AIService { @Value("${deepseek.api-key}") private String apiKey; @Value("${deepseek.model:deepseek-chat}") private String model; @Value("${deepseek.temperature:0.7}") private Double temperature; @Value("${deepseek.max-tokens:4096}") private Integer maxTokens; private static final String DEEPSEEK_URL = "https://api.deepseek.com/v1/chat/completions"; @PostConstruct public void init() { if (StrUtil.isBlank(apiKey)) { log.error("❌ DeepSeek API Key 未配置,AI 服务将不可用"); } else { log.info("✅ AI 服务初始化完成,模型:{}", model); } } /** * 发送聊天请求 * @param prompt 完整的提示词内容 * @return AI 生成的文本响应(通常为 JSON 字符串) */ public String chat(String prompt) { if (StrUtil.isBlank(apiKey)) { throw new RuntimeException("AI 服务未就绪,请检查 DeepSeek 配置"); } // 构建 OpenAI 兼容格式的请求体 Map<String, Object> requestBody = new HashMap<>(); requestBody.put("model", model); requestBody.put("messages", List.of(Map.of("role", "user", "content", prompt))); requestBody.put("temperature", temperature); requestBody.put("max_tokens", maxTokens); requestBody.put("response_format", Map.of("type", "json_object")); // 强制返回 JSON String jsonBody = JSON.toJSONString(requestBody); try (HttpResponse response = HttpRequest.post(DEEPSEEK_URL) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .body(jsonBody) .execute()) { if (!response.isOk()) { log.error("DeepSeek API 调用失败,状态码:{},响应:{}", response.getStatus(), response.body()); throw new RuntimeException("AI 服务调用失败,状态码:" + response.getStatus()); } JSONObject respJson = JSON.parseObject(response.body()); String content = respJson.getJSONArray("choices") .getJSONObject(0) .getJSONObject("message") .getString("content"); log.debug("AI 响应:{}", content); return content; } catch (Exception e) { log.error("AI 调用异常", e); throw new RuntimeException("AI 服务异常:" + e.getMessage(), e); } } }配置示例(application.yml):
deepseek: api-key: sk-your-api-key-here model: deepseek-chat temperature: 0.7 max-tokens: 4096四、提示词模板管理
将提示词独立为文件,便于维护和调优。使用PromptTemplateLoader从 classpath 加载。
4.1 提示词加载器
package org.dromara.exam.utils; import cn.hutool.core.io.resource.ResourceUtil; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; @Component public class PromptTemplateLoader { public String loadTemplate(String fileName) { try { return ResourceUtil.readStr("prompts/" + fileName, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("提示词模板加载失败: " + fileName, e); } } }4.2 试卷生成提示词(generate_paper_prompt.txt)
你是一名资深教育专家和出题老师。请严格依据以下【参考资料】生成 {question_count} 道题目,满分 {total_score} 分,题型分布建议:单选 60%、多选 20%、文本 20%。 【参考资料】 {reference_content} 【输出要求】 1. 输出必须是一个合法的 JSON 对象,不包含任何额外文字或注释。 2. JSON 结构必须严格遵循以下示例。 示例 JSON: { "questions": [ { "type": "single", "content": "Java中,下列哪个关键字用于创建线程?", "options": ["A. extends", "B. implements", "C. new", "D. synchronized"], "answer": "B", "analysis": "实现Runnable接口使用implements关键字。", "score": 1, "sort": 1 }, { "type": "multiple", "content": "以下哪些属于HTTP请求方法?", "options": ["A. GET", "B. POST", "C. SEND", "D. PUT", "E. DELETE"], "answer": ["A","B","D","E"], "analysis": "HTTP常见方法有GET、POST、PUT、DELETE等。", "score": 1, "sort": 2 }, { "type": "text", "content": "请简述面向对象编程的三大特性。", "options": null, "answer": "封装、继承、多态。", "analysis": "封装隐藏内部实现,继承扩展父类功能,多态同一接口不同实现。", "score": 1, "sort": 3 } ] }4.3 批量阅卷提示词(grading_batch_prompt.txt)
该模板要求 AI 一次性返回包含题目完整信息及评分结果的 JSON 数组,实现自包含的成绩单。
你是一名严格的阅卷老师。请根据以下【题目列表】一次性批改所有题目,并按顺序返回每一道题的评分结果。 【题目列表】(共 {question_count} 道题) {batch_questions_json} 【评分规则】 1. 单选题:学生答案与标准答案完全一致(忽略大小写)得满分,否则0分。 2. 多选题:完全正确得满分,部分正确得一半分,全错或漏选不得分。 3. 文本题:根据语义相似度和关键要点覆盖度判断,满分按比例折算(例如总分5分,语义匹配度80%则得4分)。 【输出要求】 必须返回一个严格的 JSON 数组,数组长度必须与题目列表数量一致,顺序一一对应。每个元素必须包含以下字段: - question_id:题目ID(与输入一致) - type:题型(与输入一致,取值为 single / multiple / text) - content:题干(与输入一致) - options:选项(与输入一致) - standard_answer:标准答案(与输入一致) - user_answer:学生答案(与输入一致) - score:题目分值(与输入一致) - is_correct:"1"(正确)或 "0"(错误) - score_earned:实际得分(整数) - feedback:简短评语,若错误需给出正确答案提示 【输出示例】 [ { "question_id": 1, "type": "single", "content": "Java支持哪种继承方式?", "options": ["A. 单继承", "B. 多继承", "C. 接口多继承", "D. 以上都是"], "standard_answer": "A", "user_answer": "A", "score": 5, "is_correct": "1", "score_earned": 5, "feedback": "回答正确" }, { "question_id": 2, "type": "multiple", "content": "以下哪些属于HTTP请求方法?", "options": ["A. GET", "B. POST", "C. SEND", "D. PUT", "E. DELETE"], "standard_answer": ["A","B","D","E"], "user_answer": ["A","C"], "score": 5, "is_correct": "0", "score_earned": 2, "feedback": "漏选D、E,多选C,正确答案为A、B、D、E" }, { "question_id": 3, "type": "text", "content": "请简述面向对象编程的三大特性。", "options": null, "standard_answer": "封装、继承、多态", "user_answer": "封装、继承、多态", "score": 10, "is_correct": "1", "score_earned": 10, "feedback": "要点齐全,回答正确" } ]五、核心业务实现(IAIGenerateService)
该类集成了试卷生成和批量阅卷两大核心功能,可以使用@Async实现异步处理,避免阻塞前端(本文章为了演示未使用)。
5.1 AI 生成试卷
流程:根据生成方式获取参考资料 → 构建提示词 → 调用 AI → 解析 JSON 并批量插入题目。
@Transactional(rollbackFor = Exception.class) public void generatePaper(PaperBo bo ) { try{ String reference = ""; // 生成方式:1=知识库 2=自由描述 if ("1".equals(bo.getGenType())) { // 知识库生成:genConfig 为逗号分隔的文档ID字符串 String[] ids = bo.getGenConfig().split(","); //通过文档ID获取文档内容 List<Document> documents = documentMapper.selectByIds(List.of(ids)); if (documents != null && !documents.isEmpty()) { // 拼接文档标题和内容 reference = documents.stream() .map(doc -> "【" + doc.getTitle() + "】\n" + doc.getContent()) .reduce((a, b) -> a + "\n\n---\n\n" + b) .orElse(""); } else { reference = "暂无相关参考资料。"; } } else { // 自由描述生成:genConfig 为用户输入的描述 reference = bo.getGenConfig(); } // 2. 构建提示词 String template = promptUtil.loadTemplate("generate_paper_prompt.txt"); // 替换模板中的占位符 共3个:{question_count}、{reference_content}、{total_score}分别对应试卷参数中的题目数量、参考内容、总分数 String prompt = template .replace("{question_count}", String.valueOf(bo.getQuestionCount())) .replace("{reference_content}", reference) .replace("{total_score}", String.valueOf(bo.getTotalScore())) .toString(); // 3. 调用 AI 服务 String aiResponse = aiService.chat(prompt); log.info("AI 响应:{}", aiResponse); // 4. 解析题目并入库 parseAndSaveQuestions(aiResponse, bo.getId()); }catch (Exception e){ log.error("生成试卷失败", e); throw new RuntimeException("生成试卷失败", e); } } /** * 解析题目并入库 * @param aiResponse AI 响应的 JSON 字符串 * @param paperId 试卷ID */ private void parseAndSaveQuestions(String aiResponse, Long paperId) { // 解析题目并入库的逻辑 String json = aiResponse.replaceAll("```json", "").replaceAll("```", "").trim(); JSONObject root = JSON.parseObject(json); // 从 JSON 中提取 questions 数组 JSONArray questions = root.getJSONArray("questions"); //TODO 解析题目并入库的逻辑 //从 questions 数组中提取每个对象 List<Question> questionsList = new ArrayList<>(); for (Object question : questions) { Question newQuestion = new Question(); newQuestion.setPaperId(paperId); JSONObject q = JSON.parseObject(question.toString()); newQuestion.setType(q.getString("type")); newQuestion.setContent(q.getString("content")); newQuestion.setOptions(q.getString("options")); newQuestion.setAnswer(q.getString("answer")); newQuestion.setAnalysis(q.getString("analysis")); newQuestion.setScore(q.getLong("score")); newQuestion.setSort(q.getLong("sort")); questionsList.add(newQuestion); } boolean flag = questionMapper.insertBatch(questionsList); if (flag) { LambdaUpdateWrapper<Paper> luw = Wrappers.lambdaUpdate(); luw.eq(Paper::getId, paperId); luw.set(Paper::getStatus, "1"); paperMapper.update(luw); } log.info("解析到 {} 道题目", questions.size()); }5.2 AI 批量阅卷
流程:解析用户提交的极简答案 → 构建包含题目完整信息的 JSON 数组 → 调用 AI 批量评分 → 将 AI 返回的完整成绩单直接覆盖answers_snapshot
/** * 阅卷(批量模式) */ @Transactional(rollbackFor = Exception.class) public void gradePaper(UserRecordBo bo) { try { //用户全部答案JSON 格式 [ // { "questionId": 1, "answer": "B" }, // { "questionId": 2, "answer": "[\"A\",\"C\"]" } //] String answersSnapshot = bo.getAnswersSnapshot(); if (StrUtil.isBlank(answersSnapshot)) { throw new IllegalArgumentException("答案快照不能为空"); } //获取试卷 Paper paper = paperMapper.selectById(bo.getPaperId()); if (paper == null) { throw new IllegalArgumentException("试卷不存在"); } // 1. 获取题目列表 LambdaQueryWrapper<Question> qw = Wrappers.lambdaQuery(); qw.eq(Question::getPaperId, paper.getId()); qw.orderByAsc(Question::getSort); List<Question> questions = questionMapper.selectList(qw); if (CollUtil.isEmpty(questions)) { throw new IllegalArgumentException("试卷中没有题目"); } // 2. 解析用户提交的原始答案(极简格式) List<AnswerItem> answerItems = JSON.parseArray(answersSnapshot, AnswerItem.class); // 从用户提交的答案原始格式中提取题目ID和答案 Map<Long, String> answerMap = answerItems.stream() .collect(Collectors.toMap(AnswerItem::getQuestionId, AnswerItem::getAnswer)); // 3. 构建批量阅卷的输入 JSON 数组 JSONArray batchInput = new JSONArray(); for (Question q : questions) { JSONObject item = new JSONObject(); item.put("question_id", q.getId()); item.put("type", q.getType()); item.put("content", q.getContent()); item.put("options", q.getOptions() == null ? null : JSON.parseArray(q.getOptions())); item.put("standard_answer", q.getAnswer()); item.put("score", q.getScore()); // 从用户提交的答案中获取该题的答案 item.put("user_answer", answerMap.getOrDefault(q.getId(), "")); batchInput.add(item); } // 4. 加载提示词模板并构建 Prompt String template = promptUtil.loadTemplate("grading_batch_prompt.txt"); // 替换模板中的占位符 共2个:{question_count}、{batch_questions_json}分别对应试卷参数中的题目数量、批量阅卷的输入 JSON 数组 String prompt = template .replace("{question_count}", String.valueOf(questions.size())) .replace("{batch_questions_json}", batchInput.toJSONString()); // 5. 调用 AI 批量评分 String aiResponse = aiService.chat(prompt); log.info("AI 响应:{}", aiResponse); // 从 JSON 中提取 results 数组 JSONArray resultArray = JSON.parseArray(aiResponse); //TODO 解析 AI 返回的 JSON 数组然后入库到数据库 //6. 计算总分并校验结果长度 int totalScore = 0; if (resultArray.size() != questions.size()) { log.warn("AI 返回结果数量 {} 与题目数量 {} 不一致,将按实际匹配计分", resultArray.size(), questions.size()); } for (int i = 0; i < resultArray.size(); i++) { JSONObject gradeObj = resultArray.getJSONObject(i); totalScore += gradeObj.getIntValue("score_earned"); } // 7. 更新考试记录:总分、状态、完整成绩单快照 LambdaUpdateWrapper<UserRecord> luw = Wrappers.lambdaUpdate(); luw.eq(UserRecord::getId, bo.getId()); luw.set(UserRecord::getUserScore, totalScore); luw.set(UserRecord::getStatus, "2"); // 覆盖为完整成绩单 luw.set(UserRecord::getAnswersSnapshot, resultArray.toJSONString()); userRecordMapper.update(null, luw); } catch (Exception e) { log.error("阅卷失败, recordId: {}", bo.getId(), e); throw new RuntimeException("阅卷失败: " + e.getMessage(), e); } }阅卷前:
[ { "questionId": 1, "answer": "B" }, { "questionId": 2, "answer": "[\"A\",\"C\"]" } ]阅卷后:
[ { "questionId": 1, "content": "Java中,哪个关键字用于实现封装?", "userAnswer": "B", "standardAnswer": "B", "scoreEarned": 5, "aiFeedback": "回答正确" ... } ]六、运行效果展示
6.1 生成试卷示例
基于知识库目录1,2,3是自定义到数据库的ID(Java 技术栈)生成 5 道题、总分 100 分的试卷,AI 返回并入库的题目如下(截取自数据库):
{ "questions": [ { "type": "single", "content": "Java中,下列哪个关键字用于创建线程?", "options": ["A. extends", "B. implements", "C. new", "D. synchronized"], "answer": "B", "analysis": "实现Runnable接口使用implements关键字。", "score": 1, "sort": 1 }, { "type": "multiple", "content": "以下哪些属于HTTP请求方法?", "options": ["A. GET", "B. POST", "C. SEND", "D. PUT", "E. DELETE"], "answer": ["A","B","D","E"], "analysis": "HTTP常见方法有GET、POST、PUT、DELETE等。", "score": 1, "sort": 2 }, { "type": "text", "content": "请简述面向对象编程的三大特性。", "options": null, "answer": "封装、继承、多态。", "analysis": "封装隐藏内部实现,继承扩展父类功能,多态同一接口不同实现。", "score": 1, "sort": 3 } ] }6.2 阅卷结果示例
用户作答后,AI 批改并返回的answers_snapshot内容(已格式化为可读 JSON):
[ { "question_id": 2047120715656048642, "type": "single", "content": "Java中,下列哪个特性通过访问修饰符控制对数据和方法的访问权限?", "options": ["A. 继承", "B. 封装", "C. 多态", "D. 抽象"], "standard_answer": "B", "user_answer": "A", "score": 12, "is_correct": "0", "score_earned": 0, "feedback": "错误。访问修饰符用于实现封装,正确答案是B。" } // ... 其他题目评分详情 ]七、总结与扩展建议
本文详细介绍了如何在 RuoYi-Vue-Plus 框架中集成 DeepSeek 大模型,实现 AI 智能生成试卷与批量阅卷功能。核心要点回顾:
数据库设计:使用 JSON 字段存储灵活配置,
answers_snapshot在阅卷前后切换格式,实现性能与可读性的平衡。AI 调用:采用 HTTP 直连方式,兼容 OpenAI 规范,代码简洁且易于替换其他模型。
提示词工程:将提示词外置为文件,通过占位符动态替换,方便持续优化。
批量处理:阅卷采用一次请求处理全部题目,大幅降低延迟与成本。
希望本文能为您的 AI 应用开发提供清晰、可落地的参考。完整代码与数据库脚本已随文提供,欢迎实践与交流!