
1. 选题定调这个毕设到底在做什么1.1 入校申报审批系统解决的三个真实痛点先说结论入校申报审批系统这套毕设选题核心价值不在技术多高深而在于它把一个实际存在的管理场景完整地搬到线上让学校保卫处和访客之间反复的纸质登记、电话确认、微信通知变成了一套有状态、有留痕、可追踪的流程。我做这个题目之前先在学校保卫处蹲了半下午。观察到的第一件事是门卫登记本上有大量信息填错、缺填、字迹潦草的问题。第二件事是访客到了校门口门卫要打电话联系受访老师老师在上课或者开会电话经常不接。第三件事是一个老师一天可能接待好几拨访客重复确认流程让双方都很疲惫。这三个问题看似很简单但落到系统里就分别对应了表单校验、审批流转、消息通知恰好覆盖了一个完整业务的三个核心环节。所以这个系统解决的痛点非常明确访客信息数字化、审批过程可追踪、相关人员可通知。它不是一句空泛的“提高效率”而是每一环都能说清楚“原来怎样、现在怎样、省了什么”这一点在论文的摘要和答辩自述里非常好展开。1.2 功能边界哪些功能必须做哪些果断砍掉我见过不少做毕设的同学招标一样给自己加需求要地图选座、要访客人脸识别、要对接校园一卡通、要做数据可视化大屏。我的建议是全部砍掉。毕设的时间是固定的你每多一个花哨功能就有可能在核心功能的深度上缺一块。我的功能边界定的是四个字申报、审批。具体拆开是访客或入校人员在线填报来访信息包括姓名、手机号、身份证号、来访事由、受访部门、受访人、预计入校时间。受访人校内教职工登录后看到待审批列表点击同意或者驳回驳回时填写原因。管理员保卫处可以查看全部申请记录可以按时间、部门、状态筛选。门卫端核验端根据审批通过后的二维码或审批编号核验入校。这套边界下系统只有三种角色三张核心业务表三个主要页面但整个流程闭环是完整的。功能少不等于工作量小因为每个环节你都要做深申报表单要做字段校验和身份证查重审批要做状态机通知要异步发出去核验要设计二维码。砍掉花哨功能把核心环节打磨扎实才是正确的科普方式。1.3 面向人群与答辩侧的加分逻辑这套系统适合什么样的同学做两类人最合适一是Java方向、准备走前后端分离路线的学生可以用Spring Boot Vue把全套流程串下来二是时间紧张、需要在一个月内保底完成毕设的学生因为它逻辑清晰、没有复杂的算法难题、数据结构直观只要按部就班做不会出现“做得出来但讲不清楚”的尴尬。答辩加分点其实不在功能在于你对自己系统的逻辑自洽程度。评审老师常问的三个问题审批驳回之后数据去哪了两个人同时审批同一条数据怎么办前台能不能绕过权限直接调用接口改状态这三个问题这道题天然有答案因为审批系统本身就是在处理状态和权限。你只要在答辩前把这三个问题答好在演示时主动展示状态流转和越权拦截老师打分的区间就会往上走。2. 技术选型与总体架构稳、俗、好答辩是唯一标准2.1 为什么选Spring Boot Vue而不是其他组合很多同学在选题时纠结要不要用新框架、新语言比如Python的Django或者Go语言甚至前后端一体的JSP。我的建议是除非你的指导老师有明确的框架偏好否则Spring Boot Vue是最稳的。先聊前因入校申报审批系统的业务本质是CRUD加状态流转Spring Boot对这类业务有无比成熟的经验沉淀网上资料随便一搜就是几十年积累遇到问题几乎不可能找不到答案。而Vue在前端构建表单、列表、时间线这类交互页面时非常顺手组件化写法让代码结构清晰答辩时展示源码也很有条理。再聊“俗”俗在这里是褒义词。你的目标不是展示创新而是保证在有限时间内完成、能跑通、讲得清。Spring Boot Vue的组合在高校毕设中受众广指导老师和评审老师都见过不会因为框架不熟而产生理解障碍这对答辩是隐形成本。还有一个实际原因如果你用的框架太冷门出了问题连找人都找不到我有个同学用Flask做后端卡在数据库连接池配置上整整三天换成Spring Boot一个配置类就解决了。2.2 前后端分离下的目录结构与接口约定这套系统我建议采用前后端分离但不需要复杂到单独搞微服务或者独立部署两个服务。你只需要在工程结构上分离前端一个项目目录后端一个项目目录接口走HTTP调用即可实际演示时同时启动两个服务就行。后端目录我建议按标准的Spring Boot分层结构组织实际效果是答辩时翻代码老师一眼就能看出你学过工程化规范。前端用Vue CLI创建项目按视图划分组件把“访客申报页”“审批列表页”“核验页”“登录注册页”拆成四个独立视图公共的请求封装放到一个request.js里统一设置baseURL和header。接口约定是很多自学者容易忽略但答辩一定会被问的地方。我用的统一返回格式是{ code: 200, message: success, data: {} }所有接口都遵循这个约定前端axios响应拦截器统一处理code非200直接弹错误信息200才进入业务逻辑。这套约定不仅让代码更规范更关键的是答辩时老师问“前后端怎么通信的”你可以清清楚楚讲出请求从组件到拦截器到后端的完整链路。2.3 三角色权限模型RBAC怎么落地到这张表权限设计我直接采用RBAC基于角色的访问控制模型并且是最轻量的一种用户表、角色表、用户角色关联表菜单和权限不做细粒度控制认证之后只区分角色。为什么不做细粒度权限因为系统只有三种角色管理员能放行一切教职工能审批与本人相关的申请访客只能发起申报和查看自己的记录。细粒度权限会引入权限点表和菜单表增加工作量但不增加答辩价值。RBAC的轻量落地足够应对这题。实际实现上用户表存user_id、username、passwordBCrypt加密、phone、real_name、user_type1-访客2-教职工3-管理员。登录成功后后端生成Token返回前端前端把角色信息存到Vuex和localStorage路由守卫里做角色判断比如访问审批页面时如果role不是教职工或管理员直接重定向到首页。需要强调的是前端的路由守卫只是用户体验层面的隐藏真正的权限拦截必须在后端每个需要鉴权的接口上加Spring Security或拦截器校验。我后面会专门讲这部分因为这是答辩时最容易抠的细节。3. 数据库设计审批系统的核心在状态而非表单3.1 六张核心表的字段拆解数据库是这套系统真正的骨架我有一次写到一半重构过表结构痛感很深所以把实际使用的表结构整理在这里供直接参考。总共六张表用户表、角色表、用户角色关联表、申报表、审批记录表、通知记录表。后面三张是业务核心前面三张是权限基础。申报表apply_info的核心字段如下字段名类型说明apply_idbigint主键自增visitor_namevarchar(50)访客姓名visitor_phonevarchar(20)手机号visitor_id_cardvarchar(30)身份证号visitor_companyvarchar(100)来访单位visit_reasonvarchar(500)来访事由visited_departmentvarchar(100)受访部门visited_teachervarchar(50)受访人visit_start_timedatetime计划入校时间visit_end_timedatetime计划离校时间statustinyint状态0待审批1已通过2已驳回apply_timedatetime提交时间approve_timedatetime审批时间user_idbigint申请人ID关联用户表设计时有两个容易被忽略的细节一是身份证号应该加唯一索引防止同一个人重复申报时可以在前端提示而不是制造垃圾数据二是计划入校和离校两个时间字段要分开方便校门口核验时判断当前时间是否在有效窗口内。审批记录表approve_record的设计我单独说它是这套系统的核心亮点。3.2 审批流转与状态机设计状态机听上去很高端实际落地就是一串状态值加合法的迁移路径。我的系统只有三个状态迁移路径也很简单待审批0访客提交后进入可以变更为通过或驳回。已通过1审批通过后终态。业务上可衍生成“已入校”或“已离校”但内部分别再新增字段即可不增加状态。已驳回2终态。访客修改后可重新提交重新提交后回到待审批。为什么只做三态因为复杂状态机比如待补充材料、待二级审批、审批中、部分通过看起来更专业但会给前端渲染和接口逻辑带来倍增的复杂度。对毕设而言三态能完整表达业务闭环多一态就需要多写一堆边界判断性价比极低。三态落地到代码上其实就是一个字段的枚举判断不涉及真正的状态机框架。核心价值在于你必须在service层写一个“状态迁移合法性校验”比如只有status0的记录允许被审批状态为1或2的记录再调审批接口必须返回业务异常。这一条逻辑能直接挡住一大类bug也是代码审查时的加分项。3.3 审批记录表为什么要做留痕而不是直接改状态这个设计是我认为整个系统最值得在论文里大书特书的一块。最开始我省事审批就是UPDATE状态字段。后来保卫处的老师问我如果两个老师对同一个申请出现分歧怎么办如果访客说“我没收到驳回通知”怎么证明已经通知了这些问题直接指向一个事实——审批动作本身需要被记录下来。所以我新增了审批记录表approve_record字段名类型说明record_idbigint主键apply_idbigint关联申报IDapprover_idbigint审批人IDaction_typetinyint1-通过2-驳回commentvarchar(300)审批意见create_timedatetime审批时间关键改动是审批不再直接“改状态”而是“新增一条记录 同步变更申报表状态字段”。这两步操作放在同一个事务里。这样设计的好处有三个。第一审计追踪。谁在什么时间批了哪个申请、批了什么结果、写了什么意见一切都可查这就是留痕。第二防止并发混乱。两个审批人同时点“通过”如果直接改状态后一个覆盖前一个记录就丢了改成新增审批记录即便两个人同时提交至少每条操作都有日志可回溯。第三论文里可以名正言顺地写“本系统实现了审批全流程可追溯”这句话不是空话有表结构支撑。4. 后端核心逻辑状态校验、防重复审批与权限拦截4.1 审批接口的事务与校验顺序后端接口里最重要的是审批接口approve。这个接口的开发过程值得写细一点因为它把很多常见问题都集中体现出来了。核心代码如下我会配合代码逐行讲为什么这样写。Override Transactional(rollbackFor Exception.class) public void approve(ApplyApproveRequest request) { // 1. 参数基础校验 if (request.getApplyId() null || request.getAction() null) { throw new BusinessException(参数不完整); } if (request.getAction() ! 1 request.getAction() ! 2) { throw new BusinessException(非法的审批动作); } // 2. 查询申请记录 ApplyInfo apply applyInfoMapper.selectById(request.getApplyId()); if (apply null) { throw new BusinessException(申请记录不存在); } // 3. 状态校验只有待审批状态才能操作 if (apply.getStatus() ! ApplyStatusEnum.WAITING.getCode()) { throw new BusinessException(当前状态不可审批); } // 4. 写入审批记录 ApproveRecord record new ApproveRecord(); record.setApplyId(request.getApplyId()); record.setApproverId(CurrentUserHolder.getUserId()); record.setActionType(request.getAction()); record.setComment(request.getComment()); approveRecordMapper.insert(record); // 5. 更新申报状态 apply.setStatus(request.getAction() 1 ? ApplyStatusEnum.PASSED.getCode() : ApplyStatusEnum.REJECTED.getCode()); apply.setApproveTime(new Date()); applyInfoMapper.updateById(apply); }为什么这么写关键是顺序。先查记录再校验状态再写记录再更新状态每一步都在前一步验证通过后才执行。如果先更新状态再写记录万一写记录失败状态已经被改了事务回滚虽然能兜底但代码逻辑上是不清晰、不健壮的。还有Transactional注解必须加并且要指定rollbackFor Exception.class。Spring默认只在遇到RuntimeException时才回滚如果你的代码抛的是自定义Exception不加这个参数就不会回滚数据会处于半完成状态。这个坑我踩过。4.2 越权与并发问题的处理越权是整个系统最容易被攻击的点也是答辩老师最爱问的点。我做了三层防护。第一层是接口级别的角色校验。审批接口标注了PreAuthorize(hasAnyRole(TEACHER, ADMIN))访客角色直接无法调用。第二层是数据范围校验。教职工不应该只看所有申请所以查询接口要加WHERE条件visited_teacher 当前用户真实姓名不然老师登录后看到全校所有人的申请就闹笑话了。管理员才允许查全量。第三层是当前用户获取。所有涉及审批人字段的地方必须从Token解析出的用户ID获取绝对不能信任前端传过来的值不然伪造一个评审人ID就能冒充老师审批。并发问题是另一个高频提问点。两个老师同时审批同一条数据数据库层面怎么防我的方案是在审批接口的事务内使用SELECT ... FOR UPDATE抢锁。Spring Boot的Transactional默认不会自动加行级锁你需要在查询语句上显式加上FOR UPDATE或者设计一个逻辑在更新时携带旧状态值做条件更新。int rows applyInfoMapper.updateStatus( applyId, ApplyStatusEnum.PASSED.getCode(), ApplyStatusEnum.WAITING.getCode() // where status WAITING ); if (rows 0) { throw new BusinessException(申请状态已变更请刷新后再操作); }用“期望旧状态”作为更新条件数据库层面自然会阻塞并发。先更新成功的人拿到rows1后提交的人拿到rows0然后被挡回去。这个方案避免了显式锁逻辑也更简单是我推荐的做法。4.3 通知机制站内信、邮件、短信的优先级排序入校申报系统天然需要一个通知环节访客提交申请后要通知受访老师去审批老师审批后要通知访客结果。但这在毕设里很容易做重如果接入阿里云短信服务还要注册企业资质、买签名模板时间成本完全不可控。我调研后的实际方案是三层降级第一是站内信系统内加一张通知记录表用户在页面右上角红点查看消息第二是邮件通知因为学校有现成的邮箱服务只要配置SMTP就能发成本几乎为零第三是短信放在“留有扩展接口”的位置论文里写清楚预留了短信通道但未实际接入理由是需要商用短信平台资质。实际开发时站内信做得最细状态变化时自动生成一条通知写入通知记录表前端WebSocket轮询每30秒拉取未读数量。邮件部分就是JavaMailSender直接配置代码量不大但效果很好。我在论文里专门用一页写了“通知模块的降级设计”答辩时老师很认可因为他能看到你有方案对比和取舍思考。5. 前端交互让答辩老师觉得“这系统真能用”5.1 访客端与审批端的页面划分前端页面直接决定了答辩演示的第一印象。很多时候评委还没有细看代码光是页面效果就已经在心里打了分。我的页面采用Vue Element UI布局是经典的后台管理风格左边侧边栏导航顶部用户信息主内容区放业务页面。访客端的核心页面是申报表单页。这个页面的体验做好了整个系统的用户友好度就有了基础。你需要把这些字段组织成合理的表单项姓名、手机号、身份证号放在第一行来访单位和来访事由放在第二行受访部门和受访人放在第三行入校时间段用日期时间选择器。每一个必填项在失焦时就校验而不是等到提交才提示这个细节很影响体验答辩时评审可能会试填一个非法手机号如果你当场弹出明确错误提示印象分会涨。审批端页面则是表格为主。用el-table展示待审批申请每行一个“详情”按钮和一个“审批”按钮。点击详情弹出抽屉展示这次申报的完整信息与当前状态底部放通过和驳回的操作按钮。驳回时必须在弹窗里填写原因并设为必填这里有讲究驳回原因会成为审批记录表里的一条数据访客端也可以看到这样“为什么被拒绝”不再是门卫一句模糊的话而是系统里精确的记录。5.2 审批流时间线组件与状态色标的实现我在详情抽屉里放了状态时间线这个是我认为全系统前端最出彩的交互。访客提交时间、教师审批时间、审批结果一字排开。评审老师演示时看到这个组件基本就能直观理解“留痕”的含义。时间线用Element UI的el-timeline就能实现数据来源就是申报表时间和审批记录表中的create_time。渲染逻辑很直接approve_time有值时显示审批时间线节点没有则停留在待审批节点。每个节点配一个图标和一个说明文字比如“张三提交了来访申请 - 2024-05-20 09:30”“李四老师已通过审批 - 2024-05-20 10:02”。状态色标方面我建议用统一方法0-待审批用warning橙色1-已通过用success绿色2-已驳回用danger红色。Element UI自带tag组件直接复用一套方法定义在公共的statusMap里前端所有涉及状态展示的地方都调这个映射保证全系统颜色统一。实现起来简单但会让页面看起来非常“产品化”。5.3 表单校验与复用身份信息的细节表单校验是前端最容易被忽略但是最影响体验的部分。我用了Element UI自带的rules加上自定义校验规则。身份证号18位和末位X的格式校验、手机号11位校验、来访时间必须晚于当前时间校验这些是最基础的。在此基础上我加了一个值得提到的细节同一个访客填过一次信息后下一次再申报时根据手机号自动回填姓名和身份证号。这个回填逻辑实现很简单手机号输入框失焦时调后端查询接口/api/visitor/search-by-phone命中则自动填充visitor_name和visitor_id_card。效果却非常亮眼。保安大爷看到的是“这个人上次来过填过一次就不用再输了”评审老师看到的是“你考虑了真实使用场景”。后端就几行代码的事情成本几乎为零但功能完整度和体验完整度都上了一个台阶。前端还有一个细节访客提交成功后的反馈页。提交成功不是简单跳回列表而是显示一张带审批编号和二维码的卡片。编号用于人工核验二维码用于门卫端扫码核验。这一页存在的作用是让用户明确知道“提交成功了下一步该做什么”而不是提交之后一脸迷茫。二维码生成用qrcode库编码字段是apply_id门卫端扫码后直接跳转到核验详情页。6. 论文、PPT、演示视频毕设项目的另一半工作量6.1 论文章节怎么对应系统模块很多技术做得很扎实的同学论文写出来像流水账非常可惜。核心问题在于论文结构和系统模块没有形成映射关系。我采用的论文结构如下每一章都精准对应系统的一个侧面绪论写研究背景和意义结尾用一页综述当前校园出入管理存在的主要问题正好对应1.1节那三个痛点。相关技术介绍挑Spring Boot、Vue、MySQL、MyBatis-Plus这四个技术展开每个技术写原理、为什么选它。系统需求分析写功能性需求和非功能性需求。功能需求每个模块画用例图角色有访客、教职工、管理员三类用例有八到十个。系统设计总体架构图、功能模块图、数据库ER图、表结构说明、接口设计说明。这一章是论文的骨架篇幅最大。系统实现每个功能模块配实现思路、关键技术代码片段、界面截图。实现效果截图要单独放在一个章节而不是插在需求分析里。系统测试功能测试用例表列十个左右的用例包含预期结果和实际结果。再加一个并发审批的测试用例对应你在后端做的防并发逻辑这是亮点。总结与展望写系统解决了哪些问题、还遗留哪些不足、下一步扩展方向。这个结构的特点是“系统和文档一体两面”每章都在回答不同的评审问题不存在写了但没用的内容。6.2 论文里的图怎么画最讨喜论文里的图直接决定老师翻看你论文的前三十秒感受。我强烈建议用在线绘图工具或者ProcessOn画图不要用Visio那种旧式风格。三张图必须认真画一是系统功能模块图用树状结构展示“系统-模块-功能点”三层二是业务时序图展示访客提交请求到后端处理到数据库落库到通知触发的完整过程三是数据库ER图展示六张表的关系。时序图我在这篇博文里不写具体伪代码了它的价值在于能把“新增审批记录更新申请状态放在同一事务”这个设计直观表达给导师看。ER图需要注意标明主外键关系评审老师看到表与表之间连线清晰第一感觉就是这孩子基本功扎实。功能模块图有另外一个隐性作用它就是你论文目录的可视化版本。答辩老师看模块图就能知道系统包含哪些功能后面实现章里你逐一展开对应整篇论文的连贯性就有了。论文中不要出现大段代码只需放核心片段代码行数控制在十行左右重点是配合解说文字讲思路。6.3 PPT结构与演示视频脚本PPT我走的是“成品毕设型”的结构一共8页左右每页只放核心内容封面背景和痛点一两句带过系统功能结构图数据库设计核心功能截图两张技术特色与亮点创新点与不足致谢。整个答辩讲下来控制在5分钟语速平缓一点每个页面停留时间大约35秒。整体风格记住一个原则PPT是提示词不是阅读材料。每页的文字不要超过四行核心要点用关键词而不是完整句子需要展开的部分靠嘴讲。细节全部放到演示环境和演示视频里PPT里不要塞代码。演示视频脚本我建议用四段式。第一段30秒展示项目启动过程包括数据库启动、后端启动、前端启动这是展示你整个环境搭建能力的机会。第二段90秒走主流程用访客账号提交申请等审批通过展示状态变化。第三段60秒走分支流程创建一个待审批申请用教职工账号驳回然后访客端展示驳回原因清晰交代状态流转。第四段30秒展示权限防护黑科技尝试用一个普通访客的Token去访问审批列表接口返回403这一段在答辩现场特别有冲击力。7. 部署、打包与避坑复盘7.1 本地打包、云服务器部署与数据库迁移毕设演示当天最怕的就是环境出问题。我给出的实操建议是还在开发阶段就同步做部署。提前把后端打包前端打包放到一台云服务器上去跑数据库的数据和本地完全隔离这样就不会出现“本地写了很多测试数据服务器上仍是空白”的窘境。后端打包用Maven package生成jar包注意在application.yml里为“生产环境”单独配置数据源和文件上传路径。前端打包用npm run build生成dist目录然后放到Nginx的html目录下Nginx配置里需要把/api前缀的请求代理到后端端口。示例关键配置如下server { listen 80; server_name your.server.ip; location / { root /usr/share/nginx/html/dist; index index.html; 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; } }数据库迁移更是毕设重灾区。本地MySQL版本和服务器MySQL版本不一致时导入导出经常报错。建议直接用Navicat的“转储SQL文件”导出时勾选“包含DROP TABLE IF EXISTS”导入前先手工建好库名。不要让评审老师看到你的数据表字段是英文、记录里却是中文乱码连接字符串里加useUnicodetruecharacterEncodingutf-8这串参数能解决一大部分乱码问题。7.2 常见报错与对应的处理我把自己写这套系统时踩过的坑以及同学们问得最多的问题集中列在这里第一类是MyBatis-Plus分页失效。这几乎是必考题原因是没有配置分页插件。解决方法是新增MybatisPlusConfig配置类注册PaginationInnerInterceptor然后所有分页查询就会自动按Page对象返回。如果不配你以为做了分页实际返回的是一整张表的数据性能问题倒在其次答辩时被问到分页逻辑会很难看。第二类是前端请求跨域。开发模式下前端在8080端口后端在8081端口直接请求必然跨域。解决方案有两种后端写一个CorsConfig全局配置类放行或者前端在vue.config.js配置devServer的proxy代理。正式演示时如果走Nginx部署nginx代理解决跨域这个知识点应当能说清楚。第三类是JWT过期问题。用户在演示时填表填了一半token过期前端跳回登录页填好的数据全没了体验灾难。建议把token有效期调到12小时excludePathPatterns里把登录接口、公开的查询接口都排除掉。理论上调过期时间不算最优方案但毕设演示场景里“活过一上午”比什么都重要答辩时如果有人问你再补一句“生产环境应当设置更短时间并配合刷新token机制”。第四类是二维码图片不显示。开发时一切正常部署到服务器后二维码加载不出来大概率是图片路径写死了localhost。正确做法是返回相对路径前端拼接当前访问域名再组装完整地址。一经出现就排查这一处。7.3 我踩过的坑与二次开发的扩展方向我买了个云服务器部署这套系统时因为内存只有2G后端一启动内存就爆掉前端页面白屏排查了一晚上才发现是JVM默认堆内存太大造成的。修复方案很简单启动参数加一段java -jar app.jar --spring.profiles.activeprod -Xms256m -Xmx512m限定了堆内存之后项目安然跑了一个多月。这种问题属于环境适配类经验论文里没法写但实战中非常折磨人。建议所有要做服务器部署的同学打包配置检查单里加上“启动脚本设置了-Xmx内存上限”这一条。这套系统后续能扩展的方向其实不少。最自然的是把审批从单级改成多级比如辅导员先审、保卫处再审对应加一个审批级别字段其次可以接入学校统一身份认证和现有学生/教职工账号打通再往上走可以对接短信发送、企业微信通知、访客黑名单自动拦截。这些扩展方向不用在毕设期间做完但放进论文的展望章能让整篇论文显得有后续性和思考深度。我个人实际操作中最大的体会是这套系统能不能顺利做出来完全不取决于你技术多强而取决于你有没有在最开始阶段把数据库表和状态定义想清楚。状态想清楚了后端逻辑就是照规矩办事状态模糊前后端所有环节都会在联调时互相甩锅。入校申报审批系统最核心的工具不是代码框架而是一张画满状态迁移路径的草稿纸。把那张纸画明白写代码只是时间问题。