简介:基于 Spring Boot、MyBatis、MySQL 与 Vue 技术栈开发的学生成绩管理系统完整源码,定位为高分毕业设计项目,适合高校计算机相关专业的学生用于毕业设计、课程设计或期末大作业。系统采用前后端分离架构,围绕成绩管理核心业务设计管理员、班主任、教师三种角色:管理员统一维护用户、班主任、教师、学生及基础信息,并管理成绩数据;班主任可进行学生管理和成绩查看;教师则支持个人信息维护与成绩查询,业务划分清晰,符合学校教务场景。
资源包为 RAR 压缩格式,共包含 215 个文件,大小约 3.05MB。文件主要由 72 个 Java 源码、72 个 XML 映射与配置、27 个 Vue 页面组成,覆盖后端逻辑、持久层映射和前端界面;另有 JS、CSS、数据库脚本、工程说明及启动辅助文件,方便初始化数据库与启动项目。目前已有 84 人学习下载该资源,项目代码规范、功能完整,既可直接作为毕业设计成品使用,也可作为学习 Spring Boot 与 Vue 前后端分离开发、理解权限管理实现思路的实战参考。
1. 为什么说这是最适合拿来改的毕设项目
每年毕业季,成绩管理系统都是 Java Web 方向选题里出现频率最高的题目之一,但能真正跑通、能演示、能通过导师验收的其实不多。多数网上流传的版本要么数据库脚本缺失,要么前端接口对不上,要么用了阿里大于等已停用的短信接口。而这个基于 Spring Boot + MyBatis + MySQL + Vue 的中学生成绩管理系统,属于那种“理论模型完整、代码结构简洁、运行环境主流”的典型示例:后端用三层架构把用户、班主任、教师三类角色的权限边界划清楚,前端用 Vue 管理成绩展示和数据维护,整条链路从数据库建表到页面渲染都能自洽。
它解决的痛点很明确:学校场景下成绩数据的录入、查看、统计权限是分级别的,任课老师只能看自己任教科目的成绩,班主任能看全班成绩,管理员则负责维护所有账号和基础数据。适合的人群也分两类——如果你的毕业设计选题还没敲定,这个仓库可以直接作为蓝本二次开发;如果你已经在写类似系统,它的角色权限划分和 Maven 目录组织方式也值得对照参考。
2. Spring Boot + MyBatis 的工程搭建与数据层设计
2.1 为什么是这三个后端组件而不是别的
成绩管理系统属于典型的 CRUD 加权限控制应用,业务逻辑并不复杂,难点在于数据关系的梳理。Spring Boot 负责把 MVC 的骨架搭起来,自动配置机制能在不写 web.xml 的前提下完成 Servlet 容器、事务管理器、数据源的装配。MyBatis 轻量,支持手写 SQL,对于成绩查询这种需要多表联查的场景,比 JPA 更容易控制查询性能。MySQL 则是所有表结构和无乱码环境验证过的稳定选择。
具体到工程结构,Maven 依赖在 pom.xml 里只需关注几个核心坐标:spring-boot-starter-web提供 REST 接口能力,mybatis-spring-boot-starter做 MyBatis 与 Spring 的整合,mysql-connector-java负责数据库驱动,再配合 Lombok 减少实体类的 Getter/Setter 样板代码。开发环境和运行时环境对应关系如下:
| 组件 | 开发环境 | 运行时要求 |
|---|---|---|
| JDK | 1.8 或 11 | 与打包时一致 |
| Maven | 3.6+ | 构建机安装 |
| MySQL | 8.0 或 5.7 | 服务器安装 |
| Node.js | 14+(前端构建) | 构建时使用 |
2.2 数据库表结构如何支撑三种角色
核心表设计逻辑是“用户表 + 基础信息表 + 成绩表”三条线。用户表(sys_user)保存账号密码和角色类型,角色用整数区分,0 是管理员,1 是教师,2 是班主任。学生表和教师表都通过 user_id 外键关联到用户表,这样登录后获取的是同一个身份数据的完整维度,不用来回 join 多张表查用户昵称。
成绩表(score)是查询频率最高的表,结构上把 student_id 和 course_id 做联合业务主键,另外保留 exam_id 字段用于区分期中、期末、月考等不同考试。MyBatis 的 Mapper 接口里对应这段查询逻辑:
@Mapper public interface ScoreMapper { List<ScoreVO> selectScorePage(ScoreQuery query); int insertScore(Score score); int updateScore(Score score); int deleteScore(Integer id); }XML 映射文件里实现分页与条件查询时,需要注意 if 标签的拼接顺序。因为成绩查询往往伴随后端返回的班级、学号、考试类型等条件,条件为空时不能拼入 SQL,否则会出现 WHERE 后直接跟 AND 的语法错误:
<select id="selectScorePage" resultType="com.demo.vo.ScoreVO"> select s.id, st.stu_no, sc.student_id, c.course_name, sc.score, sc.exam_type from score sc left join student st on sc.student_id = st.id left join course c on sc.course_id = c.id <where> <if test="stuNo != null and stuNo != ''"> and st.stu_no = #{stuNo} </if> <if test="courseId != null"> and sc.course_id = #{courseId} </if> <if test="examType != null and examType != ''"> and sc.exam_type = #{examType} </if> </where> order by sc.id desc </select>这里用<where>标签替代手写where 1=1,MyBatis 会自动处理第一个条件前多余的 and 或 or。#{stuNo}是预编译参数占位符,可以防止 SQL 注入,和${}的直接拼接有本质区别。
2.3 yml 配置里最容易被忽略的两个参数
application.yml 是 Spring Boot 读取数据源和 MyBatis 配置的入口。数据源需要配置驱动的时区参数,尤其是 MySQL 8.x 版本:
spring: datasource: url: jdbc:mysql://localhost:3306/grade_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.demo.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case设为 true 后,数据库字段的stu_no才能自动映射到实体类属性stuNo,否则你需要在结果映射里手动写 property 和 column 的对应关系。mapper-locations的值要和你实际放置 XML 的目录严格一致,这是很多新手启动时报Invalid bound statement (not found)的根源。
3. 后端核心业务之登录认证与角色权限控制
3.1 登录接口的完整处理链路
系统登录不依赖 Spring Security,而是走传统的拦截器方式,理解起来更直观,也更适合毕设答辩时向导师解释。用户提交账号密码后,后端在 UserService 里先根据 username 查询用户,用 BCrypt 或 MD5 比对密码,密码正确则把用户对象放进 HttpSession。这里有一个值得借鉴的细节:登录成功后不直接返回成功标记,而是把用户角色和跳转路径一并返回,前端根据 role 值决定进入哪个首页。
Controller 层的写法应该保持薄,业务逻辑集中在 Service:
@RestController @RequestMapping("/api/user") public class UserController { @Resource private UserService userService; @PostMapping("/login") public Result login(@RequestBody User user, HttpSession session) { User loginUser = userService.login(user.getUsername(), user.getPassword()); if (loginUser == null) { return Result.error("账号或密码错误"); } session.setAttribute("loginUser", loginUser); return Result.success(loginUser); } }登录判断的逻辑在 Service 实现中处理,通常还叠加用户状态校验,比如被禁用账号不能登录。返回给前端的是 Result 包装对象,结构包含 code、message、data 三个字段,code 为 200 代表正常,为 500 时前端直接弹出 message 内容。
3.2 拦截器实现未登录跳转和角色鉴权
系统有三个角色,访问权限可以按接口路径来约束。管理员接口统一以/api/admin/**开头,学生和教师相关的查询接口单独分配。一个自定义 HandlerInterceptor 就能同时承担登录与权限两层职责:
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}"); return false; } String uri = request.getRequestURI(); if (uri.startsWith("/api/admin/") && user.getRole() != 0) { response.setStatus(403); return false; } return true; } }最后在实现WebMvcConfigurer的配置类里注册拦截器,并配置排除路径,放行登录接口、静态资源和跨域预检请求:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/error"); }注意拦截器拦截到 401 时不能 return true,否则请求会继续走 Controller 层,出现“明明未登录却返回了空数据”的假象。这是排查权限漏洞时的第一个检查点。
3.3 成绩新增时的事务边界
成绩录入是整个系统写操作最频繁的功能,涉及两张表:成绩表插入一行记录,同时更新统计表中的总分与平均分。如果这些操作不在同一个事务里,出现异常时数据就会不一致。这里用@Transactional声明事务边界:
@Transactional(rollbackFor = Exception.class) public void addScore(Score score) { scoreMapper.insertScore(score); StudentStatistic stat = statisticMapper.selectByStudentId(score.getStudentId()); if (stat != null) { stat.setTotalScore(stat.getTotalScore() + score.getScore()); stat.setAvgScore(stat.getTotalScore() / stat.getCourseCount()); statisticMapper.updateStat(stat); } }这里有一条容易被忽略的规则:@Transactional默认只在运行时异常时回滚,如果方法内部捕获了异常并吞掉,事务不会回滚。在 Service 层直接让异常向上抛,由全局异常处理器统一返回错误信息,是更可控的做法。
4. Vue 前端的页面组织与接口联调
4.1 Vue CLI 项目里如何规划视图目录
前端是 Vue 2 或 Vue 3 都可运行的标准工程,页面按角色划分视图目录。src/views 下分别是 admin、teacher、clazz 三个文件夹,admin 里放用户管理、基础信息管理、成绩管理页面,teacher 里放个人信息和成绩查看,clazz 里放学生管理和成绩查看。路由模式采用 history 还是 hash 会影响部署配置,后端部署时也只需把前端打包的 dist 目录放进 Spring Boot 的 static 目录即可。
4.2 Axios 请求封装与响应拦截
跨域问题在开发环境靠 devServer 的 proxy 配置解决,代理地址指向 8080 端口。生产环境下前后端同源部署,不存在跨域烦恼。Axios 封装时需要在拦截器里统一附加 Session 凭证,前提是withCredentials设为 true:
import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000, withCredentials: true }) request.interceptors.response.use( response => { const res = response.data if (res.code === 401) { window.location.href = '/login' return Promise.reject(new Error(res.message)) } if (res.code !== 200) { this.$message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { return Promise.reject(error) } )把withCredentials配上很关键,因为后端拦截器从 Session 中取登录用户,如果前端不携带 Cookie,每次请求都视为未登录。同时后端需要配置CorsConfiguration,允许的来源要明确指定前端地址,不能直接使用*通配符,否则前端请求会触发 CORS 错误。
成绩页面的表格渲染同样走生命周期函数:
mounted() { this.loadScoreList() }, methods: { async loadScoreList() { const res = await request.get('/score/page', { params: { pageNum: this.pageNum, pageSize: this.pageSize, stuNo: this.stuNo } }) this.tableData = res.data.records this.total = res.data.total } }接口路径要和后端 Mapper 里的方法能力对应上。页面里传送的pageNum和pageSize字段,后端 PageHelper 插件需要与配置的数据库方言配套使用,MySQL 会生成 LIMIT 语句完成物理分页。
4.3 角色路由与菜单渲染的实现方式
不同角色登录后能看到的菜单项不同,常见的实现方式有两种:一是在路由配置里写meta: { role: 0 }然后用路由守卫做跳转拦截,二是后端登录接口返回权限列表,前端动态生成菜单组件。这个系统适合后者,因为管理员需要管理用户,教师菜单只有三项,后端返回的菜单数据直接驱动侧边栏的 v-for 循环,当用户尝试直接访问未授权路由时,再结合后端的角色拦截做双保险。
5. 从部署前检查到绕过常见坑的五条经验
5.1 检查数据库脚本与字符集
项目下载后首先要执行 SQL 脚本。注意数据库脚本里的建表语句要检查表名是否和 Mapper XML 里的 SQL 完全一致。MySQL 8.0 环境下,若控制台查询中文显示乱码,需要在数据源 URL 里确认characterEncoding=utf8,同时数据库连接时都使用 utf8mb4 字符集。
5.2 Maven 打包时的资源过滤坑
Spring Boot 项目打包时,如果 mapper 目录下的 XML 文件没有被打进 jar 包,运行 jar 就会报找不到 SQL。需要在 pom.xml 的 build 节点显式声明资源位置:
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build>如果开发时使用了 Maven 运行插件(spring-boot:run方式启动),资源缺失问题往往不会立刻暴露,等打包成 jar 部署时才出现。提前检查 pom 的这段配置能省去很多排查时间。
5.3 前端 npm install 失败时的降级方案
前端依赖安装阶段最容易出现问题的是 node-sass 与 Node.js 版本不兼容。如果项目中引入了相关样式依赖,看到Node Sass could not find a binding报错时,建议删除 node_modules 目录后重新执行安装,并固定 Node 版本。Vue CLI 项目本身要求 Node 版本在 8.9 以上,推荐使用 14 或 16 版本,避免高版本 Node 带来的 OpenSSL 兼容警告。若安装持续失败,可以切换 npm 镜像源后重试,不要反复强试默认源。
5.4 验证成绩查询接口的分页字段顺序
启动后端后先别急着打开前端,直接使用接口测试工具验证登录与查询接口是否正常。测试成绩查询接口时,注意 PageHelper 的页码参数是从 1 开始,如果前端传的是 0,第一页数据会查不出来。PageHelper 还会自动拦截下一条 SQL,如果查询方法里自己拼接了 LIMIT 语句,就会出现诡异的分页错乱,只能选择信任 PageHelper 的分页机制或放弃它。
5.5 演示前的最后一遍数据准备
答辩演示时,提前往系统里录入至少两名学生、三门课程和一次考试成绩。很多演示翻车是因为成绩列表空白,让导师觉得功能没有实现。另外把管理员的密码复杂度调低,演示时方便现场输入。系统的启动顺序建议先启动 MySQL 服务,再运行 Spring Boot,最后再启动前端,这个顺序符合依赖关系,也能让答辩流程流畅进行。
本文还有配套的精品资源,点击获取