
简介一份面向 Java Web 初学者的前后端分离学生管理系统源码基于 Spring Boot 与 Vue.js 实现。项目覆盖后端业务逻辑、RESTful 接口、数据库初始化脚本与前端管理页面适合毕业设计、课程设计或自学练手时参考前端已打包成静态资源放入后端 static 目录不启动 Vue 也能直接运行 SystemApplication.java 查看整体效果如需修改界面则重新执行 npm run build并将生成的前端代码放到后端 resources/static 下即可。压缩包共 141 个文件、约 6.26MB其中 74 个 Java 文件对应实体、控制器和服务层12 个 Vue 文件配合 js/css 构成前端界面sql 脚本负责初始化数据库另有 xml、properties、md 等配置与说明文档方便快速还原运行环境并读懂项目结构。目前已有 390 人学习下载。通读源码可掌握前后端分离项目的整合流程、数据交互方式以及学生信息增删改查等典型管理功能的完整实现思路。1. 一个登录框背后这套 Spring Boot Vue 学生管理系统源码到底值不值得拆你以为 Spring Boot Vue 学生管理系统只是一堆增删改查拆过这类源码的人才知道最值得读的是登录那条链路Vue 路由守卫、axios 拦截器、后端 JWT 校验串成一条完整的认证线。这套源码面向三类人做 Java 课程设计或毕设的学生、想学前后端分离的转行者、需要拿一个基础后台工程快速接业务的一线开发。典型模块是学生信息维护、班级课程管理、成绩录入查询接口走标准 REST 风格后端 Spring Boot 负责接口和鉴权前端 Vue 配 Element UI 组件库负责交互数据落在 MySQL。本文按「先认目录、再跑登录、后改业务、最后部署」的顺序拆照着走能在一小时内把工程跑起来。2. 拿到源码先别急着跑目录结构、环境版本和启动顺序解压之后先干两件事读一遍 README 或 SQL 初始化脚本把库建好再看 package.json 和 pom.xml。我碰到过好几次这种情况——工程解压后直接 npm installnpm 装完了数据库没导后端一启动就连不上库查了半天才发现错误根本不在代码。整个顺序成本很低两分钟搞定能省掉至少半小时的排查时间。2.1 后端目录启动类、controller、service、mapper、config 各管什么Spring Boot 工程解压后先找 src/main/java 下的包结构。这类源码大同小异一般是src/main/java/com/yourapp/system ├── SystemApplication.java # Spring Boot 启动类 ├── controller/ # 控制器接收参数返回统一结果 │ ├── LoginController.java │ ├── StudentController.java │ └── CourseController.java ├── service/ # 业务层事务、逻辑、调用 mapper ├── mapper/ # 数据访问层接口 XML 或注解 SQL ├── entity/ # 实体类对应数据库表 ├── config/ # 配置类CORS、拦截器、分页插件 └── utils/ # 工具类JwtUtil、ResultController 层只该做三件事接收参数、调 service、返回 Result。真正的事务和判断写在 service 层。我用这个原则判断一个工程代码质量——如果 controller 里塞了一堆 SQL 逻辑后面加需求会非常难受。pom.xml 里常见的依赖组合是 spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、jjwt、lombok。这个组合对学生管理系统这个体量很合适MyBatis-Plus 的 LambdaQueryWrapper 写条件查询比裸 SQL 快JWT 免去了 Session 共享问题lombok 省掉一堆 getter/setter。如果拿到的是没引入 MyBatis-Plus 的老版本也不用慌第四部分讲分页条件查询时会对照着说思路差别。2.2 前端目录main.js、router、store、api、views 的分工与依赖安装前端工程看 src 目录就够了node_modules 不用管。典型结构src ├── main.js # 应用入口注册 Vue、Element UI、router ├── App.vue # 根组件 ├── router/index.js # 路由表与路由守卫 ├── store/ # Vuex登录用户信息、菜单状态 ├── utils/request.js # axios 封装 ├── api/ # 按模块拆分的接口定义 │ ├── login.js │ └── student.js └── views/ # 页面组件 ├── login.vue ├── layout/ # 后台主框架侧边栏、顶栏 └── student/ ├── list.vue └── edit.vuemain.js 是入口new Vue 时注册 Element UI 和 routerrouter 管路由跳转和页面守卫store 存用户信息登录后做角色判断就从这里拿api 目录统一放接口函数views 下是页面。这个边界分清楚改功能不会到处翻。依赖安装一句话npm install这里有一个高频翻车点package.json 里如果是 node-sassNode 版本过高会直接编译失败报错信息里全是 Python、node-gyp 那串。遇到这种情况先用 nvm 切到 node 12 或 14再把 node-sass 换成 sass重新 npm install 也能过运行时兼容性不受影响。2.3 启动顺序先配库、再起后端、最后起前端一次跑通我的顺序固定是执行 SQL 脚本建库 → 改数据库密码 → 起后端 → 起前端。先起后端再起前端两边控制台日志分开看出问题好定位。application.yml 是后端命脉先把关键参数认全server: port: 8080 # 后端端口跟前端 proxy 对齐 servlet: context-path: / # 有前缀的话前端也要跟着改 spring: datasource: url: jdbc:mysql://localhost:3306/student_manager?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted # 逻辑删除字段改数据时少踩坑这三组参数对应三类问题端口不对导致前端代理 404URL 没带 timezone 导致 MySQL 报时区错误逻辑删除配置没开删除操作会变成物理删除。最后一条经常被人忽略第六部分还会用实例验证一遍。后端启动在项目根目录执行mvn spring-boot:run如果已经打成 jar 包java -jar target/xxx.jar 效果一样。前端另开终端npm run dev控制台出现 localhost:端口 后浏览器打开能看到登录页说明环境已通。没通就对照第五部分的排查记录每一条都有具体表象和改法。3. 登录流程拆到接口JWT 认证链和权限控制怎么串起来的登录是这类系统里最容易翻车的一环。前端以为带了 token、后端取不到后端认为过期、前端不跳登录页这两个问题能磨人一下午。拆完你会发现链条不长就三站axios 统一带 token、后端验证 token、路由守卫挡页面。这三站各管一段缺谁都跑不通。3.1 前端的 axios 封装请求拦截器里为什么要统一带 token登录成功后每次请求都要带身份凭证一个个接口手写 header 不现实工程里常规做法是封装 request.jsimport axios from axios const request axios.create({ baseURL: /api, // 开发环境由 devServer 代理到后端解决跨域 timeout: 10000 }) request.interceptors.request.use(config { // 登录后 token 存 localStorage这里统一塞进请求头 const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }, error Promise.reject(error)) export default request关键是第 9 行的 Authorization 头字段名必须和后端拦截器读的一致5.2 条会专门讲不一致的后果。baseURL 用 /api 是代理策略免得每个接口写死 IP 和端口上线时只改代理层不动业务代码。timeout 设 10 秒后台系统响应通常都在 200 毫秒内避免接口卡死时页面死等。3.2 后端登录接口查用户、校验密码、签发 JWT 三步在哪写后端顺序是固定的LoginController 接收用户名密码 → UserService 按用户名查用户 → BCryptPasswordEncoder.matches 校验密码 → 通过后 JwtUtil 签发 token。JwtUtil 是这个小系统里最值得抄的工具类public class JwtUtil { // 密钥按 application.yml 里 jwt.secret 配置至少 32 字节 private static final SecretKey KEY Keys.hmacShaKeyFor( your-256-bit-secret.getBytes(StandardCharsets.UTF_8)); public static String createToken(Long userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(KEY, SignatureAlgorithm.HS256) .compact(); } }HS256 要求密钥不小于 256 bit也就是 32 字节太短启动时直接报错。过期时间 7 天对学生管理系统足够改成 2 小时也行只是用户登录更频繁。claims 里只放 userId 和 role别放密码token 是客户端可见的没必要冒这个险。3.3 路由守卫与后端鉴权前端挡页面、后端挡接口两层不能省只用后端拦截器前端拿到 401 再跳转体验很差只用前端守卫后端接口裸奔懂行的用 Postman 就能调你接口。两层职责不同都得有。前端守在 routerrouter.beforeEach((to, from, next) { const whitelist [/login] // 登录页和验证码页不需要 token const token localStorage.getItem(token) if (whitelist.includes(to.path)) { next() } else if (!token) { next(/login) // 没有 token 一律回登录页 } else { next() // 放行页面组件自己控制菜单权限 } })后端守在 Spring MVC 的拦截器里常见做法是继承 HandlerInterceptor在 preHandle 里解析 token。白名单路径放 /login 和 /captcha其余全走校验。有的源码写了动态路由按用户角色用 addRoute 动态挂菜单原理不复杂后端返回菜单列表前端遍历成路由注册进 router 实例。4. 学生管理模块的实现路径分页、条件查询和表单提交跑通登录后学生管理是第一个业务模块也是改动最多的地方。这一章的核心就一个问题分页和条件查询的参数对齐了后面扩充课程、成绩模块就是复制粘贴。4.1 MyBatis-Plus 分页查询current 和 size 参数怎么传到 SQL有 MyBatis-Plus 的工程分页基本用 selectPage 解决。Controller 接 current 和 sizeService 里拼条件public IPageStudent pageStudents(int current, int size, String name, Integer gender) { PageStudent page new Page(current, size); LambdaQueryWrapperStudent wrapper new LambdaQueryWrapper(); // 姓名是模糊查用 like性别是精确查用 eq wrapper.like(StringUtils.hasText(name), Student::getName, name) .eq(gender ! null, Student::getGender, gender) .orderByDesc(Student::getCreateTime); return studentMapper.selectPage(page, wrapper); }前端把分页参数通过 params 传过去// api/student.js export function fetchStudents(params) { // params: { pageNum, pageSize, name, gender } return request.get(/student/page, { params }) }注意参数名对齐后端收 current 和 size前端 params 里经常写 pageNum 和 pageSize。很多源码在 Controller 上用 RequestParam 做了转换但有的没有出问题时先在 Network 面板看我方发的参数名和后端接收的差在哪。selectPage 返回的 IPage 里有 records、total、current、size前端表格数据源和分页器的 total 都从里面取。分页组件触发翻页时把当前页和每页条数重新组装成 params 再调一次 fetchStudents 就行。4.2 姓名模糊查询 性别精确条件一个 QueryWrapper 怎么拼条件上面代码里最关键的是 wrapper.like 的第一个参数 StringUtils.hasText(name)name 传空串时条件不拼接避免 where name like %% 查出全表。gender 用 Integer 判断是否传了等于 0 也能正常拼接。orderByDesc(Student::getCreateTime) 让新建的学生排最前面这是后台管理页的通用习惯。MyBatis-Plus 生成的是参数化 SQL不会把 name 直接拼进 SQL 字符串注入风险比手写字符串拼接低这也是我建议保留 MyBatis-Plus 的原因。如果源码里没用 MyBatis-Plus改用 XML 手写 SQL 时模糊查询要写成 like concat(%, #{name}, %)靠 #{} 预编译防注入不能直接写 %${name}%。这是两种写法最本质的区别。4.3 前端列表与表单Element UI 的 rules 校验和后端统一返回前端列表页逻辑一般是mounted 时调 fetchStudents拿到 records 给 tableData分页器 change 时重新带页码请求。Element UI 表单校验很模板化el-form refformRef :modelform :rulesrules el-form-item label姓名 propname el-input v-modelform.name / /el-form-item el-form-item label性别 propgender el-select v-modelform.gender el-option label男 :value1 / el-option label女 :value0 / /el-select /el-form-item /el-formrules 里把 name 的 required 和 message 配上提交前 formRef.validate 通过才发请求。后端的统一返回约定是这套源码的骨架public class ResultT { private Integer code; // 200 成功401 未登录500 业务异常 private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } }前端 response 拦截器一般配 code 200 算成功401 时清 token 并跳登录页。在 3.1 的 request.js 基础上补一段request.interceptors.response.use(res { if (res.data.code 401) { localStorage.removeItem(token) window.location.href /login } return res.data }, error Promise.reject(error))后端的 Validated 校验配在 Controller 参数上不通过时抛异常由全局异常处理器捕获后返回 code 500 和具体提示。前后端双轨校验前端挡无效输入后端挡绕过前端的请求。5. 启动、打包、部署常见问题排查跨域、端口与打包路径拆这类源码印象最深的不是功能实现而是环境排查。工程逻辑不复杂真正耗时间的是端口、路径、字段名这些小口子。下面四条是我每次拿新工程都先检查的位置。5.1 前端访问后端接口 404现象npm run dev 打开页面正常点登录Network 面板里请求状态 404后端控制台没有任何日志。原因有两种一是 vue.config.js 里 devServer.proxy 的 target 端口和后端实际端口不一致二是后端设置了 context-path 比如 /system前端 baseURL 还是 /api请求根本没打到后端服务上。另外 Spring Boot 版本太高也有坑Boot 2.6 之后路径匹配策略改成 PathPatternParser以前能用的 /** 拦截写法在新版本下行为不一样。解决先看后端启动日志里打印的 request mapping拿到真实接口前缀再改 vue.config.jsmodule.exports { devServer: { port: 3000, // 改成 3000 避免和后端 8080 撞 proxy: { /api: { target: http://localhost:8080, // 改成后端的实际端口 changeOrigin: true } } } }改完重启前端再点登录F12 看到响应码 200 就通了。这一步确认好后面部署改代理也是同一个逻辑。5.2 登录成功但请求 401现象登录页输入账号密码能进主页但页面上的列表接口全部返回 401刷新后更明显。原因登录成功后前端没正确保存 token或保存了但字段名跟后端对不上——后端从 Authorization 头取 token前端 localStorage 里存的是 token 还是 access_token两边名字必须一致。还有一个容易忽略的点后端返回体是 { code, msg, data: { token: xxx } }前端取 res.data.token 就会是 undefined。解决先打开浏览器控制台执行 localStorage.getItem(token)有值再看接口请求头 Authorization 是否带上没有就查 request.js 里取的值。这种 401 大多不是玄学就是字段名差一个字。5.3 Vue 打包后放进 Spring Boot 的 static白屏和路由 404现象前端打包成 dist 后直接把文件扔进 spring-boot 的 static 目录启动后打开根路径白屏直接访问 /student/list 也 404。原因vue-router 默认可能开了 history 模式。history 模式依赖服务器对任意路径返回 index.htmlSpring Boot 的静态资源处理只认实际存在的文件刷新或直接访问子路由当然 404。另外打包时没改 publicPath资源请求路径是 /js/xxx.js后端根路径带 context-path 时资源全挂。解决最省事是改 hash 模式URL 带个 #但不会刷新 404。改一行配置const router new VueRouter({ mode: hash, // history 改 hash部署到静态目录不折腾 routes })vue.config.js 里再把 publicPath 改成 ./资源相对路径加载。如果确实要用 history 模式就得在后端加转发规则非 /api 开头的路径全部 forward 到 /index.html能跑但要多维护一段代码我不推荐。5.4 后端连不上 MySQL时区、驱动和版本三类报错现象启动后端控制台报 Communications link failure或 The server time zone value йʱ is unrecognized 这种乱码时区错误。原因MySQL 8 的驱动会校验时区连接 URL 里没写 serverTimezone 就启动失败或者 mysql-connector-java 版本和本地 MySQL 版本不匹配5.x 驱动连 8.x 库经常出幺蛾子。解决老实的写法是在 URL 后面拼参数这是最通用的修法jdbc:mysql://localhost:3306/student_manager?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai如果数据库是 MySQL 8把 pom.xml 里 mysql-connector-java 换成 8.0.x数据库是 5.75.1.x 就能用。这条排查完基本能解决九成数据库启动失败。6. 验证这套系统没白下用一条数据流跑通「增学生→查学生→删学生」下载完源码别急着逛菜单菜单逛完等于没逛。我每次验证新拿到的前后端分离工程只跑一条数据流登录 → 新增学生 → 按姓名搜索 → 编辑手机号 → 删除。这条链路覆盖前端表单校验、axios 拦截器、后端参数校验、MyBatis-Plus 增删改查、数据库持久化五个环节每一步都能明确对应到代码层。具体操作浏览器打开控制台的 Network 面板勾选 Fetch/XHR登录后新增一个学生姓名输入 test_202409提交后请求列表里出现 POST /student请求头 Authorization 非空响应 code 是 200回列表搜索框输入 test_202409看 GET /student/page 请求携带了 pageNum、pageSize、name响应 records 里恰好一条点编辑把手机号改掉刷新列表确认显示新值最后删除到数据库执行一条查询确认数据状态。各层验证点对照层级验证点通过标志前端组件新增按钮事件触发Network 出现 POST /studentapi/axios拦截器自动带 token请求头 Authorization 存在controller参数校验通过后端日志打印接收参数service/mapperMyBatis 生成 insert SQL控制台打印或日志开启MySQL数据真实落库select 查询返回一行列表回显搜索能查到新增记录Table 组件展示该行这里有个我栽过的坑以前验证到第 5 步就收工结果部署后才发现 MyBatis-Plus 的逻辑删除配置没开删除变成物理删除后端报错列表还留着一半数据。从那以后每次拿到新的前后端分离源码我都会强制自己走完第 6 步把数据库表的 deleted 字段也查一遍确认项目用的是逻辑删除还是物理删除配置在 application.yml 还是实体注解里。这个工程走的是逻辑删除配置位置就是 2.3 节 global-config 那一行改动成本很低。希望帮到你。本文还有配套的精品资源点击获取