SpringBoot+Vue成绩管理系统毕设实战:从数据库设计到接口文档全解析
2026/9/9 6:55:34 网站建设 项目流程

1. 这个毕设项目到底在做什么

如果你正在找Java Web方向的毕业设计题目,或者已经在做“成绩管理系统”这类项目,那我猜你多半是为了一个目标:用最短的时间、最稳的思路,把一个能演示、能答辩、还能扩展的完整系统做出来。我的建议很直接——选一个前后端分离的SpringBoot+Vue成绩管理系统,SQL脚本一次性建好库表,接口文档用Knife4j生成,开发期把大部分精力放在业务逻辑和演示效果上,而不是折腾环境。

我见过太多同学把毕设做成“踩坑日记”了:后端接口写好了,前端调不通;前端页面做完了,数据表一对多关系理不清;答辩前一天发现数据库密码写死在配置文件里,评委一动就跑不起来。其实这些问题,在设计阶段和数据结构设计阶段就能规避掉一大半。

这套“成绩管理系统”适合谁?说实话,不只是计算机专业的学生。现在很多信息管理类、教育技术类的毕设也要求“能跑起来的系统”。你只需要具备最基础的Java和Vue语法能力,跟着正确的架构走,不用自己发明轮子,就能交付一个技术栈主流、业务完整、演示效果好的作品。它本质上解决的是学生信息、课程信息、成绩录入与查询、统计分析、用户权限这几件事,麻雀虽小,五脏俱全。

2. 技术选型:为什么是SpringBoot+Vue

2.1 后端:SpringBoot 2.7 + MyBatis-Plus

后端选型上,SpringBoot基本没有争议。原因很朴素:它帮你把SSM时代那些繁琐的配置全部自动化了,你写一个Controller、一个Service、一个Mapper,就能跑通一条完整业务流程。对于毕设这种中小型系统,SpringBoot的默认配置完全够用,不需要去碰Spring Cloud那套分布式的东西。

ORM框架我强烈建议用MyBatis-Plus,而不是纯MyBatis。纯MyBatis你写个单表查询都要手写SQL,而Plus提供Wrapper条件构造器,像selectPage这样的分页查询一行搞定。对毕设来说,代码少、出错少、答辩时还能说“使用了MyBatis-Plus提升开发效率”,收益非常明确。

有人问那JPA行不行?也行。但国内企业实际项目中MyBatis系占多数,而且用Plus写复杂多表查询的SQL时,你自己可控性更强。既然毕设是为就业做预演,技术栈贴近行业主流没什么坏处。

版本搭配上,我建议SpringBoot 2.7.x + JDK 1.8或JDK 11。这是目前最稳的组合,遇到问题和网上资料匹配度高。SpringBoot 3.x虽然新,但部分老教程和依赖的兼容性会让你卡在配置阶段,毕设阶段没必要冒这个风险。

2.2 前端:Vue 2 + Element UI

前端框架的选择,Vue比React更适合毕设场景。原因不是谁好谁差,而是Vue的上手曲线更平滑,而且Element UI组件库对后管类系统来说是个效率神器。你不需要自己写表格排序、弹窗表单、分页组件,直接调用el-tableel-dialogel-pagination,简单配置就有完整交互。

关于Vue 2还是Vue 3,我看到很多同学纠结。我的建议是:如果已经有Vue 3基础,用Vue 3+Element Plus没问题;如果之前一直学的是Vue 2,那没必要在毕设阶段强行升级。因为成绩管理系统本质是增删改查,两个版本都能实现,关键是你熟不熟。答辩时被问到“为什么用Vue 2而不用Vue 3”,你完全可以回答“Vue 2生态成熟,Element UI组件稳定,可以集中精力在业务实现上”,这个回答挑不出毛病。

2.3 数据库:MySQL 5.7/8.0 + SQL脚本一次性初始化

MySQL是绝对主流。毕设项目里没人会用Oracle或者PostgreSQL给自己增加工作量。选好版本后,最核心的一件事就是写好SQL脚本:建库、建表、插入初始数据全部包含在一个init.sql里。这样无论换到哪台电脑,你只要执行一次脚本,整个系统的数据环境就恢复了。

我习惯把SQL脚本分成三个文件:

db/init.sql # 建库建表 + 管理员账号初始化 db/data.sql # 测试数据:学生、课程、成绩等 db/clear.sql # 清理数据(保留表结构)

init.sql解决“从零开始”的问题,data.sql解决“演示有数据”的问题,clear.sql解决“重复测试”的问题。分开维护,比一个大文件实用得多。

2.4 接口文档:Knife4j增强Swagger

接口文档这块,很多同学一开始不在意,等做到前后端联调时才后悔——写完的接口叫什么、参数是什么、返回什么结构,全靠记忆,前端同学问一句你翻一次代码,效率极低。更关键的是,毕业设计文档里需要接口设计章节,手写20个接口累死人,用Knife4j自动生成文档然后截图,一目了然。

Knife4j是Swagger的增强UI,界面美观,支持调试。只需要在后端加一个依赖和几行配置,Controller上写好注解,访问一个URL就能看到所有接口文档,并且可以直接在线调用测试。

我这里给你一个可以直接抄的依赖配置片段:

<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>

配置类里开启:

@Configuration @EnableSwagger2WebMvc public class Knife4jConfig { @Bean public Docket adminApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .title("成绩管理系统接口文档") .version("1.0") .build()) .select() .apis(RequestHandlerSelectors.basePackage("com.graduation.controller")) .build(); } }

访问地址是http://localhost:8080/doc.html。注意是doc.html,不是Swagger原版的swagger-ui.html,这个坑我见过好几个人踩过。

3. 系统设计与数据库表结构

3.1 角色权限怎么设计

成绩管理系统最核心的业务不是“录入成绩”这个动作,而是谁能录入、谁能修改、谁能查看。所以权限模型决定了系统的基本骨架。

我采用的是最常见的三角色模型

  • 管理员(admin):管理教师账号、课程信息、系统基础配置
  • 教师(teacher):录入成绩、修改成绩、导出成绩、查看所授课程
  • 学生(student):查看自己的成绩、查看课程列表

对应到后端,就是Role字段加简单的拦截器校验。不需要引入Spring Security或者Shiro这种安全框架,因为毕设的核心是业务功能展示,简单用一个AuthInterceptor做登录态校验就足够了。当然如果你想在答辩时多讲点东西,可以在文档里写“预留了Spring Security扩展点”,这比实际引入要划算得多。

角色权限在数据库里的体现就是用户表的role字段:

CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT '登录密码(MD5加密存储)', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `role` varchar(20) NOT NULL COMMENT '角色: admin/teacher/student', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

这里有个非常容易忽略的细节:密码不能明文存储。毕设系统面试官或评委一定会看这一点。你可以用MD5加盐,或者用Spring自带的BCryptPasswordEncoder,代码量很少但专业度提升明显。

3.2 核心表关系拆解

一个成绩管理系统的数据模型,核心就三张业务表:学生表、课程表、成绩表。其余教师表、班级表都可以合并到用户表或扩展字段中。

学生表设计要点:

CREATE TABLE `student` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `student_no` varchar(20) NOT NULL COMMENT '学号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint(1) DEFAULT NULL COMMENT '性别: 0女 1男', `class_name` varchar(50) DEFAULT NULL COMMENT '班级', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `user_id` bigint(20) DEFAULT NULL COMMENT '关联sys_user.id', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_no` (`student_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表';

注意user_id这个字段,它的作用是把学生信息和登录账号关联起来。这样学生登录后,通过user_id就能查到对应的student_no,再查成绩就是一条SQL的事。

成绩表设计要点:

CREATE TABLE `score` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `student_id` bigint(20) NOT NULL COMMENT '学生ID', `course_id` bigint(20) NOT NULL COMMENT '课程ID', `score` decimal(5,2) DEFAULT NULL COMMENT '成绩', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_student_course` (`student_id`,`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成绩表';

看到了吗,这里有个关键点——uk_student_course唯一索引。它保证了同一个学生同一次课程成绩只能录入一次,第二次录入时要么更新要么报错,从数据库层面杜绝重复数据。这个细节在答辩时可以说是“防止成绩重复录入的兜底方案”,技术含量立刻不一样。

3.3 外键到底建不建

关于外键,我给你的建议是:物理外键不要建,逻辑外键必须有。也就是说,表结构里不写FOREIGN KEY约束,但在业务代码和逻辑层面维护引用关系。理由很简单:强制外键在删除数据时会产生一堆连锁限制,比如你要删一个教师账号,结果他名下还有课程和成绩记录,数据库直接拒绝执行,调试起来很痛苦。但如果没有外键约束,你就要在删除代码里手动判断“是否有关联数据”,防止产生孤儿记录。毕设阶段,用逻辑外键就够了。

这种设计也符合企业里“你删数据前自己负责检查”的开发习惯,别把脏数据清理的责任都推给数据库引擎。

4. 后端核心业务实现

4.1 登录接口与Token校验

登录流程是每个系统的门面,这块必须实现得干净利落。我用的是**JWT(JSON Web Token)**方案,不用Session,主要是为了配合Vue前后端分离的架构,后端不维护会话状态,前端拿到Token后放在请求头里传递。

核心代码不复杂,我建议你在pom.xml里加:

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>

登录接口的逻辑顺序是:

  1. 接收前端传参(username, password)
  2. 按用户名查询用户
  3. 对密码进行加密校验(BCrypt或MD5)
  4. 校验通过后生成Token,把用户ID、角色等信息放入Token的Claims中
  5. 返回Token给前端
@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 查询用户 SysUser user = sysUserService.getByUsername(loginDTO.getUsername()); if (user == null) { return Result.error("用户不存在"); } // 2. 密码校验 if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) { return Result.error("密码错误"); } // 3. 生成Token String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); // 4. 返回登录信息 return Result.success(new LoginResponse(token, user.getRealName(), user.getRole())); }

登录成功后,后端提供一个AuthInterceptor拦截除了放行接口以外的所有请求,核心逻辑就是从请求头的Authorization字段中解析出Token,校验通过就把用户信息放入ThreadLocal里,方便Controller层直接取用。这套模式是全网主流做法,也是面试高频题,做完这个项目,你可以顺带把“JWT认证流程”这个知识点彻底搞懂。

4.2 成绩管理的增删改查

成绩模块是整个系统的核心,但本质就是对score表的CRUD。这里真正的技术点在于联合查询:成绩列表需要展示学生姓名、班级、课程名称,而这三条信息分别在student表和course表里。

用MyBatis-Plus,我推荐直接在Mapper层写自定义SQL配合@Select注解:

@Mapper public interface ScoreMapper extends BaseMapper<Score> { @Select("SELECT s.id, st.student_no, st.name AS student_name, " + "st.class_name, c.course_name, sc.score, sc.remark " + "FROM score sc " + "LEFT JOIN student st ON sc.student_id = st.id " + "LEFT JOIN course c ON sc.course_id = c.id " + "WHERE sc.student_id = #{studentId} " + "ORDER BY sc.create_time DESC") List<ScoreVO> selectScoresByStudent(Long studentId); }

JOIN查询写清楚之后,分页可以配合PageHelper或者MyBatis-Plus的分页插件实现。我给个建议:业务代码中统一使用Page<T>对象作为返回类型,前端要的totalrecords结构一次就位,不要自己在业务层拼Map。

4.3 文件上传和导出

毕设系统里“成绩导出Excel”是一个锦上添花的加分项。用EasyExcel(阿里的开源库)实现,代码非常简洁,且学起来很快。

@GetMapping("/export") public void exportScores(HttpServletResponse response) throws IOException { List<ScoreVO> list = scoreService.getAllScoreVOs(); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("成绩表", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), ScoreVO.class).sheet("成绩表").doWrite(list); }

稍微注意下文件名编码问题,直接用中文文件名在部分浏览器上会乱码,URL编码后反而更稳定。这个功能做完,答辩时演示“一键导出Excel”,评委观感会好很多。

4.4 统一返回结果与全局异常处理

这是后端项目一定要做好的两件小事,但很多小伙伴会忽略。统一返回结果的意思是所有接口都返回固定格式JSON:

{ "code": 200, "message": "操作成功", "data": {} }

用泛型类实现:

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

全局异常处理器用@RestControllerAdvice捕获业务异常和未知异常,统一转成Result结构返回。这样前端处理请求时只需要判断code === 200就行,不用每个接口单独解析异常格式,联调效率能提升一半。

5. 前端Vue实现与联调

5.1 前端项目结构规划

Vue项目创建我用Vue CLI(或Vite,看你习惯)。建议目录结构如下:

frontend/ ├── public/ ├── src/ │ ├── api/ # 接口请求封装 │ │ ├── request.js # axios实例封装 │ │ ├── auth.js # 登录认证相关接口 │ │ ├── score.js # 成绩模块接口 │ │ └── course.js # 课程模块接口 │ ├── router/ # vue-router路由配置 │ ├── store/ # Vuex状态管理(或Pinia,Vue2就用Vuex) │ ├── views/ # 页面组件 │ │ ├── login/ │ │ ├── layout/ │ │ ├── admin/ │ │ ├── teacher/ │ │ └── student/ │ ├── components/ # 公共组件 │ └── utils/ # 工具函数

模块化是前端工程质量的分水岭。很多同学的毕设前端喜欢把所有请求直接写在.vue文件里,页面一多就全是重复代码,改一个公共URL要动十几个文件。用API层把请求集中管理,是保证“能快速定位问题”的关键。

5.2 axios封装与请求拦截器

axios封装的核心是解决重复代码统一错误处理两件事。我习惯抽一个request.js,配置好baseURL和请求拦截器。

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动注入Token service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => Promise.reject(error) ) // 响应拦截器:统一处理错误 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { Message.error('登录状态已过期,请重新登录') localStorage.removeItem('token') router.push('/login') } else { Message.error(error.message || '网络异常') } return Promise.reject(error) } ) export default service

这套封装思路在你的毕设代码里是加分项,同时也是面试常问的“前端如何统一处理接口错误”的标准答案。

5.3 路由与权限控制

前端路由控制的核心思路是:不同角色只看到自己能访问的页面。后端已经做了接口权限控制,前端的菜单和路由隐藏更多是为了交互体验。

Vue Router的beforeEach导航守卫可以实现这个逻辑:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() return } if (!token) { next('/login') return } // 如果已经登录,根据角色判断是否有权限 const role = localStorage.getItem('role') if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403') return } next() })

动态路由的方案需要和后端配合,毕设阶段用静态路由+meta.roles控制就足够了。你可以在路由表里给每个页面配置meta字段:

{ path: '/teacher/score', name: 'ScoreManage', component: () => import('@/views/teacher/ScoreManage.vue'), meta: { roles: ['admin', 'teacher'], title: '成绩管理' } }

5.4 成绩管理页面怎么设计

成绩管理页面是系统的主营业面,建议用el-card包裹查询条件区域和表格数据区域,查询条件包括:课程下拉框、班级输入框、学生姓名输入框。数据表格用el-table展示学生信息和成绩,行内操作放“编辑”“删除”按钮,底部放分页控件。

录入成绩比较推荐的方式是弹窗表单:点击“录入”按钮,弹出el-dialog,里面嵌套一个动态表单,支持多条成绩同时录入。每次保存时提交一个数组到后端,后端循环插入或更新。这是比单条录入效率高得多的交互方式,同时代码也不复杂,用v-for渲染表单行就行。

上传成绩模板这块,如果你想再做得漂亮点,可以支持下载Excel模板、上传Excel批量录入成绩。原理就是用EasyExcel的read方法读取上传的Excel文件,解析成实体列表,再批量写入数据库。这个功能演示起来非常亮眼,但工作量也会增加不少,建议放到系统功能齐全之后再扩展。

6. 接口文档集成与调试技巧

6.1 Knife4j自动化生成接口文档

接口文档是项目交付物里很重要的一环,尤其毕业设计有文档部分,很多学校老师会要求提供接口设计。手工维护Word表格效率低、容易和代码脱节,所以我强烈推荐用Knife4j自动生成。

写好Controller后,只要配合注解:

@Api(tags = "成绩管理模块") @RestController @RequestMapping("/api/score") public class ScoreController { @ApiOperation("分页查询成绩列表") @GetMapping("/page") public Result<Page<ScoreVO>> page(@RequestParam Integer pageNum, @RequestParam Integer pageSize, @RequestParam(required = false) Long courseId) { return Result.success(scoreService.queryPage(pageNum, pageSize, courseId)); } @ApiOperation("录入成绩") @PostMapping("/add") public Result<?> add(@RequestBody @Valid ScoreAddDTO dto) { scoreService.add(dto); return Result.success(null); } }

启动项目后访问http://localhost:8080/doc.html,所有接口自动分类展示,而且支持直接调试。在答辩现场,你可以一边讲解系统功能,一边在线调用接口展示返回数据,专业感和可信度会非常足。

6.2 联调时常用的调试手段

前后端联调阶段遇到最多的问题就是跨域、参数格式不匹配、返回结构对不上。我用得最多的工具是Vite/Vue CLI的代理配置,开发环境把前端请求代理到后端端口,这样浏览器不会报跨域错误,前端代码直接写/api/login这样相对路径。

以Vue CLI为例,vue.config.js里加:

const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

用代理的好处是你不需要在后端配置CORS(跨域资源共享)就能完成开发联调。但部署到生产环境时(比如用Nginx),就要在Nginx里配反向代理,把/api开头的请求转发到后端服务。这一步是标准的“前后端分离项目部署”知识,可以提前准备好。

调试过程中我还踩过一个特别典型的坑:日期格式问题。后端返回LocalDateTime时默认会带T字符,比如2024-06-01T10:00:00,前端直接显示非常难看。解决方案是在后端配置统一的JSON序列化格式:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8

这个配置加上之后,前端拿到的就是正常格式,少调半天CSS和字符串处理代码。

7. SQL脚本的使用与初始化数据

7.1 为什么必须用SQL脚本初始化

我用SQL脚本初始化的原因很直接:它能让你在任何一台电脑上5分钟恢复整个系统环境。数据库不是代码,不能打包成JAR,你只有用脚本把表结构、初始数据记录下来,才能在换电脑、更换数据库实例时快速重建环境。很多同学答辩前换台电脑,MySQL装好了但表还没建,急得满头大汗,这就是没有提前准备SQL脚本的教训。

我在项目里建议用三个脚本文件管理数据:

  • init.sql:包含建库语句、建表语句、初始管理员账号
  • data.sql:包含测试数据,比如10个学生、5门课程、50条成绩记录
  • clear.sql:清理所有业务表数据,保留表结构

init.sql里的建库语句一定要先判断是否存在:

CREATE DATABASE IF NOT EXISTS graduation_score DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE graduation_score; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;

SET FOREIGN_KEY_CHECKS = 0这个语句很关键,它能在有外键约束的前提下按任意顺序删除或重建表,避免“删表顺序不对导致删除失败”的尴尬。

7.2 初始数据要准备多少才够演示

初始数据的质量直接决定了答辩演示的效果。我建议你至少准备好这些数据:

  • 管理员账号:1个
  • 教师账号:2-3个
  • 学生账号:10个以上(分布在2-3个班级)
  • 课程数据:5门左右(覆盖专业课和公共课)
  • 成绩数据:每个学生每门课都有成绩,大约50条
  • 注意:成绩要覆盖不同分数段——优秀的、及格的、不及格的都要有

这样设计的好处是,答辩时评委说“查一下不及格的学生有哪些”,你的系统能立刻展示出过滤结果。如果所有成绩都是90分以上,反而不真实,也让你的查询和统计功能没有用武之地。

插入账号信息时,密码字段存的是加密后的值。这里建议在后端写好一个DataInitializer类,启动时自动识别“管理员账号是否已存在”,不存在就插入。但如果只是SQL脚本,你直接插入BCrypt加密后的串就行。我常用密码123456,对应的BCrypt哈希值网上很容易找到,但稳妥起见,建议用自己的应用生成一次,再写入脚本,这样保证加密算法版本一致。

7.3 MyBatis自动建表要不要用

网上很多项目会用到mybatis-plus的自动建表功能,类似ddl-auto: update在JPA里的作用。但我实际开发的经验是:毕设项目不要依赖自动建表

自动建表虽然方便,但它有几个问题:一是表结构不可控,比如字段类型、索引、注释生成的不够规范;二是默认值、唯一约束不好维护;三是调试“为什么字段不见了”比写一行SQL更费时间。与其花时间研究自动建表,不如老老实实维护一份SQL脚本,清晰可控,还能在项目文档里截图展示数据库设计。

如果你实在想在启动时自动执行SQL脚本,可以在application.yml里配置:

spring: sql: init: mode: always schema-locations: classpath:sql/init.sql >spring.datasource.url=jdbc:mysql://localhost:3306/graduation_score?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

检查时可以用一条SQL确认当前数据库和表的字符集:

SHOW CREATE TABLE student;

只要这条语句显示的CHARSETutf8mb4,连接串也带characterEncoding=utf8,数据基本不可能乱码。如果你的MySQL安装时就是默认字符集,那可能需要在建库语句里显式指定,这也是我把init.sql里的建库语句写清楚的另一个原因。

8.4 端口占用问题

开发中经常会碰到“8080端口被占用”导致SpringBoot启动失败。Mac和Windows的排查方式不同:

Windows下用:

netstat -ano | findstr 8080 taskkill /F /PID 具体PID

Mac/Linux下用:

lsof -i :8080 kill -9 具体PID

如果不想每次杀进程,直接在application.yml里修改端口号或配置server.port=0让系统随机分配。但毕设项目端口还是固定比较好,方便前端代理配置。

8.5 前端依赖安装失败

安装Vue项目依赖时经常遇到node-sass安装失败或者cnpm装出来的包结构异常。解决思路:

  • 优先用npm官方源,如果网络不佳就换淘宝源:npm config set registry https://registry.npmmirror.com
  • node-sass这个包是典型的安装困难户,新版项目建议用dart-sasssass包)替代
  • 如果lock文件损坏,删掉node_modulespackage-lock.json重新安装
  • Vite项目比Webpack项目装依赖快很多,不想等待就换Vite

9. 部署与答辩准备的额外建议

9.1 本地打包运行

毕设系统不需要上云服务器,但至少要在本地能打包运行。后端用Maven打包:

mvn clean package -DskipTests

打包后在target目录下拿到xxx.jar,运行:

java -jar target/xxx.jar

前端打包:

npm run build

Vue打包后生成dist目录,里面是纯静态文件。发布时可以把dist放在Nginx的html目录下,同时配置反向代理转发/api请求到后端。

如果嫌Nginx配置麻烦,有一个更快的方案:把前端打包产物放到SpringBoot的src/main/resources/static下,然后重新打包成一个JAR。这样一个Java进程同时托管前端和接口,部署复杂度降到最低。虽然前后端分离架构理论上不该这么做,但作为毕设演示来说完全够用,还不容易出问题。

9.2 答辩演示的演示脚本

答辩演示别临场发挥,提前设计好一条业务主流程,让你的操作连贯起来:

  1. 打开登录页,演示管理员登录
  2. 管理员创建教师账号和课程
  3. 登录学生账号,演示查看成绩和课程
  4. 退出登录,用教师账号登录,录入成绩
  5. 再登录学生账号,确认成绩已更新
  6. 导出Excel成绩表
  7. 打开Knife4j接口文档,展示一个核心接口的在线调试

这套流程设计下来大概5-8分钟,把主要功能都覆盖了,而且每一步都承接上一步,评委不会觉得你只是在“点着玩”。提前演练三遍以上,保证自己不会在关键步骤卡壳。

9.3 功能扩展方向

如果做完这些基础功能后你觉得学有余力,这几个方向可以继续扩展:

  • 成绩统计可视化:用ECharts展示班级平均分、分数段分布、课程合格率
  • 公告管理:管理员发布系统公告,师生登录后可见
  • 成绩导入导出:批量导入Excel成绩,导出按课程统计报表
  • 个人信息维护:修改密码、上传头像

注意,扩展功能要优先选“演示效果好、代码量可控”的,比如ECharts可视化就很加分,但不要一上来就做消息推送、多人协作那种复杂度高的功能,毕设终究是“完整优于复杂”。

10. 最后的体会

我自己做过好几个类似的Java Web项目,从SSH到SpringBoot,从JSP到Vue,最大的体会是:毕设项目的难度不在技术,而在整合。单看SpringBoot的登录接口不难,单看Vue的表格页面也不难,但把前后端、数据库、接口文档、部署联调串起来,就是一个完整工程能力的训练。

这套成绩管理系统,技术上没有用任何一个高深框架,但每一层都是目前行业主流的做法:SpringBoot做后端、MyBatis-Plus做持久层、JWT做认证、Vue做前端、Knife4j做接口文档、SQL脚本管理数据库结构。把这些知识点彻底弄懂,你收获的绝不仅是一个能答辩的系统,而是一个可以写进简历里的完整项目经验。

最后分享一个小技巧:开发时给每个接口都写清楚@ApiOperation注解,给每张表都加上注释,给每个VO类都加上@ApiModelProperty。这些“看不见”的功夫会在答辩和面试时体现价值——别人只会说“我这个系统能查成绩”,你可以说“我在设计阶段就把数据字典和接口规范定义清楚了”。这个区别,就是普通实现和工程化实现的区别。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询