如果你最近正在为 Java Web 方向的毕业设计选题发愁,大概率会看到“学院个人信息管理系统”这个题目。光看名字,它容易给人留下“无非就是个增删改查系统”的印象,但真正上手做一遍就会明白,这套题目把 SpringBoot+Vue 前后端分离、JWT 认证、角色权限、文件上传、Excel 导出、数据库设计和接口文档全都串起来了。这篇文章围绕我整理的这套 SpringBoot+Vue 学院个人信息管理系统平台,把头尾完整的源码、SQL 脚本和接口文档一起拆开讲,从需求、技术选型、数据库、后端接口、前端联调一直讲到部署和答辩展示,把做这个项目时真正值得注意的地方都过一遍。无论你是零基础想跟着复现,还是已经会写接口但卡在前后端联调和部署环节,都能从里面找到对应的思路。
1. 从“学院个人信息管理系统”到需求清单:这个项目究竟做了什么
1.1 业务场景:一份个人信息在学院里要流转多久
拿到这个题目之后,我习惯先画业务场景,而不是直接建表。一个学生从入学到毕业,个人信息不是静态的:入学时要采集基础信息,比如姓名、学号、学院、专业、班级、身份证号、民族、政治面貌、联系方式;在校期间会不断产生新数据,包括教育经历、实习经历、获奖记录、证书信息;学生本人有时需要更新手机号和邮箱,辅导员需要按班级筛选学生、导出名单,管理员还需要维护账号状态和字典数据。
如果一上来就把这些字段全部塞进一张表,后面写页面和接口时会很痛苦。因为不同字段的归属、更新频率、查看权限都不一样。“学院个人信息管理系统”这个题目的核心,其实是做一套围绕“个人信息状态变化”的管理工具。所以项目需求清单可以拆成这样:学生端负责维护本人资料、修改密码、上传头像;辅导员端负责查看管辖范围内的学生信息、按条件搜索、导出 Excel;系统管理员负责用户账号管理、角色配置、字典数据维护。
这套需求看起来不复杂,但已经覆盖了一个典型 Web 系统的所有关键点。不要小看它,很多同学做到一半就卡在“学生只能改自己的信息”“辅导员只能看自己班级的学生”这类边界控制上。
1.2 三种角色和权限边界:学生、辅导员、系统管理员
角色设计不用一开始搞得很复杂,三种角色足够支撑这个项目的主体逻辑。
| 角色 | 核心关注点 | 典型操作 |
|---|---|---|
| 学生 | 个人资料、账号安全 | 查看和修改个人部分信息,修改密码,上传头像 |
| 辅导员 | 管辖范围内的学生数据 | 按班级/专业查询学生列表,查看详情,导出名单 |
| 系统管理员 | 全系统账号与基础数据 | 管理用户账号,重置密码,维护角色和字典数据 |
权限设计上,最怕的是前端把按钮隐藏就以为安全了。真正的权限控制必须后端兜底。本项目在后端通过 SpringBoot 拦截器校验 JWT Token,再结合每个 Controller 接口上的角色注解来判断当前用户是否有权限。比如“辅导员查询学生列表”这个接口,只允许辅导员和管理员访问,学生角色调用时直接返回无权限错误。
这里要提一句:很多同学喜欢把权限设计成一张复杂的角色-菜单表,但在一套以信息维护为主的系统里,这会让工作量瞬间翻倍。学院个人信息管理系统的权限更多是“角色 + 接口 + 前端菜单”三层控制,先用清晰的枚举角色控制接口,再用前端路由控制菜单显示,足够应付毕设的场景。
1.3 项目交付物:源码、SQL脚本、接口文档各自该解决什么问题
完整项目交付时,除了源码,还必须有 SQL 脚本和接口文档,这三样东西定位完全不同。
源码解决“代码怎么写的”问题。后端 SpringBoot 工程包含 Controller、Service、Mapper、实体类、DTO、工具类和配置文件;前端 Vue 工程包含页面、路由、状态管理、Axios 封装和组件。一个结构清晰的源码包,应该让人打开 IDE 之后很快就能找到某个功能对应的整条调用链。
SQL 脚本解决“数据库怎么搭”的问题。里面不只是建表语句,还包括初始化数据:管理员账号、角色字典、性别字典、学院/专业字典等。脚本文件应该能在一个空的 MySQL 实例里直接执行成功,而不是建表语句和初始数据散落各处。
接口文档解决“前后端怎么约定”的问题。每个接口的 URL、请求方式、请求参数、返回示例、权限要求都应该写清楚。文档写得好,前端同学甚至可以不看后端代码直接完成联调;答辩的时候,接口文档也是展示工程能力的重要材料。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与项目结构:为什么不盲目追新版本
2.1 后端:SpringBoot + MyBatis-Plus 是稳妥组合
后端技术栈选择 SpringBoot 2.7 + MyBatis-Plus。SpringBoot 的自动配置让项目搭建成本极低,内嵌 Tomcat 也避免了部署 Web 服务器的麻烦;MyBatis-Plus 在 MyBatis 基础上提供了通用 Mapper 和条件构造器,单表 CRUD 几乎不用手写 SQL,能把精力集中在业务逻辑和权限控制上。
为什么不用 Spring Cloud 微服务?因为学院个人信息管理系统属于典型的单体应用,用一个 SpringBoot 工程就能把所有模块组织好,引入微服务反而会增加服务拆分、注册发现、分布式事务这些和题目核心无关的复杂度。为什么不用 JPA 而用 MyBatis-Plus?JPA 自由度不如 MyBatis,复杂查询和动态 SQL 不好控制;MyBatis-Plus 上手快,查询条件通过 Wrapper 对象拼接,阅读代码时逻辑更直观。对毕业设计来说,可解释性比炫技更重要。
版本选择上,我更推荐 SpringBoot 2.7 + JDK 8 或者 JDK 11。原因很实际:大多数学校机房和答辩机器的环境还停留在 JDK 8,Tomcat 兼容性也更好。如果一定要用 SpringBoot 3.x + JDK 17,也不是不行,但要注意某些第三方库和插件是否已经适配新版 API,避免在调试环境上花掉太多时间。
2.2 前端:Vue2 + Element UI 还是 Vue3 + Element Plus
前端选 Vue 基本没有争议,可争议的是版本。Vue2 配合 Element UI 的生态非常成熟,网络资料和各类踩坑记录都比 Vue3 丰富,对前端基础薄弱的同学更友好。Vue3 配合 Element Plus 的组件更新、组合式 API 也更现代,但对 node 版本和构建工具要求更高。
| 对比项 | Vue2 + Element UI | Vue3 + Element Plus |
|---|---|---|
| 上手难度 | 低,教程多 | 中,需要熟悉组合式 API |
| 构建要求 | Node 14 即可 | 推荐 Node 16+ |
| 生态成熟度 | 成熟稳定 | 持续更新 |
| 适合人群 | 前端经验较少、求稳 | 有 Vue 基础、希望展示新技术 |
我最终在这套项目里以 Vue2 结构为主线,因为代码直观,评审老师看起来也不吃力。但页面设计上保留了组件化思想,比如把整个个人信息页拆成基础信息、教育经历、获奖记录、证书信息四个子组件,每个子组件只负责自己的一块数据。这样即使后面把项目升级到 Vue3,也只是换个语法容器,业务划分不用重做。
2.3 认证方案为什么用 JWT 而不是 Session
前后端分离项目里,登录状态怎么保持是必须面对的问题。传统 Session 方案依赖服务端存储会话,前端通过 Cookie 自动携带 SessionId;但前后端分离之后,接口请求很可能跨域,Cookie 处理变得麻烦,而且如果以后要横向扩容多个后端实例,Session 同步又是一个问题。
JWT 的方式完全不同。用户登录成功后,后端生成一个包含用户标识和过期时间的签名 Token 返回给前端;前端把 Token 存在 localStorage 里,之后每次请求在请求头中带上 Authorization;后端通过过滤器解析 Token,验签成功就放行,同时从 Token 中拿到当前用户信息。整个过程中,服务端不需要保存会话数据,天然适配前后端分离。
这套项目里,我建议把 Token 过期时间设置为 2 小时,过期后让用户重新登录。不需要再做刷新 Token 的机制,否则会增加接口复杂度,毕设场景里不划算。还有一个容易忽略的点:JWT 的签名密钥不要写在代码里写死,至少放到配置文件中,部署时通过环境变量注入,避免因密钥泄露导致任意伪造 Token。
2.4 源码目录结构,先有一个整体感知
拿到源码包后,第一件事不是运行,而是先看懂目录。好的目录结构能让评审老师眼前一亮,也能让后面的扩展更容易。
text复制personal-info-system/
├── backend
│ ├── src/main/java/com/example/personalinfo
│ │ ├── controller
│ │ ├── service
│ │ ├── mapper
│ │ ├── entity
│ │ ├── dto
│ │ ├── config
│ │ └── common
│ └── src/main/resources
│ ├── application.yml
│ └── mapper
├── frontend
│ ├── src
│ │ ├── api
│ │ ├── router
│ │ ├── store
│ │ ├── views
│ │ └── utils
│ └── package.json
└── sql
└── init.sql
后端里,controller 只做参数接收和结果返回,service 装业务逻辑,mapper 层负责数据库操作,dto 放请求对象和返回对象,entity 放数据库实体,common 放统一返回结果、异常类、工具类。前端里,api 目录集中管理所有接口调用,router 是路由,store 是全局状态,views 是页面,utils 放 Axios 封装和通用函数。这样分层之后,前后端同学各干各的,边界非常清楚。
3. 数据库和SQL脚本:这套系统的数据骨架怎么搭
3.1 核心数据表:先建用户账号,再挂个人资料
数据库设计是本项目最重要的环节之一。我把表按“账号主体 + 业务扩展 + 基础字典”三个层次来规划。
| 表名 | 说明 | 关键字段 |
|---|---|---|
| sys_user | 用户账号表 | username、password、role、status、avatar |
| student_info | 学生个人资料表 | user_id、name、student_no、college、major、class_name、phone、email、id_card、political_status |
| edu_background | 教育经历表 | user_id、school_name、start_date、end_date、degree、major |
| award_record | 获奖记录表 | user_id、award_name、award_level、award_date |
| certificate_info | 证书信息表 | user_id、certificate_name、certificate_no、certificate_date |
| dict_data | 数据字典表 | dict_type、dict_label、dict_value、sort |
sys_user 表只保存账号和登录相关信息,student_info 表用来承载学生的详细个人资料,两者通过 user_id 一对一关联。为什么要把账号和个人资料拆开?因为管理员和辅导员也要登录,统一走 sys_user 表;而个人资料字段只属于学生身份,如果全部塞进用户表,后面想扩展教师、院系管理员等信息就会很别扭。
教育经历、获奖记录和证书信息都做成“一对多”的关联表。这样做的直接好处是,个人信息页面可以按照 Tab 分页展示,每类信息单独增删改查,而不是把所有信息挤在一个超长表单里。
3.2 表设计里容易被忽略的细节
有几处细节如果不注意,后面联调阶段会反复踩坑。
字符集必须使用 utf8mb4。数据库如果默认是 utf8,遇到生僻字或者表情符号时会报“Incorrect string value”错误,而 utf8mb4 是 utf8 的超集,能兼容这些字符。排序规则建议使用 utf8mb4_general_ci 或 utf8mb4_unicode_ci,一般场景下前者足够。
每个业务表都应该有 id、create_time、update_time、deleted 这几个通用字段。update_time 建议设置为 ON UPDATE CURRENT_TIMESTAMP,这样每次更新记录时自动刷新时间;deleted 字段用来做逻辑删除,避免用户误操作后数据不可恢复。
学号、用户名、手机号这些具有唯一性标识的字段,必须要加唯一索引。比如 sys_user.username 加唯一索引,student_info.student_no 也加唯一索引。索引一方面保证数据不重复,另一方面加速查询。物理外键建议不加,只在代码里做逻辑关联。MySQL 的外键约束在删除和更新时会带来很多限制,而对学院管理系统这种低频写入、高频查询的场景,逻辑外键完全够用,也更好维护。
3.3 SQL脚本中要带上初始数据和字典
一套可直接运行的 SQL 脚本里,除了建表,还要把初始数据准备好。至少包括一个管理员账号、一个辅导员账号、一个学生账号,以及性别、政治面貌、学院、专业等字典数据。
密码不能明文保存。这里用 BCrypt 对初始密码做哈希,比如统一初始化为“123456”的哈希值。BCrypt 每次生成的哈希串都不同,验证明文时用对应的匹配方法,而不是直接比较字符串。具体操作时,我习惯先在项目里写一个临时接口生成 BCrypt 串,再把生成结果填进 SQL 脚本;或者直接用一个在线工具生成,但用完后记得关闭页面,避免哈希串泄露。
字典表的设计也很关键。前端页面需要展示性别、政治面貌、民族这些下拉选项,如果硬编码在前端,后续改起来麻烦,而且接口维护不规范。通过 dict_data 表统一管理,前端根据 dict_type 查下拉选项,后端在返回学生信息时,可以把字典标签一起返回,也可以让前端单独缓存字典数据。个人更推荐前端单独调一个“根据类型查询字典”的接口,这样学生资料详情页和编辑页可以共用。
3.4 建表语句示例:核心用户表
下面给出 sys_user 表的简化建表语句,作为 SQL 脚本的参考:
sql复制CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`role` varchar(20) NOT NULL DEFAULT 'STUDENT',
`status` tinyint(4) NOT NULL DEFAULT '1',
`avatar` varchar(255) DEFAULT NULL,
`deleted` tinyint(4) NOT NULL DEFAULT '0',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户账号表';
其他业务表类似处理。注意 student_info 表要额外对 student_no 建唯一索引,并对 college、major、class_name 等常用查询字段建普通索引。很多同学建表时不加索引,数据量小的时候没什么感觉,一旦导入几千条测试数据,分页查询就明显变慢,评委试玩时也会产生负面印象。
4. 后端接口与权限控制:最容易被追问的部分
4.1 接口清单先列出来,开发才不会乱
我开发时习惯先在文档里列接口清单,再动手写代码。接口清单不仅是一张 URL 表,还要标清请求方式、权限和用途。这套项目最主要的接口如下:
| 接口 | 方法 | 权限 | 说明 |
|---|---|---|---|
| /api/auth/login | POST | 公开 | 登录并获取 Token |
| /api/auth/logout | POST | 登录 | 退出登录 |
| /api/user/info | GET | 登录 | 获取当前用户个人信息 |
| /api/user/info | PUT | 登录 | 更新个人信息 |
| /api/user/password | PUT | 登录 | 修改密码 |
| /api/student/list | GET | 辅导员/管理员 | 分页查询学生列表 |
| /api/student/export | GET | 辅导员/管理员 | 导出学生 Excel |
| /api/upload | POST | 登录 | 上传头像或附件 |
| /api/dict/type/ | GET | 登录 | 按字典类型获取字典数据 |
登录接口设计为公开权限,因为用户还没拿到 Token,其他接口都要求登录权限。学生列表和导出接口限制辅导员和管理员权限,这是学生角色不能触碰的数据边界。
4.2 统一返回结果和异常处理的理念
前后端对接最尴尬的事情是接口返回格式各写各的:有的接口返回 {code: 200, data: ...},有的直接返回业务对象,前端根本没法统一处理。这套项目里,我统一使用 Result 对象包装所有接口返回。
java复制public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}
配合全局异常处理器,业务代码里不需要到处写 try-catch。比如参数校验失败时抛出一个自定义异常,统一异常处理器捕获后返回错误码和错误信息,前端在 axios 拦截器里直接弹出提示。这样代码会干净很多,评委看源码时也能感受到工程化程度。
4.3 登录接口的实现细节
登录接口看起来简单,但它牵涉到密码校验、状态校验、Token 生成和返回值设计四个环节。
java复制@PostMapping("/login")
public Result<LoginVO> login(@RequestBody @Validated LoginDTO dto) {
return Result.success(userService.login(dto));
}
Service 里的逻辑顺序很重要:先根据 username 查用户;如果用户不存在,返回“用户名或密码错误”;如果存在但 status 为禁用状态,返回“账号已被禁用”;然后用 BCrypt 校验密码,密码错误返回同一条模糊提示,避免恶意用户通过接口爆破出有效账号;最后生成 JWT Token,返回用户 id、用户名、角色和头像。
这里有一个值得注意的设计:返回给前端的登录结果里不包含密码字段,不管密码是否加密,都不应该返回给客户端。DTO 和实体类要分开,实体类上的密码字段可以标记为 JSON 序列化忽略,或者登录返回专门用 LoginVO,只挑需要的字段返回。
4.4 更新个人信息的坑:DTO和实体要分开
个人信息更新接口是学生用得最频繁的接口,也是最容易出现越权风险的位置。如果把前端提交的数据直接映射到 student_info 实体上,那么学生可以通过手动构造请求,把自己的 role 改成 ADMIN,或者把 user_id 改成别人的 id。
正确的做法是单独定义一个 UpdateInfoDTO,只包含允许前端提交的字段:姓名、手机号、邮箱、头像、教育经历、获奖信息等。Service 层拿到 DTO 后,先通过当前 Token 获取登录用户的 id,再根据 id 去更新自己的资料,而不是信任前端传过来的 userId。角色、学号、账号状态这些字段一律不进更新接口。
参数校验也不能省。手机号用 @Pattern 校验,邮箱用 @Email 校验,学号和姓名为空时用 @NotBlank 校验。很多初学者觉得前端已经校验过了,后端不用重复做,但实际上前端校验只需要控制正常用户的操作体验,后端校验才决定系统的安全性。评审老师一问“如果别人绕过前端直接调接口怎么办”,后端校验的重要性就能讲清楚了。
4.5 文件上传与 Excel 导出的实现思路
头像上传和 Excel 导出是让系统看起来更完整的两个功能。文件上传使用 SpringBoot 的文件上传能力,限制文件大小和类型,上传成功后返回可访问的 URL,再把 URL 存到用户表或者个人资料表中。Excel 导出使用 Apache POI,后端根据查询条件查出学生列表,逐行写入 Excel 工作簿,通过 HttpServletResponse 输出流返回给前端,前端触发下载。
导出接口要注意大数据量下的内存问题。一般毕设数据量不大,直接查全部是可以的;但为了展示细节,可以加一个“最大导出 5000 条”的限制,超过时提示用户先缩小查询范围。这个细节在演示时容易被忽略,但真正运行时会避免很多尴尬情况。
5. 前端Vue页面的数据流与业务实现
5.1 路由守卫是怎么拦住未登录用户的
前端 Vue 工程所有需要登录的页面,都应该放在路由守卫的保护下。Vue Router 的 beforeEach 是标准做法:跳转之前检查 localStorage 里有没有 Token,没有 Token 就重定向到登录页;如果有 Token,但访问的是管理员专属页面,还要判断当前用户角色是否匹配。
javascript复制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()
})
这段代码虽然简单,但是系统安全体验的第一道门。它不能替代后端权限判断,但能防止普通用户通过地址栏直接跳转到管理员页面看到一片空壳或报错。
5.2 Axios封装:请求拦截器与响应拦截器
前后端联调时,如果每个页面 request 都手动拼 Token,代码会非常重复。Axios 的拦截器正好解决这个问题。请求拦截器统一从 localStorage 读取 Token,加到请求头;响应拦截器统一处理后端返回的错误码,比如 401 跳回登录页,其他错误弹出提示。
javascript复制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) {
return res
}
if (res.code === 401) {
localStorage.clear()
window.location.href = '/login'
}
Message.error(res.message)
return Promise.reject(new Error(res.message))
},
error => {
Message.error('网络异常')
return Promise.reject(error)
}
)
这样做之后,页面里的业务代码只需要调用 getInfo().then(...),不用再关心 Token 处理和异常提示,代码干净很多。有一点值得提醒:Token 失效后,前端必须清理本地存储并跳转登录页,否则用户会一直停留在页面上,点击任何按钮都是报错,体验很差。
5.3 个人信息页面:从后端数据到表单回显
个人信息页面是学生看到的主页面,通常包含四个 Tab:基本信息、教育经历、获奖记录、证书信息。页面加载时,先调用 /api/user/info 获取当前用户关联的完整数据,分别填充到主表单和子表格中;用户点击保存时,再通过 PUT /api/user/info 提交。
这里最容易出的问题是数据回显类型不一致。比如后端返回的出生日期是 yyyy-MM-dd,前端如果直接绑定到日期选择器,格式不匹配时会无法显示。解决办法是前端在接口返回后做一次格式化,或者后端在 DTO 里直接使用字符串日期,保证接口层传输格式稳定。我建议后端使用字符串日期传输,简单直接,也方便第三方系统接入。
头像上传是另一个常见坑。上传接口返回的通常是一个相对 URL,比如 /files/avatar/xxx.png,前端保存到表单时应直接存相对路径,显示时通过统一的文件访问前缀拼接成完整的图片地址。如果直接在相对路径前拼接 localhost:8080,部署到服务器后 IP 变了又要改前端代码,非常被动。
5.4 前端容易被忽视的细节:权限菜单、分页参数、加载状态
前端页面除了“能跑”,还要考虑“好用”。权限菜单要根据角色动态生成,学生登录后只看到个人中心和修改密码,辅导员登录后多一个学生管理菜单,管理员再多一个系统管理菜单。菜单数据可以从后端接口获取,也可以前端根据角色本地过滤,只要能保证和后端接口权限一致即可。
分页参数要统一。前端列表页通常传 pageNum 和 pageSize,后端返回 total 和 records 列表,前端每次翻页重新请求。需要注意页码从 1 开始还是从 0 开始,前后端约定清楚后,最好不要中间又改。加载状态也不能少,首次进入页面时应显示 loading,避免用户以为系统卡死;按钮提交时要禁用重复点击,防止用户连续双击导致重复提交数据。
别小看这些细节。答辩演示时,系统响应快、交互顺滑、没有报错弹窗,比任何口头讲解都更有说服力。
6. 从源码到能跑:环境配置与部署过程复盘
6.1 环境版本怎么对齐
我把源码包给同学时,第一步都是让每个人都尽量统一环境版本。版本不一致带来的编译问题远比业务代码本身更消耗时间。
| 依赖项 | 建议版本 | 说明 |
|---|---|---|
| JDK | 8 或 11 | 对应 SpringBoot 2.7 |
| Maven | 3.6.3+ | 用于构建后端依赖 |
| MySQL | 5.7 或 8.0 | 执行 SQL 脚本建库建表 |
| Node.js | 14 或 16 | 对应 Vue2 项目依赖 |
| npm | 6 或 8 | 随 Node.js 自带 |
如果机器上已经安装了更高版本的 JDK,比如 JDK 17,也可以尝试运行 SpringBoot 2.7,但最好在项目 pom.xml 里显式声明 java.version 并在 IDE 里切换 Project SDK,避免编译级别不一致。
6.2 后端启动:三步操作和容易出错的位置
启动后端整体上三步:导入 Maven 工程、执行 SQL 脚本、修改配置文件并启动主类。
第一步,在 IDE 中导入 backend 目录下的 pom.xml,等待 Maven 下载依赖。国内网络如果太慢,建议配置 Maven 镜像仓库,否则会卡在依赖下载上。第二步,用数据库客户端新建一个数据库,编码选择 utf8mb4,然后执行 init.sql。第三步,打开 application.yml,修改数据库地址、用户名、密码,并确认 redis 相关配置没有被启用(本项目未使用 Redis,JWT 校验直接解码 Token)。
最容易出错的是密码字段和时区配置。MySQL 8.x 的驱动和 MySQL 5.7 不一样,如果 pom 中引入的是 mysql-connector-java 8.x,连接 URL 中要配置时区:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/personal_info?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
不配 serverTimezone 在部分 MySQL 版本上会直接报错,或者在日期字段上出现时间差。另外,启动时如果发现 8080 端口被占用,可以在 application.yml 中把 server.port 改成 8081,然后前端代理同步修改成 8081,否则又会出现跨域或请求失败。
6.3 前端启动:npm install 的坑与处理
前端工程启动的过程是 npm install、npm run serve。npm install 阶段最常踩的坑是 node-sass 安装失败。这个依赖在 Node 版本升级后经常编译失败,最稳定的解法是使用 Node 14 或 16;如果还是失败,可以在 package.json 里把 node-sass 替换成 sass,并修改相应引用,动作不大,但能绕开原生模块编译问题。
另外一个必踩的点是跨域。开发环境下前端跑在 9527 端口,后端跑在 8080 端口,直接请求后端接口会被浏览器拦截。解决方式是在 vue.config.js 里配置 devServer 代理,把 /api 开头的请求转发到后端地址。
javascript复制devServer: {
port: 9527,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
配置完成后,前端代码里请求地址直接写 /api/auth/login 即可,浏览器看到的是同源请求,不会再报跨域错误。
6.4 部署到服务器时,CORS和生产环境配置
开发环境用代理解决跨域,生产环境推荐用 Nginx 反向代理。前端执行 npm run build 生成 dist 静态文件,后端执行 mvn clean package 生成 jar 包。在服务器上,Nginx 配置一个站点,将 / 指向 dist 目录,将 /api 反向代理到后端端口。
nginx复制server {
listen 80;
server_name your.domain.com;
root /opt/personal-info/frontend/dist;
index index.html;
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;
}
}
这里有两个关键点。第一,try_files 配置必须带上 /index.html,否则前端路由跳转后刷新页面会 404。第二,后端服务启动时最好用 no hup 或系统服务方式管理,不要直接用终端运行,否则关闭终端后服务就停了。我第一次部署时就没注意刷新 404 的问题,演示时切完页面一刷新直接白屏,当时真的有点狼狈。
7. 答辩展示与扩展空间:让毕设显得“有深度”
7.1 先用一条主线演示,不要念 PPT
答辩时间通常只有十分钟左右,与其把每个页面点一遍,不如走一条完整主线。我建议的顺序是:先用管理员账号登录,展示用户管理和字典维护;然后切换到学生账号,修改个人资料并上传头像;再切换到辅导员账号,按班级搜索学生,导出 Excel 并打开查看。这条路线把权限差异、CRUD、文件上传、文件导出都覆盖了,而且演示过程中自然说明了三种角色的区别。
演示前准备好测试数据和稳定环境。Wi-Fi 不稳定就开热点,不要现场安装依赖,不要现场建表。启动服务、登录账号、查询数据这些动作提前演练两遍,尤其是 Excel 导出后要确认文件能正常打开,不要在评委面前导出失败。
7.2 评委最爱追问的几个点怎么回答
评委不一定逐行读代码,但一定会从几个常见角度提问。
为什么用 JWT 而不用 Session?可以回答:前后端分离后,接口无状态更利于扩展,JWT 放在请求头里避免了跨域场景下 Cookie 的尴尬,也方便以后拆分服务。
密码是怎么存储的?可以回答:使用 BCrypt 加盐哈希,不保存明文;校验时用匹配方法,即使数据库泄露也无法反推出原始密码。
分页是怎么实现的?可以回答:MyBatis-Plus 的分页插件,前端传 pageNum 和 pageSize,后端返回 total 和当前页记录,SQL 层自动拼 limit。
如何防止学生修改自己的角色?可以回答:前端只是视觉隐藏,真正安全靠后端。接口层面只接收允许提交的字段,每次更新时从 Token 获取当前登录用户,而不是信任前端传的 userId;角色字段不在 DTO 中,Service 层还会做权限判断。
如何防止并发修改同一份资料?可以回答:可以在表里加 version 字段,更新时比对版本号,也可以利用 update_time 做最后更新时间校验。这个属于加分项,整理思路但不过度展开。
7.3 接口文档的价值在哪里,怎么展示
接口文档在开发时是契约,在答辩时是加分项。整理接口文档的时候,不用追求花哨的工具,Markdown 文件或者接口管理平台导出的网页都可以。
接口文档至少包含三块内容:接口基本信息(路径、方法、权限)、请求参数(名称、类型、是否必填、说明)、返回示例(成功和失败各一个)。我习惯把接口测试情况也整理成一张表,每个接口标注测试用例、预期结果、实际结果、是否通过,记录遇到异常后的处理办法。这比单纯贴一堆 API 列表更有说服力,能让评委看到你确实做了联调,而不是只在本地跑通了一个页面。
7.4 这套系统往深处还能怎么扩展
虽然毕设交付时可以只做基础功能,但提前梳理扩展点对答辩很有帮助。一个最常见的扩展方向是审核流程:学生修改关键信息后,提交给辅导员审核,审核通过才生效;这需要在数据表中增加审核状态字段,并新增一张审核记录表,前端增加“待审核”角标。
第二个方向是消息通知:当学生的资料审核结果出来后,站内信或邮件通知学生,技术上可以在后端引入消息推送,也可以简单用页面轮询实现。第三个方向是数据可视化:用 ECharts 展示学院各专业人数、获奖分布、政治面貌占比等统计图表,这会让系统看起来更有分析价值。第四个方向是账号双因子认证或短信验证,但工作量较大,毕设阶段量力而行。
扩展方向不必全做,选一个做深就足够。评委更关心的是你有没有意识到系统的边界和提升空间,而不是要求一个生产级产品。
最后给拿到这套源码包的同学一个建议:不要急着把它当作“能交差的作品”直接交掉。拿到项目后的第一件事,是完整读一遍 SQL 脚本和接口文档,亲手在空数据库里执行一遍初始化脚本,再用接口调试工具按业务顺序把接口全部跑通。我第一次调试时因为没先看字典表,直接启动前端,结果性别下拉框全是空的,查了半天才发现是字典数据没有初始化。这种问题不算难,但很容易让人烦躁。把这个流程走完一遍,你对系统每个模块的依赖关系会特别清楚,答辩时讲起来也会更自信。
