
简介基于SpringBoot的学生作业管理系统是一套面向计算机专业毕业生和Java学习者的完整项目源码适用于毕业设计、课程设计或项目实战练习。资源包共3个文件包含项目源码压缩包、数据库脚本及项目说明文档压缩包整体大小约5.93MB结构清晰便于快速部署与二次开发。目前已有61人学习下载。技术实现采用SpringBootMyBatis后端框架与Vue.js前端技术搭配MySQL数据库系统按B/S架构设计涵盖管理员、教师、学生三种角色学生可注册登录、查看课程和作业并在线提交教师可管理课程、布置与批改作业、维护成绩管理员则负责教师、班级、课程及网站内容管理等日常维护。除核心源码外还附带数据库脚本与项目说明文档可帮助读者快速搭建运行环境完整理解从数据库设计到前后端联调的JavaWeb开发流程。1. 毕设里的“作业管理系统”真正值钱的是数据库脚本那部分很多人拿到“基于SpringBoot的学生作业管理系统”这类毕设题目时第一反应是赶紧把登录注册、作业增删改查凑出来。但实际上这类系统真正能拉开差距的不是 Controller 里那几个接口而是数据库脚本怎么设计——学生、教师、课程、作业、提交记录、评分结果这几张表的关系直接决定了后续所有功能的开发成本和答辩时的讲解深度。SpringBoot 在这里的价值是让你用最少的配置把 REST API 跑起来把精力留给业务逻辑。这篇文章按照我接手这类毕设时的一套固定打法来讲先定数据模型再用 SpringBoot 把核心链路打通最后教你准备一套能直接导入 MySQL 的数据库脚本以及演示时怎么避开“版本太高跑不起来”的坑。适合正在做毕设的学生也适合要快速接手一个 SpringBoot 管理类项目的一线开发。2. SpringBoot 学生作业管理系统的技术选型与项目骨架2.1 为什么是 SpringBoot 而不是 SSM 或 SSH学生作业管理系统属于典型的 CRUD 密集型应用用户登录、作业发布、作业提交、教师评分、数据统计。这类系统没有高并发、没有复杂分布式事务核心诉求是“快速开发、结构清晰、方便答辩时讲明白”。SpringBoot 正好卡在这个位置上。SSHStruts2 Spring Hibernate在 2018 年后基本从教学大纲里退场了SSMSpring SpringMVC MyBatis虽然还能见到但配置文件的繁琐程度比 SpringBoot 高出一个量级。SpringBoot 的自动装配机制把数据源、事务管理器、Web 容器这些基础设施的初始化过程收敛到了spring-boot-starter-*依赖里你只需要关注自己的业务代码。关于自动装配原理我在 2.2 节用一个实际启动过程说明。一个反直觉的结论是这类毕设项目不适合一上来就引入 Spring Cloud Alibaba 或者微服务拆分。作业管理系统在一个 Tomcat 实例里跑就是最优解强行拆服务只会让答辩时被问得下不来台。正确的边界是SpringBoot 做后端、Thymeleaf 或 Vue 做前端、MySQL 存数据、MyBatis-Plus 操作数据库。2.2 项目结构设计与 Maven 依赖的最小集合拿到题目后我通常会先建一个标准的三层结构这是 SpringBoot 开发——集成类项目最常见的骨架后期加功能不加混乱。student-homework-system/ ├── src/main/java/com/example/shw/ │ ├── controller/ # 接收 HTTP 请求 │ ├── service/ # 业务逻辑 │ ├── mapper/ # MyBatis-Plus 数据访问 │ ├── entity/ # 数据库表实体 │ ├── config/ # 拦截器、跨域、上传配置 │ └── common/ # 统一返回结果、异常处理 ├── src/main/resources/ │ ├── application.yml │ └── mapper/ # XML 文件如果有复杂 SQL └── pom.xml对应的pom.xml核心依赖如下版本号用 Spring Boot 2.7.x 系最稳妥parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.13/version relativePath/ /parent dependencies !-- Web 基础内置 Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus简化 CRUD -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 参数校验Validated 注解需要 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- Lombok减少实体类样板代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这段依赖里有三个关键决策点。第一MyBatis-Plus 3.5.3.1 对应 Spring Boot 2.x如果你的 Spring Boot 是 3.x需要换用mybatis-plus-spring-boot3-starter这是毕设最常见的“走不下去”的原因——版本不匹配时启动直接报ClassNotFoundException。第二MySQL 驱动坐标在 Spring Boot 2.7 里是mysql:mysql-connector-java到了 3.x 变成了com.mysql:mysql-connector-j不要照抄别人的 pom。第三我故意没有引入 Spring Security原因是作业管理系统的权限模型很简单——教师和学生两个角色用拦截器校验登录状态 角色权限就够Spring Security 的学习成本对毕设周期不友好。2.3 application.yml 里的必配参数配置文件是整个 SpringBoot 项目能不能在本机跑起来的命门。我给出一份可直接用的配置重点看数据源和文件上传这两段server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/student_homework?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 servlet: multipart: max-file-size: 50MB max-request-size: 50MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0serverTimezoneAsia/Shanghai不写的话MySQL 8.x 会报时间差错误allowPublicKeyRetrievaltrue是解决 MySQL 8 的 caching_sha2_password 认证插件连不上问题。max-file-size设到 50MB 是因为学生作业经常包含代码压缩包或视频演示。map-underscore-to-camel-case开启后数据库字段student_id能自动映射到实体属性studentId省掉大量TableField注解。logic-delete-field: deleted是 MyBatis-Plus 的逻辑删除配置后面建表时每个表都要带deleted字段。3. 数据库脚本设计作业管理系统的表关系与 SQL 写法3.1 五张核心表的建模思路和建表 SQL数据库脚本是这个项目的灵魂答辩时老师大概率会从表设计开始问。学生作业管理系统最少需要五张表用户表含学生和教师用角色字段区分、课程表、作业表、提交记录表、评分表。为什么用户表不拆成 student 和 teacher 两张表因为两张表的字段重复度超过 80%——用户名、密码、姓名、邮箱都是同样的拆开只会让登录逻辑写两份。用一个role字段区分既满足业务又能少写一半代码数据模型上这叫“单表继承”。下面是建表 SQL 的核心部分我用 MySQL 8.0 语法存储引擎 InnoDB字符集 utf8mb4CREATE DATABASE IF NOT EXISTS student_homework DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_homework; -- 用户表学生和教师统一存储role 字段区分 CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(50) NOT NULL COMMENT 姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 角色0-学生 1-教师 2-管理员, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除0-正常 1-已删除, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT用户表; -- 课程表 CREATE TABLE course ( id BIGINT NOT NULL AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, teacher_id BIGINT NOT NULL COMMENT 教师ID关联sys_user.id, class_name VARCHAR(100) DEFAULT NULL COMMENT 授课班级, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT课程表; -- 作业表 CREATE TABLE homework ( id BIGINT NOT NULL AUTO_INCREMENT, course_id BIGINT NOT NULL COMMENT 所属课程, title VARCHAR(200) NOT NULL COMMENT 作业标题, content TEXT COMMENT 作业要求描述, deadline DATETIME NOT NULL COMMENT 截止时间, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT作业表;执行这段 SQL 时注意两点第一COLLATE utf8mb4_general_ci表示大小写不敏感的比较规则如果作业内容里需要区分大小写的英文缩写换成utf8mb4_bin第二所有表都带deleted字段配合 2.3 节配置的逻辑删除查数据时 MyBatis-Plus 会自动追加AND deleted 0删数据自动变 UPDATE这个设计在答辩时是可以加分的点。3.2 提交记录与评分表避免数据冗余的关键提交表和评分表是这个系统最见功力的地方。很多初学者会把“提交了什么”和“得了多少分”塞在同一张表里导致一个学生多次提交时旧记录被覆盖分数历史无从追溯。正确做法是分成两张表。-- 作业提交记录表每次提交一行记录 CREATE TABLE homework_submit ( id BIGINT NOT NULL AUTO_INCREMENT, homework_id BIGINT NOT NULL COMMENT 作业ID, student_id BIGINT NOT NULL COMMENT 学生ID, submit_content TEXT COMMENT 文字描述或作业内容, file_url VARCHAR(255) DEFAULT NULL COMMENT 附件存储路径, submit_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted TINYINT NOT NULL DEFAULT 0, is_late TINYINT NOT NULL DEFAULT 0 COMMENT 是否迟交0-否 1-是, PRIMARY KEY (id), KEY idx_homework_student (homework_id, student_id) ) ENGINEInnoDB COMMENT作业提交记录表; -- 评分表教师打分结果 CREATE TABLE homework_score ( id BIGINT NOT NULL AUTO_INCREMENT, submit_id BIGINT NOT NULL COMMENT 提交记录ID, teacher_id BIGINT NOT NULL COMMENT 评分教师ID, score DECIMAL(5,2) NOT NULL COMMENT 分数保留两位小数, comment VARCHAR(500) DEFAULT NULL COMMENT 评语, score_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted TINYINT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_submit (submit_id) ) ENGINEInnoDB COMMENT作业评分表;这个设计有三个好处一是支持学生多次提交每次提交都是新记录教师可以对比历次内容给出最终评价二是UNIQUE KEY uk_submit(submit_id)保证一条提交记录只能被评分一次数据层面杜绝了重复评分的脏数据三是is_late字段在提交时用 Java 端判断是否超过deadline后写入比在 SQL 里用CASE WHEN判断更直观。建议在homework_submit上加一个联合索引idx_homework_student因为高频查询是“某次作业的所有提交记录”和“某个学生对某次作业的提交历史”这个索引能让查询走 Index Range Scan表数据量到十万级时差距明显。3.3 初始化数据脚本的写法数据库脚本交付时一定要包含一批演示数据否则答辩现场你只能现场注册账号现写作业既慢又没有说服力。初始化脚本通常在建表之后执行用 INSERT 语句插入固定数据密码用 BCrypt 加密后的密文。-- 初始账号密码均为 123456 的 BCrypt 密文 INSERT INTO sys_user (username, password, real_name, role) VALUES (teacher01, $2a$10$X7GQ3G3zJ3yN3Y7XQ3G3zO3n3X3zG3Q3W3Y3X3G3Q3G3zJ3yN3Y, 张老师, 1), (student01, $2a$10$X7GQ3G3zJ3yN3Y7XQ3G3zO3n3X3zG3Q3W3Y3X3G3Q3G3zJ3yN3Y, 李明, 0), (student02, $2a$10$X7GQ3G3zJ3yN3Y7XQ3G3zO3n3X3zG3Q3W3Y3X3G3Q3G3zJ3yN3Y, 王芳, 0);注意上面这段 BCrypt 密文是我写文章用的占位实际项目中你可以先在 SpringBoot 里写一个测试类生成密文再把密文填入 SQL。初始化数据的核心原则是账号容易记、密码统一、角色齐全、业务状态覆盖正常和异常两种情况。作业表里至少要有两条记录一条未截止的演示学生还能提交一条已截止的演示迟交判定和教师评分。4. SpringBoot 核心业务实现登录拦截、作业提交与评分4.1 登录接口与登录拦截器的实现登录接口用最朴素的方式实现接收用户名密码校验通过后生成一个 UUID 作为 token存到 Redis 或内存 Map前端后续请求在 Header 里带token拦截器统一校验。毕设场景不推荐 JWT原因有三个JWT 无状态导致无法主动踢人下线、密钥管理要在答辩时解释清楚、以及作业系统根本没有分布式场景。一个ConcurrentHashMap做 token 存储配合拦截器代码量只有 JWT 方案的三分之一还能现场演示“退出登录即失效”。先写登录接口RestController RequestMapping(/api/auth) public class AuthController { Resource private UserService userService; PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 根据用户名查询用户注意 MyBatis-Plus 的 lambdaQuery 写法 SysUser user userService.lambdaQuery() .eq(SysUser::getUsername, dto.getUsername()) .one(); if (user null) { return Result.error(用户名不存在); } // BCrypt 校验密码不要用 MD5 if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(密码错误); } // 生成 token 并存入缓存 String token UUID.randomUUID().toString().replace(-, ); TokenStore.put(token, user.getId()); return Result.success(token); } }这段代码里有两个容易被忽略的点。BCrypt.checkpw是 Spring Security 的spring-security-crypto包提供的即使不引入完整 Spring Security只加这一个依赖也能用 BCrypt——很多毕设项目用 MD5 存密码答辩时被老师一句“MD5 撞库怎么办”问住BCrypt 加盐哈希是底线。TokenStore.put方法是自己写的一个简单静态类内部用ConcurrentHashMapkey 是 tokenvalue 是 userId代码量不足十行。拦截器核心实现public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 String uri request.getRequestURI(); if (uri.contains(/api/auth/)) { return true; } // 校验 token String token request.getHeader(Authorization); Long userId TokenStore.get(token); if (userId null) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\未登录或登录已过期\}); return false; } // 把用户ID存入 request后续 Controller 里可取 request.setAttribute(userId, userId); return true; } }注意拦截器里response.getWriter().write要手动指定charsetUTF-8否则返回中文会乱码。在WebMvcConfig里注册拦截器时要排除/api/auth/login和静态资源路径否则前端页面加载不出来。4.2 学生提交作业的文件上传与路径处理作业提交是系统里唯一涉及文件操作的环节。这里是坑最多的模块90% 的毕设项目把文件存在数据库 BLOB 字段里或者保存在项目根目录前者会让数据库无限膨胀后者导致重新打包后文件全丢。我用一个独立的上传目录来存文件数据库只存路径。先配置一个路径前缀file: upload-dir: D:/student-homework/upload/ # 本地调试用绝对路径 # 部署到 Linux 时可改为 /var/homework-upload/然后在FileController里写上传接口Value(${file.upload-dir}) private String uploadDir; PostMapping(/api/homework/submit) public Result submit(RequestParam(homeworkId) Long homeworkId, RequestParam(studentId) Long studentId, RequestPart(value file, required false) MultipartFile file, RequestParam(value content, required false) String content) { // 校验作业是否存在且未截止 Homework homework homeworkService.getById(homeworkId); if (homework null) { return Result.error(作业不存在); } if (LocalDateTime.now().isAfter(homework.getDeadline())) { return Result.error(已过截止时间无法提交); } String filePath null; if (file ! null !file.isEmpty()) { // 生成唯一文件名避免中文乱码和重名覆盖 String originalName file.getOriginalFilename(); String suffix originalName.substring(originalName.lastIndexOf(.)); String newName UUID.randomUUID().toString().replace(-, ) suffix; File dest new File(uploadDir newName); try { file.transferTo(dest); filePath /files/ newName; } catch (IOException e) { return Result.error(文件上传失败); } } // 保存提交记录is_late 判断截止时间 HomeworkSubmit submit new HomeworkSubmit(); submit.setHomeworkId(homeworkId); submit.setStudentId(studentId); submit.setSubmitContent(content); submit.setFileUrl(filePath); submit.setIsLate(LocalDateTime.now().isAfter(homework.getDeadline()) ? 1 : 0); submitService.save(submit); return Result.success(提交成功); }代码逻辑上三个阶段参数接收、文件落盘、记录入库。注意RequestPart(file)接收的是 multipart 格式的文件如果用RequestParam(file)会报Current request is not a multipart request的错。文件名用 UUID 重新生成避免学生上传的压缩包带中文路径导致 Linux 下解压报错。路径中/files/**需要在配置类里映射到磁盘目录代码如下Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir); }4.3 教师查询待批改作业并录入分数教师端的高频操作是“查看某次作业的所有提交记录”然后逐条打分。这里有一个查询性能的优化点不要把评分信息单独查——每次点击“批改”再查一次而是用一条 SQL 把提交记录和评分结果 LEFT JOIN 起来一次查出所有数据前端列表直接显示“已评分 / 未评分”。GetMapping(/api/teacher/submissions) public Result listSubmissions(RequestParam Long homeworkId) { ListSubmissionVO list submitMapper.selectSubmissionWithScore(homeworkId); return Result.success(list); }对应的 Mapper 注解 SQLSelect(SELECT s.id, s.homework_id, s.student_id, s.file_url, s.submit_time, s.is_late, sc.score, sc.comment, u.real_name FROM homework_submit s LEFT JOIN homework_score sc ON sc.submit_id s.id AND sc.deleted 0 LEFT JOIN sys_user u ON u.id s.student_id WHERE s.homework_id #{homeworkId} AND s.deleted 0 ORDER BY s.submit_time DESC) ListSubmissionVO selectSubmissionWithScore(Param(homeworkId) Long homeworkId);LEFT JOIN用不用AND sc.deleted 0有讲究——逻辑删除字段必须加在 JOIN 条件的 ON 里不能放在 WHERE 里否则 LEFT JOIN 会退化成 INNER JOIN导致没有评分记录的提交行被过滤掉。这是实际开发中很典型的一个逻辑删除带来的坑。SubmissionVO是一个聚合了提交信息和评分信息的视图对象字段来自三张表用 VO 承接而不是直接返回实体类分页也能直接用 MyBatis-Plus 的Page对象封装。5. 从“能跑”到“能答辩”环境准备与 Web 端集成技巧5.1 IDEA 新建 SpringBoot 项目时处理版本太高的三个办法SpringBoot 3.x 已经发布数年IDEA 自带的 Spring Initializr 默认拉的是 3.x 版本而很多毕设选题时间早于这个时间点教学环境还是 2.7.x——这个“springboot版本太高”的问题每年都在上演。处理方式从优到劣排序第一在 start.spring.io 网页上把 Spring Boot 版本手动切到 2.7.13再在 IDEA 里导入第二在 IDEA 的 Project Structure 里修改 Maven 的 pom.xml 父版本号强制拉回 2.7.13第三如果你依赖了 Spring AI 等新生态就必须用 3.2 以上版本此时 MyBatis-Plus 要换成mybatis-plus-spring-boot3-starterjavax.servlet-api要换jakarta.servlet-api。选择哪个方案取决于需求矩阵版本低了新依赖不支持版本高了老教程的代码编译报错。5.2 用仅有的演示时间展示三条关键链路答辩的演示时间通常只有 10 到 15 分钟不要从头开始登录然后创建课程、发布作业、提交作业一步步走完。我的做法是提前把数据库脚本跑完确保三组数据就位教师账号下已有两门课程、每门课下有两个已发布的作业一个未截止一个已截止、两名学生各提交过一份未批改的作业。演示时直接登录教师账号打开“待批改列表”现场给一份作业打分然后切到学生视角刷新列表看到分数和评语。另一条容易被忽略的演示点是定时任务。在 SpringBoot 主类或单独配置类里写上Scheduled定时任务用日志输出展示“每晚零点扫描过期且未提交的作业自动发送提醒短信”代码量很小但能直接回应老师“系统有没有自动化能力”的追问。定时任务的EnableScheduling注解不要忘记加加主类上即可。5.3 交付清单让项目源码和数据库脚本开箱即用交付给评审或下载者的 zip 包里我习惯放五个文件student_homework.sql建表 初始化数据一体的脚本、pom.xml、application.yml示例文件、一份部署说明.txt、以及源码目录。SQL 脚本顶部加一行SET NAMES utf8mb4防止 Windows 下导入时中文乱码。deployment.txt里写清楚 JDK 版本要求1.8 或 11、MySQL 版本要求5.7 或 8.0、初始化账号密码——这份文档省下的答疑时间远超写它花掉的十分钟。本文还有配套的精品资源点击获取