每到毕业季,总有一批人被毕设项目搞得焦头烂额,尤其是 Java Web 方向的学生干部管理系统这类题目,看起来平平无奇,真动手写代码才发现,从需求到数据库、从后端接口到前端页面,每一层都有坑。这套 SpringBoot + Vue 的前后端分离项目,我前后完整做过两遍——一遍是帮学生捋思路,一遍是把整套源码、SQL 脚本和接口文档重新整理成了可直接交付的版本。今天把这套系统的完整设计思路、核心实现和踩坑记录一次性讲清楚,希望能让准备做同类毕设的朋友少走弯路。
这套系统解决的痛点很明确:高校里学生干部的管理长期依赖纸质表格和 Excel,班长换届、活动报名、综测加分、评优评先这些事分散在辅导员、学生会、班级多个角色手里,数据不透明,流程也难追溯。用管理系统统一管起来之后,学生基本信息、干部岗位、活动签到、例会记录、加分审核、评优申报都能在一个平台上闭环流转。如果你是正在选毕设题目的同学,或者说手头已经拿到了类似题目但不知道从哪里下手,这篇文章可以当作战术参考。
1. 项目全景与核心需求拆解
1.1 学生干部管理到底在管什么
很多同学拿到题目第一反应是"不就是 CRUD 吗",真开始分析业务才发现情况复杂得多。学生干部管理系统不是一个简单的单表增删改查,它至少覆盖四个核心业务域:基础信息维护、组织活动运转、量化考核评价、评优流程审批。
基础信息维护包括学生档案、班级信息、干部岗位登记,这是整个系统的数据底座。组织活动运转则涉及活动发布、报名、签到、例会记录,这些数据后续会变成加分和评优的佐证材料。量化考核评价是系统的核心难点——因为每个学校对干部工作的量化标准不一样,有的看活动参与次数,有的看例会考勤,有的看专项任务完成情况,这些规则必须做成可配置的加分项,而不是写死在代码里。评优流程审批则是把最终结果走完整个审核链路:学生申报、班级初审、学院复核、最终公示,每一个节点都要留痕。
实际做需求分析时,我建议你把角色先列出来。这套系统里至少有四类角色:超级管理员负责系统配置和全局数据维护,学院管理员负责本院的学生和活动管理,学生干部是日常使用频率最高的角色,普通学生则可以查看公示信息、报名活动、提交评优申请。不同角色看到的功能菜单完全不同,这直接决定了前端路由设计和后端接口的权限控制策略。
1.2 毕设项目如何从需求落地为功能模块
需求拆解之后要落地成功能模块,这里我习惯画一张模块图来理清边界——虽然不能在这个位置画图,但你可以想象一张标准的系统功能树。系统管理模块负责用户、角色、菜单、字典的管理,这是所有后台系统的标配底座。学生管理模块包含学生档案的增删改查、班级信息维护、Excel 批量导入导出。干部管理模块管的是岗位类型和任职记录,比如某学生某学期担任班长、某学期担任团支书,这些任职记录要能查历史。
活动管理模块是这个系统里业务量最大的部分,包含活动发布、报名审核、签到统计、活动总结。量化考核模块负责加分项目配置、加分申请提交与审核、学生积分明细查询。评优管理模块则管理评优批次(比如"校级优秀学生干部"评选)、学生在线申报、两级审核、结果公示。最后是数据看板模块,用图表展示学生干部分布、活动参与率、积分排名这些统计信息,这也是答辩时最容易加分的展示点。
功能模块拆到这里,你会发现工作量其实不小,但是对于 SpringBoot + Vue 这个技术栈来说都是常规操作。真正需要动脑筋的是表结构设计和权限控制方案,这两个我做完了,后面专门拆开讲。
2. 技术选型与架构设计思路
2.1 为什么是 SpringBoot + Vue 前后端分离
现在做 Java Web 毕设,SpringBoot + Vue 几乎是默认答案,但我要说的是这个选择背后的逻辑,而不只是跟风。SpringBoot 的优势在于起步依赖和自动化配置,一个最简单的 Web 项目只需要引入 spring-boot-starter-web,不用再折腾 Tomcat 配置和繁琐的 XML 配置文件,这对毕设阶段来说能把精力集中在业务代码上。
Vue 这边我推荐用 Vue 2 + Element-UI,不推荐一上来就追 Vue 3。理由很现实:网上能查到的资料、已有的轮子和组件库大多基于 Vue 2,Element-UI 的表单组件、表格组件、弹窗组件都非常成熟,做管理后台类项目效率极高。你选 Vue 3 + Element Plus 也行,但遇到坑时排查成本会高一些,毕设时间紧张时没必要跟自己过不去。
前后端分离的开发模式还有一个好处,就是接口文档能真正派上用场——前端不用等后端全部写完才开工,只要约定好接口格式,两边可以并行开发。不过这需要在项目启动时就定好接口规范,所以接口文档不是最后补的,而是开发过程中的"契约"。后面我会专门讲这套系统里接口文档是怎么组织的。
2.2 后端技术栈明细与选型理由
后端这边我的默认组合是 SpringBoot 2.7 + MyBatis-Plus + MySQL 8 + Lombok + Hutool,认证授权用 JWT + 自定义拦截器。SpringBoot 版本我建议锁定 2.7.x,原因很朴素:2.7 是 2.x 版本的最后一个稳定大版本,文档和解决方案都齐全,很多教程都是基于它写的。如果你用 3.x,JDK 版本要求、某些第三方库的兼容性都会多出不少变量,毕设没有必要冒这个险。
MyBatis-Plus 是提升开发效率的关键——单表 CRUD 基本不用写 SQL,自带的分页插件、条件构造器、逻辑删除功能都很好用。这套系统里的学生档案查询、活动列表筛选,用 LambdaQueryWrapper 几行代码就能搞定复杂的多条件拼接。Hutool 则是一个工具类库的集合,处理日期格式、Excel 导入导出、随机 ID 生成、树形结构转换都有现成的方法,省掉大量手写工具类的时间。
JWT 做登录认证的思路是这样的:用户登录成功后后端签发一个 token 返回给前端,前端每次请求把 token 放在请求头里,后端用拦截器统一校验。相比于传统的 Session 方案,JWT 不需要在服务端保存会话状态,在前后端分离架构下更自然。学生干部系统这种量级的项目,用 JWT + 拦截器足够,没必要上 Spring Security 那一整套复杂配置——如果只是要一个登录认证和简单的角色判断,Spring Security 的配置成本反而会拖慢你的开发进度。
2.3 前端技术栈明细与工程结构
前端我用的组合是 Vue 2 + Vue Router + Vuex + Axios + Element-UI + ECharts。Vue Router 负责页面路由,Vuex 负责全局状态管理,Axios 负责请求后端接口,Element-UI 提供 UI 组件,ECharts 用来做数据看板的图表。工程结构上,我把代码按 src/api、src/router、src/store、src/views、src/components 分层组织,api 目录下按后端模块拆文件,比如 user.js 放用户相关接口,activity.js 放活动相关接口,这样前后端接口对应关系一目了然。
组件级别的设计也有一点心得。管理后台里的"列表页 + 搜索表单 + 新增/编辑弹窗"是最高频的页面模式,我建议把分页表格、表单弹窗这些抽象成公共组件或者至少统一写法。别小看这件事,当你有七八个业务模块都要做增删改查时,统一模式能省下大量重复开发时间。前端请求封装也要统一处理——所有请求走同一个 Axios 实例,带上 token,响应统一解包,遇到 401 状态码统一跳转登录页,这些逻辑写一次,全项目复用。
3. 数据库设计与 SQL 脚本实现
3.1 核心表结构如何设计
数据库设计是这套系统的地基,地基本来就不稳,后面代码写得再好也是白搭。我在设计时把表拆成了六个核心域:用户与角色、组织与学生、干部任职、活动业务、量化考核、评优审批。
用户与角色这里要区分两个概念:系统登录账号和学生档案其实是两回事。学生可能还没登录过系统,但档案已经录进去了,所以要有独立的 sys_user 表存登录账号,student 表存学生基本信息,通过绑定字段关联。如果你把登录账号字段直接塞进 student 表,后面做"未注册用户能否导入""一个学生多个角色"这些场景时就会很别扭,建议别省这张表。
组织与学生这一块包括 class_info(班级)、major_info(专业)、student(学生),这些表之间用外键关联,但实际写代码时我不建议数据库层面真的建外键约束——逻辑外键就够了,外键约束在批量操作和测试数据清理时会带来很多麻烦。干部任职表 cadre_appointment 是重点,它记录某个学生在某个时间段担任某个岗位,包含任职开始时间、结束时间、是否考核合格等字段,评优加分时主要就是查这张表。
活动业务这块有 activity(活动)、activity_registration(报名)、activity_sign_in(签到)。活动表要存发布人、活动主题、类型、开始时间、结束时间、地点、报名截止时间、人数上限、活动状态这几类字段。报名表和签到表都是活动表的子表,报名表记录学生和报名时间及审核状态,签到表记录实际到场情况,这两张表是后续统计活动参与率的数据来源。
量化考核和评优审批这两块是系统业务逻辑最复杂的部分。考核部分有 bonus_rule(加分规则配置)、bonus_record(加分记录)、review_apply(评优申报)、review_audit(审批记录)。加分规则要支持动态配置,比如"组织一次活动 +2 分""参加一次活动 +1 分""获得院级荣誉 +3 分",这些分数和规则都由管理员维护。加分记录则要记录谁申请的、为谁加的、加了多少分、依据是什么、审核人是谁。评优申报表要记录评优批次、申报人、申报理由、佐证材料链接、当前状态和各级审核意见。
3.2 SQL 脚本的初始化数据策略
SQL 脚本不能只建表,必须包含初始化数据,这是很多毕设减分的地方。我在这套系统里重点初始化了以下几类数据。第一类是管理员账号,内置一个超级管理员账号,密码通过 MD5 加盐或者 BCrypt 加密后直接以密文写入 SQL,保证脚本导入后就能登录。第二类是角色与菜单的关联数据,前端动态菜单依赖后端返回的菜单列表,这些菜单数据要在 SQL 里预置,不然系统登录进去啥都显示不出来。
第三类是基础配置数据,比如学校/学院默认信息、活动类型字典、加分规则示例数据。第四类是学生示例数据,把自己写的小脚本生成 50 条左右的学生示例记录,分数、学号、班级都做得真实一点,这样前端页面和列表展示才有内容可以看,答辩演示时也更好看。如果你有纪律,可以把密码统一初始化成同一个值,方便演示和验证。
初始化数据之外,SQL 脚本的编码和命名规范也要注意。脚本文件名建议带版本号,比如 schema_v1.0.sql、data_v1.0.sql,创建表之前要加 DROP TABLE IF EXISTS 避免重复导入报错。所有表结构注释必须完整,字段注释、表注释都要写清楚,这一点在后面写接口文档和维护时都能省很多事。排序规则统一用 utf8mb4_general_ci,别问为什么,等你遇到中文乱码问题就知道这个决定了。
3.3 字段设计与常用 SQL 技巧
字段类型选择上,有几个经验可以分享。所有表的逻辑主键建议用自增 BIGINT,不要用 UUID 或者雪花 ID,因为毕设系统并发量很小,自增 ID 既简单又方便排查数据。时间字段统一用 DATETIME,不要用 TIMESTAMP——TIMESTAMP 有 2038 年问题,而且在不同 MySQL 时区配置下行为不一致,DATETIME 更稳定。
状态字段我习惯用 TINYINT 加注释,比如活动状态 0-草稿、1-报名中、2-进行中、3-已结束、4-已取消,比用字符串更省空间查询也更快。所有需要"删除"功能的表都建议加一个 deleted 字段配合 MyBatis-Plus 的逻辑删除,而不是物理 DELETE,这样误删数据还能恢复。我在这个项目里做学生档案删除时就体验到了逻辑删除的好处,后面批量导入时不小心导重复了,把误删的数据恢复回来非常省心。
SQL 脚本里还可以预置几个常用的查询视图,比如学生积分汇总视图、活动报名统计视图。不过要注意,视图只用来查询展示,不要在视图上做更新操作。另外我会在脚本里顺手创建好常用的索引,比如按学号、按班级、按活动状态和开始时间、按学生和加分记录等场景的组合索引,这样后面联调时查询性能基本不用操心。索引的具体字段要看业务查询的 where 条件,调整好这两个地方之后数据库查询速度会快不少。
4. 后端 SpringBoot 核心模块开发
4.1 统一返回格式与全局异常处理
前后端分离项目,接口返回格式必须先定好,不然后面联调寸步难行。我的做法是定义一个统一响应类,结构为 code、message、data 三段。code 为业务状态码,比如 200 表示成功,4001 表示参数校验失败,4010 表示未登录或 token 过期,5000 表示系统异常。data 是真正的业务数据,可能是对象、列表或者分页结果。前端 Axios 响应拦截器拿到这个结构后统一判断,code 为 200 时才往下走业务逻辑,否则弹出错误提示。
全局异常处理在 SpringBoot 里用 @RestControllerAdvice 实现。我做了三个层次的异常处理:参数校验异常返回参数错误信息,业务异常返回带业务码的提示,系统异常做兜底。这里有个细节:全局异常处理别把真实异常堆栈直接返回给前端,服务端日志里打完整堆栈,接口返回统一模糊化的错误消息。对于毕设系统来说,这一个细节能让你排查问题的时候快速定位到出错原因。
实现关键代码如下,核心就是一个返回码枚举、一个响应类、一个全局异常处理器。每一层都分工明确:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result<Void> handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result<Void> handleValidException(MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldError().getDefaultMessage(); return Result.error(4001, message); } @ExceptionHandler(Exception.class) public Result<Void> handleException(Exception e) { log.error("系统异常", e); return Result.error(5000, "系统繁忙,请稍后再试"); } }4.2 登录认证与权限控制落地
登录认证我用的是 JWT,具体做法是自定义一个 JwtUtil 工具类负责生成和解析 token,token 里只放用户 ID 和角色编码这两个关键信息,过期时间设置为两小时。接着写一个 TokenInterceptor 拦截器,在 preHandle 方法里从请求头解析 token 并校验,校验通过后把用户信息放入 ThreadLocal 供后续业务方法使用。拦截器注册时要注意放行登录接口和静态资源路径,这个配置很容易漏。
权限控制这里我没有引入复杂的权限框架,而是用自定义注解 + 拦截器的方式做角色校验。自定义一个 @RequireRole 注解,标注在 Controller 方法上,拦截器里通过 HandlerMethod 获取注解,再判断当前用户角色列表是否匹配。比如评优审核接口标 @RequireRole("ADMIN,COLLEGE_ADMIN"),前端只有对应角色的用户才能调通。这种轻量级方案优点是逻辑透明、代码量少,对于毕设项目足够,而且答辩时你能讲得清楚。
前端配合这套方案的是路由守卫和动态菜单。用户登录后后端返回一个带角色标识的 token,前端再调 getUserInfo 接口获取当前用户的菜单权限列表。Vue Router 配置里,路由守卫在跳转前检查 token 是否存在、当前路由是否需要特定角色,不满足就重定向到登录页或 403 页面。菜单权限的实现则可以更简单——不同角色返回不同菜单树,侧边栏直接渲染动态菜单。如果嫌动态菜单太复杂,也可以做成按角色隐藏显示,但答辩深度上动态菜单明显更有说服力。
4.3 核心业务逻辑的编码实现
核心业务这块,我挑两个有代表性的功能讲代码实现思路。第一个是学生批量导入。前端上传 Excel 文件,后端用 Hutool 的 ExcelReader 读取每行数据,逐行校验学号是否重复、班级是否存在、必填字段是否为空,把校验失败的行和原因收集起来,最后返回导入结果,格式为总条数、成功数、失败数以及失败明细。这里不能做到一半失败直接返回错误,必须给用户一个完整的汇总报告,这是实际使用场景里最容易被吐槽的设计问题。批量导入底层的写法是:
public ImportResult importStudents(MultipartFile file) { List<Student> studentList = new ArrayList<>(); List<ErrorRow> errorRows = new ArrayList<>(); ExcelReader reader = ExcelUtil.getReader(file.getInputStream()); List<List<Object>> rows = reader.read(); for (int i = 1; i < rows.size(); i++) { List<Object> row = rows.get(i); // 逐行解析并校验,错误信息记录到 errorRows // 正常数据放入 studentList } studentService.saveBatch(studentList); return ImportResult.build(rows.size() - 1, studentList.size(), errorRows); }第二个是评优申报流程。学生在前端提交评优申请,后端 Controller 接收后先把申请记录插入 review_apply 表,同时在 review_audit 表插入一条待审核的记录,流程状态设为"待班级初审"。班级管理员审核通过后,流程状态改为"待学院复核",学院复核通过后状态改为"已通过",整个状态流转用状态机模式控制,用代码把允许的流转路径限定好。加分记录的逻辑同理:规则配置好了,学生申请加分时,后端要校验条件是否满足、该规则是否被允许学生自申请,校验通过后再进入审核环节。这里最需要注意的一点是,状态流转的每一步都要更新操作时间和操作人,这是审核类系统的基础要求。
4.4 文件上传、Excel 导出与统计接口
系统里涉及文件上传的场景有两个:评优申报时的佐证材料上传,以及活动宣传图的展示上传。文件上传我用的方案是本地存储——在服务器上配置一个上传目录,存储时生成随机文件名防止路径穿越和重名,数据库里保存相对路径,访问时通过映射配置把目录映射为 URL 路径。生产环境当然应该用 OSS,但毕设项目用本地存储足够,让答辩老师看到你有文件上传能力就行。
Excel 导出用 Hutool 的 ExcelWriter,比如导出学生干部名单时,查询出数据后一行一行写入 Excel,设置表头字体加粗、自动列宽、冻结行。有一点值得注意:导出数据量很大时要分批查询写入,避免一次性把所有数据加载到内存引发 OOM。对于学生干部管理系统这个量级,单次导出几千条直接写也没问题,但批量导出多个表时,该分批还是要分批。
统计接口是答辩演示时的亮点。比如按班级统计干部人数、按月份统计活动数量、按活动统计报名率、按学生统计积分排名,这些都是典型的"一个 SQL 搞定"的聚合查询。用 MyBatis-Plus 写这些统计查询时,可以直接用 QueryWrapper 的 select 方法配合 apply 写聚合函数,也可以用自定义 SQL 在 Mapper 里写。我的经验是统计类接口用自定义 SQL 更直观,代码好读,性能也可控。
<select id="selectActivityStats" resultType="java.util.Map"> SELECT DATE_FORMAT(start_time, '%Y-%m') AS month, COUNT(*) AS activityCount FROM activity WHERE deleted = 0 GROUP BY DATE_FORMAT(start_time, '%Y-%m') ORDER BY month </select>5. 前端 Vue 页面与交互实现
5.1 项目脚手架与工程化配置
前端工程我用 Vue CLI 创建,创建后第一时间做好三件事:配置代理、封装请求、规划布局。vite 时代的脚手架另说,Vue CLI 在做管理后台时依然很顺手。配置文件核心是 devServer 代理,开发环境把 /api 前缀的请求代理到后端的 8080 端口,这样前后端联调时不会有跨域问题——别在项目里到处用 cors 插件硬解决跨域,那只是掩盖问题,用代理才是最合适的方法。
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };请求封装的思路我在前面提过,核心就是创建统一的 Axios 实例,设置 baseURL 为 /api,请求拦截器里把 token 从 localStorage 取出放到 Authorization 头,响应拦截器里解包统一响应格式。code 为 4010 时清理本地登录状态并跳转登录页。这个封装在整个项目里只用写一次,所有页面模块复用,属于前端工程化的基础功课。
5.2 登录页与动态菜单实现
登录页的实现是前后端接口打通的第一关。页面交互不复杂,一个表单输入账号密码,调登录接口后成功后把 token 存到 localStorage,然后调 getInfo 接口获取用户基本信息,再存进 Vuex。关键在 getUserInfo 接口返回的数据结构里要包含 roles 和 menus,前端拿到 menus 后先转成路由结构,再动态注册到 Vue Router 里,侧边栏同步渲染菜单。
这里容易踩的坑是页面刷新后路由丢失。因为动态路由是登录后才 addRoutes 加进去的,刷新页面后 Vuex 里的数据重置,路由就没了。解决方法是提供一个初始化方法,在根实例 created 阶段检查本地有没有 token,有的话重新拉取用户信息和菜单,重新动态注册路由。这个逻辑放到 router.beforeEach 里做,写一次就解决了。
动态菜单数据格式上,我建议后端返回树形结构,节点包含 path、name、title、icon、children,前端直接遍历渲染 Element-UI 的 el-menu 组件。这个结构直接映射路由配置,后端维护 menu 表时也能方便地管理不同角色可见的菜单项。如果菜单多级嵌套,递归渲染组件要注意节点 index 的唯一性,我用的是完整路由路径做 index,避免菜单跳转错乱。
5.3 学生管理页与活动管理页核心功能
学生管理页是系统里最典型的列表页。页面结构是上方搜索表单(学号、姓名、班级、状态)、中间表格、下方分页。搜索表单提交后拼接查询参数请求列表接口,表格列展示学号、姓名、性别、班级、政治面貌、职务、联系电话、状态。新增和编辑共用一个弹窗表单,通过 dialog 的 title 区分是新增还是编辑,表单校验规则用 Element-UI 的 rules 配置,接口提交成功后刷新列表。
批量导入导出功能在这个页面的使用频率非常高。导入按钮选择文件后调后端导入接口,拿到结果后在页面上弹出一个结果展示层,把成功数和失败明细列出来。导出按钮直接调用文件下载接口,后端设置响应头 Content-Disposition 让浏览器触发下载。这里要注意 Axios 下载二进制文件时需要设置 responseType 为 blob,否则下载下来的文件会乱码,这个坑我踩过,第一次导出出来的 Excel 全是乱码,排查半天才发现是没有设置响应类型。
活动管理页的逻辑要复杂一些。列表页展示活动信息之外还要根据登录角色显示不同操作按钮,比如管理员可以编辑、删除、审核报名,学生只能报名或取消报名。活动详情页要展示活动基本信息、报名入口、签到二维码或者签到码、参与名单。报名状态的数据交互是:页面加载时调活动详情接口同时查询当前用户对活动的报名状态,根据状态去渲染按钮。活动创建和编辑的表单里,使用 el-date-picker 选择时间范围,提交时拆成开始时间和结束时间两个字段传后端。
5.4 数据看板与 ECharts 图表展示
数据看板放在首页,是整套系统的门面。统计维度我做了四个:干部总数与岗位分布、活动月度趋势、班级活动参与率排行、学生积分 TOP10。使用 ECharts 时,在组件 mounted 里初始化图表实例,请求统计接口拿到数据后通过 setOption 更新图表。图表容器必须在页面渲染完成后才能初始化,所以在 mounted 里操作,同时要注意组件销毁时 dispose 实例,避免内存泄漏。
Tab 标签页切换图表时的坑比较多,比如图表初始宽度为 0 导致图表只占一半宽度、切换后图表不显示等。我的处理方法是:所有图表统一放到一个方法 initCharts 里,标签页切换后使用 nextTick 重新初始化或调用 resize 方法。为了不重复请求接口,也可以在拿到数据后缓存一份,切换标签时只重渲染图表。数据看板不适合放太多图表,四五个核心指标就够了,界面清爽,演示效果好。
6. 接口文档设计与前后端联调
6.1 接口文档的标准化组织方式
接口文档在这套系统里不是可选项,而是必须交付的产物。我用 Apifox 管理接口,按后端模块分组。每个模块的分组下有列表、详情、新增、修改、删除等标准接口,以及业务特有的接口,比如登录、导出、导入、审核、统计等。接口文档的每个接口必须包含五要素:接口地址、请求方法、请求参数、响应示例、错误码说明。有些同学喜欢用 Swagger 自动生成文档,我认为自动生成适合自用,交付给前端同学或者答辩老师时,Apifox 这类工具组织的文档结构更清晰。
响应示例要给出真实可用的 JSON 结构,不要给省略版。请求参数要区分为 header 参数、query 参数、body 参数,body 参数里的对象嵌套结构也要标注清楚。状态码约定在文档开头单独说明。我在实际开发中先把接口规范列出来,前后端各拿一份按规范开发,接口文档随着后端代码同步更新,这样基本不需要花额外时间补文档。
6.2 核心接口清单与调用时序
登录模块两个接口:POST /api/auth/login 接收账号密码返回 token 和用户基础信息,GET /api/auth/info 返回用户详情、角色和菜单。学生管理模块五个接口:GET /api/students 分页列表、GET /api/students/{id} 详情、POST /api/students 新增、PUT /api/students/{id} 修改、DELETE /api/students/{id} 删除,外加 POST /api/students/import 导入和 GET /api/students/export 导出。
活动管理模块接口有:POST /api/activities 发布活动、GET /api/activities 分页列表、GET /api/activities/{id} 详情、POST /api/activities/{id}/signup 报名、POST /api/activities/{id}/sign-in 签到、GET /api/activities/{id}/registrations 报名名单。评优管理模块的接口则包括评优批次管理、学生申报、两级审核、公示列表。每个模块的接口写完代码后我基本都要在 Apifox 里跑一遍,确保返回示例真实可参考。
调用时序上有一条主链路最容易出问题——学生从登录到报名活动再到积分的完整流程。登录获取 token,带 token 访问活动列表,点击报名提交报名请求,活动结束后管理员确认参与,系统按规则生成加分记录,学生侧积分变动。这条链路跨了多个模块,联调时最容易发现接口参数对不上、状态字段不一致的问题。建议项目收尾前专门把这条主流程跑通三遍以上才算稳。
6.3 联调阶段的分工与验收标准
前后端联调阶段,建议按模块推进不要全部写完再联调。先跑通登录和用户信息接口,确认 token 流程没问题后,再逐模块推进学生、活动、考核、评优。每次联调前把接口文档更新到最新版本,后端自测过的接口标记为"待联调"或"已通过",前端按文档调用过程中发现的任何不一致都记录成待办,修完再验证。这个流程虽然听起来简单,但能避免"前端传参名字和后端字段对不上""返回数据类型不对"这类低级问题反复出现。
验收标准方面,功能层面每个页面必须完成增删改查的完整流程,权限控制要验证不同角色登录后的可见差异,数据统计接口的数据要与列表页数据一致。还应该检查边界情况,比如翻页到最后一页删除数据后返回空页的处理、搜索无结果页面的提示、必填字段为空时的校验提示。这些看起来琐碎,实际都是在答辩演示时被老师经常提问的点。
7. 常见问题与排查实录
7.1 数据库连接与初始化失败
SQL 脚本导入失败是我见到过频率最高的问题。常见的原因有三个:一是脚本里有中文注释但导入工具没有声明 utf8mb4,导入后中文乱码;二是 MySQL 版本差异导致某些字段类型不兼容,比如旧版本不支持 utf8mb4 或者 JSON 类型;三是脚本中表创建顺序不对,外键依赖的表还没建好就先创建了子表。解决办法是导入前检查数据库字符集、执行前先看报错信息,最稳妥的方式是把建表语句按依赖顺序排列,并在开头加上 DROP TABLE IF EXISTS。
数据库连接失败的排查路径很固定:先确认 MySQL 服务是否启动,再确认数据库和用户是否存在,然后检查连接 URL 中的地址、端口、库名是否正确,最后看密码是否匹配。SpringBoot 配置文件里顺便说一句,用户名和密码最好放 application-dev.yml 中用本地配置,正式提交项目时把敏感信息移除或者是说明修改方法,避免交源码给老师时泄漏自己数据库账号。
7.2 跨域、token 失效与接口报错
开发环境下最容易遇到的是跨域报错。如果你发现浏览器控制台提示 CORS 错误,先检查是否走了前端代理,再检查后端有没有额外配置跨域。用 Vue CLI 代理后基本不需要后端配置 CORS,两部分都配置反而会产生冲突,比如预检请求被拦截或者响应头重复。我的做法是前端代理为主,后端不配 CORS,保持整套链路干净。
token 失效的表现是请求返回 4010,通常原因是 token 过期、token 被手动清空、或者后端 JWT 密钥没对齐。排查时先看控制台打印的响应 code,再看 localStorage 里的 token 是否存在,最后检查后端拦截器的放行路径配置。有的同学把 token 存到了 sessionStorage,刷新页面后 token 还在但是 Vuex 里的用户信息丢了,就会出现页面混乱的情况,统一用 localStorage 存会省心很多。
接口报错 500 时,首选打开后端控制台看异常堆栈。这里有个实用技巧:开发阶段把后端的日志级别调到 DEBUG,能直接看到 MyBatis-Plus 生成的 SQL,排查参数绑定、字段名拼写问题特别有用。我调试学生列表接口时,遇到过前端传了 sortField 但后端和数据库字段大小写不一致的问题,全靠 SQL 日志一眼定位。
7.3 常用的排查手段和流程优化
针对这套系统可以拿几个最常出问题的模块做针对性优化。学生导入模块可以加一个模板下载功能,用户先下模板,按模板填写再上传,导入逻辑就很难出问题。活动签到模块如果使用二维码签到,前后端要约定好二维码内容格式,我用的是活动 ID + 签到码,签到码每次活动开始时由后端生成并返回,避免二维码被提前截图滥用。评优流程的状态机建议把所有允许的状态变更列表集中维护在一个 Map 或枚举里,新增状态时不会改漏。
还有一个容易被忽略的是统一时间格式。前后端时间字段如果格式不统一,列表展示会很难看,比如出现"2025-05-10T08:00:00.000+00:00"这种带时区的格式。我的处理方式是后端全局配置 Jackson 的日期格式为 yyyy-MM-dd HH:mm:ss,前端对日期再配合 Element-UI 的格式化函数做兜底,两边都处理好,时间展示就完全可控了。实际做完这个项目,我最直观的体会是管理类系统的难点从来不是某个技术点,而是数据流转的一致性——用户怎么来、角色边界在哪、一条数据从创建到归档经历了哪些状态变更,把这些梳理清楚,代码写起来反而很顺。希望这份拆解能把你的毕设之路铺得平一点,遇到具体问题再回头对照相关小节排查,大概率能找到答案。