AIGrader:一个 AI 作业批改平台的 Java EE 课设实战
教师布置作业,AI 秒级批改,学生即时查看并订正,教师复核确认------一个完整的作业批改闭环。
目录
- 这个项目是什么
- 功能全景
- 技术架构
- [核心模块:AI 批改引擎](#核心模块:AI 批改引擎)
- 核心模块:个性化评语与学习报告
- 核心模块:动态模型切换
- 核心模块:认证与权限
- 前端实现
- 总结与收获
这个项目是什么
AIGrader 是一个基于 AI 的中小学作业批改平台。教师在线布置作业,学生在线答题,AI 自动批改并生成评语,学生查看结果后可以订正,教师最终复核确认。
核心闭环:教师布置 → AI 秒级批改 → 学生查看与订正 → 教师复核。
技术栈
| 层级 | 技术 |
|---|---|
| 后端框架 | Spring Boot 3.4.5 + Java 21 + Maven |
| AI 引擎 | Spring AI 1.0.0-M6 + DeepSeek(支持动态切换模型) |
| 安全认证 | Spring Security + JWT(无状态 Token) |
| 数据库 | PostgreSQL 16 + pgvector 扩展 |
| 缓存 | Redis 7(DB=2) |
| 前端框架 | React 18 + Vite + TypeScript |
| UI 组件 | Ant Design 5 |
| 图表 | Recharts 2.15 |
| 状态管理 | Zustand |
项目地址:github.com/xiaodangjia105/AIGrader
这是我的 Java EE 课程设计项目,一个人完成的全栈开发。后端 63 个 Java 文件,前端 21 个 TS/TSX 文件,9 张数据库表,30+ 个 REST API。
功能全景
系统有三种角色:教师、学生、管理员。每个角色看到的界面和能做的事完全不同。
教师端
| 功能 | 说明 |
|---|---|
| 作业管理 | 创建作业、从题库选题组卷、查看布置历史 |
| AI 批改结果查看 | 查看每个学生的 AI 批改结果,含分数、评语、扣分原因 |
| 批改复核 | 对 AI 批改结果进行人工修正,调整分数或评语 |
| 个性化评语 | AI 根据学生历史表现生成个性化评语,教师可编辑后发送 |
| 班级统计 | 查看班级完成率、平均分、分数分布 |
学生端
| 功能 | 说明 |
|---|---|
| 查看作业 | 查看教师布置的待完成作业列表 |
| 在线答题 | 支持选择题、判断题、填空题、简答题、作文题 |
| 查看批改结果 | 即时查看 AI 批改的分数、评语和正确答案 |
| 错题订正 | 对错误答案进行订正,系统记录订正历史 |
| 学习报告 | AI 分析薄弱知识点,生成个性化学习建议 |
管理后台
| 功能 | 说明 |
|---|---|
| 题库管理 | 题目的 CRUD 操作,支持 JSON/CSV 批量导入 |
| 用户管理 | 查看和管理所有用户 |
| AI 配置 | 动态切换 AI 模型和 API Key,无需重启服务 |
| AI 准确率趋势 | 跟踪 AI 批改的准确率变化 |
技术架构
#mermaid-svg-i3p4rRFrXi48UwZR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-i3p4rRFrXi48UwZR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-i3p4rRFrXi48UwZR .error-icon{fill:#552222;}#mermaid-svg-i3p4rRFrXi48UwZR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i3p4rRFrXi48UwZR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i3p4rRFrXi48UwZR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i3p4rRFrXi48UwZR .marker.cross{stroke:#333333;}#mermaid-svg-i3p4rRFrXi48UwZR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i3p4rRFrXi48UwZR p{margin:0;}#mermaid-svg-i3p4rRFrXi48UwZR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-i3p4rRFrXi48UwZR .cluster-label text{fill:#333;}#mermaid-svg-i3p4rRFrXi48UwZR .cluster-label span{color:#333;}#mermaid-svg-i3p4rRFrXi48UwZR .cluster-label span p{background-color:transparent;}#mermaid-svg-i3p4rRFrXi48UwZR .label text,#mermaid-svg-i3p4rRFrXi48UwZR span{fill:#333;color:#333;}#mermaid-svg-i3p4rRFrXi48UwZR .node rect,#mermaid-svg-i3p4rRFrXi48UwZR .node circle,#mermaid-svg-i3p4rRFrXi48UwZR .node ellipse,#mermaid-svg-i3p4rRFrXi48UwZR .node polygon,#mermaid-svg-i3p4rRFrXi48UwZR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i3p4rRFrXi48UwZR .rough-node .label text,#mermaid-svg-i3p4rRFrXi48UwZR .node .label text,#mermaid-svg-i3p4rRFrXi48UwZR .image-shape .label,#mermaid-svg-i3p4rRFrXi48UwZR .icon-shape .label{text-anchor:middle;}#mermaid-svg-i3p4rRFrXi48UwZR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-i3p4rRFrXi48UwZR .rough-node .label,#mermaid-svg-i3p4rRFrXi48UwZR .node .label,#mermaid-svg-i3p4rRFrXi48UwZR .image-shape .label,#mermaid-svg-i3p4rRFrXi48UwZR .icon-shape .label{text-align:center;}#mermaid-svg-i3p4rRFrXi48UwZR .node.clickable{cursor:pointer;}#mermaid-svg-i3p4rRFrXi48UwZR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-i3p4rRFrXi48UwZR .arrowheadPath{fill:#333333;}#mermaid-svg-i3p4rRFrXi48UwZR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-i3p4rRFrXi48UwZR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-i3p4rRFrXi48UwZR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i3p4rRFrXi48UwZR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-i3p4rRFrXi48UwZR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i3p4rRFrXi48UwZR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-i3p4rRFrXi48UwZR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-i3p4rRFrXi48UwZR .cluster text{fill:#333;}#mermaid-svg-i3p4rRFrXi48UwZR .cluster span{color:#333;}#mermaid-svg-i3p4rRFrXi48UwZR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-i3p4rRFrXi48UwZR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-i3p4rRFrXi48UwZR rect.text{fill:none;stroke-width:0;}#mermaid-svg-i3p4rRFrXi48UwZR .icon-shape,#mermaid-svg-i3p4rRFrXi48UwZR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i3p4rRFrXi48UwZR .icon-shape p,#mermaid-svg-i3p4rRFrXi48UwZR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-i3p4rRFrXi48UwZR .icon-shape .label rect,#mermaid-svg-i3p4rRFrXi48UwZR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i3p4rRFrXi48UwZR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-i3p4rRFrXi48UwZR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-i3p4rRFrXi48UwZR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后端
前端
React + TypeScript
Axios + JWT 拦截器
Spring Security + JWT
Controller 层
Service 层
AI 批改引擎
Repository 层
PostgreSQL + pgvector
Redis 7
Spring AI + DeepSeek
后端采用标准的 Spring Boot 分层架构:
- Controller 层(8 个控制器):处理 HTTP 请求,参数校验,调用 Service
- Service 层(10 个服务):核心业务逻辑,事务管理
- Repository 层(9 个仓库):JPA 数据访问,自动建表
- Entity 层(9 个实体):数据库表映射
- DTO 层(12 个对象):数据传输对象,隔离内部模型和 API 契约
前端结构:
- Pages:按角色分组------教师端 5 页、学生端 5 页、管理后台 4 页
- Components:共享组件(表格、表单、图表)
- Services:API 调用层,Axios + JWT 拦截器
- Store:Zustand 状态管理
数据库有 9 张表,其中 PostgreSQL 的 pgvector 扩展用于存储和检索向量数据(支持未来的智能题目推荐功能)。Redis 用于 JWT Token 黑名单和会话缓存。
核心模块:AI 批改引擎
这是整个系统最核心的部分。不同题型的批改逻辑完全不同------选择题可以精确匹配,填空题需要模糊匹配,主观题需要 AI 理解语义。用一个统一的接口处理所有题型,是策略模式的典型应用场景。
策略模式设计
java
public interface GradingStrategy {
/**
* 批改一道题
* @param question 题目信息(题干、正确答案、分值)
* @param answer 学生答案
* @return 批改结果(分数、评语、扣分原因)
*/
GradingResult grade(Question question, String answer);
}
三个实现类:
- ChoiceGradingStrategy(选择题/判断题):精确匹配答案,不需要调 AI。分数要么满分要么零分。
- FillBlankGradingStrategy(填空题):先精确匹配,失败后调 AI 做语义相似度判断。比如答案是"二氧化碳",学生写了"CO2",也算对。
- SubjectiveGradingStrategy(简答题/作文题):调 DeepSeek API,让 AI 根据评分标准打分。
工厂模式根据题型分发:
java
public class GradingStrategyFactory {
private final Map<QuestionType, GradingStrategy> strategies;
public GradingStrategy getStrategy(QuestionType type) {
GradingStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的题型: " + type);
}
return strategy;
}
}
Prompt 工程
主观题批改的关键在 Prompt 设计。PromptTemplateService 按学科组织 Prompt 模板,不同学科的评分标准不同:
- 数学:看计算过程是否正确、步骤是否完整、最终答案是否准确
- 语文:看表达是否通顺、逻辑是否清晰、是否有错别字
- 英语:看语法是否正确、词汇是否恰当、表达是否地道
AI 要求返回结构化的 JSON:
json
{
"score": 85,
"maxScore": 100,
"comment": "解题思路正确,但第三步计算有误",
"deductions": [
{"reason": "第三步符号错误", "points": 10},
{"reason": "缺少单位", "points": 5}
]
}
结构化输出的好处:前端可以直接渲染每个扣分项,学生能清楚看到哪里扣了分、为什么扣。
降级策略
AI 调用可能失败(网络超时、API 限流)。系统做了降级处理:AI 不可用时,主观题给默认分数并标记"待人工复核",不会阻断整个批改流程。
核心模块:个性化评语与学习报告
个性化评语(CommentService)
传统做法:教师手动写评语,工作量大,而且很难做到个性化。
AIGrader 的做法:AI 根据学生的答题历史和成绩趋势自动生成评语。Prompt 里包含学生最近 5 次作业的成绩、本次作业的得分、错误题目类型等上下文,让 AI 生成有针对性的评语。
比如一个学生上次考了 60 分,这次考了 85 分,AI 会生成"进步明显,尤其是应用题部分有了很大提升,继续保持"。如果一个学生连续三次在几何题上丢分,AI 会建议"几何部分需要加强,建议复习三角形全等的判定条件"。
教师可以编辑 AI 生成的评语后再发送给学生。
学习报告(ReportService)
学习报告是学生端的核心功能。ReportService 分析学生的所有答题记录,生成:
- 薄弱知识点分析:按题目标签统计正确率,找出正确率最低的知识点
- 成绩趋势:最近 N 次作业的分数变化曲线
- AI 学习建议:根据薄弱知识点,推荐具体的复习方向
报告数据用 Recharts 在前端渲染成图表,学生一目了然。
核心模块:动态模型切换
AiConfigService 实现了一个不需要重启服务就能切换 AI 模型的能力。
配置存储在数据库的 ai_config 表中,包含:
- 当前使用的模型名称(如
deepseek-chat) - API Key
- API Base URL
- 模型参数(temperature、max_tokens 等)
管理后台的"AI 配置"页面可以修改这些配置,保存后立即生效。下次 AI 调用时,AiConfigService 从数据库读取最新配置,用 Spring AI 的统一接口创建新的客户端实例。
这个设计的好处:
- 不重启切换模型:DeepSeek 不稳定时,可以切到通义千问或 Kimi
- API Key 轮换:Key 过期或泄露时,直接在界面上换
- 参数调优:调整 temperature 来控制 AI 评分的稳定性
Spring AI 的价值在这里体现得很明显------它提供了统一的 AI 调用接口,切换底层模型只需要改配置,代码不需要动。
核心模块:认证与权限
JWT 无状态认证
系统使用 Spring Security + JWT 实现无状态认证。用户登录后签发 Token,后续请求通过 Token 认证,服务端不存储会话状态。
三种角色的权限隔离:
| 角色 | 能访问的 API |
|---|---|
| 教师 | /api/assignments/**、/api/grading/**、/api/statistics/** |
| 学生 | /api/submissions/**、/api/student/** |
| 管理员 | /api/admin/**、/api/questions/** |
用 @PreAuthorize 注解在 Controller 方法上声明权限:
java
@PreAuthorize("hasRole('TEACHER')")
@PostMapping("/assignments")
public ResponseEntity<ApiResponse<Assignment>> createAssignment(...) { ... }
Token 黑名单
JWT 签发后到过期前无法单方面废止。系统用 Redis 实现了 Token 黑名单:用户登出时,把 Token 的唯一标识加入 Redis,设置过期时间等于 Token 剩余有效期。验证 Token 时先查黑名单,命中则拒绝。
前端实现
技术选型
前端用 React 18 + TypeScript + Vite 构建,UI 组件库选 Ant Design 5。状态管理用 Zustand------比 Redux 轻量得多,一个文件就能定义一个 store,不需要 action、reducer、middleware 那一套。
页面结构
frontend/src/pages/
├── LoginPage.tsx # 登录页(共用)
├── teacher/
│ ├── AssignmentPage.tsx # 作业管理
│ ├── GradingPage.tsx # 批改结果查看
│ ├── ReviewPage.tsx # 批改复核
│ ├── StatisticsPage.tsx # 班级统计
│ └── CommentPage.tsx # 个性化评语
├── student/
│ ├── HomeworkPage.tsx # 作业列表
│ ├── AnswerPage.tsx # 在线答题
│ ├── ResultPage.tsx # 批改结果
│ ├── CorrectPage.tsx # 错题订正
│ └── ReportPage.tsx # 学习报告
└── admin/
├── QuestionPage.tsx # 题库管理
├── UserPage.tsx # 用户管理
├── AiConfigPage.tsx # AI 配置
n └── AccuracyPage.tsx # AI 准确率趋势
API 调用层
Axios 实例配置了 JWT 拦截器:
- 请求拦截:自动从 Zustand store 读取 Token,附加到请求头
- 响应拦截:遇到 401 自动跳转登录页,清除本地 Token
所有 API 调用统一封装在 services/api.ts 中,按模块分组(auth、assignment、submission、grading 等)。
图表
Recharts 用于两个核心场景:
- 班级统计:柱状图展示分数分布,折线图展示平均分趋势
- 学习报告:雷达图展示各知识点掌握程度,折线图展示成绩变化
总结与收获
这个课设让我从零搭建了一个完整的全栈应用,覆盖了后端开发、前端开发、AI 集成、数据库设计、安全认证等多个方面。
几个关键收获:
策略模式的实际应用。AI 批改引擎用策略模式处理不同题型,比 if-else 链清晰得多,新增题型只需要加一个实现类。
Prompt 工程的重要性。AI 批改的质量取决于 Prompt 的设计。结构化输出(JSON)让前端可以直接渲染,不需要再做文本解析。
Spring AI 的价值。统一的 AI 调用接口让模型切换变得很简单------改配置就能切模型,代码不需要动。
无状态认证的取舍。JWT 让后端可以水平扩展,但 Token 无法主动失效,需要额外引入 Redis 黑名单。没有银弹,只有 trade-off。
当前的局限:只支持单 Sheet 的作业模板、AI 调用有延迟(批量批改时较慢)、大数据量下性能有待优化。后续计划支持多 Sheet、批量转换和本地模型。
项目地址:github.com/xiaodangjia105/AIGrader
觉得有用的话,点个 Star 支持一下。