
每年三四月份后台总会收到一堆关于毕业设计选题的私信今年问得最集中的就是“大学新生报到系统”这类题目。说实话这个题被问得多不是没道理的它不偏门、业务清晰、前后端都有内容可写又不会难到做不完。我手上有几届学生先后用 SpringBoot Vue MySQL 做过类似的系统踩过的坑、答辩被追问的问题今天一次性整理出来。这篇不是那种逐行敲代码的保姆级教程而是把一套完整的新生报到系统从业务建模、数据库设计、后端接口开发、前端页面搭建到论文撰写和部署的决策链路讲清楚标出哪些地方容易翻车、该怎么预防。适合正在做毕设的学生也适合刚工作不久、想拿这个题练手的前后端新人。1. 新生报到系统到底在做什么把线下排队场景搬到线上1.1 先理解真实痛点再谈功能设计高校开学季的报到现场经历过的人都懂体育场支起一排帐篷新生拖着行李箱先排队交学费、再排队领宿舍号、然后跑去系里核对个人信息每一个窗口都要排十几分钟。遇上家长比学生多的热门专业整个流程能拖一整天。大学新生报到系统要解决的核心问题就是把这些原本在线下窗口排队完成的手续拆到线上。新生入学前在家里就能完成预注册、填资料、选宿舍、确认缴费状态到校后只需要出示报到码或身份证件工作人员在系统里一键确认流程就走完了。这个场景听起来简单但它天然涵盖了用户管理、角色权限、信息增删改查、状态流转、报表统计等一整套常规业务系统的要素非常适合拿来做毕业设计。1.2 系统角色与核心流程拆解做系统之前先把角色和流程画明白。我一般建议学生在纸上画一遍业务流转图画完再去建数据库表顺序反了后面必返工。系统主要有四类角色学生入学前登录系统完成预注册、填写个人信息、选择宿舍到校后出示身份信息完成现场报到。学院管理员/辅导员负责审核学生信息、确认学生现场报到、查看本学院报到进度。财务人员维护缴费状态可与报到流程联动也可以只做状态标记。系统管理员维护最基础的数据包括学院、专业、班级、宿舍楼栋、用户账号还能配置报到流程的启停。核心流程对应的是业务主线新生收到录取通知书在规定时间内登录系统完成线上预注册完善个人资料并提交审核学院管理员审核通过后学生在线选择宿舍并确认缴费状态报到当天到校工作人员扫描或确认身份系统记录报到时间整个流程结束。这条主线理顺之后功能清单基本就浮出水面了。这里给一份可以直接用的功能模块表模块核心功能涉及角色用户与权限管理登录、角色区分、密码重置、账号禁用系统管理员新生信息管理录入/导入学生档案、信息审核、修改维护管理员、学院管理员专业班级管理学院-专业-班级三级结构维护系统管理员宿舍分配管理宿舍楼栋/房间维护、学生选宿舍、调宿学生、管理员报到流程管理预注册开关、现场报到确认、进度查询管理员、学生统计报表报到率、各学院人数、专业分布、缴费情况管理员、学院管理员这套模块设计起来不算难但胜在结构完整。做毕设时老师想看到的是你对业务的理解是不是成体系而不是功能堆得越多越好。1.3 为什么这个题是毕业设计的“安全区”每年都有人问我这个题是不是太简单了我的判断是它属于上限不高、但下限很稳的选题。所谓下限很稳是指它业务逻辑清晰即使水平一般也能老老实实把每个功能做出来论文素材不会缺。所谓上限不高是因为它不像热门电商系统那样需要处理高并发、分布式事务这些重型议题想出彩就得靠细节。正因为如此这个题特别适合用来体现“工程化思维”分层结构、异常处理、并发控制、数据库约束、前端路由权限这些细节做好了一样能拿高分。2. 技术栈选型的理由为什么偏偏是 SpringBoot Vue MySQL2.1 SpringBoot 解决的是什么问题十年前做毕设很多人用的是传统的 SSMSpring SpringMVC MyBatis组合光是繁琐的 XML 配置文件就能劝退一批人。SpringBoot 的核心理念是“约定大于配置”它内嵌 Tomcat不需要单独部署 Web 容器一个java -jar就能启动项目。这对毕设周期来说非常关键省下的是最枯燥的环境搭建时间投入的是业务和代码设计。另外SpringBoot 生态太成熟了。整合 MyBatis、整合分页插件、整合 JWT 鉴权网上资料一搜一大把遇到问题容易找到解决办法。这些看似不起眼的“可搜索性”在赶论文时就是救命稻草。2.2 Vue 前端为什么不是 JSP 或纯静态页面如果只是做给老师看用 JSP 模板渲染确实更快。但题目写在“SpringBootVueMySQL”本身就是默认了前后端分离的架构。Vue 的核心优势是组件化开发。新生报到系统里有大量重复出现的元素表格、表单弹窗、步骤条、统计卡片这些都能抽成组件复用。配合 Vue Router 管理页面跳转、Vuex 或 Pinia 管登录状态再加上 Element UI 提供现成的界面组件一个没有专业前端经验的在校生也能在两三周内搭出一套干净的后台界面。Vue 2 还是 Vue 3 怎么选我的建议是求稳选 Vue 2 Vue CLI Element UI这套组合教程最多遇到的坑几乎都有人踩过并写了解法。如果你的指导老师明确要求 Vue 3那可以用 Vue 3 Vite Element Plus代码写起来更简洁但要注意部分第三方插件的兼容性需要自己排查。2.3 MySQL 与持久层框架的搭配MySQL 不必多解释免费、轻量、安装教程铺天盖地最重要的是它完全够用。新生报到系统的数据量在真实场景下也就几万条MySQL 没有任何压力。持久层我推荐 MyBatis Plus而不是纯 MyBatis 或 JPA。理由是 MyBatis Plus 把单表 CRUD 全部封装好了BaseMapper里自带增删改查方法可以少写大量重复 SQL而需要写复杂 SQL 的报表统计、多表联查它又保留了手写 XML 的灵活性。对时间和精力都紧张的毕设阶段来说这是性价比最高的选择。分页功能是后台管理系统不可避免的。MyBatis Plus 自带分页插件一行配置就能实现比手写LIMIT和count要省事得多。但我也建议学生在论文里能把底层原理讲清楚知道它对 ThreadLocal 和拦截器的使用逻辑答辩时才不会被问住。2.4 别乱加“看起来高级”的技术每次看到有学生想把 Redis、RabbitMQ、Elasticsearch 塞进这种系统我都要劝一句加技术栈的前提是业务真的需要并且你能把原理说透。一个新生报到系统真实并发量可能只有几十用 Redis 做缓存带来的性能提升完全感知不到却平白多了一道缓存和数据库的一致性维护部署时又多了一个进程要启动。答辩老师如果问一句“缓存穿透怎么办”“缓存和数据库不一致怎么解决”答不上来反而暴露短板。技术选型最忌讳的就是“为了用而用”这一点在论文里也要写得克制。3. 数据库设计报到系统里最容易翻车的表关系3.1 核心表清单与设计思路数据库是这类管理系统的地基。很多学生一上来就堆表想到什么加什么最后表之间关联乱成一团。我习惯先列主表清单再逐表设计字段最后画 ER 图核对关系。一套完整的表结构至少包含这些sys_user登录账号表存用户名、密码、角色、状态。student学生基础信息表存学号、姓名、身份证号、联系方式、家庭住址、学院、班级等。college / major / class学院表、专业表、班级表构成三级组织架构。dormitory宿舍表存楼栋、房间号、床位数。student_dormitory学生与宿舍的关联表方便后续调宿和退宿。register_record报到记录表核心业务表记录学生报到时间和状态。system_config系统配置表用来控制报到流程各环节的开关。这些表之间不是靠一个外键硬拼而是通过逻辑关联形成业务闭环。比如学生表里存user_id关联账号表存class_id关联班级表班级表里存major_id关联专业表专业表里存college_id关联学院表。这样从一名学生出发可以顺着链路查到它的学院、专业、班级也能在报表统计时按任意层级聚合。3.2 用户表与学生表为什么必须拆开第一次做这类系统的同学最容易犯的错是把所有字段堆在一张表里账号、密码、学号、姓名、学院、班级全部塞进sys_user觉得这样省事。问题是登录账号是通用认证信息任何角色都需要而学号、班级、宿舍这些是学生特有的业务属性。如果全部混在一起将来想给辅导员建账号就得塞一堆空字段想查“哪些学生还没注册账号”SQL 写起来也别扭。拆开之后sys_user只管认证和角色student表通过user_id和账号表关联。辅导员、管理员账号不需要在student表里有记录数据模型就干净了。这一点不仅在论文里好解释实际开发时扩展也方便。3.3 宿舍分配的关系设计宿舍和学生是典型的“一对多”一个宿舍可以住多人一个学生在简化场景下只有一个宿舍。为了预留调宿的可能我建议用独立的关联表student_dormitory而不是在学生表里直接加一个dormitory_id字段。关联表的设计还能容纳更多业务字段比如入住时间、离开时间、备注。更重要的是可以在student_id上加唯一索引从数据库层面保证一个学生不会有两条在宿记录。这个细节写在论文里是加分项说明你考虑了数据完整性约束。3.4 关键建表 SQL 与多表关联查询示例建表时字符集一定要用utf8mb4因为 MySQL 的utf8并不是真正的全量 UTF-8存不了生僻字和 emoji。新生姓名里出现生僻字的情况并不少见别在这里踩坑。CREATE TABLE student ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, user_id BIGINT NOT NULL COMMENT 关联账号ID, student_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 性别 0未知 1男 2女, id_card VARCHAR(18) DEFAULT NULL COMMENT 身份证号, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, college_id BIGINT NOT NULL COMMENT 学院ID, class_id BIGINT DEFAULT NULL COMMENT 班级ID, address VARCHAR(255) DEFAULT NULL COMMENT 家庭地址, emergency_contact VARCHAR(50) DEFAULT NULL COMMENT 紧急联系人, emergency_phone VARCHAR(20) DEFAULT NULL COMMENT 紧急联系电话, status TINYINT DEFAULT 0 COMMENT 信息审核状态 0待审核 1通过 2驳回, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_student_no (student_no), KEY idx_user_id (user_id), KEY idx_class_id (class_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生基础信息表;多表查询是本系统的常态。例如后台学生列表要展示“学院、专业、班级”名称就要把三张表关联起来。我用LEFT JOIN而不是INNER JOIN是为了保证即使班级信息缺失学生主记录也不会被过滤掉。SELECT s.id, s.student_no, s.name, s.gender, s.phone, c.class_name, m.major_name, og.college_name FROM student s LEFT JOIN class c ON s.class_id c.id LEFT JOIN major m ON c.major_id m.id LEFT JOIN college og ON m.college_id og.id WHERE s.name LIKE CONCAT(%, #{keyword}, %) ORDER BY s.id DESC LIMIT #{offset}, #{pageSize};3.5 时间字段与状态字段的规范时间字段统一用DATETIME还是TIMESTAMP我建议统一用DATETIME范围更宽也不受 2038 年问题影响。默认值直接写CURRENT_TIMESTAMP更新时间用ON UPDATE CURRENT_TIMESTAMP这样每次修改记录时不需要手动维护时间字段。状态字段要用TINYINT而不是VARCHAR存中文这是规范问题。数据库里存的是数字页面显示时用枚举或字典翻译。好处是查询效率高、不容易输入脏数据以后扩展状态也方便。4. 后端落地核心接口的代码组织与并发控制4.1 分层结构与统一返回结果后端我要求项目必须分层controller、service、mapper再加entity、dto、vo几个辅助包。这不是为了凑代码量而是答辩时老师一定会问哪怕只问一句“你项目怎么组织的”一个清晰的分层结构就能体现工程意识。所有接口的返回结果统一封装避免有的接口返回对象、有的返回Map前端联调时痛苦不堪。一个极简的ResultT长这样Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T ResultT error(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }统一返回结果的意义在于无论登录、查询列表还是保存数据前端 axios 拿到的是同一个结构处理逻辑只用写一次。配合全局异常处理器把业务异常转换成Result.error返回接口层就不需要每个方法都写try-catch。4.2 登录鉴权JWT 还是 Session前后端分离架构下Session 方案需要处理跨域携带 Cookie 的问题比较麻烦。我更推荐 JWT用户登录成功后后端生成一个带过期时间的令牌返回前端前端每次请求在请求头里带上Authorization: Bearer token后端用拦截器解析验证。JWT 的好处是无状态不用在服务端存登录会话水平扩展时天然友好缺点也很明显就是无法主动吊销只能等它过期。在毕设场景里这个缺点可以接受而且 JWT 本身就是高频面试点写在论文里比写 Session 更能体现技术深度。实现时核心是两个组件一个 JWT 工具类负责生成和解析令牌一个拦截器负责拦截需要登录的接口从请求头取出令牌校验身份如果是管理员或辅导员接口再校验角色权限。登录接口本身要放行不能被拦截器挡住。4.3 防止重复报到事务、唯一索引与并发窗口现场报到是整个系统的核心动作它的关键问题不是写入慢而是“防止重复”。一个学生到窗口被扫了两次如果代码是“先查有没有记录没有就插入”在高并发下存在经典的时间窗口两个请求同时查到没有记录然后同时插入产生两条报到记录。解决思路是从两个层面同时下手应用层在register方法上加Transactional先检查该学生是否已有报到记录没有再插入。这个方法在低并发下是没问题的。数据库层在register_record表的student_id字段上加唯一索引。这是最后一道防线就算应用层出了问题数据库也会拒绝第二条记录的插入直接抛异常再由全局异常处理器捕获并提示“该学生已报到”。这个设计一定要写进论文里它是整个系统里最有说服力的技术点之一。你可以明确说清楚“为什么不能只靠应用层判断”因为先查后插存在并发间隙唯一索引才是强保证。Override Transactional(rollbackFor Exception.class) public void register(RegisterRequest request) { Long studentId request.getStudentId(); int count registerRecordMapper.countByStudentId(studentId); if (count 0) { throw new BusinessException(该学生已完成报到请勿重复操作); } RegisterRecord record new RegisterRecord(); record.setStudentId(studentId); record.setRegisterTime(new Date()); record.setOperatorId(request.getOperatorId()); record.setStatus(1); registerRecordMapper.insert(record); // 更新学生的报到状态 studentMapper.updateRegisterStatus(studentId, 1); }4.4 报表统计分组聚合 SQL 的正确写法统计模块最常写的是“按学院统计报到人数和报到率”。这里要注意LEFT JOIN的使用场景学生表是主体报到记录表是附属未报到的学生不应该被过滤掉。SELECT og.college_name, COUNT(DISTINCT s.id) AS total_students, COUNT(DISTINCT rr.student_id) AS registered_students FROM student s LEFT JOIN college og ON s.college_id og.id LEFT JOIN register_record rr ON rr.student_id s.id AND rr.status 1 GROUP BY og.college_name;写这段 SQL 有几个关键点。第一GROUP BY后面只写分组字段MySQL 5.7 之后默认开启了ONLY_FULL_GROUP_BY查询列必须出现在GROUP BY里或者是聚合函数否则直接报错。第二DISTINCT是为了防止学生表和报到记录表有一对多关联时产生重复计数。第三条件rr.status 1写在JOIN的ON里而不是WHERE里否则会变成INNER JOIN把未报到的学生过滤掉。5. Vue 前端工程化落地从脚手架到接口联调5.1 环境初始化与依赖安装前端要跑起来第一步是装 Node.js 环境。Vue 2 Vue CLI 建议用 Node 14 或 16太新的 Node 版本比如 18、20在安装某些旧依赖时可能有兼容问题。装好之后执行vue create register-front cd register-front npm install element-ui axios vue-router3 vuex3 npm run serve这里要特别提醒Vue 2 对应的vue-router必须是 3.xvuex必须是 3.x装成 4.x 版本会导致项目直接报错。很多同学在这里卡了一整天其实只是版本没对齐。Element UI 的引入有两种方式全量引入和按需引入。毕设项目我建议全量引入省事反正打包体积大几 MB 对最终交付没有实质影响。但论文里可以提一句“生产环境可按需引入以减少打包体积”显示你懂这个优化方向。5.2 路由与登录守卫的实现思路前端路由的核心不只是页面跳转还要处理“未登录不能看”“角色不同能看的页面也不同”这类权限问题。登录成功后把 token 存到localStorage路由跳转之前用全局前置守卫判断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(/401); return; } next(); });这段逻辑虽然简单却体现了前端层面最基础的权限控制。动态路由的高级玩法按角色从后端拉取路由表可以不做但把静态路由的权限拦截讲清楚已经足够在答辩时交代了。5.3 axios 封装与跨域代理axios 不能直接在生产代码里到处this.$http.get先封装一层。核心作用是设置基础 URL、统一从请求头带 token、统一处理接口返回码和异常提示。import axios from axios; import { Message } from element-ui; import router from /router; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; }); request.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) { localStorage.removeItem(token); router.push(/login); } Message.error(error.message || 请求失败); return Promise.reject(error); } ); export default request;开发环境的跨域问题不需要后端开启 CORS用前端代理更干净。在vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } };原理是让前端开发服务器把/api开头的请求转发到后端地址浏览器只和前端服务器通信不存在跨域。这个知识点在热词里有“vue安装及环境配置”“vue安装依赖”属于高频卡点建议特别留意。5.4 核心页面开发与路由传参的坑系统核心页面可以拆成这么几个登录页、仪表盘首页、学生信息管理页、报到管理页现场确认、宿舍分配页、统计报表页、系统配置页。表格类页面直接用 Element UI 的el-table加上搜索表单和分页组件表单弹窗用el-dialogel-form报到流程用el-steps步骤条展示进度统计页用v-charts或 ECharts 画柱状图和饼图。列表页跳转详情或编辑页时会涉及路由传参。我踩过的坑是用params配合动态路由传参页面刷新后参数丢失。所以在毕设项目里我统一推荐用query传参缺点是参数会出现在 URL 上但对非敏感数据来说完全没问题。this.$router.push({ path: /student/detail, query: { id: row.id } }); // 详情页接收 const studentId this.$route.query.id;6. 论文撰写与答辩准备把系统“包装”成完整工作量6.1 论文结构怎么安排不少学生代码写完了论文憋不出来主要是因为平时没有记录文档素材。按照学校常规的毕设论文框架建议套这个结构第一章 绪论写背景、意义、国内外研究现状、论文结构安排。第二章 相关技术介绍SpringBoot、Vue、MySQL、MyBatis Plus每个写清楚“是什么、为什么选它”。第三章 需求分析业务流程分析、功能性需求、非功能性需求性能、安全、易用性。第四章 系统设计系统总体架构、功能模块设计、数据库表设计。第五章 系统实现按模块贴关键代码配页面截图讲实现逻辑。第六章 系统测试测试环境、功能测试用例、测试结果分析。核心原则是凡是写了“系统设计”的地方必须有图凡是贴了代码的地方必须有解释。老师们最反感的就是满页代码没有任何文字说明。6.2 图表怎么画最稳妥画图工具有很多我推荐 draw.io 或 ProcessOn画完直接导出图片嵌入论文。需要准备的图至少有四类系统总体架构图前端、后端、数据库三层、系统功能结构图树状结构展示模块、数据库 ER 图、核心业务时序图报到流程。如果时间充裕再补一张部署架构图。图表要注意编号规范图和文字之间要有“如图所示”的引导句让老师看图前知道应该关注什么。6.3 创新点怎么包装才不虚“创新点”不是让你发明新算法而是把你做的合理设计用专业术语表达出来。新生报到系统可以提炼四个方向报到流程可配置化通过配置表控制各环节的启用与关闭而不是把流程写死在代码里。防重复报到的双保险机制应用层事务和数据库唯一索引结合。基于 JWT 的无状态登录鉴权与角色权限控制。前后端分离架构下统一响应结构、全局异常处理等工程化设计。包装创新点时要注意你能讲多少就写多少。一个能讲透的细节好过三个背不熟的术语。6.4 高频答辩问题清单与应对思路根据我这几年的观察答辩老师翻来覆去爱问的问题就那几类提前准备完全背得下来为什么用 SpringBoot 而不用传统的 SSM 答SpringBoot 简化配置、内嵌容器、自动装配开发效率高生态成熟更适合快速交付的项目。数据库为什么这么设计是否符合三范式 答不仅是三范式还要讲反范式。比如宿舍分配用关系表而不是在学生表存一个宿舍 ID是为了支持多次调宿报表查询用 LEFT JOIN 适当冗余字段是空间换时间。如果同一时间几千个新生同时报到系统怎么办 这是个陷阱题。别慌先承认当前系统面向毕设规模然后话锋一转即使在这个规模下我也通过数据库唯一索引、事务、分页查询等手段保证了数据正确性如果是企业级部署可以考虑增加服务器负载均衡、引入消息队列削峰填谷。前端页面刷新生效不了为什么 答Vue Router 的 history 模式刷新会请求真实路径后端没有对应路由就会 404。解决方法是 Nginx 配置 try_files 回退到 index.html或者使用 hash 模式。7. 部署、测试与真实环境里躲不开的坑7.1 本地环境的版本矩阵部署前先统一版本不然环境问题能耗掉你两天。经过多台机器验证下面这套组合最稳定JDK 1.8SpringBoot 2.7.xSpringBoot 3.x 要求 JDK 17很多老教程不兼容毕设别折腾MySQL 8.0Navicat 或 WorkbenchNode.js 14 或 16Vue CLI 4/5Maven 3.6后端依赖尽量锁定版本号不要用最新的因为最新的 MyBatis Plus 或 MySQL 驱动经常有 API 变动。给出一份我常用的版本参考组件推荐版本说明SpringBoot2.7.18JDK8 下最后一个稳定大版本线MyBatis Plus3.5.3稳定文档齐全MySQL Connector/J8.0.33配 MySQL 8JWT 库jjwt0.9.1老牌稳定Element UI2.15.xVue2 配套7.2 前后端打包部署流程前端开发完不要只在本地跑要能打包。npm run build执行完生成dist目录里面是纯静态文件。有两种部署方式第一种是丢进 Nginx配置一个站点根目录指向它把接口反向代理到 SpringBoot 地址。第二种更简单粗暴直接把dist复制到 SpringBoot 项目的src/main/resources/static目录下重新打包访问http://localhost:8080就能同时看到页面和接口。但第二种方式有一个坑如果前端用了history模式路由刷新内部页面会 404。要么改用hash路由要么在 Nginx 里配置try_files。毕设演示为了避免麻烦直接改成 hash 模式最省心。后端打包mvn clean package -DskipTests打出来的 jar 用java -jar启动即可。7.3 MySQL 连接报错的几个经典场景部署环节最容易翻车的不是代码而是数据库连接配置。报错一Access denied for user rootlocalhost。检查密码是否对MySQL 8 的 root 默认用caching_sha2_password认证老驱动连不上。解决方式把连接串加上allowPublicKeyRetrievaltrueuseSSLfalse或者把用户改回mysql_native_password。报错二The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这是时区问题在连接串加serverTimezoneAsia/Shanghai。完整的 JDBC 连接串参考jdbc:mysql://localhost:3306/register_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue7.4 测试用例要测什么测试章节千万不要只写“系统运行正常”要有实际用例。列一个测试表覆盖核心功能和异常分支例如编号测试项操作步骤预期结果实际结果T01学生登录输入正确账号密码登录成功跳转首页通过T02学生登录输入错误密码提示账号或密码错误通过T03现场报到正常扫码确认生成报到记录状态更新通过T04现场报到对已报到学生重复确认提示“已完成报到”不生成重复记录通过T05宿舍分配宿舍已满仍选择提示该宿舍已满不允许分配通过T06越权访问学生访问管理员接口提示无权限拦截请求通过这里面的 T04 和 T06 特别关键因为它们在验证第一章提到的“防重复报到”和“权限控制”这两个设计点是需要展示核心技术价值的地方。测试环境和步骤写清楚测试结果截图放进论文工作量立刻丰满起来。7.5 部署后的自检清单项目部署完先别急着截图按这个顺序快速自检一遍登录页面能否正常加载前端打包文件有没有路径错乱。数据库初始化脚本能否直接执行或能通过接口自动初始化。新增、编辑、删除、查询、分页五个基本操作各自走一遍。刷新页面、清除浏览器缓存后登录态是否失效。在后端日志里确认接口响应时间和异常信息没有明显报错。这一套检查完基本不会在答辩现场出丑。做这类系统做久了我最大的体会是毕设难的不是技术而是把每一步的“为什么”想明白。答辩老师不会要求你把系统做成商业软件但一定会追着你问设计理由。只要你能把业务拆清楚把数据库关系讲出依据把代码分层组织井然把踩过的环境坑说清解决过程这套毕设就是稳的。最后再多说一个小技巧项目全部做完后花两天时间把从小到大整套部署流程写成图文文档从安装 JDK 开始截图一直截到浏览器打开系统页面。答辩的时候老师问“这个系统是不是你自己做的”你直接把这份文档翻出来说“我可以现场从零部署一遍”这一个动作比任何口头解释都有说服力。