news 2026/10/10 4:20:32

Spring Boot+Vue校友录管理系统:毕业设计开发全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot+Vue校友录管理系统:毕业设计开发全流程解析

简介:一份基于SpringBoot与Vue的校友录管理系统毕业设计源码包,适合Java相关专业学生或需要信息管理系统参考的开发者。项目完整实现了校友信息的展示、编辑、查询与删除,采用典型前后端分离架构,前端为Vue动态页面,后端为SpringBoot服务,配合MySQL数据库持久化,经测试可完美运行。源码按前端代码、管理端代码、服务器端代码三部分组织,目录清晰。

资源共384个文件,包含100个Java源文件、83个Vue组件、40个JS脚本、19个CSS样式等,分别承担后端业务、前端界面、页面交互与样式控制,另有SQL脚本、XML映射和说明文档,压缩包仅14.39MB。目前已有63人学习/下载。

除完整源码外,还附带项目说明文档和数据库表结构文档,详细记录系统设计思路、运行环境配置、部署步骤及表字段信息,可帮助快速搭建环境并二次开发。通过阅读源码可巩固Java编程、SpringBoot配置、Vue组件化开发及前后端联调等技能,是毕业设计和工程实践的高质量参考。

1. 校友录管理系统:为什么说它是 Spring Boot + Vue 毕业设计里最省心的那类

每年毕业设计季,总有一批人卡在同一个问题上:选题要前后端分离、要有数据库、要能演示给答辩老师看,但自己又没有完整做过一个项目。这个校友录管理系统源码,装的就是 Spring Boot + Vue + MySQL 三件套,后端是标准的三层结构,前端有管理端和门户端两套页面,还附带说明文档和数据库脚本。适合两类人:一类是做毕业设计想直接改改用的同学,另一类是刚学完框架想找个完整项目顺一遍前后端分工的初学者。它对 Spring Boot 版本、Vue 组件写法、MyBatis 数据访问这些常见问题的处理方式,反而比很多 Demo 更接近真实项目,这也是我拆完以后觉得值得写一篇笔记的原因。

2. 从需求到表结构:校友录系统的数据设计和选型逻辑

2.1 为什么是 Spring Boot + Vue + MySQL:技术栈选型不是拍脑袋

很多同学选技术栈是照着招聘要求凑的,但毕业设计答辩时老师第一个问题就是「你为什么选这套组合」。这个问题答不好,后面演示得再顺都会扣分。校友录管理系统这套组合,选型理由其实是能站得住的:Spring Boot 负责把配置和部署成本压下来,内置 Tomcat,打包成 jar 就能跑,不用像 SSH 时代那样手动装容器;Vue 负责页面交互,管理后台、校友列表、信息编辑这类操作都是典型的单页应用场景,Vue 的组件化写起来比 JSP 舒服太多;MySQL 则是关系型数据模型最自然的落点,用户、班级、学生、教师、公告,每张表之间都有明确的外键关系。

从数据访问层看,这类项目用 MyBatis-Plus 是主流做法,省去大量手写 XML 的样板代码。分页、条件查询、逻辑删除都有现成方法,对毕业设计来说代码量和可读性比纯 MyBatis 更好,也比 JPA 更容易向答辩老师讲清楚 SQL 在哪。前端用 Vue 2 + Element UI 的组合也符合实际情况:Element UI 的表单、表格、分页组件开箱即用,能在几天内把管理界面撑起来,不用花时间写样式。这套选型到 2024 年依然是性价比最高的组合之一,不是因为它最新,而是因为它资料最多、报错最好查。

2.2 核心数据表设计:用户、班级、学生、校友动态

校友录系统的主线是「把一届一届的人组织起来」。我会拆成五张核心表:系统用户表(账号、密码、角色)、班级表(届别、院系、专业、班级名称)、学生信息表(学号、姓名、联系方式、工作单位)、教师表、校友动态表(发布人、内容、发布时间、图片)。可先建数据库,再建表,SQL 脚本长这样:

CREATE DATABASE IF NOT EXISTS alumni_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE alumni_system; CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT 'MD5/BCrypt 加密后的密码', role VARCHAR(20) NOT NULL DEFAULT 'STUDENT' COMMENT 'STUDENT/TEACHER/ADMIN', real_name VARCHAR(50) COMMENT '姓名', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB COMMENT='系统用户表';

这段脚本里有几个值得注意的参数。字符集用 utf8mb4 而不是 utf8,是因为 utf8 在 MySQL 里存不了 emoji 和生僻字,校友填工作单位时经常有特殊符号,用 utf8mb4 能少一次线上事故。password 字段长度留 100,如果后端用 BCrypt 编码,生成的字符串长度在 60 左右,50 不够放。create_time 用 DATETIME 加默认值,插入时不用手动维护。

学生表和班级表之间通过 class_id 关联,老师选题时一定要求「按班级查学生」这种功能,所以外键字段必须建索引。校友动态表建议用逻辑删除,加一个 deleted 字段,避免真正执行 DELETE 导致历史数据丢失,MyBatis-Plus 的 @TableLogic 注解可以直接处理。设计表的时候还有一个经验:把「届别」「专业」这类被频繁查询的字段直接冗余进学生表,而不是每次三表联查。对数据量小的系统来说,冗余换性能是划算的。

2.3 数据库初始化与连接配置:按这张配置一定跑得起来

拿到源码第一步不是急着点运行,而是先导入数据库脚本,再核对连接配置。常见做法是把 SQL 文件放在项目根目录的 sql 文件夹下,用 Navicat 或命令行执行。MySQL 5.7 和 8.0 都能跑,但如果本机装的是 8.0,要注意驱动和时区配置。后端 application.yml 的关键配置如下:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/alumni_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这段配置里最关键的是 JDBC 连接串。driver-class-name 必须用 com.mysql.cj.jdbc.Driver,这是 MySQL 8 的新驱动类名,老项目里的 com.mysql.jdbc.Driver 在 8.0 下会直接报 ClassNotFound。serverTimezone=Asia/Shanghai 是解决时区报错的关键,不加这一项,连接时会提示 CST 时区无法识别。allowPublicKeyRetrieval=true 是为了解决 MySQL 8 使用 caching_sha2_password 认证插件时,首次连接无法获取公钥的问题,这个坑我在第 5 章会细说。

提示:密码改成你自己本机 MySQL 的密码。如果连不上,先确认 MySQL 服务有没有启动,再确认用户权限是否允许 localhost 登录。

mybatis-plus 的 map-underscore-to-camel-case 配为 true 后,数据库字段 class_id 能自动映射到实体类属性 classId,不用每个字段都写映射。log-impl 配成 StdOutImpl,控制台会打印每一条 SQL,方便你对照前端操作查后端实际执行的语句,调试效率会高很多。

3. Spring Boot 后端:接口分层、权限控制和关键代码走读

3.1 项目结构与分层:Controller、Service、Mapper 各管什么

打开源码先看包结构,这个项目的分层是标准的 controller / service / mapper / entity 四层。实体类对应数据库表结构,Mapper 负责数据访问,Service 层写业务逻辑,Controller 只做参数接收和结果返回。很多同学写毕设的时候喜欢把业务逻辑直接堆在 Controller 里,看似省事,答辩时老师问「你的事务控制在哪」就答不上了。分层的目的不是好看,是为了事务和复用:一个 Service 方法上加 @Transactional,多个 Mapper 操作要么全成功、要么全回滚,这在 Controller 里是做不到的。

@Service public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService { @Override public PageResult<StudentVO> pageStudents(StudentQuery query) { LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(query.getName()), Student::getName, query.getName()); wrapper.eq(query.getClassId() != null, Student::getClassId, query.getClassId()); wrapper.orderByDesc(Student::getCreateTime); Page<Student> page = this.page(new Page<>(query.getPageNum(), query.getPageSize()), wrapper); // 组装班级名称等冗余字段 return convertToPageResult(page); } }

这段代码用的是 MyBatis-Plus 的 LambdaQueryWrapper,它的价值在于条件构造是类型安全的,字段名写错会在编译期报错,而不是运行期才发现 SQL 异常。注意 eq 和 like 方法的第一个参数是 boolean 条件,query.getClassId() 为 null 时,这个条件会被自动忽略,不用像传统写法那样拼 SQL 字符串。分页用 Page 对象,前端传 pageNum 和 pageSize,后端返回总记录数和当前页数据列表,这是 JSON 接口最通用的格式。

分层之后,前端拿到的数据是脱敏加工后的 VO(View Object),而不是数据库实体直接序列化。比如学生列表要显示班级名称,但实体里只有班级 ID;用户表的密码字段是绝对不能出现在接口返回里的。在 Service 层做一次实体到 VO 的转换,既是安全要求,也是前后端职责边界清晰的体现。这个习惯在真实企业项目里是强制规范,从毕设开始养成不亏。

3.2 登录鉴权:JWT 拦截器与 Spring Security 取舍

校友录系统有管理员、教师、学生三种角色,所以登录鉴权是必修模块。项目的做法是 JWT + 拦截器,没有上 Spring Security,这个取舍我认为很合理。Security 功能强但学习曲线陡,配置类动辄上百行,对毕设来说是过度设计。JWT 的核心思想是服务端不保存会话,用户登录成功后返回一个签名后的 token,后续请求在请求头带 Authorization: Bearer token,拦截器解析并校验。

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if ("OPTIONS".equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); } try { Claims claims = JwtUtil.parse(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录或登录已过期\"}"); return false; } } }

preHandle 方法在请求进入 Controller 之前执行。第一判断 OPTIONS 请求直接放行,是因为前后端分离时,浏览器跨域请求会先发一个预检请求,这个请求不带业务 token,拦截器必须放行,否则前端所有跨域请求都会卡死在 CORS 阶段。token 解析通过后,把 userId 和 role 放进 request 属性,Controller 里用 @RequestAttribute 就能取到当前登录人的身份,避免每次都查数据库。

JWT 工具类里需要配置过期时间,常见做法是 12 小时或 24 小时。有一个坑:如果 JWT 里放的角色信息被前端改动之后再发回来,拦截器解出来的数据是服务端当初签发的,不会被前端篡改,因为签名密钥在服务端。这个机制天然防篡改,你甚至可以直接从前端传过来的 token 里读出用户名来显示。

3.3 核心接口示例:校友列表分页查询与文件上传

分页查询接口是最常被答辩老师问细节的接口。Controller 接收 pageNum、pageSize、name、classId 四个参数,Service 层组装条件,返回统一的结果对象。统一返回结果的封装很关键,项目里应该有 Result 这个类,包含 code、msg、data 三个字段。

@RestController @RequestMapping("/api/student") public class StudentController { @GetMapping("/page") public Result<PageResult<StudentVO>> page( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String name, @RequestParam(required = false) Long classId) { StudentQuery query = new StudentQuery(); query.setPageNum(pageNum); query.setPageSize(pageSize); query.setName(name); query.setClassId(classId); return Result.success(studentService.pageStudents(query)); } }

param 的配置里,defaultValue = "1" 和 "10" 保证前端不传分页参数时接口不会报错;required = false 表示查询条件可省略。这里的命名值得学:URL 参数用 pageNum、pageSize,而不是 page、limit,前一套和后端 Page 对象字段直接对应,省去一层参数映射。

文件上传接口是另一个高频功能。校友动态要传图片,后端接收 MultipartFile,存储到指定目录后返回访问路径。常见做法是把文件写到运行目录下的 upload 文件夹,再配置一个静态资源映射,把磁盘路径暴露成 /files/** 的 URL。上传接口有几个参数必须固定:文件名要做防重处理,不然有人传个人.jpg,另一个人也传个人.jpg,后者会把前者覆盖掉。我一般用 System.currentTimeMillis() + 随机数拼一个新文件名。路径用绝对路径,不要用相对路径,避免 jar 包运行时当前目录不一致导致文件写丢。

4. Vue 前端:从环境搭建到页面联调,把管理端和门户端跑起来

4.1 前端工程结构:Vue CLI 环境、Element UI、Axios 封装

前端项目的打开方式和后端不同,它不是一个 jar 包跑起来,而是需要 Node.js 环境,通过 npm 安装依赖后再启动开发服务器。这套源码的前端用的 Vue CLI 构建,工程内有 package.json、vue.config.js、src 目录三层结构。如果你是从 IDEA 里打开前端目录,注意 IDEA 需要安装 Vue 插件才能高亮 .vue 文件,环境不对的人经常会看到满屏红色波浪线,以为是代码错误,其实是编辑器没识别语言。配置环境时,Node 版本建议 16 或 18,太新的 Node 20 有时会和旧版依赖产生兼容警告。

// src/utils/request.js import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API || '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error('网络异常,请检查后端服务') return Promise.reject(error) } ) export default service

这段 Axios 封装的核心是拦截器。请求拦截器统一从 localStorage 取出 token,塞进请求头,这样每个业务接口的调用处都不用重复写 token 逻辑。响应拦截器统一处理返回码,后端返回 code 401 时自动跳登录页,这就是前面 JWT 拦截器抛出的未登录状态,前端拦截后引导用户重新登录。将 baseURL 配置到环境变量而不是写死,是因为开发环境走代理、生产环境走 Nginx 转发,url 前缀不同,写死会导致换环境就要改代码。

Element UI 的引入一般是在 main.js 里 Vue.use(ElementUI)。组件按需引入能减小打包体积,但毕设项目用全量引入更省事,不用考虑每个组件对应的样式文件有没有单独导入。表单校验用 Element UI 自带规则,v-model 绑定的字段要和后端实体字段名保持一致,这样提交的时候可以直接把整个表单对象交给 axios。

4.2 路由与权限控制:Vue Router 守卫怎么控制页面

校友录前端分成两块:门户端面向普通用户,管理端面向管理员。Vue Router 用路由表把这两个区域的页面组织起来。管理端的路由不能通过 URL 直接访问,需要登录后才能进,这就是路由守卫的作用。路由守卫的核心问题,可以看看这套标准的判断逻辑:

// src/router/index.js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) const routes = [ { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/admin', component: () => import('@/layout/AdminLayout.vue'), meta: { requiresAuth: true, roles: ['ADMIN'] }, children: [ { path: 'students', component: () => import('@/views/admin/StudentList.vue') }, { path: 'classes', component: () => import('@/views/admin/ClassList.vue') } ] }, { path: '/', component: () => import('@/views/portal/Home.vue') } ] router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') if (to.meta.requiresAuth) { if (!token) { next('/login') return } if (to.meta.roles && !to.meta.roles.includes(role)) { next('/') return } } next() })

路由懒加载用 import() 函数实现,每个页面按需加载,这样打包出来的文件会被拆分成多个小块,首屏加载速度更快。meta 字段是给守卫用的元数据,requiresAuth 表示需要登录,roles 限制角色。守卫函数里先判断 token 是否存在,再判断角色是否匹配,两层判断就可以覆盖「未登录访问后台」和「学生账号访问管理端」两种越权情况。

值得注意的一点是,路由守卫只是前端控制,真正的权限校验还是后端拦截器在做。前端隐藏菜单只是体验优化,接口层面的 401 判断兜底,这套「前端守卫 + 后端过滤」的双层机制才是合格项目的做法。只信前端守卫的同学,用 curl 直接调接口就能绕过页面限制,这在真实项目里是重大安全漏洞。

4.3 前后端联调:跨域配置与接口对接

前后端分离最常见的卡点是跨域。前端的开发服务器跑在 8080 端口或 3000 端口,后端接口在 8080,浏览器从不同端口请求资源会被同源策略拦住。用 Vue CLI 开发时,最顺手的方式是在 vue.config.js 里配置 devServer 代理:

// vue.config.js module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }

devServer 的 port 配成 8081,避免和后端 8080 端口冲突。proxy 的意思是,前端发到 /api 开头的请求,开发服务器会转发到 target 指向的后端地址。这里的 pathRewrite 值得说清楚:如果后端 Controller 的路径是 /api/student/page,那前端请求 /api/student/page,代理原样转发即可,不需要 rewrite;如果后端路径没有 /api 前缀,就需要用 pathRewrite 把前缀去掉。我看源码时发现不少同学卡在这一步,请求发出去变成了 404,就是因为后端路径与代理目标拼接后对不上。

proxy 配好之后,前端的 baseURL 写成 '/api' 就能解决开发环境跨域。但上线部署时不是这个逻辑,生产环境是 Nginx 统一监听端口,前端静态文件和后端接口在同一个域名下,Nginx 再把 /api 开头的请求转给后端进程,这又是另一套配置。理解了「开发代理」和「生产反向代理」的区别,面试时聊项目部署才不会被问倒。

联调时还有一个常用技巧:在浏览器开发者工具的 Network 面板查看请求 URL、请求头和响应体。如果某个接口返回 500,先看响应体里的错误堆栈,同时去后端控制台看 MyBatis-Plus 打印的 SQL。一般分页失败是参数类型不对,登录失败是密码加密方式不统一,前端传过来的密码没有做同样规则的加密,后端比对必然失败。

5. 用户反馈与避坑:版本太高、MySQL 8 认证、Vue 项目发给别人后跑不起来的五个教训

5.1 现象:Spring Boot 版本太高导致项目启动失败

有同学看到源码 pom.xml 里是 Spring Boot 2.7,觉得不够潮,非要手动升到 3.2,结果一启动就报错。原因很简单:Spring Boot 3.0 是一个分水岭,包名从 javax 改成了 jakarta,项目里所有 import javax.servlet 的代码全会编译报错;同时 3.x 要求 JDK 17 及以上,如果你本机还是 JDK 8,连 Maven 编译都过不去。解决方法是别乱升级,源码里用什么版本就用什么版本,Java 8 或 11 对应 Spring Boot 2.7.x 和 MyBatis-Plus 3.5.x 是经过了大量验证的组合。

这里要提醒一句:毕业设计的目标是跑通、讲清、能演示,不是追求框架最新。「你为什么不升级到 Spring Boot 3」这个问题在答辩现场几乎不会被问,反而「你项目里用的是什么版本,为什么选这个版本」更常见。版本一致性是玄学最少的地方,别在这个问题上给自己挖坑。

5.2 现象:MySQL 8 驱动报错 Public Key Retrieval is not allowed

连接数据库时控制台抛出这个异常,同时伴随 Access denied for user。原因是 MySQL 8 默认认证插件是 caching_sha2_password,它要求客户端在连接时先取服务器的 RSA 公钥做密码加密传输,而 JDBC 驱动出于安全策略默认拒绝了自动取公钥。解决方式是两步:一是把连接 URL 加上 allowPublicKeyRetrieval=true,二是把 useSSL=false 一并写上。如果你用的 MySQL 是 5.7,不存在这个问题,driver-class-name 写成 com.mysql.jdbc.Driver 也能跑,但我会建议不管数据库哪个版本,驱动类名统一用 com.mysql.cj.jdbc.Driver。

另一个隐藏点:MySQL 8 的 root 用户密码校验规则默认要求强密码。初始化脚本里如果写的密码是 123456 这种弱口令,可能在安装 MySQL 时就被拒绝了。遇到这种报错别急着改代码,先检查 MySQL 用户本身能不能登录。

5.3 现象:端口被占用,后端启动时报 Web server failed to start

启动 Spring Boot 提示 Port 8080 was already in use,说明有别的进程占用了端口。有可能是之前启动的后端没关闭,也可能是其他开发工具的端口配置重叠。常见解决方法是命令行执行 netstat -ano | findstr 8080 查看 PID,再用 taskkill /F /PID 杀掉对应进程。更省心的做法是直接在 application.yml 改一个端口,比如 8081,但注意前端 proxy 的 target 要同步改。这个坑的难点不在解决,而在于它很容易被误判成代码问题,浪费半小时去查 Controller。

5.4 现象:Vue 项目源码发给别人后 npm run dev 报错

把源码打包发给同学的场景很常见,但接手方经常碰到几个连环问题。第一,node_modules 目录太大,发压缩包时通常被排除掉,接手方需要先执行 npm install 安装依赖;第二,安装过程中 node-sass 这类原生模块在 Node 版本不匹配时编译失败,报错信息是一大串 Python 和 C++ 相关的内容,看着吓人,其实是版本引发的;第三,接口地址写死成本机 localhost,接手方根本连不上发件人的电脑。

解决方法是提前做三件事:删掉 node_modules 再压缩;在 README 里写明 Node 版本要求(16 或 18);把接口地址和代理配置抽到 .env.development 文件里,交给接手方自行修改。Vue 项目源码发给别人后跑不起来,绝大多数不是代码问题,而是环境差异问题。这也解释了为什么真实项目都强调环境统一,Docker 化部署就是用来根治这类问题的。

5.5 现象:上传的图片在页面上显示 404

前端上传校友头像或动态图片成功后,页面访问返回的图片路径提示 404。原因通常是上传目录和静态资源映射不一致:文件被写到了项目运行目录的某个 upload 文件夹,但 Spring Boot 没有把那个目录暴露成可访问的 URL。解决方式是在配置类里加上资源映射:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath = System.getProperty("user.dir") + "/upload/"; registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); } }

addResourceHandler 定义浏览器访问的 URL 模式,addResourceLocations 指向真实的磁盘路径。这里路径末尾一定带斜杠,否则文件拼接会出问题。还有一点:用 System.getProperty("user.dir") 拿的是启动命令所在的目录,如果用 IDEA 启动和用 jar 包启动,得到的目录可能不同,导致文件存储位置漂移。更稳的做法是把上传路径写进配置文件,不同环境各配各的。

6. 收尾:验证核心链路、替换数据初始化脚本并完成 IDE 内部署

6.1 按接口清单验证一遍核心链路

跑通一个项目最怕的就是「启动了但功能没走通」。启动后端和前端后,我建议按顺序点一遍:第一步,注册一个新学生账号,看数据库对应表是否新增记录;第二步,用这个账号登录,观察控制台打印的 SQL,确认密码校验走了预期逻辑;第三步,进入管理端,创建班级、添加学生,列表数据能正确分页展示;第四步,发一条带图片的校友动态,刷新页面确认图片能加载出来;第五步,用一个普通学生账号直接访问管理端路由,确认被路由守卫踢回首页。这套验证做完,基本可以确认系统的核心链路是通的,不会在答辩现场翻车。

6.2 替换数据初始化脚本:让系统看起来像自己的

山师的毕业设计最容易犯的问题,是演示数据全是「张三」「李四」这种凑数数据。拿到源码后,把 SQL 脚本里的初始数据替换成一套完整且合理的校友数据:比如 5 个班级、每个班级 20 名学生、对应 10 名教师、几十条校友动态。这些数据会比代码本身更打动答辩老师。替换时要注意 s_user 表的密码字段不能直接改明文,必须用后端工具类生成 BCrypt 密文后再写入,否则前端登录永远失败。

6.3 从 IDEA 启动到打包部署:最终演示用 jar 包更保险

答辩现场环境不确定,强烈建议提前打包成 jar 再运行一次。后端用 Maven 面板执行 package 命令,在 target 目录生成 jar 包,命令行 java -jar 启动;前端执行 npm run build,生成 dist 目录,把 dist 里面的文件复制到后端项目的 static 目录下,重新打包,就得到一个前端页面和后端接口都在同一个 jar 里的应用。这样演示时只需要一条命令,不用同时开两个终端。顺带一个小技巧:在 resources 目录放一个 banner.txt,启动时控制台会输出你自定义的 ASCII 字符画,用网上的 Spring Boot Banner 在线生成器,10 秒钟搞定,答辩演示时一个小小的仪式感,观感会好很多。

从那次以后,我每次拿到一份新源码,都强制自己先跑通再拆代码、先备份 SQL 再改表结构、先在本地完整部署一次再改业务逻辑。这套流程省掉过太多折腾,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 4:20:03

SHP-2靶向研究新焦点:Tyr542磷酸化调控与抑制剂策略解析

1. 为什么Y542这个位点值得单独拿出来讨论1.1 从“不可成药”到“例外中的例外”做肿瘤靶向研究的人&#xff0c;应该都听过一句老话&#xff1a;激酶抑制剂是过去二十年的主角&#xff0c;磷酸酶抑制剂是永远的下一个风口。PTP家族&#xff08;蛋白酪氨酸磷酸酶&#xff09;很…

作者头像 李华
网站建设 2026/10/10 4:20:01

给 Claude Code 装上长期记忆:claude-mem 原理与实战

1. 为什么要给 Claude 加上"记忆"如果你用过一段时间的 Claude Code&#xff0c;大概率遇到过同一个尴尬场景&#xff1a;前两天刚和它一起把一个服务的接口从 REST 改成 GraphQL&#xff0c;今天开个新会话&#xff0c;它又一本正经地问你"这个项目现在用的是 …

作者头像 李华
网站建设 2026/10/10 4:19:59

PHP程序员失业后如何停止自我攻击:技能迁移与职业转型指南

1. 从“技能困境”到“自我攻击”&#xff1a;我看到太多人卡在同一个地方这段时间我在几个技术社群里潜水&#xff0c;发现一个挺让人揪心的现象——不少失业的PHP程序员&#xff0c;尤其是干了三到五年、甚至七八年的那批人&#xff0c;明明技术底子还在&#xff0c;却在求职…

作者头像 李华
网站建设 2026/10/10 4:17:23

项目代号aa---(13):第13版重构实录与减法工程思想

接到不少新项目时&#xff0c;我的习惯是先看代号&#xff0c;再看文档。说实话&#xff0c;很多项目正文写得稀里糊涂&#xff0c;真正有价值的信息往往藏在命名和版本号里。就拿最近在整理的aa---(13)来说&#xff0c;乍一看像乱码&#xff0c;仔细拆开却是一套完整的工作流痕…

作者头像 李华
网站建设 2026/10/10 4:17:20

Text-to-CAD实战:从自然语言到可编辑几何的工作流与踩坑指南

第一次看到 text-to-cad 的演示视频时&#xff0c;我的第一反应是&#xff1a;这不就是给 CAD 加了张"嘴"吗&#xff1f;但真正上手之后我才发现&#xff0c;这件事比想象中麻烦得多。它不只是把一句话变成模型&#xff0c;而是要把自然语言里的模糊意图&#xff0c;…

作者头像 李华