尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于SpringBoot和Vue的作业管理毕业设计实战解析

基于SpringBoot和Vue的作业管理毕业设计实战解析 简介基于SpringBoot与Vue构建的小学家校一体“作业帮”毕业设计项目面向计算机专业毕业设计、课程设计及需要项目实战的Java全栈学习者。系统聚焦小学家庭作业发布、在线讲解、练习与评估场景支持家长、教师、学生三类账号协同达成家校沟通与学习管理一体化。压缩包共1523个文件约59.22MB源码覆盖Java后端、Vue前端、SQL数据库脚本以及HTML、CSS、JS等静态资源与配置文件并附带开发说明文档、操作演示视频、代码注释和部署环境说明。项目结构按模块划分包含前后端分离实现、数据表备份及环境配置经过严格调试可正常运行可直接作为毕业设计或期末大作业使用。目前已有216人学习下载资源还提供了详细的部署说明与系统介绍能帮助开发者快速理解前后端技术栈和设计思路适合用于二次开发或答辩展示。1. 这个毕业设计题目的含金量藏在业务闭环里“毕业设计源码”在网上随手能搜到一大堆但真正能顺利答辩、能演示、能写进简历的项目并不多。原因很一致代码量大不等于业务完整页面多不等于逻辑闭环。这个题目值得做的原因在于作业管理是小学家校场景里最高频、最容易让人听懂的业务。学生要交作业教师要留作业和批改家长要查看反馈三个角色天然构成了完整业务闭环技术上又正好是 SpringBoot Vue 前后端分离的标准形态把 REST API、数据库设计、文件上传、状态流转、权限控制这些点全部串了起来。标题里的“作业帮”指的就是围绕作业流做的辅助工具和拍照搜题的 App 不是一回事下文统一用作业模块来描述。适合两类人看正拿着任务书还没开工的学生以及想把这个系统改造成简历项目的初级开发。读懂这套项目等于掌握了把一个小型业务端到端落地的最小可行方案。2. 技术选型与数据模型五张表撑起家校闭环的底层逻辑2.1 SpringBoot 版本怎么选2.7.x 是当下最稳妥的毕业设计基座无论是从网上下载的源码 zip还是自己新建工程版本选择是第一道坎。Spring Boot 3.x 已经出来很久但它把javax包名整体切换成了jakarta很多老代码、老教程里的import javax.servlet直接编译不过。另外Spring Boot 3.x 对 JDK 的最低要求是 17不少学校机房或学生本机还在用 JDK 8。所以这里我用的是Spring Boot 2.7.x JDK 8 MySQL 5.7/8.0的组合。2.7.x 是目前兼容性最好的版本既能用javax命名空间也能平滑升到 2.7 最新的补丁社区里搜得到的提问和解决方案也大多是针对这一代版本的。如果你拿到的源码是 3.x别急着跑先看一下pom.xml里的java.version和所有jakarta前缀的依赖确认本机 JDK 版本能对上。提示springboot版本太高是高频搜词九成是装了 JDK 8 却在跑 Spring Boot 3.x。先执行java -version如果是 1.8那就锁死 Spring Boot 2.7.x 分支。2.2 核心表结构用户、班级、作业、提交四类实体数据库课程设计里最常见的错误是把所有字段堆在一张表里。这个项目的业务维度有四个人学生、家长、教师、班级、作业、提交记录。我一般会把核心表拆成下面这样既符合第三范式也能让 ORM 层少写很多冗余字段。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) NOT NULL, role TINYINT NOT NULL COMMENT 1学生 2家长 3教师 4管理员, class_id BIGINT DEFAULT NULL COMMENT 学生和班主任关联班级, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE class_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, grade INT NOT NULL COMMENT 年级如2025, class_name VARCHAR(50) NOT NULL, head_teacher_id BIGINT DEFAULT NULL ); CREATE TABLE homework ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, subject VARCHAR(20) NOT NULL, class_id BIGINT NOT NULL, create_by BIGINT NOT NULL, deadline DATETIME NOT NULL, status TINYINT DEFAULT 0 COMMENT 0未开始 1进行中 2已截止 3已归档, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE homework_submission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, homework_id BIGINT NOT NULL, student_id BIGINT NOT NULL, text_content TEXT, file_url VARCHAR(255), submit_time DATETIME, score INT, comment_text VARCHAR(500), parent_feedback VARCHAR(500), status TINYINT DEFAULT 0 COMMENT 0未提交 1已提交 2已批改, UNIQUE KEY uk_homework_student (homework_id, student_id) );sys_user里用role字段区分四种身份而不是建四张用户表是因为四种角色的公共字段完全一致分表除了增加关联查询没有收益家长登录后要看到孩子数据可以在sys_user里加一个student_id字段或者在关联表里维护毕设场景用一个binds_student_id字段即可。homework_submission是整张数据模型的核心学生提交、教师批改、家长查看反馈最终都落在这一张表上唯一索引uk_homework_student从数据库层面挡住了重复提交。2.3 作业状态流转从发布到归档的四个阶段作业不是一个只有“存在 / 不存在”的二元对象。它从教师点击发布开始到最终归档应该有一个清晰的状态机。状态管理用整数存数据库业务层写枚举类做转换。表格呈现这几个状态更直观状态值含义触发动作谁触发0未开始教师创建作业草稿教师1进行中发布作业且当前时间 deadline教师 / 定时任务2已截止当前时间 deadline定时任务3已归档教师关闭本次作业生成班级统计教师状态迁移看起来简单真正需要理解的是定时任务的实现方式。毕设阶段不需要引入 XXL-Job 或 ElasticJob一个Scheduled注解就能完成扫描Component public class HomeworkStatusJob { Scheduled(cron 0 */5 * * * ?) public void refreshStatus() { // 把超过截止时间且仍处于进行中的作业置为已截止 homeworkMapper.updateStatusByDeadline(); // 把长期处于截止状态、教师已确认结束的作业置为归档 homeworkMapper.updateStatusToArchived(); } }cron表达式0 */5 * * * ?表示每 5 分钟执行一次对毕设演示来说这个频率足够如果你在答辩时需要看到“立即截止”的效果也可以把轮询时间调成 1 分钟或者直接在查询作业详情时实时比对当前时间与deadline动态计算出逻辑状态这样省掉定时任务的配置两种方案都能讲出道理。2.4 演示数据初始化别让数据库裸奔着首页拿到的 zip 里一般会附带.sql文件但很多初学 SpringBoot 的人会遇到数据库同步或初始化脚本执行不成功的情况。我的做法是在application.yml里配置两种方式第一次启动用schema.sql建表用data.sql灌演示数据跑通后把初始化模式关掉避免每次重启都重置数据。spring: sql: init: mode: never schema-locations: classpath:db/schema.sql >Component public class JwtUtil { Value(${jwt.secret}) private String secret; private SecretKey getKey() { return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); } public String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 12)) .signWith(getKey(), SignatureAlgorithm.HS256) .compact(); } } Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { writeError(response, 未登录); return false; } try { Claims claims Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token.replace(Bearer , )) .getBody(); request.setAttribute(userId, Long.valueOf(claims.getSubject())); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { writeError(response, 登录已过期); return false; } } }JWT 的核心是解析出 payload 里的 userId 和 role 后塞进 request 属性后续 Controller 只需要从请求中取userId不需要再查一次用户表生成 Token 时的 12 小时有效期比较适中演示结束也不会频繁重登。需要理解的是签名密钥不能让客户端知道硬编码在代码里只适合本地开发正式项目要从配置中心或环境变量读取。3.2 作业发布接口文件上传 定向班级教师创建作业是这个系统的第一个核心动作。接口设计为POST /api/homework请求体里带一个可选的MultipartFile表示作业附件在没有网盘服务的毕设项目里文件直接落在服务器磁盘上即可。PostMapping(/api/homework) public Result createHomework(RequestParam(title) String title, RequestParam(content) String content, RequestParam(classId) Long classId, RequestParam(value subject, required false) String subject, RequestParam(value file, required false) MultipartFile file, RequestAttribute(userId) Long teacherId) { if (title null || title.length() 100) { return Result.error(标题长度不符合要求); } Homework homework new Homework(); homework.setTitle(title); homework.setContent(content); homework.setClassId(classId); homework.setCreateBy(teacherId); // 默认给当前时间加3天作为截止时间 homework.setDeadline(LocalDateTime.now().plusDays(3)); homework.setStatus(0); if (file ! null !file.isEmpty()) { String filePath FileStorageUtil.save(file); homework.setFileUrl(filePath); } homeworkService.create(homework); return Result.success(homework); }参数是最容易在答辩时被追问的部分classId决定了这份作业推送给哪些学生实现上其实只需要在查询学生列表时按class_id过滤deadline用LocalDateTime而不是Date可以避免 Java 8 日期类型与 MySQL 时区不一致的问题文件存储目录最好配置成可路径拼接的绝对地址比如/data/upload/homework/这样前端用 nginx 映射目录就能直接访问不需要再写下载接口。这里的RequestAttribute(userId)就是从拦截器里取出来的登录凭证避免前端每次请求都传用户 ID 造成越权风险。3.3 学生提交与教师批改状态与事务的双重保护提交作业接口需要做两层判断一是homework当前状态必须是进行中二是该学生是否已经提交过。PostMapping(/api/homework/submit) public Result submit(RequestParam(homeworkId) Long homeworkId, RequestParam(value textContent, required false) String textContent, RequestParam(value file, required false) MultipartFile file, RequestAttribute(userId) Long studentId, RequestAttribute(role) String role) { if (!1.equals(role)) { return Result.error(仅学生可提交作业); } Homework homework homeworkMapper.selectById(homeworkId); if (homework null || homework.getStatus() ! 1) { return Result.error(作业不在进行中无法提交); } if (homework.getDeadline().isBefore(LocalDateTime.now())) { return Result.error(已超过截止时间); } HomeworkSubmission submission new HomeworkSubmission(); submission.setHomeworkId(homeworkId); submission.setStudentId(studentId); submission.setTextContent(textContent); submission.setSubmitTime(LocalDateTime.now()); submission.setStatus(1); if (file ! null !file.isEmpty()) { submission.setFileUrl(FileStorageUtil.save(file)); } try { homeworkSubmissionMapper.insert(submission); } catch (DuplicateKeyException e) { return Result.error(你已经提交过这份作业); } return Result.success(submission); }这里的坑在数据库约束与业务代码的配合uk_homework_student唯一索引会拦截同一学生的重复插入代码里捕获DuplicateKeyException后转为友好提示这比先查再插更安全因为并发情况下两个请求可能同时通过查询。教师在批改时则要校验 submission 状态是否为已提交将score、comment_text和status 2一并更新同时把parent_feedback字段留给家长填写疑问。3.4 家长查看反馈一对多关联里的边界问题家长登录后看到的是孩子的作业列表和教师评语。实现上会暴露出一个典型的“越权”隐患前端传一个学生 ID后端直接按这个 ID 查数据攻击者改成别的学生 ID 就能看到他人信息。正确的做法是从家长账号关联的学生 ID 出发不允许前端指定。GetMapping(/api/homework/parent-list) public Result parentHomeworkList(RequestAttribute(userId) Long parentId) { Long studentId userMapper.selectBindStudentId(parentId); if (studentId null) { return Result.error(当前账号未绑定学生); } ListHomeworkSubmissionVO list homeworkMapper.selectSubmissionsByStudent(studentId); return Result.success(list); }selectSubmissionsByStudent里要 JOIN 出homework.title、homework.subject、homework.deadline并按submit_time倒序排列这样家长端首页能直接看到最近最先批改的作业。这个接口的 SQL 值得在答辩时展开讲它体现的不是单表查询而是多对一关系映射的功底。4. 前端实现Vue 项目里把作业流转做成可演示的页面4.1 路由与角色菜单用动态路由把三种端串起来Vue 前端最容易被代码小白写崩的就是路由和状态管理。拿到的 zip 里通常已经配置了vue-router和vuex或pinia我建议把角色控制放在路由守卫里而不是每个组件里// router/index.js const routes [ { path: /login, component: Login }, { path: /, component: Layout, redirect: /dashboard, meta: { requiresAuth: true }, children: [] } ] router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.path /login) { next() return } if (!token) { next(/login) return } // 根据角色加载路由 if (!router.hasRoute(teacherHomework)) { const extraRoutes buildRoutesByRole(role) extraRoutes.forEach(r router.addRoute(r)) } next() })这里有个 Vue 路由的高频考点addRoute添加的路由在刷新页面后会丢失所以守卫里用hasRoute判断是否已注册未注册则重新添加。动态路由带来了一个副作用直接访问/teacher/homework/edit这类深层地址时刷新 404 的概率会变大后文会讲 nginx 侧如何补救。4.2 axios 封装统一加 token、统一处理 401前端所有接口请求基本都要带 Token错误提示也不该每个页面写一遍。做到 axios 封装里是最常见的做法// request.js import axios from axios import { ElMessage } from element-plus import router from ./router const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器把token塞进header service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器401统一跳登录 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response?.status 401) { localStorage.clear() router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) }) export default service调用时用service.get(...)或service.post(...)业务代码里不需要再关心 Token 和错误弹窗。401 跳转要放在响应拦截器里而不是请求拦截器因为未登录的返回码只有服务端响应后才能拿到。这套封装面试经常被追问为什么用拦截器而不是每个页面 try-catch答案是统一收口能让业务代码干净很多。4.3 作业提交流表单表单校验 文件上传学生端提交页需要同时支持文本内容和附件用 Element Plus 的el-form和el-upload组合实现。上传组件有个容易踩的坑action属性是上传组件的内部请求地址但它绕过了封装好的 axios 实例所以不能直接写/api/homework/submit而是要在headers里手动带 Token否则后端会让未登录请求直接 401 退出。template el-form refsubmitForm :modelform :rulesrules label-width80px el-form-item label文字说明 proptextContent el-input typetextarea v-modelform.textContent rows4 / /el-form-item el-form-item label作业文件 el-upload :actionuploadUrl :headersuploadHeaders :limit1 :on-successhandleUploadSuccess :before-uploadbeforeUpload el-button typeprimary上传附件/el-button template #tip span支持图片和PDF不超过10MB/span /template /el-upload /el-form-item el-form-item el-button typesuccess clicksubmit提交作业/el-button /el-form-item /el-form /template script setup import { ref, reactive } from vue import { ElMessage } from element-plus import request from ../request const uploadUrl /api/homework/submit const uploadHeaders { Authorization: Bearer ${localStorage.getItem(token)} } function beforeUpload(file) { if (file.size 10 * 1024 * 1024) { ElMessage.error(附件大小不能超过10MB) return false } return true } function handleUploadSuccess(res) { if (res.code 200) { ElMessage.success(作业提交成功) } else { ElMessage.error(res.message) } } /scriptel-upload会在选择文件后立刻发起上传请求它不会等待表单里其它字段填写完成。这里真正严谨的流程应该是先不把上传挂载在提交表单上而是异步上传后把返回的文件地址临时存起来等用户点击“提交作业”时再连同文字内容一起 POST 到业务接口。把action直接指向提交接口属于演示用简化方案答辩时如果有人追问你要能说出第二种做法的差异。4.4 家长端与统计页让数据从“能看”变成“有用”家长端不需要提交操作页面可以做得比教师端更轻量。展示的核心数据是最近一周的作业提交列表每一行显示学科、作业标题、教师评语、提交时间和得分再加一个简单的状态徽标。el-table :datalist stripe el-table-column propsubject label科目 width80 / el-table-column proptitle label作业内容 / el-table-column propscore label得分 width70 template #default{ row } {{ row.status 2 ? row.score : 未批改 }} /template /el-table-column el-table-column propcommentText label教师评语 / /el-table教师端可以额外加一个纯前端计算的统计卡片本周作业发布数、已批改数、平均得分。计算不用后端接口直接对列表做reduce量级只有几十条数据不需要额外请求这是面试官能接受的最佳取舍。5. 部署与排错把 zip 里的代码跑成能演示的系统5.1 数据库初始化与后端启动顺序拿到源码包后先不要急着mvn spring-boot:run我一般按下面这个顺序操作能避开八成环境问题# 1. 创建数据库并指定字符集 mysql -uroot -p -e CREATE DATABASE homework_db DEFAULT CHARACTER SET utf8mb4; # 2. 导入sql文件 mysql -uroot -p homework_db homework_db.sql # 3. 在application.yml中核对数据库账号密码和端口 # 4. 启动后端 mvn spring-boot:run启动后先用接口验证而不是先打开前端页面。验证登录接口是最快的自检方式curl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:teacher01,password:123456}能拿到token字段就说明数据库连接、MyBatis 映射、账号数据这三层都通了。常见失败场景有两个一是Public Key Retrieval is not allowed在 jdbc url 上加allowPublicKeyRetrievaltrueuseSSLfalse二是时区报错加serverTimezoneAsia/Shanghai。5.2 前端构建与 nginx 部署的历史路由兜底后端跑通后前端先用开发模式联调最后再走打包部署npm install npm run dev # 本地联调一般监听 5173 或 8081 npm run build # 生产构建输出到 dist 目录Vue Router 如果开了history模式直接双击打开index.html会白屏必须配 nginx。同时热词里经常搜到vue打包后布局异常多半是静态资源路径写成了绝对路径/assets/而不是相对路径或者 nginx root 配置错了层级。下面是能直接抄的配置server { listen 80; server_name localhost; root /opt/homework/dist; index index.html; # 前端路由history模式的兜底 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 作业附件的访问映射 location /upload/ { alias /data/upload/homework/; } }try_files $uri $uri/ /index.html;解决的是地址栏直接输入/teacher/homework后刷新 404 的问题这也是“打包后布局异常”的常见元凶之一。/upload/的 alias 要和后端FileStorageUtil.save返回的路径前缀对应上否则教师端能看到附件名但点开是 404。5.3 避坑清单三个高频问题五分钟内定位第一个坑是springboot版本太高导致的javax缺失。检查pom.xml里parent版本号是否是 2.7.x如果是 3.x要么升级 JDK 到 17要么把 spring-boot-starter-parent 降回 2.7.x后者更省事。第二个坑是 MyBatis 的 XML 文件没有被编译到target/classes。成功启动但访问接口报Invalid bound statement (not found)在pom.xml的build里加一段配置resources resource directorysrc/main/resources/directory includes include**/*.xml/include include**/*.yml/include /includes /resource /resources第三个坑是数据库时间字段与 Java 类型不匹配。LocalDateTime映射DATETIME没有问题但如果表中用了TIMESTAMP在 MySQL 8.0 下要确认 jdbc url 带上了serverTimezoneAsia/Shanghai否则 deadline 的判断会差 8 个小时。验收步骤建议做成一条线教师发布新作业 - 家长端口能看到该作业处于待提交状态 - 学生提交后教师端待批改列表出现记录 - 教师批改打分 - 家长端刷新后看到得分和评语。这条链路完整走通数据库初始化、后端接口、前端页面和文件服务就全部验证到位了。本文还有配套的精品资源点击获取
返回列表